diff --git a/scripts/test_approved_plan_continuity_gate.mjs b/scripts/test_approved_plan_continuity_gate.mjs new file mode 100644 index 0000000..e3775d7 --- /dev/null +++ b/scripts/test_approved_plan_continuity_gate.mjs @@ -0,0 +1,114 @@ +#!/usr/bin/env node +import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; +import { spawnSync } from 'node:child_process'; +import { fileURLToPath } from 'node:url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const gateScript = path.join(__dirname, 'approved_plan_continuity_gate.mjs'); + +function createFixture(files = {}) { + const root = mkdtempSync(path.join(os.tmpdir(), 'approved-plan-continuity-')); + + for (const [relativePath, content] of Object.entries(files)) { + const filePath = path.join(root, relativePath); + mkdirSync(path.dirname(filePath), { recursive: true }); + writeFileSync(filePath, typeof content === 'string' ? content : `${JSON.stringify(content, null, 2)}\n`); + } + + return { + root, + path(...segments) { + return path.join(root, ...segments); + }, + cleanup() { + rmSync(root, { recursive: true, force: true }); + }, + }; +} + +function runGate({ args = [], stdin = null } = {}) { + const result = spawnSync(process.execPath, [gateScript, ...args], { + input: stdin, + encoding: 'utf8', + }); + + let json = null; + if (result.stdout && result.stdout.trim()) { + try { + json = JSON.parse(result.stdout); + } catch { + json = null; + } + } + + return { + status: result.status, + stdout: result.stdout, + stderr: result.stderr, + json, + }; +} + +const tests = [ + { + name: 'skeleton: gate script responds with placeholder envelope when given fixture input', + run() { + const fixture = createFixture({ + 'input.json': { + planId: 'plan-skeleton', + currentTask: 'task-5', + }, + }); + + try { + const result = runGate({ + args: ['--compact', '--input', fixture.path('input.json')], + }); + + if (result.status !== 0 && result.status !== null) { + throw new Error(`expected controlled execution, got status=${result.status}\n${result.stderr || result.stdout}`); + } + + if (!result.json || typeof result.json !== 'object') { + throw new Error(`expected JSON output\nstdout=${result.stdout}`); + } + + if (result.json.gate !== 'approved_plan_continuity') { + throw new Error(`expected gate=approved_plan_continuity, got ${JSON.stringify(result.json.gate)}`); + } + } finally { + fixture.cleanup(); + } + }, + }, +]; + +const results = []; +let failed = false; + +for (const test of tests) { + try { + test.run(); + results.push({ test: test.name, ok: true }); + } catch (error) { + failed = true; + results.push({ + test: test.name, + ok: false, + error: error instanceof Error ? error.message : String(error), + }); + } +} + +const summary = { + total: tests.length, + passed: results.filter((entry) => entry.ok).length, + failed: results.filter((entry) => !entry.ok).length, +}; + +process.stdout.write(`${JSON.stringify({ summary, results }, null, 2)}\n`); + +if (failed) process.exit(1);