feat(quick-260909-dgj): Migrationsverbindung von Laufzeitverbindung trennen (Task 2/4)

- apps/api/scripts/migrate-and-start.sh: TESSERA_MIGRATE_DATABASE_URL fuer
  den Migrationsschritt, DATABASE_URL bleibt unveraendert fuer den
  Laufzeitschritt; leer/nicht gesetzt = bisheriges Verhalten
- Dockerfile kopiert scripts/ und ruft das Skript als CMD auf; exec statt
  &&-Verkettung, damit Signale den Node-Prozess erreichen
- docker-compose.yml/.prod.yml reichen TESSERA_MIGRATE_DATABASE_URL durch
  (leerer Vorgabewert); docker-compose.dev.yml/.ci.yml unveraendert
- .env.example erklaert beide Variablen mit Platzhaltern, Umstellung bleibt
  auskommentiert
- 5 Tests in start-script.spec.ts (rot vor dem Skript, jetzt gruen)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FYZcd3SSmo14QTqWx2KKzU
This commit is contained in:
2026-09-09 10:00:45 +02:00
parent b3375ae016
commit a5f99e52e3
6 changed files with 159 additions and 1 deletions
+96
View File
@@ -0,0 +1,96 @@
import { execFileSync } from 'node:child_process';
import { join } from 'node:path';
import { describe, expect, it } from 'vitest';
/**
* Prueft apps/api/scripts/migrate-and-start.sh ausschliesslich ueber die
* Ausgabebetriebsart --print-plan — es wird weder Prisma noch Node
* aufgerufen, keine Datenbankverbindung aufgebaut (Task 2, WINDOWS #18).
*/
const SCRIPT_PATH = join(__dirname, '../../scripts/migrate-and-start.sh');
const SECRET_MARKER = 'tessera-test-secret-marker-9f3c1e';
function runScript(env: Record<string, string>): { stdout: string; status: number } {
try {
const stdout = execFileSync('sh', [SCRIPT_PATH, '--print-plan'], {
env,
encoding: 'utf-8',
});
return { stdout, status: 0 };
} catch (err: any) {
return { stdout: (err.stdout ?? '').toString(), status: err.status ?? 1 };
}
}
describe('migrate-and-start.sh --print-plan', () => {
it('Test 1 (Rueckwaertsvertraeglichkeit): ohne TESSERA_MIGRATE_DATABASE_URL melden beide Schritte DATABASE_URL', () => {
const { stdout, status } = runScript({
PATH: process.env.PATH ?? '',
DATABASE_URL: `postgresql://x:${SECRET_MARKER}@db:5432/tessera`,
});
expect(status).toBe(0);
expect(stdout).toContain('DATABASE_URL');
// Kein Wert, nur der Variablenname darf erscheinen
expect(stdout).not.toContain(SECRET_MARKER);
// beide Zeilen (Migration + Laufzeit) nennen DATABASE_URL als Quelle
const dbUrlMentions = (stdout.match(/DATABASE_URL/g) ?? []).length;
expect(dbUrlMentions).toBeGreaterThanOrEqual(2);
});
it('Test 2 (Trennung): mit gesetztem TESSERA_MIGRATE_DATABASE_URL nennt der Migrationsschritt die Migrationsvariable, der Laufzeitschritt bleibt bei DATABASE_URL', () => {
const { stdout, status } = runScript({
PATH: process.env.PATH ?? '',
DATABASE_URL: `postgresql://app:${SECRET_MARKER}@db:5432/tessera`,
TESSERA_MIGRATE_DATABASE_URL: `postgresql://owner:${SECRET_MARKER}@db:5432/tessera`,
});
expect(status).toBe(0);
expect(stdout).toContain('TESSERA_MIGRATE_DATABASE_URL');
expect(stdout).toContain('DATABASE_URL');
expect(stdout).not.toContain(SECRET_MARKER);
});
it('Test 3 (leer zaehlt als nicht gesetzt): TESSERA_MIGRATE_DATABASE_URL="" verhaelt sich wie Test 1', () => {
const { stdout, status } = runScript({
PATH: process.env.PATH ?? '',
DATABASE_URL: `postgresql://x:${SECRET_MARKER}@db:5432/tessera`,
TESSERA_MIGRATE_DATABASE_URL: '',
});
expect(status).toBe(0);
expect(stdout).not.toContain('TESSERA_MIGRATE_DATABASE_URL wird verwendet');
const dbUrlMentions = (stdout.match(/DATABASE_URL/g) ?? []).length;
expect(dbUrlMentions).toBeGreaterThanOrEqual(2);
});
it('Test 4 (kein Geheimnisabfluss): keiner der beiden Verbindungswerte erscheint in der Ausgabe', () => {
const { stdout } = runScript({
PATH: process.env.PATH ?? '',
DATABASE_URL: `postgresql://app:${SECRET_MARKER}@db:5432/tessera`,
TESSERA_MIGRATE_DATABASE_URL: `postgresql://owner:${SECRET_MARKER}b@db:5432/tessera`,
});
expect(stdout).not.toContain(SECRET_MARKER);
});
it('Test 5 (fehlende Angabe): ohne DATABASE_URL bricht das Skript mit Ende-Code ungleich 0 ab und nennt den Variablennamen', () => {
let stdout = '';
let stderr = '';
let status = 0;
try {
stdout = execFileSync('sh', [SCRIPT_PATH, '--print-plan'], {
env: { PATH: process.env.PATH ?? '' },
encoding: 'utf-8',
});
} catch (err: any) {
status = err.status ?? 1;
stdout = (err.stdout ?? '').toString();
stderr = (err.stderr ?? '').toString();
}
expect(status).not.toBe(0);
expect(stdout + stderr).toContain('DATABASE_URL');
});
});