import { describe, expect, it, vi } from 'vitest' import { defaultConcurrency, formatGateResultReason, gatesForMode, runGate, runGates, type Gate, type GateResult, } from './run-gates.ts' function gate(id: string, options: Partial = {}): Gate { return { id, label: id, displayCommand: `run ${id}`, command: process.execPath, args: ['-e', ''], ...options, } } function resultFor(subject: Gate, status: GateResult['status'] = 'passed'): GateResult { return { gate: subject, status, durationMs: 10, output: [], exitCode: status === 'passed' ? 0 : 1, signalCode: null, } } function withPnpmEntrypoint(action: () => T): T { const previous = process.env.npm_execpath process.env.npm_execpath = '/private/pnpm.cjs' try { return action() } finally { if (previous === undefined) Reflect.deleteProperty(process.env, 'npm_execpath') else process.env.npm_execpath = previous } } function withEnv(name: string, value: string | undefined, action: () => T): T { const previous = process.env[name] if (value === undefined) Reflect.deleteProperty(process.env, name) else process.env[name] = value try { return action() } finally { if (previous === undefined) Reflect.deleteProperty(process.env, name) else process.env[name] = previous } } describe('gate graph validation', () => { it.each([ 'ci-primary', 'ci-linux-primary', 'ci-static', 'ci-lint-contracts-ready', 'ci-coverage', 'ci-snapshot', 'ci-artifacts', 'ci-consumers', 'ci-windows-blocking', 'ci-windows-complete', 'ci-windows-observational', 'node-compat', 'check-all', 'doc-sync', ] as const)('constructs and executes preflight for a valid non-empty %s graph', async (mode) => { const subject = withPnpmEntrypoint(() => gatesForMode(mode)) const execute = vi.fn(async (item: Gate) => resultFor(item)) await expect(runGates(subject, subject.length, execute)).resolves.toHaveLength(subject.length) }) it('keeps the public repository link policy in the documentation gate', () => { const ids = withPnpmEntrypoint(() => gatesForMode('doc-sync').map(subject => subject.id)) expect(ids).toContain('public-repository-links') }) it.each(['ci-primary', 'ci-static', 'check-all'] as const)( 'keeps the DSH package license policy in %s', (mode) => { const ids = withPnpmEntrypoint(() => gatesForMode(mode).map(subject => subject.id)) expect(ids).toContain('dsh-package-licenses') }, ) it('keeps native Windows coverage blocking while portability inventory remains observational', () => { const gates = withPnpmEntrypoint(() => gatesForMode('ci-windows-complete')) const byId = new Map(gates.map(subject => [subject.id, subject])) expect(byId.get('coverage')?.allowFailure).not.toBe(true) expect(byId.get('coverage-exempt-heavy')?.allowFailure).not.toBe(true) expect(byId.get('duplication')?.allowFailure).toBe(true) }) it.each([ ['empty', [], /gate graph has no gates/], ['duplicate ids', [gate('same'), gate('same')], /duplicate gate id "same"/], ['unknown dependencies', [gate('subject', { needs: ['missing'] })], /depends on unknown gate "missing"/], ['cycles', [gate('first', { needs: ['second'] }), gate('second', { needs: ['first'] })], /dependency cycle: first -> second -> first/], ] as const)('rejects %s before starting a child', async (_label, invalid, message) => { const execute = vi.fn(async (subject: Gate) => resultFor(subject)) await expect(runGates([...invalid], 1, execute)).rejects.toThrow(message) expect(execute).not.toHaveBeenCalled() }) it('rejects an invalid worker count before starting a child', async () => { const execute = vi.fn(async (subject: Gate) => resultFor(subject)) await expect(runGates([gate('subject')], 0, execute)).rejects.toThrow('max concurrency must be a positive integer') expect(execute).not.toHaveBeenCalled() }) it('skips dependents after their prerequisite fails', async () => { const dependent = gate('dependent', { needs: ['root'] }) const root = gate('root') const execute = vi.fn(async (subject: Gate) => resultFor(subject, 'failed')) const results = await runGates([dependent, root], 1, execute) expect(execute).toHaveBeenCalledOnce() expect(execute).toHaveBeenCalledWith(root) expect(results[0]).toMatchObject({ gate: dependent, status: 'skipped', error: 'dependency failed or skipped: root' }) }) }) describe('Oxlint gate', () => { it('uses the package script when no worker bound is configured', () => { const subject = withEnv('DSH_OXLINT_THREADS', undefined, () => withPnpmEntrypoint(() => gatesForMode('ci-lint-contracts-ready')[0])) expect(subject).toMatchObject({ id: 'lint', displayCommand: 'pnpm run lint:contracts-ready', command: process.execPath, args: ['/private/pnpm.cjs', 'run', 'lint:contracts-ready'], }) }) it('surfaces the configured worker bound on the shared package script', () => { const subject = withEnv('DSH_OXLINT_THREADS', '4', () => withPnpmEntrypoint(() => gatesForMode('ci-lint-contracts-ready')[0])) expect(subject).toMatchObject({ id: 'lint', displayCommand: 'DSH_OXLINT_THREADS=4 pnpm run lint:contracts-ready', command: process.execPath, args: ['/private/pnpm.cjs', 'run', 'lint:contracts-ready'], }) }) }) describe('Typert contract preparation', () => { it('prepares primary source consumers once before they run', () => { const subject = withEnv('DSH_OXLINT_THREADS', undefined, () => withPnpmEntrypoint(() => gatesForMode('ci-primary'))) expect(subject.find(item => item.id === 'typert-contracts')).toMatchObject({ displayCommand: 'pnpm run build:lib:host', args: ['/private/pnpm.cjs', 'run', 'build:lib:host'], }) for (const [id, script] of [ ['typecheck', 'typecheck:contracts-ready'], ['lint', 'lint:contracts-ready'], ['doc-typecheck', 'doc-typecheck:contracts-ready'], ] as const) { expect(subject.find(item => item.id === id)).toMatchObject({ displayCommand: `pnpm run ${script}`, args: ['/private/pnpm.cjs', 'run', script], needs: ['typert-contracts'], }) } expect(subject.find(item => item.id === 'build')?.needs).toEqual([ 'typecheck', 'lint', 'doc-typecheck', ]) }) it('reuses contracts from the validated consumer build', () => { const subject = withPnpmEntrypoint(() => gatesForMode('ci-consumers')) expect(subject.find(item => item.id === 'lint-and-duplication')).toMatchObject({ displayCommand: 'pnpm run check:ci:lint:contracts-ready', args: ['/private/pnpm.cjs', 'run', 'check:ci:lint:contracts-ready'], }) expect(subject.find(item => item.id === 'doc-typecheck')).toMatchObject({ displayCommand: 'pnpm run doc-typecheck:contracts-ready', args: ['/private/pnpm.cjs', 'run', 'doc-typecheck:contracts-ready'], }) }) it('keeps standalone doc sync responsible for preparation', () => { const docTypecheck = withPnpmEntrypoint(() => gatesForMode('doc-sync').find(item => item.id === 'doc-typecheck')) expect(docTypecheck?.displayCommand).toBe('pnpm run doc-typecheck') }) }) describe('Node compatibility graph', () => { it('runs the jsdom environment smoke on every advertised Node line', () => { const subject = withPnpmEntrypoint(() => gatesForMode('node-compat')) expect(subject.find(item => item.id === 'vitest-jsdom-smoke')).toMatchObject({ label: 'Vitest jsdom smoke', args: [ '/private/pnpm.cjs', 'exec', 'vitest', 'run', 'scripts/vitest-environment.compat.spec.ts', ], }) }) }) describe('Node 24 lane ownership', () => { it('keeps the static lane source-only', () => { const subject = withPnpmEntrypoint(() => gatesForMode('ci-static')) expect(subject.map(item => item.id)).not.toContain('build') expect(subject.map(item => item.id)).not.toContain('doc-typecheck') }) it('owns the build and orders its artifact consumers', () => { const subject = withPnpmEntrypoint(() => gatesForMode('ci-consumers')) expect(defaultConcurrency('ci-consumers', subject.length, 4)).toEqual({ workers: 10, source: 'ci-consumers gate count', }) expect(subject.map(item => item.id)).toEqual([ 'build', 'node-compat', 'publint', 'built-package-invariants', 'lint-and-duplication', 'snapshot', 'web-snapshot', 'doc-typecheck', 'node-next-types', 'built-bin-smoke', ]) expect(subject.find(item => item.id === 'publint')?.needs).toEqual(['build']) expect(subject.find(item => item.id === 'built-package-invariants')?.needs).toEqual(['publint']) expect(subject.find(item => item.id === 'lint-and-duplication')?.needs).toEqual(['built-package-invariants']) for (const id of [ 'snapshot', 'web-snapshot', 'doc-typecheck', 'node-next-types', 'built-bin-smoke', ]) { expect(subject.find(item => item.id === id)?.needs).toEqual(['built-package-invariants']) } expect(subject.find(item => item.id === 'snapshot')?.env).toEqual({ DSH_EXAMPLE_MODE: 'lib' }) expect(subject.find(item => item.id === 'doc-typecheck')?.env).toEqual({ DSH_DOC_TYPECHECK_USE_BUILD_OUTPUT: '1', }) expect(subject.find(item => item.id === 'built-bin-smoke')?.args).toEqual( expect.arrayContaining([ 'packages/subagent/subagent-codex/tests/loader-composition.e2e.ts', 'packages/subagent/subagent-claude-code/tests/loader-composition.e2e.ts', ]), ) expect(subject.find(item => item.id === 'web-snapshot')).toMatchObject({ displayCommand: 'DSH_SNAPSHOT=replay pnpm run test:web:built', env: { DSH_SNAPSHOT: 'replay' }, }) }) }) describe('Linux primary graph', () => { it('adds the same compare-only web gate after built client artifacts', () => { const subject = withPnpmEntrypoint(() => gatesForMode('ci-linux-primary')) const web = subject.find(item => item.id === 'web-snapshot') expect(web).toMatchObject({ displayCommand: 'DSH_SNAPSHOT=replay pnpm run test:web:built', env: { DSH_SNAPSHOT: 'replay' }, needs: ['built-package-invariants'], }) }) }) describe('gate process outcomes', () => { it.skipIf(process.platform === 'win32')('reports signal termination independently from exit status', async () => { const result = await runGate(gate('terminated', { args: ['-e', "process.kill(process.pid, 'SIGTERM')"], })) expect(result.status).toBe('failed') expect(result.exitCode).toBeNull() expect(result.signalCode).toBe('SIGTERM') expect(formatGateResultReason(result)).toBe('signal SIGTERM') }) })