import type { TestCase, TestCaseStatus } from './test-case'; import { canStartTestCase, canTcTransition, getTestCaseRoundNo } from './test-case'; export interface TestCaseWorkflowResult { ok: boolean; patch?: Partial; message?: string; } export interface TestCaseTransitionOptions { now?: Date; failReason?: string; blockReason?: string; } export function normalizeTestCaseOnCreate(testCase: TestCase): TestCase { return { ...testCase, roundNo: getTestCaseRoundNo(testCase), status: 'pending', startedAt: undefined, completedAt: undefined, executedAt: undefined, failReason: undefined, blockReason: undefined, }; } export function applyTestCaseTransition( testCase: TestCase, to: TestCaseStatus, options: TestCaseTransitionOptions = {}, ): TestCaseWorkflowResult { if (!canTcTransition(testCase.status, to)) { return { ok: false, message: `不允许从「${testCase.status}」流转到「${to}」` }; } const nowIso = (options.now ?? new Date()).toISOString(); if (to === 'running' && !canStartTestCase(testCase)) { return { ok: false, message: '开始测试前需要先领取并填写计划开始和计划结束时间' }; } const patch: Partial = { status: to, aiDraft: false, executedAt: nowIso, }; if (to === 'running') { if (!testCase.startedAt) patch.startedAt = nowIso; patch.completedAt = undefined; patch.failReason = undefined; patch.blockReason = undefined; } if (to === 'passed' || to === 'failed' || to === 'blocked') { patch.completedAt = nowIso; } if (to === 'failed' && options.failReason?.trim()) patch.failReason = options.failReason.trim(); if (to === 'blocked' && options.blockReason?.trim()) patch.blockReason = options.blockReason.trim(); return { ok: true, patch }; }