import { describe, expect, it } from 'vitest' import { readFileSync, readdirSync, statSync } from 'node:fs' import { join } from 'node:path' import { de } from '@/lib/i18n/de' import { OPT_IN_PERMISSIONS, PERMISSIONS } from '@/lib/auth/permissions' describe('Sprachkonvention', () => { 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', () => { const flat = JSON.stringify(de) expect(flat).not.toMatch(/!/) expect(flat.toLowerCase()).not.toMatch(/erfolgreich|entschuldigung/) }) it('haelt deutsche Oberflaechentexte aus dem JSX heraus', () => { // Umlaute und Eszett sind der billigste verlaessliche Marker fuer einen // deutschen Text. Alles Sichtbare gehoert nach lib/i18n/de.ts, siehe README. // // Geprueft werden Komponenten und Seiten (.tsx) sowie Server Actions: // deren Fehlermeldungen landen in der Oberflaeche. Kommentare zaehlen // nicht, die duerfen und sollen deutsch sein. const offenders: string[] = [] const roots = ['app', 'components'] function stripComments(source: string): string { // Blockkommentare durch gleich viele Zeilenumbrueche ersetzen, damit die // Zeilennummern stimmen bleiben. return source .replace(/\/\*[\s\S]*?\*\//g, (match) => match.replace(/[^\n]/g, ' ')) .replace(/\/\/.*$/gm, '') } function walk(dir: string) { for (const name of readdirSync(dir)) { const path = join(dir, name) if (statSync(path).isDirectory()) { walk(path) continue } if (!name.endsWith('.tsx') && name !== 'actions.ts') continue const source = stripComments(readFileSync(path, 'utf8')) for (const [index, line] of source.split('\n').entries()) { if (/[äöüÄÖÜß]/.test(line)) { offenders.push(`${path}:${index + 1}`) } } } } for (const root of roots) walk(join(process.cwd(), root)) expect(offenders).toEqual([]) }) }) describe('Rechte-Enum', () => { it('kennt die vier Rechte, die Phase 1 anlegen muss', () => { for (const permission of [ 'entry.edit_own', 'entry.edit_any', 'entry.retract', 'case.edit_closed', ]) { expect(PERMISSIONS).toContain(permission) } }) it('haelt die nachtraeglichen Eingriffe im Auslieferungszustand zurueck', () => { // Der Leiter schaltet sie bewusst frei, siehe docs/03-auth-und-rechte.md. // Gemeinsames Merkmal: sie greifen in bereits Geschriebenes ein. expect([...OPT_IN_PERMISSIONS].sort()).toEqual([ 'document.retract', 'entry.edit_any', 'entry.edit_own', 'entry.retract', ]) }) })