// @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(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) }) })