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
375 Vorkommen in 210 Dateien. Betrifft Kommentare, Oberflaechentexte und die README; an keiner Stelle Bedeutung, nur Zeichensetzung.
161 lines
5.4 KiB
TypeScript
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)
|
|
})
|
|
})
|