Some checks failed
Deploy Production / Build, push, deploy, verify (push) Has been cancelled
- 移除已迁移业务 AppData 运行时 fallback,改走领域 API 和关系表快读 - 补齐需求产品负责人、版本计划任务 JSON 和成员 username 回填迁移 - 统一治理字典入口,并补充 AI provider、数据源契约和领域服务测试 Co-Authored-By: Codex GPT-5 <codex@openai.com>
268 lines
9.2 KiB
TypeScript
268 lines
9.2 KiB
TypeScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
|
|
import { buildVersionDataScope, buildVersionDataScopeMap, selectVersionDataScope } from './version-data-scope';
|
|
import type { Bug } from './bug';
|
|
import type { DevTask } from './dev-task';
|
|
import type { OvertimeRecord } from './overtime';
|
|
import type { Requirement } from './requirement';
|
|
import type { TestCase } from './test-case';
|
|
import type { VersionPlan } from './version-plan';
|
|
|
|
function requirement(id: string, versionId: string | undefined): Requirement {
|
|
return {
|
|
id,
|
|
code: id.toUpperCase(),
|
|
title: `Requirement ${id}`,
|
|
description: '',
|
|
productId: 'product-1',
|
|
projectId: 'project-1',
|
|
versionId,
|
|
sourceType: 'internal',
|
|
sourceTarget: 'product',
|
|
platforms: ['web'],
|
|
typeId: 'type-1',
|
|
status: 'planned',
|
|
priority: 'P1',
|
|
effort: 'M',
|
|
creator: 'PM',
|
|
createdAt: '2026-07-02T09:00:00.000Z',
|
|
};
|
|
}
|
|
|
|
function plan(id: string, versionId: string, type: VersionPlan['type']): VersionPlan {
|
|
return {
|
|
id,
|
|
versionId,
|
|
type,
|
|
title: `Plan ${id}`,
|
|
owner: 'PM',
|
|
startTime: '2026-07-02T09:00:00.000Z',
|
|
endTime: '2026-07-02T18:00:00.000Z',
|
|
status: 'pending',
|
|
createdAt: '2026-07-02T09:00:00.000Z',
|
|
addedBy: 'PM',
|
|
};
|
|
}
|
|
|
|
function devTask(id: string, patch: Partial<DevTask>): DevTask {
|
|
return {
|
|
id,
|
|
taskNo: id.toUpperCase(),
|
|
versionId: undefined,
|
|
requirementId: '',
|
|
title: `Task ${id}`,
|
|
categoryId: 'frontend',
|
|
assigneeId: 'Alice',
|
|
priority: 'P1',
|
|
expectedStartAt: '',
|
|
expectedEndAt: '',
|
|
status: 'todo',
|
|
isBlocked: false,
|
|
createdBy: 'PM',
|
|
createdAt: '2026-07-02T09:00:00.000Z',
|
|
updatedAt: '2026-07-02T09:00:00.000Z',
|
|
...patch,
|
|
};
|
|
}
|
|
|
|
function testCase(id: string, versionId: string): TestCase {
|
|
return {
|
|
id,
|
|
caseNo: id.toUpperCase(),
|
|
versionId,
|
|
title: `Case ${id}`,
|
|
categoryId: 'testing',
|
|
priority: 'P1',
|
|
status: 'pending',
|
|
createdBy: 'QA',
|
|
createdAt: '2026-07-02T09:00:00.000Z',
|
|
updatedAt: '2026-07-02T09:00:00.000Z',
|
|
};
|
|
}
|
|
|
|
function bug(id: string, versionId: string): Bug {
|
|
return {
|
|
id,
|
|
bugNo: id.toUpperCase(),
|
|
versionId,
|
|
testCaseId: 'tc-1',
|
|
title: `Bug ${id}`,
|
|
description: '',
|
|
severity: 'major',
|
|
priority: 'P1',
|
|
reportedBy: 'QA',
|
|
assigneeId: 'Bob',
|
|
status: 'open',
|
|
createdAt: '2026-07-02T09:00:00.000Z',
|
|
updatedAt: '2026-07-02T09:00:00.000Z',
|
|
};
|
|
}
|
|
|
|
function overtime(id: string, versionId: string): OvertimeRecord {
|
|
return {
|
|
id,
|
|
projectId: 'project-1',
|
|
versionId,
|
|
person: 'Alice',
|
|
startTime: '2026-07-02T19:00:00.000Z',
|
|
endTime: '2026-07-02T21:00:00.000Z',
|
|
duration: 2,
|
|
reasonId: 'reason-4',
|
|
createdAt: '2026-07-02T21:00:00.000Z',
|
|
};
|
|
}
|
|
|
|
test('buildVersionDataScope scopes version detail data once', () => {
|
|
const scope = buildVersionDataScope({
|
|
versionId: 'version-1',
|
|
requirements: [requirement('req-1', 'version-1'), requirement('req-2', 'version-2')],
|
|
plans: [plan('plan-1', 'version-1', 'research'), plan('plan-2', 'version-1', 'product'), plan('plan-3', 'version-2', 'ui')],
|
|
devTasks: [
|
|
devTask('dev-direct', { versionId: 'version-1', requirementId: '' }),
|
|
devTask('dev-legacy', { requirementId: 'req-1' }),
|
|
devTask('dev-other', { versionId: 'version-2', requirementId: 'req-2' }),
|
|
],
|
|
testCases: [testCase('tc-1', 'version-1'), testCase('tc-2', 'version-2')],
|
|
bugs: [bug('bug-1', 'version-1'), bug('bug-2', 'version-2')],
|
|
overtimeRecords: [overtime('ot-1', 'version-1'), overtime('ot-2', 'version-2')],
|
|
});
|
|
|
|
assert.deepEqual(scope.requirementIds, ['req-1']);
|
|
assert.deepEqual(scope.plans.map((item) => item.id), ['plan-1', 'plan-2']);
|
|
assert.deepEqual(scope.plansByType.research.map((item) => item.id), ['plan-1']);
|
|
assert.deepEqual(scope.plansByType.product.map((item) => item.id), ['plan-2']);
|
|
assert.deepEqual(scope.devTasks.map((item) => item.id), ['dev-direct', 'dev-legacy']);
|
|
assert.deepEqual(scope.testCases.map((item) => item.id), ['tc-1']);
|
|
assert.deepEqual(scope.bugs.map((item) => item.id), ['bug-1']);
|
|
assert.deepEqual(scope.overtimeRecords.map((item) => item.id), ['ot-1']);
|
|
});
|
|
|
|
test('buildVersionDataScopeMap indexes many versions in one pass', () => {
|
|
const scopeMap = buildVersionDataScopeMap({
|
|
versionIds: ['version-1', 'version-2', 'version-empty'],
|
|
requirements: [
|
|
requirement('req-1', 'version-1'),
|
|
requirement('req-2', 'version-2'),
|
|
requirement('req-outside', 'version-3'),
|
|
],
|
|
plans: [
|
|
plan('plan-1', 'version-1', 'research'),
|
|
plan('plan-2', 'version-2', 'ui'),
|
|
plan('plan-outside', 'version-3', 'product'),
|
|
],
|
|
devTasks: [
|
|
devTask('dev-direct', { versionId: 'version-2', requirementId: 'req-1' }),
|
|
devTask('dev-legacy', { requirementId: 'req-1' }),
|
|
devTask('dev-outside', { requirementId: 'req-outside' }),
|
|
],
|
|
testCases: [testCase('tc-1', 'version-1'), testCase('tc-2', 'version-2'), testCase('tc-outside', 'version-3')],
|
|
bugs: [bug('bug-1', 'version-1'), bug('bug-2', 'version-2'), bug('bug-outside', 'version-3')],
|
|
overtimeRecords: [overtime('ot-1', 'version-1'), overtime('ot-2', 'version-2'), overtime('ot-outside', 'version-3')],
|
|
});
|
|
|
|
assert.deepEqual(scopeMap['version-1'].requirements.map((item) => item.id), ['req-1']);
|
|
assert.deepEqual(scopeMap['version-1'].devTasks.map((item) => item.id), ['dev-legacy']);
|
|
assert.deepEqual(scopeMap['version-2'].requirements.map((item) => item.id), ['req-2']);
|
|
assert.deepEqual(scopeMap['version-2'].devTasks.map((item) => item.id), ['dev-direct']);
|
|
assert.deepEqual(scopeMap['version-2'].plansByType.ui.map((item) => item.id), ['plan-2']);
|
|
assert.deepEqual(scopeMap['version-empty'].requirements, []);
|
|
assert.deepEqual(Object.keys(scopeMap).sort(), ['version-1', 'version-2', 'version-empty']);
|
|
});
|
|
|
|
test('selectVersionDataScope prefers non-empty V2.2 scope but keeps store overtime records', () => {
|
|
const storeScope = buildVersionDataScope({
|
|
versionId: 'version-1',
|
|
requirements: [requirement('req-app', 'version-1')],
|
|
plans: [plan('plan-app', 'version-1', 'research')],
|
|
devTasks: [devTask('dev-app', { versionId: 'version-1' })],
|
|
testCases: [testCase('tc-app', 'version-1')],
|
|
bugs: [bug('bug-app', 'version-1')],
|
|
overtimeRecords: [overtime('ot-app', 'version-1')],
|
|
});
|
|
const v22Scope = buildVersionDataScope({
|
|
versionId: 'version-1',
|
|
requirements: [requirement('req-v22', 'version-1')],
|
|
plans: [plan('plan-v22', 'version-1', 'product')],
|
|
devTasks: [devTask('dev-v22', { versionId: 'version-1' })],
|
|
testCases: [],
|
|
bugs: [],
|
|
overtimeRecords: [],
|
|
});
|
|
|
|
const selected = selectVersionDataScope({ storeScope, v22Scope });
|
|
|
|
assert.equal(selected?.requirements[0].id, 'req-v22');
|
|
assert.equal(selected?.plans[0].id, 'plan-v22');
|
|
assert.deepEqual(selected?.overtimeRecords.map((item) => item.id), ['ot-app']);
|
|
});
|
|
|
|
test('selectVersionDataScope overlays local store changes onto V2.2 rows by id', () => {
|
|
const storeScope = buildVersionDataScope({
|
|
versionId: 'version-1',
|
|
requirements: [
|
|
{ ...requirement('req-v22', 'version-1'), title: '本地标题' },
|
|
{ ...requirement('req-local', 'version-1'), title: '本地新增' },
|
|
],
|
|
plans: [
|
|
{
|
|
...plan('plan-v22', 'version-1', 'product'),
|
|
status: 'in_progress',
|
|
requirementCoverage: [
|
|
{
|
|
requirementId: 'req-v22',
|
|
status: 'not_started',
|
|
currentWorkStartedAt: '2026-07-09T09:30:00.000Z',
|
|
updatedAt: '2026-07-09T09:30:00.000Z',
|
|
updatedBy: 'PM',
|
|
},
|
|
],
|
|
},
|
|
],
|
|
devTasks: [devTask('dev-v22', { versionId: 'version-1', status: 'in_progress' })],
|
|
testCases: [testCase('tc-v22', 'version-1')],
|
|
bugs: [bug('bug-v22', 'version-1')],
|
|
overtimeRecords: [],
|
|
});
|
|
const v22Scope = buildVersionDataScope({
|
|
versionId: 'version-1',
|
|
requirements: [requirement('req-v22', 'version-1')],
|
|
plans: [plan('plan-v22', 'version-1', 'product')],
|
|
devTasks: [devTask('dev-v22', { versionId: 'version-1', status: 'todo' })],
|
|
testCases: [testCase('tc-v22', 'version-1')],
|
|
bugs: [bug('bug-v22', 'version-1')],
|
|
overtimeRecords: [],
|
|
});
|
|
|
|
const selected = selectVersionDataScope({ storeScope, v22Scope });
|
|
|
|
assert.equal(selected?.requirements.find((item) => item.id === 'req-v22')?.title, '本地标题');
|
|
assert.equal(selected?.requirements.some((item) => item.id === 'req-local'), true);
|
|
assert.equal(selected?.plans[0].status, 'in_progress');
|
|
assert.equal(selected?.plans[0].requirementCoverage?.[0]?.currentWorkStartedAt, '2026-07-09T09:30:00.000Z');
|
|
assert.equal(selected?.devTasks[0].status, 'in_progress');
|
|
});
|
|
|
|
test('selectVersionDataScope falls back to store scope when V2.2 scope is empty', () => {
|
|
const storeScope = buildVersionDataScope({
|
|
versionId: 'version-1',
|
|
requirements: [requirement('req-app', 'version-1')],
|
|
plans: [],
|
|
devTasks: [],
|
|
testCases: [],
|
|
bugs: [],
|
|
});
|
|
const emptyV22Scope = buildVersionDataScope({
|
|
versionId: 'version-1',
|
|
requirements: [],
|
|
plans: [],
|
|
devTasks: [],
|
|
testCases: [],
|
|
bugs: [],
|
|
});
|
|
|
|
const selected = selectVersionDataScope({ storeScope, v22Scope: emptyV22Scope });
|
|
|
|
assert.equal(selected?.requirements[0].id, 'req-app');
|
|
});
|