import assert from 'node:assert/strict'; import test from 'node:test'; import type { VersionWithContext } from './derive'; import type { XiaobaoVersionRisk } from './xiaobao-risk'; import { filterXiaobaoRiskWarnings, filterXiaobaoWarningVersions, formatRemainingWork, getXiaobaoWarningRiskCount, sanitizeRiskInsight, } from './xiaobao-warning-view'; function version(patch: Partial = {}): VersionWithContext { return { id: 'ver-1', name: 'V1.0', status: 'developing', releaseDate: null, createdAt: '2026-06-29T00:00:00.000Z', productId: 'prod-1', productName: 'FTB', projectId: 'proj-1', projectName: 'Project', expectedReleaseDate: '2026-07-05T10:00:00.000Z', members: [{ name: 'Alice', role: 'frontend' }], ...patch, }; } function risk(patch: Partial = {}): XiaobaoVersionRisk { return { versionId: 'ver-1', versionName: 'V1.0', productId: 'prod-1', productName: 'Product A', projectId: 'proj-1', projectName: 'Project A', riskScore: 68, riskLevel: 'attention', expectedReleaseDate: '2026-07-05', confidence: 80, confidenceLevel: 'high', delayDays: 0, remainingWorkHours: 8, reasons: [], silentRisks: [], signals: { unfinishedCount: 1, openBugCount: 0, criticalBugCount: 0, failedTestCount: 0, blockedCount: 0, silentRiskCount: 0, }, currentSnapshot: { versionId: 'ver-1', date: '2026-06-30', riskScore: 68, riskLevel: 'attention', openBugCount: 0, criticalBugCount: 0, failedTestCount: 0, blockedCount: 0, silentRiskCount: 0, confidence: 80, createdAt: '2026-06-30T10:00:00.000Z', }, trend: { direction: 'flat', delta: 0, summary: 'Risk is stable.', pattern: 'stable' }, ...patch, } as XiaobaoVersionRisk; } test('filterXiaobaoWarningVersions lets managers see every unfinished version', () => { const result = filterXiaobaoWarningVersions( [ version({ id: 'ver-1', status: 'developing', members: [{ name: 'Alice', role: 'frontend' }] }), version({ id: 'ver-2', status: 'planned', members: [{ name: 'Bob', role: 'testing' }] }), version({ id: 'ver-3', status: 'released', members: [{ name: 'Alice', role: 'frontend' }] }), ], { canManage: true, userName: 'Alice' }, ); assert.deepEqual(result.map((item) => item.id), ['ver-1', 'ver-2']); }); test('filterXiaobaoWarningVersions limits non-managers to versions where they are a member', () => { const result = filterXiaobaoWarningVersions( [ version({ id: 'ver-1', status: 'developing', members: [{ name: 'Alice', role: 'frontend' }] }), version({ id: 'ver-2', status: 'developing', members: [{ name: 'Bob', role: 'testing' }] }), version({ id: 'ver-3', status: 'closed', members: [{ name: 'Alice', role: 'frontend' }] }), ], { canManage: false, userName: 'Alice' }, ); assert.deepEqual(result.map((item) => item.id), ['ver-1']); }); test('filterXiaobaoRiskWarnings hides on_track risks and badge count follows visible risks', () => { const risks = [ risk({ versionId: 'ver-1', riskLevel: 'on_track', riskScore: 10 }), risk({ versionId: 'ver-2', riskLevel: 'attention', riskScore: 38 }), risk({ versionId: 'ver-3', riskLevel: 'blocked', riskScore: 100 }), ]; assert.deepEqual(filterXiaobaoRiskWarnings(risks).map((item) => item.versionId), ['ver-2', 'ver-3']); assert.equal(getXiaobaoWarningRiskCount(risks), 2); }); test('filterXiaobaoRiskWarnings filters by product project and risk tier', () => { const risks = [ risk({ versionId: 'ver-1', productId: 'prod-1', projectId: 'proj-1', riskLevel: 'attention', riskScore: 38 }), risk({ versionId: 'ver-2', productId: 'prod-1', projectId: 'proj-2', riskLevel: 'likely_delayed', riskScore: 88 }), risk({ versionId: 'ver-3', productId: 'prod-2', projectId: 'proj-3', riskLevel: 'blocked', riskScore: 100 }), ]; assert.deepEqual( filterXiaobaoRiskWarnings(risks, { productId: 'prod-1' }).map((item) => item.versionId), ['ver-1', 'ver-2'], ); assert.deepEqual( filterXiaobaoRiskWarnings(risks, { productId: 'prod-1', projectId: 'proj-2' }).map((item) => item.versionId), ['ver-2'], ); assert.deepEqual( filterXiaobaoRiskWarnings(risks, { riskFilter: 'high' }).map((item) => item.versionId), ['ver-2', 'ver-3'], ); }); test('formatRemainingWork keeps hours and adds work-day conversion', () => { assert.equal(formatRemainingWork(0), '0h / 0天'); assert.equal(formatRemainingWork(8), '8h / 1天'); assert.equal(formatRemainingWork(12), '12h / 1.5天'); assert.equal(formatRemainingWork(0.5), '0.5h / 0.1天'); }); test('sanitizeRiskInsight filters invalid page refresh suggested actions', () => { const result = sanitizeRiskInsight({ summary: '风险上升', why: ['P1 Bug 增加'], forecast: '预计延期 1 天', suggestedActions: [ '手动触发页面刷新,等待小宝预警自动更新', '优先处理 3 个 P1 Bug,并同步测试负责人复测', ], ownerHints: ['研发负责人协调修复顺序'], generatedAt: '2026-06-30T10:00:00.000Z', }); assert.deepEqual(result.suggestedActions, ['优先处理 3 个 P1 Bug,并同步测试负责人复测']); });