import { describe, expect, it } from 'vitest' import { breakdownSnapshot, recoveryOptions, workflowCheckpoints } from './selectors' import type { Checkpoint } from './types' import type { BreakdownState } from '../breakdown/types' /** 只构造测试所需的真实 checkpoint 字段,避免页面依赖虚构 DTO。 */ function checkpoint(index: number, state: BreakdownState, workflowName = 'breakdown'): Checkpoint { return { checkpointId: String(index), workflowName, createdAt: new Date(index * 1000).toISOString(), state } } describe('Checkpoint 选择与恢复', () => { it('按 graph 隔离并保持输入不可变', () => { const records = [checkpoint(2, {}), checkpoint(1, {}, 'create-drama')] expect(workflowCheckpoints(records, 'breakdown').map(item => item.checkpointId)).toEqual(['2']) expect(records[0]?.checkpointId).toBe('2') }) it('只有错误的最新 checkpoint 仍能展示上一份成果,同时保留最新失败状态', () => { const result = breakdownSnapshot([ checkpoint(1, { breakdownResult: { subjectCandidates: [] }, runConfig: { groupSize: 3, modules: ['character'], episodeGroups: [] } }), checkpoint(2, { workflowExecution: { executionId: 'e', status: 'failed', startedAt: '', errorMessage: '模型未返回 JSON' } }) ]) expect(result?.runConfig?.groupSize).toBe(3) expect(result?.workflowExecution?.status).toBe('failed') }) it('新阶段不能继承上一轮 completed 状态', () => { const result = breakdownSnapshot([ checkpoint(1, { workflowExecution: { executionId: 'e', status: 'completed', startedAt: '' }, breakdownResult: {} }), checkpoint(2, { runConfig: { groupSize: 2, modules: ['scene'], episodeGroups: [] } }) ]) expect(result?.workflowExecution).toBeUndefined() expect(result?.runConfig?.groupSize).toBe(2) }) it('恢复窗口和后端一致,过旧的失败任务不启用重试', () => { const records = Array.from({ length: 11 }, (_, index) => checkpoint( index, index === 0 ? { tasks: [ { taskId: 't', module: 'prop', status: 'failed', attempt: 1, group: { groupId: 'g', groupNo: 1, startEpisodeNo: 1, endEpisodeNo: 1, episodes: [] } } ] } : {} ) ) expect(recoveryOptions(records).retry).toBe(false) expect(recoveryOptions(records.slice(0, 10)).retry).toBe(true) }) })