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

176 lines
5.5 KiB
TypeScript

// @vitest-environment node
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
import { db } from '@/lib/db'
import { enqueueCaseNotification } from '@/lib/notify/enqueue'
import type { Prisma } from '@afis/db'
/**
* Discord kennt unsere Klassifizierung nicht.
*
* Deshalb entscheidet **diese Anwendung**, wer eine Meldung bekommt und was
* darin stehen darf; der Worker liefert nur aus. Diese Tests prüfen genau die
* beiden Zusicherungen, auf denen das beruht.
*/
const url = process.env.DIRECT_DATABASE_URL ?? process.env.DATABASE_URL
const suite = url ? describe : describe.skip
const marke = `notify-${Date.now()}`
suite('Benachrichtigungen', () => {
const nutzer = {
autor: `${marke}-autor`,
stufe1: `${marke}-stufe1`,
stufe4: `${marke}-stufe4`,
aus: `${marke}-aus`,
ausgeschlossen: `${marke}-ausgeschlossen`,
}
let geheimId = ''
let geheimNummer = ''
async function empfaengerVon(kind: string): Promise<string[]> {
const zeilen = await db.notificationOutbox.findMany({
where: { caseId: geheimId, kind },
select: { recipientId: true },
})
return zeilen
.map((z) => z.recipientId)
.filter((id): id is string => id !== null)
.sort()
}
beforeAll(async () => {
const vorlage = await db.documentTemplate.create({
data: { key: `${marke}-vorlage`, version: 1, name: 'Prüfvorlage', fields: [] },
})
// Eine Rolle mit Stufe 4, damit die Rechteauflösung etwas zu tun hat.
const rolle = await db.roleMapping.create({
data: {
discordRoleId: `${marke}-rolle`,
label: 'Prüfstufe 4',
clearanceLevel: 4,
permissions: [],
grantsMembership: true,
category: 'CLEARANCE',
},
})
for (const [name, id] of Object.entries(nutzer)) {
await db.user.create({
data: {
id,
username: id,
displayName: id,
notifyMode: name === 'aus' ? 'OFF' : 'ALL',
...(name === 'stufe4' || name === 'ausgeschlossen'
? { roles: { create: { discordRoleId: rolle.discordRoleId } } }
: {}),
},
})
}
const akte = await db.case.create({
data: {
caseNumber: `Meld-2026-${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`,
title: 'Streng vertraulicher Vorgang',
templateId: vorlage.id,
templateKey: vorlage.key,
createdById: nutzer.autor,
classification: 4,
status: 'OPEN',
},
})
geheimId = akte.id
geheimNummer = akte.caseNumber
await db.caseExclusion.create({
data: { caseId: akte.id, userId: nutzer.ausgeschlossen, reason: 'Betroffener' },
})
})
afterAll(async () => {
await db.$disconnect()
})
it('schreibt nur an die, die die Akte auch sehen dürfen', async () => {
await db.$transaction(async (tx) => {
await enqueueCaseNotification(tx as Prisma.TransactionClient, {
kind: 'entry.new',
caseId: geheimId,
caseNumber: geheimNummer,
classification: 4,
title: 'Streng vertraulicher Vorgang',
actorId: nutzer.autor,
actorName: 'Autor',
})
})
const ziele = await empfaengerVon('entry.new')
// Stufe 4 bekommt die Meldung.
expect(ziele).toContain(nutzer.stufe4)
// Stufe 1 nicht - die Akte gibt es für ihn nicht.
expect(ziele).not.toContain(nutzer.stufe1)
// Wer abgeschaltet hat, bekommt nichts.
expect(ziele).not.toContain(nutzer.aus)
// Der Ausschluss schlägt auch die Stufe.
expect(ziele).not.toContain(nutzer.ausgeschlossen)
// Und der Auslöser bekommt keine Meldung über sich selbst.
expect(ziele).not.toContain(nutzer.autor)
})
it('lässt den Titel ab Stufe 3 weg', async () => {
const zeile = await db.notificationOutbox.findFirstOrThrow({
where: { caseId: geheimId, kind: 'entry.new' },
select: { meta: true },
})
const meta = zeile.meta as { caseNumber?: string; title?: string | null }
// Das Aktenzeichen darf hinaus, der Titel nicht: Discord kennt unsere
// Klassifizierung nicht, der Link führt zur App, die selbst prüft.
expect(meta.caseNumber).toBe(geheimNummer)
expect(meta.title).toBeNull()
})
it('legt eine Zeile für den allgemeinen Kanal an, ohne Empfänger', async () => {
const allgemein = await db.notificationOutbox.findMany({
where: { caseId: geheimId, kind: 'entry.new', recipientId: null },
})
expect(allgemein).toHaveLength(1)
})
it('nimmt bei niedriger Stufe den Titel mit', async () => {
const offen = await db.case.create({
data: {
caseNumber: `Offn-2026-${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`,
title: 'Harmloser Vorgang',
templateId: (await db.documentTemplate.findFirstOrThrow({
where: { key: `${marke}-vorlage` },
})).id,
templateKey: `${marke}-vorlage`,
createdById: nutzer.autor,
classification: 1,
status: 'OPEN',
},
})
await db.$transaction(async (tx) => {
await enqueueCaseNotification(tx as Prisma.TransactionClient, {
kind: 'entry.new',
caseId: offen.id,
caseNumber: offen.caseNumber,
classification: 1,
title: 'Harmloser Vorgang',
actorId: nutzer.autor,
actorName: 'Autor',
})
})
const zeile = await db.notificationOutbox.findFirstOrThrow({
where: { caseId: offen.id },
select: { meta: true },
})
expect((zeile.meta as { title?: string | null }).title).toBe('Harmloser Vorgang')
})
})