Files
AfiS-Aktensystem/web/tests/zuletzt-geoeffnet.test.ts
Harlekin e0b03ea336
Prüfung / Lint, Typen, Tests (push) Canceled after 0s
Prüfung / Images bauen (pdf) (push) Canceled after 0s
Prüfung / Images bauen (web) (push) Canceled after 0s
Prüfung / Images bauen (worker) (push) Canceled after 0s
Images / bauen (pdf, docker/pdf.Dockerfile) (push) Canceled after 0s
Images / bauen (web, docker/web.Dockerfile) (push) Canceled after 0s
Images / bauen (worker, docker/worker.Dockerfile) (push) Canceled after 0s
Gedankenstriche durch Bindestriche ersetzt
375 Vorkommen in 210 Dateien. Betrifft Kommentare, Oberflaechentexte und die
README; an keiner Stelle Bedeutung, nur Zeichensetzung.
2026-09-04 08:54:52 +02:00

169 lines
5.8 KiB
TypeScript

// @vitest-environment node
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
import { db } from '@/lib/db'
import { clearRecent, listRecent, noteOpened, RECENT_MAX } from '@/lib/recent/service'
import type { EffectivePermissions, Permission } from '@/lib/auth/permissions'
/**
* Die Sprungliste ist eine Bequemlichkeit - und genau deshalb der Ort, an dem
* eine Abkürzung besonders verlockend wäre: die Kennungen liegen ja schon da,
* warum sie nochmal prüfen? Weil eine vorgemerkte Kennung keine Berechtigung
* ist. Diese Datei prüft vor allem, dass die Liste nichts durchlässt.
*/
const url = process.env.DIRECT_DATABASE_URL ?? process.env.DATABASE_URL
const suite = url ? describe : describe.skip
const marke = `recent-${Date.now()}`
function perms(
userId: string,
clearanceLevel: number,
options: { isAdmin?: boolean; permissions?: Permission[] } = {},
): EffectivePermissions {
return {
userId,
clearanceLevel,
permissions: new Set<Permission>(options.permissions ?? []),
isAdmin: options.isAdmin ?? false,
hasMembership: true,
}
}
suite('Zuletzt geöffnet', () => {
const nutzer = { a: `${marke}-a`, b: `${marke}-b` }
let offenId = ''
let offenNummer = ''
let geheimId = ''
let personId = ''
beforeAll(async () => {
const vorlage = await db.documentTemplate.create({
data: { key: `${marke}-vorlage`, version: 1, name: 'Prüfvorlage', fields: [] },
})
for (const id of Object.values(nutzer)) {
await db.user.create({ data: { id, username: id, displayName: id } })
}
const gemeinsam = {
templateId: vorlage.id,
templateKey: vorlage.key,
createdById: nutzer.a,
}
const offen = await db.case.create({
data: {
...gemeinsam,
caseNumber: `Rez-2026-${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`,
title: 'Offener Vorgang',
classification: 1,
status: 'OPEN',
},
})
const geheim = await db.case.create({
data: {
...gemeinsam,
caseNumber: `Rgh-2026-${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`,
title: 'Streng vertraulich',
classification: 4,
status: 'OPEN',
},
})
const person = await db.personnelFile.create({
data: { displayName: `${marke}-Person`, classification: 1, createdById: nutzer.a },
})
offenId = offen.id
offenNummer = offen.caseNumber
geheimId = geheim.id
personId = person.id
})
afterAll(async () => {
await db.$disconnect()
})
it('merkt sich einen Sprung und zeigt ihn wieder', async () => {
await noteOpened(nutzer.a, 'CASE', offenId)
const liste = await listRecent(perms(nutzer.a, 1))
expect(liste.map((e) => e.targetId)).toContain(offenId)
expect(liste[0]?.href).toBe(`/faelle/${offenNummer}`)
expect(liste[0]?.label).toBe(offenNummer)
})
it('führt keinen Verlauf: zweimal öffnen bleibt eine Zeile', async () => {
await noteOpened(nutzer.a, 'CASE', offenId)
await noteOpened(nutzer.a, 'CASE', offenId)
const zeilen = await db.recentView.count({
where: { userId: nutzer.a, targetType: 'CASE', targetId: offenId },
})
expect(zeilen).toBe(1)
})
it('zeigt eine zu hoch eingestufte Akte nicht, auch wenn sie vorgemerkt ist', async () => {
// Der Fall, der zählt: die Kennung liegt in der Liste, die Freigabe fehlt.
// Etwa weil die Akte nach dem Öffnen hochgestuft wurde.
await noteOpened(nutzer.a, 'CASE', geheimId)
const hoch = await listRecent(perms(nutzer.a, 4))
expect(hoch.map((e) => e.targetId)).toContain(geheimId)
const niedrig = await listRecent(perms(nutzer.a, 1))
expect(niedrig.map((e) => e.targetId)).not.toContain(geheimId)
// Und keine Lücke, kein Platzhalter: die Zeile fehlt einfach.
expect(niedrig.every((e) => e.label.length > 0)).toBe(true)
})
it('zeigt Personalakten nur, wer sie überhaupt aufrufen darf', async () => {
await noteOpened(nutzer.a, 'PERSONNEL', personId)
const ohneRecht = await listRecent(perms(nutzer.a, 4))
expect(ohneRecht.map((e) => e.targetId)).not.toContain(personId)
const mitRecht = await listRecent(perms(nutzer.a, 4, { permissions: ['personnel.read'] }))
expect(mitRecht.map((e) => e.targetId)).toContain(personId)
})
it('zeigt niemandem die Liste eines anderen', async () => {
await noteOpened(nutzer.b, 'CASE', offenId)
// Nutzer a hat die Akte auch geöffnet - aber b bekommt seine eigene Zeile,
// und ein Administrator bekommt seine eigene, nicht die von b.
const admin = await listRecent(perms(`${marke}-admin`, 5, { isAdmin: true }))
expect(admin).toHaveLength(0)
})
it('behält nur die jüngsten Einträge', async () => {
const vorlage = await db.documentTemplate.findFirstOrThrow({
where: { key: `${marke}-vorlage` },
})
const viele = nutzer.b
for (let i = 0; i < RECENT_MAX + 3; i++) {
const akte = await db.case.create({
data: {
templateId: vorlage.id,
templateKey: vorlage.key,
createdById: viele,
caseNumber: `Rvi-${Date.now()}-${i}`,
title: `Vorgang ${i}`,
classification: 1,
status: 'OPEN',
},
})
await noteOpened(viele, 'CASE', akte.id)
}
const zeilen = await db.recentView.count({ where: { userId: viele } })
expect(zeilen).toBeLessThanOrEqual(RECENT_MAX)
// Die zuerst geöffnete Akte ist hinten herausgefallen.
const liste = await listRecent(perms(viele, 1), RECENT_MAX)
expect(liste.map((e) => e.targetId)).not.toContain(offenId)
})
it('lässt sich leeren', async () => {
await noteOpened(nutzer.a, 'CASE', offenId)
expect(await clearRecent(nutzer.a)).toBeGreaterThan(0)
expect(await listRecent(perms(nutzer.a, 5, { isAdmin: true }))).toHaveLength(0)
})
})