import { readFileSync } from 'node:fs' import { describe, expect, it } from 'vitest' /** * Die Beispielkonfiguration darf keine Falle enthalten. * * `.env.example` wird nach `.env` kopiert und dann Zeile für Zeile * ausgefüllt. Was schon einen Wert hat, wird dabei leicht überlesen — und * genau das ist am 31.08.2026 passiert: * * S3_ENDPOINT=https://fsn1.your-objectstorage.com * * Die Zeile war als Beispiel gedacht. Sie stand aber aktiv in der `.env`, * und damit sprach die Anwendung monatelang mit einem fremden Anbieter * statt mit dem Objektspeicher nebenan. Jede Ablage scheiterte mit * `NoSuchBucket`, und die Zugangs-ID des lokalen Speichers ging bei jedem * Versuch nach draußen. * * Verschärfend: Die `docker-compose.yml` hatte längst einen brauchbaren * Standard (`${S3_ENDPOINT:-http://minio:9000}`). Der greift aber nur bei * leerer Variable — die Beispieldatei hebelte ihn aus. Der Mechanismus, * der den Fehler verhindern sollte, war durch die Beispieldatei * wirkungslos. * * Diese Tests halten fest: Ein Wert steht in der Beispieldatei nur, wenn * er für die mitgelieferte Zusammenstellung auch richtig ist. */ const beispiel = readFileSync('.env.example', 'utf8') /** Alle aktiven Zuweisungen - Kommentarzeilen zählen nicht. */ function zuweisungen(): [string, string][] { return beispiel .split('\n') .map((zeile) => zeile.trim()) .filter((zeile) => zeile !== '' && !zeile.startsWith('#')) .map((zeile) => { const stelle = zeile.indexOf('=') return [zeile.slice(0, stelle), zeile.slice(stelle + 1)] as [string, string] }) } /** * Rechner, die zur mitgelieferten Zusammenstellung gehören. Alles andere * ist ein fremder Host und hat in der Beispieldatei nichts verloren. */ const EIGENE_HOSTS = ['localhost', '127.0.0.1', '0.0.0.0', 'minio', 'db'] describe('Beispielkonfiguration', () => { it('zeigt mit keinem aktiven Wert auf einen fremden Host', () => { const fremde = zuweisungen() .filter(([, wert]) => /https?:\/\//.test(wert)) .filter(([, wert]) => { const treffer = wert.match(/https?:\/\/([^:/"']+)/) return treffer ? !EIGENE_HOSTS.includes(treffer[1]!) : false }) .map(([name, wert]) => `${name}=${wert}`) expect( fremde, 'Diese Zeilen zeigen nach draußen. Wer die Datei kopiert und die ' + 'Zeile übersieht, schickt seine Daten an einen fremden Rechner.', ).toEqual([]) }) it('lässt S3_ENDPOINT auskommentiert, damit der Compose-Standard greift', () => { const aktiv = zuweisungen().some(([name]) => name === 'S3_ENDPOINT') expect( aktiv, 'Mit gesetztem S3_ENDPOINT greift ${S3_ENDPOINT:-http://minio:9000} ' + 'aus der docker-compose.yml nicht mehr.', ).toBe(false) }) it('erklärt trotzdem, wann man S3_ENDPOINT braucht', () => { // Auskommentieren allein hilft nicht - dann sucht jemand mit externem // Speicher vergeblich nach der Einstellung. expect(beispiel).toMatch(/#\s*S3_ENDPOINT=/) expect(beispiel).toMatch(/EXTERNER Objektspeicher/) }) it('enthält keine persönliche E-Mail-Adresse', () => { // Stand einmal drin: die Adresse des Betreibers als Vorgabe fuer den // Seed. In einer mitgelieferten Beispieldatei hat sie nichts zu suchen. const adressen = zuweisungen() // Verbindungszeichenfolgen tragen ein "@" vor dem Rechnernamen - // das ist keine E-Mail-Adresse. .filter(([, wert]) => !wert.includes('://')) .filter(([, wert]) => /[\w.+-]+@[\w-]+\.[a-z]{2,}/i.test(wert)) .filter(([, wert]) => !/example\.(de|com|org)/.test(wert)) .map(([name, wert]) => `${name}=${wert}`) expect(adressen).toEqual([]) }) it('findet überhaupt Zuweisungen - sonst prüft der Test nichts', () => { expect(zuweisungen().length).toBeGreaterThan(15) }) })