Files
AfiS-Aktensystem/web/tests/verknuepfungen.test.ts
T
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

161 lines
5.4 KiB
TypeScript

// @vitest-environment node
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
import { createPrismaClient, loadEnvFromRepoRoot, type PrismaClient } from '@afis/db'
import { listLinks } from '@/lib/cases/links'
import { listParticipants } from '@/lib/cases/participants'
import { visibleCaseWhere } from '@/lib/access/caseFilter'
import type { EffectivePermissions, Permission } from '@/lib/auth/permissions'
/**
* Verknüpfungen zeigen in beide Richtungen. Eine Akte, die der Leser nicht
* sehen darf, darf auch von der Gegenseite aus nicht auftauchen - sonst
* verrät die verknüpfte Akte ihre Existenz.
*/
loadEnvFromRepoRoot()
const url = process.env.DIRECT_DATABASE_URL ?? process.env.DATABASE_URL
const suite = url ? describe : describe.skip
let db: PrismaClient
const marke = `link-${Date.now()}`
function perms(userId: string, clearanceLevel: number): EffectivePermissions {
return {
userId,
clearanceLevel,
permissions: new Set<Permission>(),
isAdmin: false,
hasMembership: true,
}
}
suite('Verknüpfungen zwischen Akten', () => {
const nutzer = { stufe1: `${marke}-stufe1`, stufe4: `${marke}-stufe4` }
let offenId = ''
let geheimId = ''
let zweitOffenId = ''
beforeAll(async () => {
db = createPrismaClient(url)
const template = 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: template.id,
templateKey: template.key,
createdById: nutzer.stufe4,
}
const offen = await db.case.create({
data: {
...gemeinsam,
caseNumber: `${marke}-a`.slice(0, 40),
title: 'Offen',
classification: 1,
status: 'OPEN',
},
})
const geheim = await db.case.create({
data: {
...gemeinsam,
caseNumber: `${marke}-b`.slice(0, 40),
title: 'Geheim',
classification: 4,
status: 'OPEN',
},
})
const zweitOffen = await db.case.create({
data: {
...gemeinsam,
caseNumber: `${marke}-c`.slice(0, 40),
title: 'Auch offen',
classification: 1,
status: 'OPEN',
},
})
offenId = offen.id
geheimId = geheim.id
zweitOffenId = zweitOffen.id
await db.caseLink.createMany({
data: [
// Hinaus: offen -> geheim
{ fromId: offen.id, toId: geheim.id, kind: 'RELATED', createdById: nutzer.stufe4 },
// Herein: geheim -> offen, dieselbe Falle aus der anderen Richtung
{ fromId: geheim.id, toId: offen.id, kind: 'SUCCESSOR', createdById: nutzer.stufe4 },
// Unverfänglich
{ fromId: offen.id, toId: zweitOffen.id, kind: 'RELATED', createdById: nutzer.stufe4 },
],
})
})
afterAll(async () => {
await db.$disconnect()
})
it('verbirgt eine zu hoch eingestufte Akte in beiden Richtungen', async () => {
const links = await listLinks(perms(nutzer.stufe1, 1), offenId)
const ziele = links.map((link) => link.other.id)
expect(ziele).toContain(zweitOffenId)
expect(ziele).not.toContain(geheimId)
// Auch die eingehende Verknüpfung darf nicht als leerer Platzhalter stehen.
expect(links).toHaveLength(1)
})
it('zeigt der Stufe 4 beide Richtungen mit ihrer jeweiligen Bedeutung', async () => {
const links = await listLinks(perms(nutzer.stufe4, 4), offenId)
const richtungen = links
.filter((link) => link.other.id === geheimId)
.map((link) => `${link.direction}:${link.kind}`)
.sort()
expect(richtungen).toEqual(['in:SUCCESSOR', 'out:RELATED'])
})
it('nennt den Namen eines Beteiligten, verweist aber nur auf sichtbare Personalakten', async () => {
const verborgen = await db.personnelFile.create({
data: { displayName: `${marke} Verborgene Person`, classification: 4 },
})
await db.caseParticipant.create({
data: { caseId: offenId, personnelFileId: verborgen.id, role: 'SUBJECT' },
})
const beteiligte = await listParticipants(perms(nutzer.stufe1, 1), offenId)
const eintrag = beteiligte.find((b) => b.displayName?.includes('Verborgene Person'))
// Wer die Ermittlung lesen darf, muss wissen, gegen wen sie läuft.
expect(eintrag).toBeDefined()
// Was darüber hinaus über die Person aktenkundig ist, bleibt zu.
expect(eintrag?.personnelFileId).toBeNull()
const fuerStufe4 = await listParticipants(perms(nutzer.stufe4, 4), offenId)
expect(
fuerStufe4.find((b) => b.displayName?.includes('Verborgene Person'))?.personnelFileId,
).toBe(verborgen.id)
})
it('gibt einem Ermittler Zugriff auf die Akte über seiner Stufe', async () => {
// Die Rolle entscheidet: nur wer an der Akte arbeitet, öffnet sie. Als
// Zeuge stünde er zwar in der Liste, käme aber nicht hinein.
await db.caseParticipant.create({
data: { caseId: geheimId, userId: nutzer.stufe1, role: 'INVESTIGATOR' },
})
const p = perms(nutzer.stufe1, 1)
const treffer = await db.case.findMany({
where: { AND: [visibleCaseWhere(p), { id: geheimId }] },
select: { id: true },
})
expect(treffer).toHaveLength(1)
// Und damit auch auf die Verknüpfung, die vorher verborgen war.
const links = await listLinks(p, offenId)
expect(links.map((link) => link.other.id)).toContain(geheimId)
})
})