import test from 'node:test'; import assert from 'node:assert/strict'; import type { TestCase } from './test-case'; import { canStartTestCase, hasTestCasePlan, needsTestCaseClaim } from './test-case'; import { applyTestCaseTransition, normalizeTestCaseOnCreate } from './test-case-workflow'; function tc(patch: Partial = {}): TestCase { return { id: 'tc-1', caseNo: 'TC-001', versionId: 'version-1', title: '拖拽排序正常', description: '验证拖拽排序', categoryId: 'cat-test-functional', priority: 'P2', status: 'pending', assigneeId: 'QA', plannedTestAt: '2026-06-25T01:00:00.000Z', plannedEndAt: '2026-06-25T02:00:00.000Z', createdBy: 'QA', createdAt: '2026-06-25T00:00:00.000Z', updatedAt: '2026-06-25T00:00:00.000Z', ...patch, }; } test('normalizeTestCaseOnCreate forces pending and clears actual timestamps', () => { const result = normalizeTestCaseOnCreate(tc({ status: 'running', startedAt: '2026-06-25T01:00:00.000Z', completedAt: '2026-06-25T02:00:00.000Z', })); assert.equal(result.status, 'pending'); assert.equal(result.startedAt, undefined); assert.equal(result.completedAt, undefined); }); test('normalizeTestCaseOnCreate defaults missing roundNo to the first test round', () => { const result = normalizeTestCaseOnCreate(tc()); assert.equal(result.roundNo, 1); }); test('pending to running writes startedAt', () => { const result = applyTestCaseTransition(tc(), 'running', { now: new Date('2026-06-25T01:00:00.000Z'), }); assert.equal(result.ok, true); assert.equal(result.patch?.startedAt, '2026-06-25T01:00:00.000Z'); }); test('AI test case without an assignee must be claimed with a plan before running', () => { const draft = tc({ assigneeId: undefined, plannedTestAt: undefined, plannedEndAt: undefined, aiDraft: true, }); assert.equal(needsTestCaseClaim(draft), true); assert.equal(hasTestCasePlan(draft), false); assert.equal(canStartTestCase(draft), false); const result = applyTestCaseTransition(draft, 'running', { now: new Date('2026-06-25T01:00:00.000Z'), }); assert.equal(result.ok, false); }); test('recommended assignee test case still needs a plan before running', () => { const draft = tc({ assigneeId: 'QA', plannedTestAt: undefined, plannedEndAt: undefined, aiDraft: true, }); assert.equal(needsTestCaseClaim(draft), false); assert.equal(hasTestCasePlan(draft), false); assert.equal(canStartTestCase(draft), false); const result = applyTestCaseTransition(draft, 'running', { now: new Date('2026-06-25T01:00:00.000Z'), }); assert.equal(result.ok, false); }); test('running to passed writes completedAt', () => { const result = applyTestCaseTransition(tc({ status: 'running', startedAt: '2026-06-25T01:00:00.000Z', }), 'passed', { now: new Date('2026-06-25T02:00:00.000Z'), }); assert.equal(result.ok, true); assert.equal(result.patch?.completedAt, '2026-06-25T02:00:00.000Z'); }); test('failed back to running clears failure and completion reason', () => { const result = applyTestCaseTransition(tc({ status: 'failed', startedAt: '2026-06-25T01:00:00.000Z', completedAt: '2026-06-25T02:00:00.000Z', failReason: '排序未保存', }), 'running', { now: new Date('2026-06-25T03:00:00.000Z'), }); assert.equal(result.ok, true); assert.equal(result.patch?.startedAt, undefined); assert.equal(result.patch?.completedAt, undefined); assert.equal(result.patch?.failReason, undefined); }); test('invalid transition is rejected', () => { const result = applyTestCaseTransition(tc(), 'passed', { now: new Date('2026-06-25T02:00:00.000Z'), }); assert.equal(result.ok, false); });