import { readFileSync } from 'node:fs' import { describe, expect, it } from 'vitest' /** * Die Installationsanleitung muss ausführbar sein, nicht nur lesbar. * * Anlass ist eine Fehlersuche vom 31.08.2026. Drei Stellen der Anleitung * haben dabei Zeit gekostet — keine davon war falsch formuliert, alle drei * waren schlicht nicht durchdacht: * * 1. `mc alias set` wurde einmalig gesetzt. Der Alias liegt in `/root/.mc` * **im Behälter** und ist nach jedem `docker compose up -d minio` weg. * `mc ls lokal` liefert dann kommentarlos nichts — man hält seine * Dokumente für verschwunden. * 2. Der Bucket-Name stand fest im Befehl, die Anwendung liest ihn aus * `S3_BUCKET`. Weichen sie ab, zeigen beide aneinander vorbei. * 3. `set -a && source .env` exportiert alle Werte in die Shell — und die * haben danach Vorrang vor der `.env`-Datei. Wer später etwas ändert * und neu startet, sieht keine Wirkung. * * Diese Prüfungen lesen den Text. Sie können die Anleitung nicht * ausführen, aber sie schlagen an, sobald jemand in eine der Fallen * zurückfällt. */ const anleitung = readFileSync('docs/INSTALLATION.md', 'utf8') /** Alle Zeilen mit einem mc-Aufruf, ohne Fließtext und Zitate. */ function mcZeilen(): string[] { return anleitung .split('\n') .filter((zeile) => /\bmc (ls|mb|mirror|cp|rm|stat)\b/.test(zeile)) .filter((zeile) => !zeile.trim().startsWith('>')) } describe('Installationsanleitung', () => { it('setzt bei jedem mc-Aufruf den Alias selbst', () => { // Sonst funktioniert der Befehl nur, solange der Behaelter nicht neu // erstellt wurde - und das sagt einem niemand. const abschnitte = anleitung.split('```') const ohneAlias = abschnitte .filter((block) => /\bmc (ls|mb|mirror|cp|rm|stat)\b/.test(block)) .filter((block) => !block.includes('mc alias set')) expect( ohneAlias.length, 'Ein mc-Befehl ohne "mc alias set" im selben Block läuft nur, solange ' + 'der MinIO-Behälter nicht neu erstellt wurde.', ).toBe(0) }) it('verdrahtet den Namen des Ablagebereichs nicht fest', () => { const festVerdrahtet = mcZeilen().filter((zeile) => /lokal\/betreuungs-cockpit/.test(zeile)) expect( festVerdrahtet, 'Der Name gehört aus $S3_BUCKET - sonst zeigen Anwendung und ' + 'Ablagebereich aneinander vorbei, sobald jemand ihn ändert.', ).toEqual([]) }) it('empfiehlt kein "source .env" mehr', () => { // Exportierte Werte ueberstimmen danach jedes "docker compose up". const zeilen = anleitung .split('\n') .filter((zeile) => !zeile.trim().startsWith('>')) .filter((zeile) => !zeile.trim().startsWith('#')) .filter((zeile) => /source \.env/.test(zeile)) expect(zeilen).toEqual([]) }) it('erklärt trotzdem, warum es nicht empfohlen wird', () => { // Weglassen allein hilft nicht: Wer den Kniff von woanders kennt, // benutzt ihn weiter. expect(anleitung).toMatch(/Vorrang vor der/) }) it('sagt, wo die Daten liegen', () => { expect(anleitung).toMatch(/## Wo deine Daten liegen/) expect(anleitung).toMatch(/docker volume inspect/) expect(anleitung).toMatch(/minio-daten/) expect(anleitung).toMatch(/postgres-daten/) }) it('zeigt, wie man Akte und Speicher abgleicht', () => { // Die wichtigste Kontrolle: Zu jedem Akteneintrag muss eine Datei // gehoeren, sonst belegt eine Pruefsumme ein Dokument, das es nicht // mehr gibt. expect(anleitung).toMatch(/objektSchluessel/) expect(anleitung).toMatch(/comm -23/) }) it('nennt die Schlüssel, ohne die nichts mehr lesbar ist', () => { const abschnitt = anleitung.slice(anleitung.indexOf('## Wo deine Daten liegen')) for (const schluessel of ['ENC_KEY_V1', 'SICHERUNG_PASSWORT', 'MINIO_KMS_SECRET_KEY']) { expect(abschnitt, `${schluessel} fehlt in der Aufzählung`).toContain(schluessel) } }) it('weist docker-compose.override.yml als Ort für eigene Anpassungen aus', () => { expect(anleitung).toMatch(/docker-compose\.override\.yml/) expect(readFileSync('.gitignore', 'utf8')).toMatch(/docker-compose\.override\.yml/) }) it('behauptet nicht mehr, die Dokumente seien ungesichert', () => { // Stand bis v1.11.1 so drin und stimmt seit der Sicherung nicht mehr. expect(anleitung).not.toMatch(/Was das Backup nicht enthält:.*Dateien im Objektspeicher/s) }) })