blob: 1969468cec4d7b2b04790adad562b1d4615c39b1 [file]
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import test from 'node:test';
import { formatGitHubOutputs, planTests } from './ci-test-plan.mjs';
const dirs = [
'packages/core',
'packages/storage',
'packages/runtime',
'packages/runtime-host',
'packages/cli',
'packages/ui',
'apps/desktop',
];
const graph = {
dirs,
dependents: new Map([
['packages/core', new Set(['packages/storage', 'packages/runtime'])],
['packages/storage', new Set(['packages/runtime', 'packages/runtime-host'])],
['packages/runtime', new Set(['packages/runtime-host', 'packages/cli', 'apps/desktop'])],
['packages/runtime-host', new Set(['packages/cli', 'apps/desktop'])],
['packages/cli', new Set()],
['packages/ui', new Set(['apps/desktop'])],
['apps/desktop', new Set()],
]),
testDirs: new Set(dirs),
};
test('documentation-only changes do not select code validation', () => {
const plan = planTests(['docs/ci.md'], { graph });
assert.equal(plan.code, false);
assert.equal(plan.astryxSurface, false);
assert.deepEqual(plan.workspaces, []);
});
test('the Astryx inventory can run without selecting the code suite', () => {
const plan = planTests(['docs/astryx-surface-file-inventory.md'], { graph });
assert.equal(plan.code, false);
assert.equal(plan.astryxSurface, true);
});
test('desktop renderer changes retain Electron and Storybook coverage', () => {
const plan = planTests(['apps/desktop/src/renderer/app.tsx'], { graph });
assert.equal(plan.code, true);
assert.equal(plan.e2e, true);
assert.equal(plan.storybook, true);
assert.equal(plan.astryxSurface, true);
assert.deepEqual(plan.standardWorkspaces, ['apps/desktop']);
});
test('Storybook catalog changes avoid real-window E2E and workspace tests', () => {
const plan = planTests(['apps/desktop/stories/settings.stories.tsx'], { graph });
assert.equal(plan.code, true);
assert.equal(plan.e2e, false);
assert.equal(plan.storybook, true);
assert.deepEqual(plan.workspaces, []);
});
test('runtime changes retain the dedicated Runtime Host lane', () => {
const plan = planTests(['packages/runtime/src/runtime.ts'], { graph });
assert.equal(plan.runtimeHost, true);
assert.equal(plan.runtimeSandbox, true);
assert.deepEqual(plan.standardWorkspaces, ['packages/runtime', 'packages/cli', 'apps/desktop']);
});
test('CLI release inputs select installed-package validation', () => {
const plan = planTests(['packages/runtime/src/runtime.ts'], { graph });
assert.equal(plan.cliPackage, true);
});
test('release metadata selects installed-package validation', () => {
assert.equal(planTests(['LICENSE'], { graph }).cliPackage, true);
});
test('desktop-only changes skip installed-package validation', () => {
assert.equal(planTests(['apps/desktop/src/main.ts'], { graph }).cliPackage, false);
});
test('full selection covers every live surface', () => {
const plan = planTests([], { graph, forceFull: true });
assert.equal(plan.full, true);
assert.equal(plan.cliPackage, true);
assert.equal(plan.code, true);
assert.equal(plan.e2e, true);
assert.equal(plan.storybook, true);
assert.equal(plan.runtimeHost, true);
assert.deepEqual(plan.workspaces, dirs);
});
test('unknown top-level code fails safe to full selection', () => {
assert.equal(planTests(['unknown.config'], { graph }).full, true);
});
test('full-suite authority files select every surface', () => {
for (const path of ['package-lock.json', '.github/workflows/ci.yml']) {
assert.equal(planTests([path], { graph }).full, true, path);
}
});
test('GitHub output matches the selections consumed by CI', () => {
const output = formatGitHubOutputs(planTests([], { graph, forceFull: true }));
const outputKeys = new Set(output.split('\n').map((line) => line.split('=', 1)[0]));
const workflow = readWorkflow('ci.yml');
const consumedKeys = new Set(
[...workflow.matchAll(/steps\.plan\.outputs\.([a-z0-9_]+)/gu)].map((match) => match[1]),
);
assert.deepEqual(outputKeys, consumedKeys);
});
test('core CI validates pull requests and the resulting main branch state', () => {
const workflow = readWorkflow('ci.yml');
assert.match(workflow, /pull_request:\n\s+branches: \[main\]/u);
assert.match(workflow, /push:\n\s+branches: \[main\]/u);
assert.match(
workflow,
/BASE_SHA: \$\{\{ github\.event_name == 'push' && github\.event\.before \|\| github\.event\.pull_request\.base\.sha \}\}/u,
);
assert.match(
workflow,
/HEAD_SHA: \$\{\{ github\.event_name == 'push' && github\.sha \|\| github\.event\.pull_request\.head\.sha \}\}/u,
);
assert.match(workflow, /\[\[ "\$BASE_SHA" =~ \^0\+\$ \]\]/u);
});
test('core CI uses the Windows inventory package-script authority', () => {
const workflow = readWorkflow('ci.yml');
assert.match(workflow, /run: npm run windows:inventory/u);
assert.doesNotMatch(workflow, /run: node scripts\/windows-test-inventory\.mjs --check/u);
});
test('CI planner contracts run before dependency setup on every change', () => {
const workflow = readWorkflow('ci.yml');
const testStepStart = workflow.indexOf(' - name: Test CI planner\n');
const setupNodeStart = workflow.indexOf(' - uses: actions/setup-node@');
const testStepEnd = workflow.indexOf('\n - ', testStepStart + 1);
assert.ok(testStepStart >= 0);
assert.ok(testStepStart < setupNodeStart);
assert.doesNotMatch(workflow.slice(testStepStart, testStepEnd), /\n\s+if:/u);
});
test('core CI validates affected installed CLI packages on its existing runner', () => {
const workflow = readWorkflow('ci.yml');
const toolchain = workflow.indexOf(
'npm install --global --no-audit --no-fund "$(node -p \'require("./package.json").packageManager\')"',
);
const pack = workflow.indexOf('run: npm run release:cli:pack');
assert.match(workflow, /if: steps\.plan\.outputs\.cli_package == 'true'/u);
assert.ok(toolchain >= 0);
assert.ok(toolchain < pack);
assert.match(workflow, /run: npm run release:cli:smoke/u);
});
test('specialized platform workflows never create pull request jobs', () => {
const cli = readWorkflow('cli-package-validation.yml');
const baseline = readWorkflow('windows-baseline.yml');
const recovery = readWorkflow('windows-recovery.yml');
for (const workflow of [cli, baseline, recovery]) {
assert.doesNotMatch(workflow, /\n pull_request:/u);
assert.match(workflow, /\n workflow_dispatch:/u);
}
assert.match(cli, /\n workflow_call:/u);
assert.match(baseline, /\n schedule:/u);
});
function readWorkflow(name) {
return readFileSync(new URL(`../.github/workflows/${name}`, import.meta.url), 'utf8');
}