/** * Gate on the wiring of the --keep exit path (celilo#1313). * * `cele2e run --keep` kept the per-test project but tore the SHARED * infrastructure down anyway, so the kept stack had no DNS, no CA and no * registry — half a system, in the one mode the debugging method prescribes. * The mechanism was a wiring bug: the end-of-run `stopSharedInfra()` call * carried no flagKeep guard, while the per-test path right above it was * guarded. Like the sibling exit-cleanup gate, the unit gate is on the wiring; * the behavioral proof (one cheap suite run with --keep, shared containers * inspected afterwards) is an e2e run and needs the rig. * * Read the source instead of importing the runner: main() runs at import time * (module top-level), so importing it would execute a docker run. */ import { expect, test } from 'bun:test'; import { readFileSync } from 'node:fs'; import { dirname, join } from 'node:path'; import { fileURLToPath } from 'node:url'; const SRC = readFileSync(join(dirname(fileURLToPath(import.meta.url)), 'runner.ts'), 'utf-8'); /** The end-of-run block, from the results summary to the last-run write. */ function endOfRunBlock(): string { return SRC.slice(SRC.indexOf('saveTiming(history);\n saveBlockTiming(blockTiming);')); } test('end-of-run stopSharedInfra is guarded by flagKeep (celilo#1313)', () => { // Today the call ran unconditionally, so a --keep run destroyed exactly the // shared infra the kept stack depends on. const block = endOfRunBlock(); const guardAt = block.indexOf('if (flagKeep)'); const stopAt = block.indexOf('stopSharedInfra()'); expect(guardAt).toBeGreaterThan(-1); expect(stopAt).toBeGreaterThan(-1); // The unconditional call sat BEFORE any guard; after the fix the guard // precedes it, so the call is inside the flagKeep branch. expect(guardAt).toBeLessThan(stopAt); }); test('a --keep exit names what was kept, not just what stopped (celilo#1313)', () => { // "Stopping shared infrastructure..." printed while the user had asked to // keep things, and nothing said DNS and certs would be missing. The kept // path must name the shared pieces (DNS, CA, registry) and the one teardown // command that reaps them. const block = endOfRunBlock(); const keepPath = block.slice(block.indexOf('if (flagKeep)'), block.indexOf('stopSharedInfra()')); expect(keepPath).toContain('DNS'); expect(keepPath).toContain('cele2e down'); }); test('the kept lock marks under --keep even when no project was captured', () => { // With shared infra left up, a --keep run that failed to capture a project // (startNetwork failure) still leaves a live stack. The kept lock is the // instrument that says so: the next run's live-stack guard reads the same // state, but the refusal reads as contention rather than a deliberate kept // stack unless the lock marks. Idempotent with the per-test markKept(). const block = endOfRunBlock(); const keepPath = block.slice(block.indexOf('if (flagKeep)'), block.indexOf('} else')); expect(keepPath).toContain('markKept()'); }); test('SIGINT still stops shared infra unconditionally (abort is not a kept stack)', () => { // Pinned as intended behavior: Ctrl-C is an abort, the persistent project // was never written and no kept lock was marked, so tearing the shared // pieces down is right. The next run's live-stack guard names the remedy // for anything the abort left live. const sigint = SRC.slice(SRC.indexOf("process.on('SIGINT'"), SRC.indexOf('for (let i = 0;')); expect(sigint).toContain('stopSharedInfra()'); expect(sigint).not.toContain('flagKeep'); });