veritas-kanban/scripts/select-ci-test-scope.test.mjs

259 lines
7.7 KiB
JavaScript

import assert from 'node:assert/strict';
import test from 'node:test';
import {
affectedWorkspaces,
classifyCiTestScope,
coverageWorkspaces,
diffRangeFor,
githubOutputLines,
isDependencyFreeScopeControlPath,
isDocumentationPath,
} from './select-ci-test-scope.mjs';
test('classifies documentation-only pull requests without unit tests', () => {
const result = classifyCiTestScope({
eventName: 'pull_request',
changedFiles: ['README.md', 'docs/AGENTS-TEMPLATE.md'],
});
assert.equal(result.scope, 'none');
assert.deepEqual(result.packages, []);
});
test('dependency-free cadence controls do not trigger workspace unit tests', () => {
const result = classifyCiTestScope({
eventName: 'pull_request',
changedFiles: [
'CHANGELOG.md',
'scripts/check-delivery-cadence.mjs',
'scripts/check-delivery-cadence.test.mjs',
],
});
assert.equal(result.scope, 'none');
assert.deepEqual(result.packages, []);
assert.equal(isDependencyFreeScopeControlPath('scripts/check-delivery-cadence.mjs'), true);
});
test('cadence controls do not turn an ordinary workspace change into a test milestone', () => {
const result = classifyCiTestScope({
eventName: 'pull_request',
changedFiles: ['scripts/check-delivery-cadence.mjs', 'server/src/routes/tasks.ts'],
});
assert.equal(result.scope, 'none');
assert.deepEqual(result.packages, ['server']);
});
test('records affected workspaces without testing an ordinary code change', () => {
const result = classifyCiTestScope({
eventName: 'pull_request',
changedFiles: ['web/src/App.tsx', 'server/src/routes/tasks.ts', 'README.md'],
});
assert.equal(result.scope, 'none');
assert.deepEqual(result.packages, ['server', 'web']);
});
test('selects critical coverage only for the full milestone scope', () => {
const files = [
'server/src/storage/file-storage.ts',
'web/src/components/Board.tsx',
'mcp/src/tools/tasks.ts',
];
assert.deepEqual(coverageWorkspaces(files), []);
assert.deepEqual(coverageWorkspaces(files, 'focused'), []);
assert.deepEqual(coverageWorkspaces(files, 'none'), []);
assert.deepEqual(coverageWorkspaces(files, 'full'), ['server', 'web', 'cli', 'mcp', 'desktop']);
});
test('defers governed critical-path coverage until a milestone', () => {
assert.deepEqual(
coverageWorkspaces([
'server/src/__tests__/provider-completion-service.test.ts',
'server/src/schemas/auth-schemas.ts',
'shared/src/utils/api-permissions.ts',
'web/src/__tests__/useWebSocket.test.ts',
'mcp/src/__tests__/task-tools.test.ts',
]),
[]
);
});
test('ci:full overrides a documentation-only pull request', () => {
const result = classifyCiTestScope({
eventName: 'pull_request',
labels: ['documentation', 'ci:full'],
changedFiles: ['docs/V6-GA-CHECKLIST.md'],
});
assert.equal(result.scope, 'full');
});
test('CI control paths require an explicit milestone instead of auto-running the full suite', () => {
const paths = [
'.github/workflows/ci.yml',
'scripts/select-ci-test-scope.mjs',
'scripts/verify-full-suite-job-evidence.mjs',
'scripts/run-coverage.mjs',
'scripts/check-coverage-policy.test.mjs',
'docs/testing/critical-path-coverage.json',
'web/vitest.config.ts',
];
for (const file of paths) {
const result = classifyCiTestScope({
eventName: 'pull_request',
changedFiles: [file],
});
assert.equal(result.scope, 'none', file);
}
});
test('records shared, storage, desktop, and manifest workspaces without automatic tests', () => {
const cases = [
{
file: 'shared/src/types/task.types.ts',
packages: ['server', 'web', 'cli', 'mcp', 'desktop'],
},
{ file: 'server/src/storage/sqlite/repositories.ts', packages: ['server'] },
{ file: 'desktop/src/main/index.ts', packages: ['desktop'] },
{ file: 'server/package.json', packages: ['server'] },
{ file: 'package.json', packages: ['server', 'web', 'cli', 'mcp', 'desktop'] },
{ file: 'pnpm-lock.yaml', packages: ['server', 'web', 'cli', 'mcp', 'desktop'] },
];
for (const { file, packages } of cases) {
const result = classifyCiTestScope({
eventName: 'pull_request',
changedFiles: [file],
});
assert.equal(result.scope, 'none', file);
assert.deepEqual(result.packages, packages, file);
}
});
test('scheduled and explicitly full manual runs select the full suite', () => {
assert.equal(classifyCiTestScope({ eventName: 'schedule', changedFiles: [] }).scope, 'full');
assert.equal(
classifyCiTestScope({
eventName: 'workflow_dispatch',
manualScope: 'full',
changedFiles: ['README.md'],
}).scope,
'full'
);
});
test('focused manual runs still classify the selected range by risk', () => {
assert.equal(
classifyCiTestScope({
eventName: 'workflow_dispatch',
manualScope: 'focused',
changedFiles: ['server/src/routes/tasks.ts'],
}).scope,
'focused'
);
assert.equal(
classifyCiTestScope({
eventName: 'workflow_dispatch',
manualScope: 'focused',
changedFiles: ['pnpm-lock.yaml'],
}).scope,
'focused'
);
});
test('ordinary post-merge pushes defer tests while recording affected packages', () => {
const result = classifyCiTestScope({
eventName: 'push',
changedFiles: ['cli/src/commands/doctor.ts'],
});
assert.equal(result.scope, 'none');
assert.deepEqual(result.packages, ['cli']);
});
test('unknown non-documentation paths wait for an explicit milestone', () => {
const result = classifyCiTestScope({
eventName: 'pull_request',
changedFiles: ['site/src/runtime.ts'],
});
assert.equal(result.scope, 'none');
});
test('deleted source paths are recorded without automatic test escalation', () => {
assert.equal(
classifyCiTestScope({
eventName: 'pull_request',
changedFiles: ['server/src/obsolete.ts'],
deletedFiles: ['server/src/obsolete.ts'],
}).scope,
'none'
);
assert.equal(
classifyCiTestScope({
eventName: 'pull_request',
changedFiles: ['site/src/obsolete.ts'],
deletedFiles: ['site/src/obsolete.ts'],
}).scope,
'none'
);
assert.equal(
classifyCiTestScope({
eventName: 'pull_request',
changedFiles: ['docs/obsolete.md'],
deletedFiles: ['docs/obsolete.md'],
}).scope,
'none'
);
});
test('empty ranges select no unit-test tier', () => {
const result = classifyCiTestScope({
eventName: 'push',
changedFiles: [],
});
assert.equal(result.scope, 'none');
});
test('documentation and workspace helpers are deterministic', () => {
assert.equal(isDocumentationPath('prompt-registry/review.md'), true);
assert.equal(isDocumentationPath('server/src/index.ts'), false);
assert.deepEqual(
affectedWorkspaces([
'desktop/src/main/index.ts',
'mcp/src/index.ts',
'server/src/index.ts',
'server/src/routes/tasks.ts',
]),
['server', 'mcp', 'desktop']
);
});
test('uses two-dot push ranges, three-dot review ranges, and rejects non-SHAs', () => {
const base = 'a'.repeat(40);
const head = 'b'.repeat(40);
assert.equal(diffRangeFor(base, head, 'push'), `${base}..${head}`);
assert.equal(diffRangeFor(base, head, 'pull_request'), `${base}...${head}`);
assert.throws(
() => diffRangeFor('--output=/tmp/unsafe', head, 'workflow_dispatch'),
/hexadecimal commit IDs/
);
});
test('exports the resolved base SHA for downstream coverage gates', () => {
const baseSha = 'a'.repeat(40);
const headSha = 'b'.repeat(40);
const output = githubOutputLines(
{ scope: 'focused', packages: ['server'], files: [], reason: 'test' },
{ baseSha, headSha, eventName: 'workflow_dispatch' }
);
assert.match(output, new RegExp(`^base_sha=${baseSha}$`, 'm'));
});