import { test } from 'node:test' import assert from 'node:assert/strict' import { createReleaseChecklist, type ReleaseCheckDeps } from '../src/shared/release-checklist.ts' const COMMITS = [ { hash: 'abc123', subject: 'task-99: 全链路压测记录器' }, { hash: 'def456', subject: 'progress 99' }, { hash: 'ghi789', subject: 'task-98: 健康检查执行器' }, { hash: 'jkl012', subject: 'progress 98' }, ] function makeDeps(over: Partial = {}): ReleaseCheckDeps & { calls: string[] } { const calls: string[] = [] return { calls, listCommits: async () => { calls.push('list') return COMMITS }, rollback: async () => { calls.push('rollback') return { ok: true, output: '回滚成功' } }, verify: async () => { calls.push('verify') return { ok: true, output: '页面正常' } }, ...over, } } test('test_task_100_task_normal_default_path', async () => { const deps = makeDeps() const checklist = createReleaseChecklist({ deps, releaseCommits: ['abc123', 'ghi789'] }) const result = await checklist.run() assert.equal(result.ok, true) assert.equal(result.steps.length, 3) assert.deepEqual(result.steps.map((s) => s.id), ['rollback-drill', 'commit-map', 'deliverables']) assert.equal(result.rollback.ok, true) assert.equal(result.commitMap.checked, 2) assert.equal(result.commitMap.missing.length, 0) assert.equal(result.commitMap.unverified.length, 0) assert.equal(result.deliverables.passed, true) assert.equal(result.deliverables.total, 0, '默认无交付物清单') assert.deepEqual(deps.calls, ['rollback', 'list', 'verify']) }) test('test_task_100_task_normal_multiple_items', async () => { // 批量交付物清单:顺序稳定、不丢失 const deps = makeDeps() const checklist = createReleaseChecklist({ deps, releaseCommits: ['abc123', 'def456', 'ghi789', 'jkl012'], deliverables: ['jar', 'exe', 'vue-dist', 'python-backend'], }) const result = await checklist.run() assert.equal(result.deliverables.total, 4) assert.deepEqual(result.deliverables.items, ['jar', 'exe', 'vue-dist', 'python-backend']) assert.equal(result.commitMap.checked, 4) assert.deepEqual(result.commitMap.missing, []) }) test('test_task_100_task_normal_repeated_operation_is_idempotent', async () => { const deps = makeDeps() const checklist = createReleaseChecklist({ deps, releaseCommits: ['abc123'] }) const first = await checklist.run() const second = await checklist.run() assert.equal(first.ok, second.ok) assert.deepEqual(first.commitMap.missing, second.commitMap.missing) // 重复执行不产生重复状态:回滚演练与验证都恰好各执行两次(每轮一次) assert.equal(deps.calls.filter((c) => c === 'rollback').length, 2) assert.equal(deps.calls.filter((c) => c === 'verify').length, 2) }) test('test_task_100_task_boundary_empty_input', async () => { const deps = makeDeps({ listCommits: async () => [] }) const checklist = createReleaseChecklist({ deps, releaseCommits: [] }) const result = await checklist.run() assert.equal(result.commitMap.checked, 0) assert.deepEqual(result.commitMap.missing, []) assert.equal(result.deliverables.total, 0) assert.equal(result.deliverables.passed, true) assert.equal(result.ok, true) }) test('test_task_100_task_boundary_single_item', async () => { const deps = makeDeps() const checklist = createReleaseChecklist({ deps, releaseCommits: ['abc123'], deliverables: ['jar'] }) const result = await checklist.run() assert.equal(result.commitMap.checked, 1) assert.equal(result.commitMap.missing.length, 0) assert.equal(result.deliverables.total, 1) assert.equal(result.ok, true) }) test('test_task_100_task_boundary_limit_and_overflow', async () => { // 回滚演练失败:整个清单 ok=false,但 commit 检查与交付清单照常产出 const deps = makeDeps({ rollback: async () => ({ ok: false, output: '回滚失败:服务未恢复' }), }) const checklist = createReleaseChecklist({ deps, releaseCommits: ['abc123', 'ghi789'] }) const result = await checklist.run() assert.equal(result.ok, false) assert.equal(result.rollback.ok, false) assert.equal(result.commitMap.checked, 2, '失败不中断后续步骤') assert.equal(result.deliverables.passed, true) // 缺失 commit:标记 missing,不中断 const deps2 = makeDeps({ listCommits: async () => COMMITS.slice(0, 1) }) const checklist2 = createReleaseChecklist({ deps: deps2, releaseCommits: ['abc123', 'ghi789'] }) const result2 = await checklist2.run() assert.equal(result2.commitMap.missing.length, 1) assert.equal(result2.commitMap.missing[0], 'ghi789') assert.equal(result2.ok, false) }) test('test_task_100_task_invalid_input_rejected', async () => { assert.throws(() => createReleaseChecklist({} as never), /deps 必须是对象/) const noList = makeDeps() delete (noList as Partial).listCommits assert.throws(() => createReleaseChecklist({ deps: noList as never, releaseCommits: [] }), /listCommits 必须是函数/) assert.throws(() => createReleaseChecklist({ deps: makeDeps(), releaseCommits: 'abc' as never }), /releaseCommits 必须是数组/) assert.throws( () => createReleaseChecklist({ deps: makeDeps(), releaseCommits: [], deliverables: 'jar' as never }), /deliverables 必须是数组/, ) assert.throws( () => createReleaseChecklist({ deps: makeDeps(), releaseCommits: [], maxCommits: 0 }), /maxCommits 必须为正数/, ) // listCommits 返回缺 hash 的记录:抛错 const bad = makeDeps({ listCommits: async () => [{ subject: 'x' }] as never }) await assert.rejects(() => createReleaseChecklist({ deps: bad, releaseCommits: ['a'] }).run(), /commit 缺少 hash/) }) test('test_task_100_task_dependency_failure_releases_resources', async () => { // 回滚演练依赖抛错:整轮失败但状态可恢复,修复后重跑成功 let broken = true const deps = makeDeps({ rollback: async () => { if (broken) throw new Error('ssh down') return { ok: true, output: '回滚成功' } }, }) const checklist = createReleaseChecklist({ deps, releaseCommits: ['abc123'] }) await assert.rejects(() => checklist.run(), /ssh down/) broken = false const result = await checklist.run() assert.equal(result.ok, true) assert.equal(result.rollback.ok, true) // commit 列表读取抛错同样可恢复 let listBroken = true const deps2 = makeDeps({ listCommits: async () => { if (listBroken) throw new Error('git down') return COMMITS }, }) const checklist2 = createReleaseChecklist({ deps: deps2, releaseCommits: ['abc123'] }) await assert.rejects(() => checklist2.run(), /git down/) listBroken = false const result2 = await checklist2.run() assert.equal(result2.ok, true) assert.equal(result2.commitMap.checked, 1) })