import assert from 'node:assert/strict'; import test from 'node:test'; import type { VersionWithContext } from './derive'; import type { V22XiaobaoWarningSummary } from './v22-api'; import { buildXiaobaoRisksFromV22Summaries, collectV22XiaobaoLatestInsights, filterV22XiaobaoSummariesForVisibleVersions, mergeV22XiaobaoLatestInsights, shouldLoadXiaobaoStoreFallback, } from './xiaobao-v22-summary'; test('buildXiaobaoRisksFromV22Summaries preserves precomputed risk details with version context', () => { const risks = buildXiaobaoRisksFromV22Summaries({ summaries: [{ versionId: 'version-1', riskLevel: 'likely_delayed', riskScore: 86, confidence: 72, forecastReleaseDate: '2026-07-09T00:00:00.000Z', riskSignature: 'signature-1', recomputedAt: '2026-07-03T09:00:00.000Z', updatedAt: '2026-07-03T09:05:00.000Z', summary: { delayDays: 2, remainingWorkHours: 30, reasons: [{ key: 'forecast_delay', title: 'Forecast delay', detail: 'Forecast release date moved after the expected release date.', severity: 'danger', count: 1, }], silentRisks: [{ key: 'no_activity', title: 'No activity', detail: 'No work activity for four days.', itemId: 'dev-1', itemType: 'dev_task', }], signals: { unfinishedCount: 5, openBugCount: 2, criticalBugCount: 1, failedTestCount: 1, blockedCount: 0, silentRiskCount: 1, daysToExpectedRelease: 1, }, dailyEvidence: { todayDeliveries: [{ id: 'ev-1', title: 'Delivery', summary: 'Submitted core task.', occurredAt: '2026-07-03T08:00:00.000Z' }], todayProgress: [], todayCreations: [], todayRisks: [], progressNotes: [], needsProgressItems: [], recentActivityCount: 3, totalActivityCount: 1, todayActualHours: 2, lastActivityAt: '2026-07-03T08:00:00.000Z', }, trend: { direction: 'up', delta: 16, summary: 'Risk rose by 16 points.', pattern: 'score_delta', }, currentSnapshot: { date: '2026-07-03', openBugCount: 2, criticalBugCount: 1, failedTestCount: 1, blockedCount: 0, silentRiskCount: 1, createdAt: '2026-07-03T09:00:00.000Z', }, }, }], versions: [version()], snapshots: [], now: new Date('2026-07-03T10:00:00.000Z'), }); assert.equal(risks.length, 1); assert.equal(risks[0].versionName, 'Payments V1.0'); assert.equal(risks[0].productName, 'Payments'); assert.equal(risks[0].projectName, 'Wallet'); assert.equal(risks[0].expectedReleaseDate, '2026-07-07'); assert.equal(risks[0].riskLevel, 'likely_delayed'); assert.equal(risks[0].riskScore, 86); assert.equal(risks[0].confidence, 72); assert.equal(risks[0].confidenceLevel, 'medium'); assert.equal(risks[0].forecastReleaseDate, '2026-07-09T00:00:00.000Z'); assert.equal(risks[0].delayDays, 2); assert.equal(risks[0].remainingWorkHours, 30); assert.equal(risks[0].reasons[0].key, 'forecast_delay'); assert.equal(risks[0].silentRisks[0].itemType, 'dev_task'); assert.equal(risks[0].signals.openBugCount, 2); assert.equal(risks[0].dailyEvidence?.todayDeliveries[0].summary, 'Submitted core task.'); assert.equal(risks[0].currentSnapshot.date, '2026-07-03'); assert.equal(risks[0].currentSnapshot.riskScore, 86); assert.equal(risks[0].currentSnapshot.riskLevel, 'likely_delayed'); assert.equal(risks[0].trend.summary, 'Risk rose by 16 points.'); }); test('buildXiaobaoRisksFromV22Summaries builds compatible defaults from sparse summaries', () => { const risks = buildXiaobaoRisksFromV22Summaries({ summaries: [{ versionId: 'version-1', riskLevel: 'at_risk', riskScore: 66, confidence: 48, forecastReleaseDate: '2026-07-09T00:00:00.000Z', riskSignature: 'signature-1', updatedAt: '2026-07-03T09:05:00.000Z', summary: { reasons: 'not-an-array' }, }], versions: [version({ expectedReleaseDate: '2026-07-07' })], snapshots: [{ versionId: 'version-1', date: '2026-07-01', riskScore: 40, riskLevel: 'attention', openBugCount: 1, criticalBugCount: 0, failedTestCount: 0, blockedCount: 0, silentRiskCount: 0, confidence: 80, createdAt: '2026-07-01T09:00:00.000Z', }], now: new Date('2026-07-03T10:00:00.000Z'), }); assert.equal(risks[0].delayDays, 2); assert.equal(risks[0].remainingWorkHours, 0); assert.equal(risks[0].confidenceLevel, 'low'); assert.equal(risks[0].reasons.length, 1); assert.equal(risks[0].reasons[0].key, 'precomputed_risk'); assert.equal(risks[0].signals.unfinishedCount, 0); assert.equal(risks[0].currentSnapshot.openBugCount, 0); assert.equal(risks[0].currentSnapshot.createdAt, '2026-07-03T09:05:00.000Z'); assert.equal(risks[0].trend.direction, 'up'); }); test('shouldLoadXiaobaoStoreFallback waits for V2.2 summary before loading relation-backed stores', () => { assert.equal(shouldLoadXiaobaoStoreFallback('idle'), false); assert.equal(shouldLoadXiaobaoStoreFallback('loading'), false); assert.equal(shouldLoadXiaobaoStoreFallback('ready'), false); assert.equal(shouldLoadXiaobaoStoreFallback('empty'), true); assert.equal(shouldLoadXiaobaoStoreFallback('failed'), true); }); test('filterV22XiaobaoSummariesForVisibleVersions keeps only visible version summaries', () => { const summaries = [ summary({ versionId: 'visible-1', riskScore: 80 }), summary({ versionId: 'hidden-1', riskScore: 95 }), summary({ versionId: 'visible-2', riskScore: 70 }), ]; const result = filterV22XiaobaoSummariesForVisibleVersions(summaries, [ version({ id: 'visible-1' }), version({ id: 'visible-2' }), ]); assert.deepEqual(result.map((item) => item.versionId), ['visible-1', 'visible-2']); }); test('collectV22XiaobaoLatestInsights exposes relation-backed AI insights', () => { const result = collectV22XiaobaoLatestInsights([ summary({ versionId: 'version-1', latestInsight: { versionId: 'version-1', riskSignature: 'signature-1', generatedAt: '2026-07-08T10:00:00.000Z', providerInfo: { model: 'claude-test' }, insight: { summary: '高风险版本需要处理阻塞。', why: ['存在阻塞项'], forecast: '预计延期 1 天。', suggestedActions: ['先解除阻塞'], ownerHints: ['确认阻塞负责人'], generatedAt: '2026-07-08T10:00:00.000Z', }, }, }), summary({ versionId: 'version-2' }), ]); assert.equal(result.length, 1); assert.equal(result[0].versionId, 'version-1'); assert.equal(result[0].riskSignature, 'signature-1'); assert.equal(result[0].insight.summary, '高风险版本需要处理阻塞。'); assert.equal(result[0].providerInfo?.model, 'claude-test'); }); test('mergeV22XiaobaoLatestInsights prefers relation-backed insights over legacy cache duplicates', () => { const relationInsight = { versionId: 'version-1', riskSignature: 'signature-1', generatedAt: '2026-07-08T10:00:00.000Z', insight: { summary: '关系表新解读。', why: ['new'], forecast: 'new forecast', suggestedActions: ['new action'], ownerHints: ['new owner'], generatedAt: '2026-07-08T10:00:00.000Z', }, }; const legacyInsight = { versionId: 'version-1', riskSignature: 'signature-1', generatedAt: '2026-07-08T09:00:00.000Z', insight: { summary: '旧缓存解读。', why: ['old'], forecast: 'old forecast', suggestedActions: ['old action'], ownerHints: ['old owner'], generatedAt: '2026-07-08T09:00:00.000Z', }, }; const result = mergeV22XiaobaoLatestInsights([relationInsight], [legacyInsight]); assert.equal(result.length, 1); assert.equal(result[0].insight.summary, '关系表新解读。'); }); function summary(patch: Partial = {}): V22XiaobaoWarningSummary { return { versionId: 'version-1', riskLevel: 'at_risk', riskScore: 60, confidence: 80, riskSignature: 'signature', summary: {}, updatedAt: '2026-07-03T09:00:00.000Z', ...patch, }; } function version(patch: Partial = {}): VersionWithContext { return { id: 'version-1', name: 'Payments V1.0', status: 'developing', releaseDate: null, createdAt: '2026-07-01T00:00:00.000Z', productId: 'product-1', productName: 'Payments', projectId: 'project-1', projectName: 'Wallet', expectedReleaseDate: '2026-07-07', members: [{ name: 'Alice', role: 'frontend' }], ...patch, } as VersionWithContext; }