// @vitest-environment node import { afterAll, beforeAll, describe, expect, it } from 'vitest' import { db } from '@/lib/db' import { loadDashboard } from '@/lib/dashboard/load' import { listWatched, toggleWatch } from '@/lib/cases/watch' import { AccessError } from '@/lib/errors' import type { EffectivePermissions, Permission } from '@/lib/auth/permissions' /** * Die Übersicht ist der Ort, an dem eine unachtsame Abfrage am ehesten eine * Akte verrät: fünf Listen auf einer Seite, jede mit eigenem Filter. Geprüft * wird deshalb jede einzeln gegen eine zu hoch eingestufte Akte. */ const url = process.env.DIRECT_DATABASE_URL ?? process.env.DATABASE_URL const suite = url ? describe : describe.skip const marke = `dash-${Date.now()}` function perms( userId: string, clearanceLevel: number, options: { isAdmin?: boolean; permissions?: Permission[] } = {}, ): EffectivePermissions { return { userId, clearanceLevel, permissions: new Set(options.permissions ?? []), isAdmin: options.isAdmin ?? false, hasMembership: true, } } suite('Übersicht und Beobachtungsliste', () => { const nutzer = { stufe1: `${marke}-stufe1`, stufe4: `${marke}-stufe4` } let offenNummer = '' let geheimId = '' let geheimNummer = '' 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.stufe4, } const offen = await db.case.create({ data: { ...gemeinsam, caseNumber: `Dash-2026-${String(Date.now()).slice(-4)}`, title: 'Offener Vorgang', classification: 1, status: 'OPEN', dueAt: new Date(Date.now() + 2 * 86_400_000), }, }) const geheim = await db.case.create({ data: { ...gemeinsam, caseNumber: `Dghm-2026-${String(Date.now()).slice(-4)}`, title: 'Streng vertraulich', classification: 4, status: 'OPEN', // Auch eine Frist: sie darf in keiner Liste auftauchen. dueAt: new Date(Date.now() + 86_400_000), }, }) offenNummer = offen.caseNumber geheimId = geheim.id geheimNummer = geheim.caseNumber // Der Stufe-1-Nutzer ist an beiden Akten beteiligt — an der geheimen aber // nicht über sein Konto, sondern gar nicht. Er darf sie nicht sehen. await db.caseParticipant.create({ data: { caseId: offen.id, userId: nutzer.stufe1, role: 'INVESTIGATOR' }, }) }) afterAll(async () => { await db.$disconnect() }) it('hält eine zu hoch eingestufte Akte aus jeder Liste heraus', async () => { const daten = await loadDashboard(perms(nutzer.stufe1, 1)) const alle = [ ...daten.meine, ...daten.fristen, ...daten.zuletzt, ...daten.beobachtet, ].map((akte) => akte.id) expect(alle).not.toContain(geheimId) // Und die eigene ist da, sonst prüft der Test nichts. expect(daten.meine.map((a) => a.caseNumber)).toContain(offenNummer) expect(daten.fristen.map((a) => a.caseNumber)).toContain(offenNummer) }) it('zeigt der Stufe 4 die Frist der geheimen Akte', async () => { const daten = await loadDashboard(perms(nutzer.stufe4, 4)) expect(daten.fristen.map((a) => a.id)).toContain(geheimId) }) it('lässt nur beobachten, was man sehen darf', async () => { // Beobachten ist kein Weg an der Stufe vorbei. await expect(toggleWatch(perms(nutzer.stufe1, 1), geheimNummer)).rejects.toThrow(AccessError) const an = await toggleWatch(perms(nutzer.stufe1, 1), offenNummer) expect(an.watching).toBe(true) expect((await listWatched(perms(nutzer.stufe1, 1))).map((a) => a.caseNumber)).toContain( offenNummer, ) const aus = await toggleWatch(perms(nutzer.stufe1, 1), offenNummer) expect(aus.watching).toBe(false) }) it('nimmt eine beobachtete Akte aus der Liste, sobald sie zu hoch steigt', async () => { const p = perms(nutzer.stufe4, 4) await toggleWatch(p, geheimNummer) expect((await listWatched(p)).map((a) => a.id)).toContain(geheimId) // Derselbe Beobachtungseintrag, aber jetzt aus Sicht einer niedrigeren // Stufe: die Liste läuft durch dieselbe Sichtbarkeitsprüfung. const abgestiegen = perms(nutzer.stufe4, 1) expect((await listWatched(abgestiegen)).map((a) => a.id)).not.toContain(geheimId) }) })