import { readFileSync } from 'node:fs' import { describe, expect, it } from 'vitest' /** * Was die Anwendung liest, muss der Behälter auch bekommen. * * Die `docker-compose.yml` zählt die Umgebungsvariablen des Dienstes `app` * einzeln auf — sie hat kein `env_file:`. Wer einen neuen Wert einführt, * ihn in `.env.example` dokumentiert und in `env.ts` ausliest, hat damit * noch nichts erreicht: Im Behälter kommt er nicht an. * * Genau das ist mit v1.11.0 passiert. `ERSTEINRICHTUNG_GEHEIMNIS` und * `VERTRAUE_PROXY` standen in der Anleitung, in der Beispieldatei und im * Code — nur nicht in der Compose-Datei. Die Anleitung war damit schlicht * falsch, und der Fehler war von außen nicht zu sehen: `VERTRAUE_PROXY` * fehlt still, die Anmeldesperre zählt dann eine Dimension weniger. * * Dieser Test liest beide Seiten und vergleicht sie. Er prüft kein * Verhalten, sondern die Verpackung — wie `skripte.test.ts`. */ const compose = readFileSync('docker-compose.yml', 'utf8') const envDatei = readFileSync('src/lib/env.ts', 'utf8') const beispiel = readFileSync('.env.example', 'utf8') /** * Alles, was `env.ts` über seine Helfer ausliest. * * `process.env.X` direkt wird mitgenommen, ebenso `pflicht('X')`, * `zahl('X', …)`, `schalter('X', …)` und `schluessel32('X')`. */ function gelesen(): Set { const namen = new Set() for (const treffer of envDatei.matchAll(/process\.env\.([A-Z_0-9]+)/g)) { namen.add(treffer[1]!) } for (const treffer of envDatei.matchAll( /(?:pflicht|zahl|schalter|schluessel32)\('([A-Z_0-9]+)'/g, )) { namen.add(treffer[1]!) } return namen } /** Die Variablen, die der Dienst `app` durchreicht. */ function durchgereicht(): Set { const abschnitt = compose.slice(compose.indexOf('\n app:'), compose.indexOf('\n sicherung:')) const namen = new Set() for (const treffer of abschnitt.matchAll(/^ {6}([A-Z_0-9]+):/gm)) { namen.add(treffer[1]!) } return namen } /** * Was absichtlich nicht durchgereicht wird - jeweils mit Grund. * * Wer hier etwas ergänzt, nimmt es bewusst aus der Prüfung. Das ist * erlaubt, aber es soll dabeistehen, warum. */ const AUSNAHMEN = new Map([ ['NODE_ENV', 'wird im Dienst gesetzt, kommt nicht aus der .env'], ['NEXT_PUBLIC_GIT_COMMIT', 'Bauargument, steckt schon im Abbild'], ['NEXT_PUBLIC_BUILD_ZEIT', 'Bauargument, steckt schon im Abbild'], ['DATEI_SPEICHER_PFAD', 'nur fuer den Dateitreiber; im Behaelter laeuft s3'], ['TESSERACT_BEFEHL', 'Standard "tesseract" stimmt im Abbild immer'], ]) describe('Umgebungsvariablen erreichen den Behälter', () => { it('reicht jede Variable durch, die die Anwendung ausliest', () => { const fehlend = [...gelesen()] .filter((name) => !durchgereicht().has(name)) .filter((name) => !AUSNAHMEN.has(name)) // ENC_KEY_V2, V3 ... werden dynamisch gesucht; nur V1 ist Pflicht. .filter((name) => !/^ENC_KEY_V\d+$/.test(name)) .sort() expect( fehlend, 'Diese Werte liest die Anwendung, aber die docker-compose.yml gibt sie ' + 'dem Dienst "app" nicht mit. In der .env stehen sie dann wirkungslos.', ).toEqual([]) }) it('erklärt jede Ausnahme', () => { for (const [name, grund] of AUSNAHMEN) { expect(grund.length, `Ausnahme ${name} ohne Begründung`).toBeGreaterThan(10) } }) it('dokumentiert die beiden Werte aus v1.11.0 in der Beispieldatei', () => { // Sie waren der Anlass. Verschwinden sie aus .env.example, merkt es // sonst niemand, bis jemand eine Neuinstallation versucht. expect(beispiel).toMatch(/ERSTEINRICHTUNG_GEHEIMNIS=/) expect(beispiel).toMatch(/VERTRAUE_PROXY=/) }) it('lässt VERTRAUE_PROXY ohne Vorgabewert', () => { // Die strengere Einstellung bleibt die Voreinstellung. Ein Vorgabewert // "true" waere bequem und falsch: Ohne Proxy ist der weitergereichte // Kopf frei erfunden. expect(compose).toMatch(/VERTRAUE_PROXY: \$\{VERTRAUE_PROXY:-\}/) }) it('findet überhaupt etwas - sonst prüft der Test nichts', () => { // Ein stiller Ausfall der Erkennung waere schlimmer als eine Luecke: // Der Test staende gruen da und pruefte nichts. expect(gelesen().size).toBeGreaterThan(15) expect(durchgereicht().size).toBeGreaterThan(15) }) })