import { test } from 'node:test' import assert from 'node:assert/strict' import { createVerificationRunner, type VerificationRunnerDeps } from '../src/shared/verification-runner.ts' const STEPS = [ { id: 'java-tests', label: 'Java 全量测试' }, { id: 'python-unittest', label: 'Python unittest' }, { id: 'vue-typecheck', label: 'Vue 类型检查' }, { id: 'vue-build', label: 'Vue 构建' }, ] function makeDeps(over: Partial = {}): VerificationRunnerDeps & { runs: string[] } { const runs: string[] = [] return { runs, run: async (stepId: string) => { runs.push(stepId) return { ok: true, output: `${stepId} ok` } }, ...over, } } test('test_task_097_build_normal_default_path', async () => { const deps = makeDeps() const runner = createVerificationRunner({ steps: STEPS, run: deps.run }) const result = await runner.runAll() assert.equal(result.ok, true) assert.equal(result.passed.length, 4) assert.deepEqual(result.failed, []) assert.deepEqual(result.skipped, []) assert.deepEqual(deps.runs, ['java-tests', 'python-unittest', 'vue-typecheck', 'vue-build'], '按序执行全部步骤') }) test('test_task_097_build_normal_multiple_items', async () => { const deps = makeDeps() const runner = createVerificationRunner({ steps: STEPS, run: deps.run }) const result = await runner.runAll() // 批量场景:结果不丢失且顺序稳定 assert.deepEqual( result.passed.map((p) => p.id), ['java-tests', 'python-unittest', 'vue-typecheck', 'vue-build'], ) assert.equal(result.passed[0].output, 'java-tests ok') assert.equal(result.passed[3].output, 'vue-build ok') }) test('test_task_097_build_normal_repeated_operation_is_idempotent', async () => { const deps = makeDeps() const runner = createVerificationRunner({ steps: STEPS, run: deps.run }) const first = await runner.runAll() const second = await runner.runAll() assert.equal(first.ok, second.ok) assert.deepEqual(first.passed.map((p) => p.stepId), second.passed.map((p) => p.stepId)) // 重复执行不产生重复状态:步骤执行次数线性增长 assert.deepEqual(deps.runs, [...STEPS.map((s) => s.id), ...STEPS.map((s) => s.id)]) }) test('test_task_097_build_boundary_empty_input', async () => { const deps = makeDeps() const runner = createVerificationRunner({ steps: [], run: deps.run }) const result = await runner.runAll() assert.equal(result.ok, true) assert.deepEqual(result.passed, []) assert.deepEqual(result.failed, []) assert.deepEqual(deps.runs, [], '无步骤不执行任何命令') }) test('test_task_097_build_boundary_single_item', async () => { const deps = makeDeps() const runner = createVerificationRunner({ steps: [STEPS[0]], run: deps.run }) const result = await runner.runAll() assert.equal(result.ok, true) assert.equal(result.passed.length, 1) assert.equal(result.passed[0].id, 'java-tests') assert.deepEqual(deps.runs, ['java-tests']) }) test('test_task_097_build_boundary_limit_and_overflow', async () => { // 失败即停止:失败后的步骤被跳过,不发生无界执行 const deps = makeDeps({ run: async (stepId: string) => { deps.runs.push(stepId) if (stepId === 'python-unittest') return { ok: false, output: '2 tests failed' } return { ok: true, output: `${stepId} ok` } }, }) const runner = createVerificationRunner({ steps: STEPS, run: deps.run, stopOnFailure: true }) const result = await runner.runAll() assert.equal(result.ok, false) assert.deepEqual(result.failed.map((f) => f.id), ['python-unittest']) assert.deepEqual(result.skipped, ['vue-typecheck', 'vue-build'], '失败后的步骤被跳过') assert.deepEqual(deps.runs, ['java-tests', 'python-unittest']) // 非 stopOnFailure:全部执行,失败累计 const deps2 = makeDeps({ run: async (stepId: string) => { deps2.runs.push(stepId) if (stepId === 'vue-typecheck') return { ok: false, output: 'type error' } return { ok: true, output: `${stepId} ok` } }, }) const runner2 = createVerificationRunner({ steps: STEPS, run: deps2.run, stopOnFailure: false }) const result2 = await runner2.runAll() assert.equal(result2.ok, false) assert.equal(result2.failed.length, 1) assert.equal(result2.passed.length, 3) assert.deepEqual(result2.skipped, []) }) test('test_task_097_build_invalid_input_rejected', async () => { // 构造器校验同步抛错 assert.throws(() => createVerificationRunner({} as never), /steps 必须是数组/) assert.throws( () => createVerificationRunner({ steps: 'x' as never, run: async () => ({ ok: true, output: '' }) }), /steps 必须是数组/, ) assert.throws( () => createVerificationRunner({ steps: [{ id: '', label: 'x' }], run: async () => ({ ok: true, output: '' }) }), /step id 不能为空/, ) assert.throws( () => createVerificationRunner({ steps: [], run: undefined as never }), /run 必须是函数/, ) assert.throws( () => createVerificationRunner({ steps: [], run: async () => ({ ok: true, output: '' }), maxSteps: 0 }), /maxSteps 必须为正数/, ) // run 返回非法结果:抛错 const runner = createVerificationRunner({ steps: STEPS, run: async () => ({ ok: true }) as never }) await assert.rejects(() => runner.runAll(), /output 必须是字符串/) }) test('test_task_097_build_dependency_failure_releases_resources', async () => { // 命令执行器抛错:剩余步骤终止、结果标记失败,恢复后同一 runner 可重跑 let broken = true const deps = makeDeps({ run: async (stepId: string) => { if (broken) throw new Error('command spawn down') deps.runs.push(stepId) return { ok: true, output: `${stepId} ok` } }, }) const runner = createVerificationRunner({ steps: STEPS, run: deps.run }) await assert.rejects(() => runner.runAll(), /command spawn down/) assert.deepEqual(deps.runs, [], '抛错时不产生执行记录') broken = false const result = await runner.runAll() assert.equal(result.ok, true) assert.equal(result.passed.length, 4) assert.deepEqual(deps.runs, STEPS.map((s) => s.id), '恢复后四端全部执行') })