import { describe, it } from 'node:test'; import assert from 'node:assert/strict'; import { spawnSync } from 'node:child_process'; import { mkdtempSync, readFileSync, rmSync } from 'node:fs'; import { join } from 'node:path'; import { tmpdir } from 'node:os'; const SCRIPT = 'scripts/ci-test-plan.mjs'; describe('ci-test-plan', () => { it('requires explicit manual opt-in and preprovisions the full workflow job', () => { const workflow = readFileSync('.github/workflows/ci.yml', 'utf8'); assert.match(workflow, /skill_workflow:\s*description:[^\n]+\s*type: boolean\s*default: false/); const job = workflow.split('\n skill-workflow:')[1]; assert.doesNotMatch(job.split('\n steps:')[0], /runner\./, 'runner context is unavailable at job-level env'); assert.match(job, /if: github.event_name == 'workflow_dispatch' && inputs.skill_workflow/); assert.ok(job.indexOf('bun run fetch:engine') < job.indexOf('bun run test:skill-workflow')); assert.ok(job.indexOf('playwright install --with-deps chromium') < job.indexOf('bun run test:skill-workflow')); const protocol = workflow.split('\n skill-behavior:')[1].split('\n skill-workflow:')[0]; assert.match(protocol, /bun run fetch:engine/); assert.doesNotMatch(protocol, /IMPECCABLE_SKILL_BEHAVIOR_MODELS:/, 'protocol coverage must retain the multi-family defaults'); assert.match(protocol, /GOOGLE_CLOUD_API_KEY:/); assert.match(protocol, /ANTHROPIC_API_KEY:/); }); it('keeps docs-only pull requests on the core suite', () => { const outputs = runPlan({ GITHUB_EVENT_NAME: 'pull_request', CI_CHANGED_FILES: 'README.md', }); assert.equal(outputs.core, 'true'); assert.equal(outputs.detector, 'false'); assert.equal(outputs.live, 'false'); assert.equal(outputs.framework, 'false'); assert.equal(outputs.live_e2e, 'false'); assert.equal(outputs.live_e2e_accept_cleanup, 'false'); assert.equal(outputs.live_svelte_adapter_deepseek, 'false'); }); it('routes extension changes to detector tests only', () => { const outputs = runPlan({ GITHUB_EVENT_NAME: 'pull_request', CI_CHANGED_FILES: 'extension/manifest.json', }); assert.equal(outputs.detector, 'true'); assert.equal(outputs.live, 'false'); assert.equal(outputs.framework, 'false'); }); it('routes an engine version bump to every binary-driven lane', () => { const outputs = runPlan({ GITHUB_EVENT_NAME: 'pull_request', CI_CHANGED_FILES: 'ENGINE_VERSION', }); assert.equal(outputs.framework, 'true'); assert.equal(outputs.live_e2e, 'true'); assert.equal(outputs.live_e2e_accept_cleanup, 'true'); assert.equal(outputs.live_svelte_adapter_deepseek, 'true'); assert.equal(outputs.detector, 'false'); }); it('routes skill setup changes to the skill behavior lane', () => { const outputs = runPlan({ GITHUB_EVENT_NAME: 'pull_request', CI_CHANGED_FILES: 'skill/SKILL.src.md', }); assert.equal(outputs.skill_behavior, 'true'); assert.equal(outputs.detector, 'false'); assert.equal(outputs.live, 'false'); }); it('forces deterministic suites on push without forcing opt-in E2E suites', () => { const outputs = runPlan({ GITHUB_EVENT_NAME: 'push', CI_CHANGED_FILES: 'README.md', }); assert.equal(outputs.core, 'true'); assert.equal(outputs.detector, 'true'); assert.equal(outputs.live, 'true'); assert.equal(outputs.framework, 'true'); assert.equal(outputs.cli_remote_e2e, 'false'); assert.equal(outputs.live_e2e, 'false'); assert.equal(outputs.live_e2e_accept_cleanup, 'false'); assert.equal(outputs.live_svelte_adapter_deepseek, 'false'); }); it('enables remote smoke suites on manual dispatch', () => { const outputs = runPlan({ GITHUB_EVENT_NAME: 'workflow_dispatch', CI_CHANGED_FILES: 'README.md', }); assert.equal(outputs.cli_remote_e2e, 'true'); assert.equal(outputs.live_e2e, 'true'); assert.equal(outputs.live_e2e_accept_cleanup, 'true'); assert.equal(outputs.skill_behavior, 'true'); assert.equal(outputs.live_svelte_adapter_deepseek, 'true'); }); it('exposes planned opt-in suite outputs to workflow jobs', () => { const workflow = readFileSync('.github/workflows/ci.yml', 'utf-8'); assert.match(workflow, /live_e2e_accept_cleanup:\s*\$\{\{\s*steps\.plan\.outputs\.live_e2e_accept_cleanup\s*\}\}/); assert.match(workflow, /live_svelte_adapter_deepseek:\s*\$\{\{\s*steps\.plan\.outputs\.live_svelte_adapter_deepseek\s*\}\}/); assert.match(workflow, /live-e2e-accept-cleanup:/); assert.match(workflow, /live-svelte-adapter-deepseek:/); }); it('schedule events run only the deterministic suites plus the full live-e2e matrix', () => { const outputs = runPlan({ GITHUB_EVENT_NAME: 'schedule' }); assert.equal(outputs.live_e2e, 'true'); assert.equal(outputs.live_e2e_accept_cleanup, 'false'); assert.equal(outputs.skill_behavior, 'false'); assert.equal(outputs.live_svelte_adapter_deepseek, 'false'); assert.equal(outputs.cli_remote_e2e, 'false'); assert.equal(outputs.core, 'true'); assert.equal(outputs.detector, 'true'); assert.equal(outputs.live, 'true'); assert.equal(outputs.framework, 'true'); }); }); function runPlan(env) { const tmp = mkdtempSync(join(tmpdir(), 'impeccable-ci-plan-')); const outputPath = join(tmp, 'github-output'); try { const result = spawnSync(process.execPath, [SCRIPT], { cwd: process.cwd(), encoding: 'utf-8', env: { ...process.env, GITHUB_OUTPUT: outputPath, ...env, }, }); assert.equal(result.status, 0, result.stderr || result.stdout); return Object.fromEntries( readFileSync(outputPath, 'utf-8') .trim() .split(/\r?\n/) .filter(Boolean) .map((line) => line.split('=')), ); } finally { rmSync(tmp, { recursive: true, force: true }); } }