// @vitest-environment node import { afterAll, beforeAll, describe, expect, it } from 'vitest' import { createPrismaClient, type PrismaClient } from '@afis/db' import type { EffectivePermissions, Permission } from '@/lib/auth/permissions' const url = process.env.DIRECT_DATABASE_URL ?? process.env.DATABASE_URL const suite = url ? describe : describe.skip let db: PrismaClient const marke = `abschnitt-${Date.now()}` const NUTZER = `${marke}-nutzer` const ZWEITER = `${marke}-zweiter` function perms(level: number, userId = NUTZER): EffectivePermissions { const permissions: Permission[] = ['case.create', 'case.edit', 'entry.create'] return { userId, clearanceLevel: level, permissions: new Set(permissions), isAdmin: false, hasMembership: true } } const FELDER = [ { key: 'sachverhalt', label: 'Sachverhalt', type: 'longtext' }, { key: 'bewertung', label: 'Bewertung', type: 'longtext', requiredToClose: true }, { key: 'zeugenname', label: 'Zeuge', type: 'shorttext', minClearance: 4 }, { key: 'hinweis', label: 'Hinweis', type: 'paragraph', text: 'Fester Text mit {{sachverhalt}}' }, ] let caseNumber = '' let caseId = '' let templateId = '' suite('Abschnitte', () => { beforeAll(async () => { db = createPrismaClient(url) const vorlage = await db.caseTemplate.create({ data: { key: `${marke}-vorlage`, version: 1, name: 'Prüfvorlage', fields: FELDER }, }) templateId = vorlage.id for (const id of [NUTZER, ZWEITER]) { await db.user.create({ data: { id, username: id, displayName: id } }) } const { createCase } = await import('@/lib/cases/service') const akte = await createCase(perms(5), { title: 'Abschnittsprüfung', classification: 1, templateKey: `${marke}-vorlage`, }) caseNumber = akte.caseNumber caseId = akte.id }) afterAll(async () => { await db.$disconnect() }) it('legt beim ersten Speichern Version 1 an', async () => { const { saveSection } = await import('@/lib/cases/sectionService') const ergebnis = await saveSection(perms(5), { caseNumber, fieldKey: 'sachverhalt', value: 'Erster Stand', baseVersion: 0, }) expect(ergebnis.version).toBe(1) }) it('erkennt einen Konflikt, statt still zu überschreiben', async () => { const { saveSection, ConflictError } = await import('@/lib/cases/sectionService') // Zwei laden dieselbe Fassung. await saveSection(perms(5), { caseNumber, fieldKey: 'bewertung', value: 'Fassung A', baseVersion: 0, }) // Einer speichert, der andere hat noch die alte Version im Formular. await saveSection(perms(5), { caseNumber, fieldKey: 'bewertung', value: 'Fassung B', baseVersion: 1, }) await expect( saveSection(perms(5, ZWEITER), { caseNumber, fieldKey: 'bewertung', value: 'Fassung C', baseVersion: 1, }), ).rejects.toBeInstanceOf(ConflictError) // Der Konflikt bringt die fremde Fassung mit, damit die Oberfläche beide // nebeneinander zeigen kann. try { await saveSection(perms(5, ZWEITER), { caseNumber, fieldKey: 'bewertung', value: 'Fassung C', baseVersion: 1, }) } catch (error) { expect((error as InstanceType).current).toMatchObject({ value: 'Fassung B', version: 2, }) } }) it('schreibt jede Fassung in die Versionstabelle', async () => { const section = await db.caseSection.findUniqueOrThrow({ where: { caseId_fieldKey: { caseId, fieldKey: 'bewertung' } }, }) const versionen = await db.caseSectionVersion.findMany({ where: { sectionId: section.id }, orderBy: { version: 'asc' }, }) expect(versionen.map((v) => v.value)).toEqual(['Fassung A']) expect(section.currentVersion).toBe(2) }) it('ersetzt ein Feld über der Stufe des Lesers serverseitig', async () => { const { saveSection, loadSections } = await import('@/lib/cases/sectionService') await saveSection(perms(5), { caseNumber, fieldKey: 'zeugenname', value: 'Wachmann Perreira', baseVersion: 0, }) const fuerNiedrig = await loadSections(perms(2), caseId, templateId) const feld = fuerNiedrig.find((s) => s.fieldKey === 'zeugenname') expect(feld?.visible).toBe(false) // Der echte Wert verlässt den Server nicht. expect(JSON.stringify(fuerNiedrig)).not.toContain('Perreira') expect(feld?.value).toMatchObject({ redacted: true, requiredLevel: 4 }) const fuerHoch = await loadSections(perms(4), caseId, templateId) expect(fuerHoch.find((s) => s.fieldKey === 'zeugenname')?.value).toBe('Wachmann Perreira') }) it('lässt niemanden ein Feld über die eigene Stufe heben', async () => { const { saveSection } = await import('@/lib/cases/sectionService') const { AccessError } = await import('@/lib/cases/service') await expect( saveSection(perms(2), { caseNumber, fieldKey: 'sachverhalt', value: 'Heimlich hochstufen', baseVersion: 1, minClearance: 5, }), ).rejects.toBeInstanceOf(AccessError) }) it('gibt Fliesstext als Segmente zurück, mit angewandter Schwärzung', async () => { const { saveSection, loadSections } = await import('@/lib/cases/sectionService') await saveSection(perms(5), { caseNumber, fieldKey: 'sachverhalt', value: 'Offen [[c4]]geheim[[/c4]] Ende', baseVersion: 1, }) const niedrig = await loadSections(perms(1), caseId, templateId) const feld = niedrig.find((s) => s.fieldKey === 'sachverhalt') if (!feld || feld.visible !== true) throw new Error('Abschnitt sollte sichtbar sein') expect(JSON.stringify(feld.segments)).not.toContain('geheim') expect(JSON.stringify(feld.segments)).toContain('Offen') }) })