feat(v2.2): 完成高频读取热路径
This commit is contained in:
196
apps/web/lib/xiaobao-v22-summary.test.ts
Normal file
196
apps/web/lib/xiaobao-v22-summary.test.ts
Normal file
@@ -0,0 +1,196 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
|
||||
import type { VersionWithContext } from './derive';
|
||||
import type { V22XiaobaoWarningSummary } from './v22-api';
|
||||
import {
|
||||
buildXiaobaoRisksFromV22Summaries,
|
||||
filterV22XiaobaoSummariesForVisibleVersions,
|
||||
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']);
|
||||
});
|
||||
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user