import assert from 'node:assert/strict'; import test from 'node:test'; import { calcXiaobaoVersionRisk } from './xiaobao-risk'; import type { DevTask } from './dev-task'; import type { TestCase } from './test-case'; import type { Bug } from './bug'; import type { VersionDailyEvidence } from './xiaobao-risk-evidence'; function version(patch: any = {}) { return { id: 'ver-1', name: 'V1.0', status: 'developing', productName: 'FTB', projectName: '项目管理', expectedReleaseDate: '2026-07-03T10:00:00.000Z', members: [{ name: '张三', role: 'frontend' }], ...patch, }; } function devTask(patch: Partial = {}): DevTask { return { id: 'dev-1', taskNo: 'DEV-001', requirementId: 'req-1', title: '实现版本风险页', categoryId: 'frontend', assigneeId: 'm-1', priority: 'P1', expectedStartAt: '2026-07-01T09:00:00.000Z', expectedEndAt: '2026-07-03T18:00:00.000Z', estimateHours: 40, status: 'todo', isBlocked: false, createdBy: 'm-1', createdAt: '2026-07-01T09:00:00.000Z', updatedAt: '2026-07-01T09:00:00.000Z', ...patch, }; } function testCase(patch: Partial = {}): TestCase { return { id: 'tc-1', caseNo: 'TC-001', versionId: 'ver-1', title: '验收版本风险页', categoryId: 'ui-interaction', priority: 'P1', assigneeId: 'm-2', status: 'pending', estimateHours: 16, createdBy: 'm-2', createdAt: '2026-07-01T09:00:00.000Z', updatedAt: '2026-07-01T09:00:00.000Z', ...patch, }; } function bug(patch: Partial = {}): Bug { return { id: 'bug-1', bugNo: 'BUG-001', versionId: 'ver-1', testCaseId: 'tc-1', title: '发布阻断缺陷', description: '关键流程无法通过', severity: 'critical', priority: 'P1', reportedBy: 'm-2', assigneeId: 'm-1', status: 'open', createdAt: '2026-07-01T09:00:00.000Z', updatedAt: '2026-07-01T09:00:00.000Z', ...patch, }; } test('calcXiaobaoVersionRisk predicts delay when remaining work exceeds release date', () => { const risk = calcXiaobaoVersionRisk({ version: version(), devTasks: [devTask()], testCases: [testCase()], bugs: [], now: new Date('2026-07-02T01:00:00.000Z'), }); assert.equal(risk.riskLevel, 'likely_delayed'); assert.ok(risk.riskScore >= 70); assert.ok(risk.delayDays > 0); assert.ok(risk.reasons.some((reason: { key: string }) => reason.key === 'remaining_work')); }); test('calcXiaobaoVersionRisk marks blocked when critical Bug / blocked dev task / failed test exist', () => { const risk = calcXiaobaoVersionRisk({ version: version(), devTasks: [devTask({ isBlocked: true, blockReason: '依赖接口未完成' })], testCases: [testCase({ status: 'failed', failReason: '核心流程失败' })], bugs: [bug()], now: new Date('2026-07-02T01:00:00.000Z'), }); assert.equal(risk.riskLevel, 'blocked'); assert.ok(risk.reasons.some((reason: { key: string }) => reason.key === 'critical_bug')); assert.ok(risk.reasons.some((reason: { key: string }) => reason.key === 'blocked_work')); }); test('calcXiaobaoVersionRisk preserves daily evidence on the returned risk object', () => { const dailyEvidence: VersionDailyEvidence = { todayDeliveries: [{ id: 'ev-1', title: '提交提测', summary: '完成核心流程', occurredAt: '2026-07-02T02:00:00.000Z' }], todayProgress: [], todayRisks: [], progressNotes: [], needsProgressItems: [], recentActivityCount: 1, lastActivityAt: '2026-07-02T02:00:00.000Z', }; const risk = calcXiaobaoVersionRisk({ version: version(), devTasks: [], testCases: [], bugs: [], dailyEvidence, now: new Date('2026-07-02T01:00:00.000Z'), }); assert.equal(risk.dailyEvidence, dailyEvidence); });