262 lines
8.8 KiB
TypeScript
262 lines
8.8 KiB
TypeScript
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,
|
|
shouldLoadXiaobaoAppDataFallback,
|
|
} 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('shouldLoadXiaobaoAppDataFallback waits for V2.2 summary before loading heavy AppData stores', () => {
|
|
assert.equal(shouldLoadXiaobaoAppDataFallback('idle'), false);
|
|
assert.equal(shouldLoadXiaobaoAppDataFallback('loading'), false);
|
|
assert.equal(shouldLoadXiaobaoAppDataFallback('ready'), false);
|
|
assert.equal(shouldLoadXiaobaoAppDataFallback('empty'), true);
|
|
assert.equal(shouldLoadXiaobaoAppDataFallback('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> = {}): 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> = {}): 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;
|
|
}
|