task-100: 发布前回滚演练、commit 对应关系检查和交付清单

This commit is contained in:
2026-08-30 23:18:25 +08:00
parent 6593b8f007
commit 4d84d362cf
2 changed files with 303 additions and 0 deletions
@@ -0,0 +1,164 @@
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> = {}): 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<ReleaseCheckDeps>).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)
})