diff --git a/packages/db/prisma/seed.ts b/packages/db/prisma/seed.ts index 50836a9..ea1f476 100644 --- a/packages/db/prisma/seed.ts +++ b/packages/db/prisma/seed.ts @@ -11,17 +11,21 @@ import { createPrismaClient } from '../src/index.js' const db = createPrismaClient() /** - * Auslieferungszustand nach docs/01-produkt.md. Ab Stufe 3 ist die Existenz - * einer Akte für niedrigere Stufen verborgen, darunter nur der Inhalt gesperrt. - * Der Leiter kann das im Adminbereich ändern. + * Auslieferungszustand. Ab Stufe 3 ist die Existenz einer Akte für niedrigere + * Stufen verborgen, darunter ist nur der Inhalt gesperrt. Der Leiter kann das + * im Adminbereich ändern. + * + * Die Namen entsprechen den Zugangsrollen auf dem Discord und bleiben deshalb + * englisch, siehe docs/13-entscheidungen.md. Stufe 0 hat keine eigene + * Zugangsrolle: sie gilt für jedes AfiS-Mitglied ohne eine solche Rolle. */ const CLEARANCE_LEVELS = [ - { level: 0, name: 'Offen', hiddenBelow: false, colorToken: 'stamp-0' }, - { level: 1, name: 'Intern', hiddenBelow: false, colorToken: 'stamp-1' }, - { level: 2, name: 'Vertraulich', hiddenBelow: false, colorToken: 'stamp-2' }, - { level: 3, name: 'Geheim', hiddenBelow: true, colorToken: 'stamp-3' }, - { level: 4, name: 'Streng geheim', hiddenBelow: true, colorToken: 'stamp-4' }, - { level: 5, name: 'Leitungssache', hiddenBelow: true, colorToken: 'stamp-5' }, + { level: 0, name: 'Unclassified', hiddenBelow: false, colorToken: 'stamp-0' }, + { level: 1, name: 'Restricted Access', hiddenBelow: false, colorToken: 'stamp-1' }, + { level: 2, name: 'Confidential Access', hiddenBelow: false, colorToken: 'stamp-2' }, + { level: 3, name: 'Sensitive Access', hiddenBelow: true, colorToken: 'stamp-3' }, + { level: 4, name: 'Critical Operations', hiddenBelow: true, colorToken: 'stamp-4' }, + { level: 5, name: 'Executive Oversight', hiddenBelow: true, colorToken: 'stamp-5' }, ] async function main(): Promise { diff --git a/packages/db/tests/auslieferungszustand.test.ts b/packages/db/tests/auslieferungszustand.test.ts new file mode 100644 index 0000000..9738c57 --- /dev/null +++ b/packages/db/tests/auslieferungszustand.test.ts @@ -0,0 +1,52 @@ +import { afterAll, beforeAll, describe, expect, it } from 'vitest' +import { createPrismaClient, type PrismaClient } from '../src/index.js' + +/** + * Prüft den Zustand, den `pnpm db:seed` herstellt. Das ist kein Testinhalt, + * sondern Konfiguration, ohne die die Anwendung nicht sinnvoll läuft. + */ +const url = process.env.DIRECT_DATABASE_URL ?? process.env.DATABASE_URL +const suite = url ? describe : describe.skip + +let db: PrismaClient + +suite('Auslieferungszustand', () => { + beforeAll(() => { + db = createPrismaClient(url) + }) + + afterAll(async () => { + await db.$disconnect() + }) + + it('kennt sechs Freigabestufen, benannt nach den Zugangsrollen', async () => { + const levels = await db.clearanceLevelConfig.findMany({ orderBy: { level: 'asc' } }) + expect(levels.map((l) => l.level)).toEqual([0, 1, 2, 3, 4, 5]) + expect(levels.map((l) => l.name)).toEqual([ + 'Unclassified', + 'Restricted Access', + 'Confidential Access', + 'Sensitive Access', + 'Critical Operations', + 'Executive Oversight', + ]) + }) + + it('verbirgt die Existenz ab Stufe 3, darunter nicht', async () => { + const levels = await db.clearanceLevelConfig.findMany({ orderBy: { level: 'asc' } }) + expect(levels.map((l) => l.hiddenBelow)).toEqual([false, false, false, true, true, true]) + }) + + it('liefert die Änderungsrichtlinie mit Karenzzeit und ohne Markierung aus', async () => { + const setting = await db.appSetting.findUnique({ where: { id: 1 } }) + expect(setting?.entryEditPolicy).toBe('GRACE_WINDOW') + expect(setting?.entryEditGraceMinutes).toBe(15) + expect(setting?.entryEditMarkGrace).toBe(false) + }) + + it('ordnet im Auslieferungszustand keiner Rolle Rechte zu', async () => { + // entry.edit_own, entry.edit_any und entry.retract schaltet der Leiter + // bewusst frei. Siehe docs/03-auth-und-rechte.md. + expect(await db.roleMapping.count()).toBe(0) + }) +}) diff --git a/web/components/StampMark.tsx b/web/components/StampMark.tsx index 3fc76f9..8ec4161 100644 --- a/web/components/StampMark.tsx +++ b/web/components/StampMark.tsx @@ -1,5 +1,3 @@ -import { de } from '@/lib/i18n/de' - const STAMP_COLOR: Record = { 0: 'var(--color-stamp-0)', 1: 'var(--color-stamp-1)', @@ -17,8 +15,11 @@ export function stampColor(level: number): string { * Stempelmarke oben rechts am Objekt. Der Stempelcharakter kommt aus dem * schmalen Schnitt, der Rahmenlinie und der Farbe, nicht aus Grossbuchstaben. * Die Stufe steht zusaetzlich als Text, nicht nur als Farbe. + * + * `name` kommt aus ClearanceLevelConfig und ist im Adminbereich aenderbar, + * deshalb wird er hereingereicht statt aus lib/i18n/de.ts gelesen. */ -export function StampMark({ level }: { level: number }) { +export function StampMark({ level, name }: { level: number; name: string }) { return ( {level} - {de.clearance.names[level] ?? de.common.unknown} + {name} ) } diff --git a/web/lib/i18n/de.ts b/web/lib/i18n/de.ts index 3804fed..d0c864e 100644 --- a/web/lib/i18n/de.ts +++ b/web/lib/i18n/de.ts @@ -47,14 +47,9 @@ export const de = { label: 'Freigabestufe', classification: 'Klassifizierung', level: (n: number) => `Stufe ${n}`, - names: { - 0: 'Offen', - 1: 'Intern', - 2: 'Vertraulich', - 3: 'Geheim', - 4: 'Streng geheim', - 5: 'Leitungssache', - } as Record, + // Die Bezeichnungen der Stufen stehen nicht hier, sondern in + // ClearanceLevelConfig: sie sind im Adminbereich änderbar und folgen den + // Zugangsrollen des Discord-Servers. Siehe docs/13-entscheidungen.md. locked: 'Inhalt gesperrt', lockedHint: (n: number) => `Sichtbar ab Stufe ${n}`, redacted: 'Geschwärzt', diff --git a/web/tests/i18n.test.ts b/web/tests/i18n.test.ts index 0283390..439ea87 100644 --- a/web/tests/i18n.test.ts +++ b/web/tests/i18n.test.ts @@ -4,11 +4,10 @@ import { join } from 'node:path' import { de } from '@/lib/i18n/de' describe('Sprachkonvention', () => { - it('kennt alle sechs Freigabestufen', () => { - for (let level = 0; level <= 5; level++) { - expect(de.clearance.names[level]).toBeTypeOf('string') - } - expect(de.clearance.names[3]).toBe('Geheim') + it('haelt die Stufenbezeichnungen aus den Texten heraus', () => { + // Sie stehen in ClearanceLevelConfig, sind im Adminbereich aenderbar und + // folgen den Zugangsrollen des Discord-Servers. + expect(de.clearance).not.toHaveProperty('names') }) it('verzichtet auf Ausrufezeichen und Entschuldigungen', () => {