205 lines
7.6 KiB
TypeScript
205 lines
7.6 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
|
|
import { __resetApiAvailabilityForTests, resolveApiBase } from './api';
|
|
import {
|
|
loadV22RequirementsPage,
|
|
loadV22VersionDetailData,
|
|
loadV22WorkspaceData,
|
|
loadV22XiaobaoWarnings,
|
|
} from './v22-api';
|
|
|
|
test('V2.2 version detail API maps relation rows to existing frontend scope types', async () => {
|
|
const originalFetch = globalThis.fetch;
|
|
const apiBase = resolveApiBase();
|
|
const calls: Array<{ url: string; method: string }> = [];
|
|
|
|
globalThis.fetch = (async (input: RequestInfo | URL, init?: RequestInit) => {
|
|
const url = String(input);
|
|
calls.push({ url, method: init?.method ?? 'GET' });
|
|
if (url === `${apiBase}/config/ai`) {
|
|
return jsonResponse({});
|
|
}
|
|
if (url === `${apiBase}/v2.2/versions/version-1/detail-data`) {
|
|
return jsonResponse({
|
|
version: {
|
|
id: 'version-1',
|
|
productId: 'product-1',
|
|
projectId: 'project-1',
|
|
name: 'Project A V1.0',
|
|
releaseDate: '2026-01-20T00:00:00.000Z',
|
|
createdAt: '2026-01-01T00:00:00.000Z',
|
|
},
|
|
requirements: [{
|
|
id: 'req-1',
|
|
productId: 'product-1',
|
|
projectId: 'project-1',
|
|
versionId: 'version-1',
|
|
code: 'REQ-001',
|
|
title: 'Login',
|
|
description: 'Password login',
|
|
status: 'adopted',
|
|
priority: 1,
|
|
type: 'feature',
|
|
sourceType: 'customer',
|
|
sourceTarget: 'ACME',
|
|
platform: 'web, ios',
|
|
creatorId: 'member-1',
|
|
createdAt: '2026-01-01T00:00:00.000Z',
|
|
}],
|
|
versionPlans: [{
|
|
id: 'plan-1',
|
|
versionId: 'version-1',
|
|
type: 'research',
|
|
title: 'Research login',
|
|
status: 'pending',
|
|
ownerId: 'member-1',
|
|
expectedStartAt: '2026-01-01',
|
|
expectedEndAt: '2026-01-05T18:00:00.000Z',
|
|
requirementCoverage: [{ requirementId: 'req-1', status: 'completed', updatedAt: '2026-01-02T00:00:00.000Z', updatedBy: 'member-1' }],
|
|
logs: [],
|
|
createdAt: '2026-01-01T00:00:00.000Z',
|
|
}],
|
|
devTasks: [{
|
|
id: 'dev-1',
|
|
versionId: 'version-1',
|
|
requirementId: 'req-1',
|
|
categoryId: 'cat-dev',
|
|
code: 'DEV-001',
|
|
title: 'Build login',
|
|
description: 'Implement login',
|
|
status: 'in_progress',
|
|
priority: 2,
|
|
assigneeId: 'member-2',
|
|
creatorId: 'member-1',
|
|
isBlocked: false,
|
|
expectedStartAt: '2026-01-02T09:00:00.000Z',
|
|
expectedEndAt: '2026-01-03T18:00:00.000Z',
|
|
startDate: '2026-01-02T09:00:00.000Z',
|
|
completedAt: null,
|
|
estimateHours: 8,
|
|
references: [{ type: 'requirement', id: 'req-1', label: 'REQ-001' }],
|
|
aiDraft: false,
|
|
createdAt: '2026-01-02T08:00:00.000Z',
|
|
updatedAt: '2026-01-02T09:00:00.000Z',
|
|
}],
|
|
testCases: [{
|
|
id: 'tc-1',
|
|
versionId: 'version-1',
|
|
requirementId: 'req-1',
|
|
categoryId: 'cat-test',
|
|
code: 'TC-001',
|
|
title: 'Verify login',
|
|
description: 'Check password login',
|
|
status: 'pending',
|
|
roundNo: 1,
|
|
priority: 3,
|
|
assigneeId: 'member-3',
|
|
creatorId: 'member-1',
|
|
references: [],
|
|
aiDraft: false,
|
|
createdAt: '2026-01-03T08:00:00.000Z',
|
|
updatedAt: '2026-01-03T08:00:00.000Z',
|
|
}],
|
|
bugs: [{
|
|
id: 'bug-1',
|
|
versionId: 'version-1',
|
|
testCaseId: 'tc-1',
|
|
code: 'BUG-001',
|
|
title: 'Login error',
|
|
description: 'Wrong prompt',
|
|
status: 'open',
|
|
severity: 'major',
|
|
priority: 0,
|
|
assigneeId: 'member-2',
|
|
reporterId: 'member-3',
|
|
createdAt: '2026-01-04T08:00:00.000Z',
|
|
updatedAt: '2026-01-04T08:00:00.000Z',
|
|
}],
|
|
});
|
|
}
|
|
throw new Error(`Unexpected fetch ${url}`);
|
|
}) as typeof fetch;
|
|
|
|
try {
|
|
__resetApiAvailabilityForTests();
|
|
const result = await loadV22VersionDetailData('version-1');
|
|
|
|
assert.equal(calls[1].url, `${apiBase}/v2.2/versions/version-1/detail-data`);
|
|
assert.equal(result.scope.requirements[0].typeId, 'feature');
|
|
assert.deepEqual(result.scope.requirements[0].platforms, ['web', 'ios']);
|
|
assert.equal(result.scope.requirements[0].priority, 'P1');
|
|
assert.equal(result.scope.requirements[0].creator, 'member-1');
|
|
assert.equal(result.scope.devTasks[0].taskNo, 'DEV-001');
|
|
assert.equal(result.scope.devTasks[0].actualStartAt, '2026-01-02T09:00:00.000Z');
|
|
assert.equal(result.scope.devTasks[0].actualEndAt, undefined);
|
|
assert.equal(result.scope.devTasks[0].createdBy, 'member-1');
|
|
assert.equal(result.scope.testCases[0].caseNo, 'TC-001');
|
|
assert.equal(result.scope.testCases[0].createdBy, 'member-1');
|
|
assert.equal(result.scope.bugs[0].bugNo, 'BUG-001');
|
|
assert.equal(result.scope.bugs[0].reportedBy, 'member-3');
|
|
assert.equal(result.scope.plans[0].owner, 'member-1');
|
|
assert.equal(result.scope.plans[0].startTime, '2026-01-01T00:00:00.000Z');
|
|
assert.equal(result.scope.plansByType.research[0].id, 'plan-1');
|
|
assert.equal(result.scope.requirementIdSet.has('req-1'), true);
|
|
} finally {
|
|
globalThis.fetch = originalFetch;
|
|
}
|
|
});
|
|
|
|
test('V2.2 list APIs build scoped query strings without full-table reads', async () => {
|
|
const originalFetch = globalThis.fetch;
|
|
const apiBase = resolveApiBase();
|
|
const urls: string[] = [];
|
|
|
|
globalThis.fetch = (async (input: RequestInfo | URL) => {
|
|
const url = String(input);
|
|
urls.push(url);
|
|
if (url === `${apiBase}/config/ai`) return jsonResponse({});
|
|
if (url.startsWith(`${apiBase}/v2.2/requirements?`)) return jsonResponse({ items: [], nextCursor: 'req-2' });
|
|
if (url.startsWith(`${apiBase}/v2.2/workspace?`)) return jsonResponse({ versionPlans: [], devTasks: [], testCases: [], bugs: [] });
|
|
if (url.startsWith(`${apiBase}/v2.2/xiaobao-warning?`)) return jsonResponse([]);
|
|
throw new Error(`Unexpected fetch ${url}`);
|
|
}) as typeof fetch;
|
|
|
|
try {
|
|
__resetApiAvailabilityForTests();
|
|
await loadV22RequirementsPage({
|
|
productId: 'product-1',
|
|
projectId: 'project-1',
|
|
status: 'adopted',
|
|
priority: 'P1',
|
|
type: 'feature',
|
|
q: '登录',
|
|
sort: 'created_at_asc',
|
|
cursor: 'req-1',
|
|
limit: 50,
|
|
});
|
|
await loadV22WorkspaceData('member-1');
|
|
await loadV22XiaobaoWarnings({ userId: 'member-1' });
|
|
|
|
const requirementUrl = new URL(urls[1]);
|
|
assert.equal(requirementUrl.pathname.endsWith('/v2.2/requirements'), true);
|
|
assert.equal(requirementUrl.searchParams.get('productId'), 'product-1');
|
|
assert.equal(requirementUrl.searchParams.get('projectId'), 'project-1');
|
|
assert.equal(requirementUrl.searchParams.get('status'), 'adopted');
|
|
assert.equal(requirementUrl.searchParams.get('priority'), 'P1');
|
|
assert.equal(requirementUrl.searchParams.get('type'), 'feature');
|
|
assert.equal(requirementUrl.searchParams.get('q'), '登录');
|
|
assert.equal(requirementUrl.searchParams.get('sort'), 'created_at_asc');
|
|
assert.equal(requirementUrl.searchParams.get('cursor'), 'req-1');
|
|
assert.equal(requirementUrl.searchParams.get('limit'), '50');
|
|
assert.equal(new URL(urls[2]).searchParams.get('userId'), 'member-1');
|
|
assert.equal(new URL(urls[3]).searchParams.get('userId'), 'member-1');
|
|
} finally {
|
|
globalThis.fetch = originalFetch;
|
|
}
|
|
});
|
|
|
|
function jsonResponse(body: unknown): Response {
|
|
return new Response(JSON.stringify(body), {
|
|
status: 200,
|
|
headers: { 'Content-Type': 'application/json' },
|
|
});
|
|
}
|