Files
ftb-project-management/apps/web/lib/no-broad-business-fetch-source.test.ts
2e595c7e72
Some checks failed
Deploy Production / Build, push, deploy, verify (push) Has been cancelled
refactor(data): 收口关系表运行时数据源
- 移除已迁移业务 AppData 运行时 fallback,改走领域 API 和关系表快读
- 补齐需求产品负责人、版本计划任务 JSON 和成员 username 回填迁移
- 统一治理字典入口,并补充 AI provider、数据源契约和领域服务测试

Co-Authored-By: Codex GPT-5 <codex@openai.com>
2026-07-09 14:59:49 +08:00

90 lines
3.7 KiB
TypeScript

import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import { join } from 'node:path';
import test from 'node:test';
function source(path: string) {
return readFileSync(join(process.cwd(), path), 'utf8');
}
function assertNoBroadBusinessFetches(path: string, allowedPatterns: RegExp[] = []) {
const text = allowedPatterns.reduce(
(current, pattern) => current.replace(pattern, ''),
source(path),
);
for (const method of [
'fetchRequirements',
'fetchPlans',
'fetchTasks',
'fetchDevTasks',
'fetchTestCases',
'fetchBugs',
]) {
assert.doesNotMatch(
text,
new RegExp(`\\b${method}\\(\\s*\\)`),
`${path} must not call ${method}() without a relation scope`,
);
}
}
test('version detail runtime loads requirements and work data by relation scope', () => {
const text = source('app/versions/[id]/page.tsx');
assert.match(text, /fetchRequirements\(\{ productId: version\.productId, versionId \}\)/);
assert.match(text, /fetchRequirements\(\{ productId: version\.productId, versionId, force: true \}\)/);
assertNoBroadBusinessFetches('app/versions/[id]/page.tsx');
});
test('version list hydrates visible versions through scoped relation partitions', () => {
const text = source('app/versions/page.tsx');
assert.match(text, /fetchRequirements\(\{ productId: version\.productId, versionId: version\.id \}\)/);
assert.match(text, /fetchPlans\(\{ versionId: version\.id \}\)/);
assert.match(text, /fetchDevTasks\(\{ versionId: version\.id \}\)/);
assert.match(text, /fetchTestCases\(\{ versionId: version\.id \}\)/);
assertNoBroadBusinessFetches('app/versions/page.tsx');
});
test('workspace fallback and drawer hydration remain version scoped', () => {
const hook = source('hooks/useWorkspaceWorkItems.ts');
const page = source('app/workspace/page.tsx');
assert.match(hook, /hydrateWorkspaceStoreFallback/);
assert.match(page, /hydrateWorkspaceStoreFallback/);
assert.match(page, /fetchRequirements\(\{ productId: versionContext\.productId, versionId: item\.versionId \}\)/);
assertNoBroadBusinessFetches('hooks/useWorkspaceWorkItems.ts');
assertNoBroadBusinessFetches('app/workspace/page.tsx');
});
test('xiaobao fallback hydrates visible versions by relation scope only', () => {
const text = source('hooks/useXiaobaoWarningRisks.ts');
assert.match(text, /hydrateXiaobaoStoreFallback/);
assert.match(text, /fetchRequirements\(\{ productId: version\.productId, versionId: version\.id \}\)/);
assert.match(text, /fetchPlans\(\{ versionId: version\.id \}\)/);
assert.match(text, /fetchTasks\(\{ versionId: version\.id \}\)/);
assert.match(text, /fetchTestCases\(\{ versionId: version\.id \}\)/);
assert.match(text, /fetchBugs\(\{ versionId: version\.id \}\)/);
assertNoBroadBusinessFetches('hooks/useXiaobaoWarningRisks.ts');
});
test('overtime modal loads selected version requirements through relation scope', () => {
const text = source('app/overtime/page.tsx');
assert.match(text, /onVersionChange=\{\(version\) => fetchRequirements\(\{ productId: version\.productId, versionId: version\.id \}\)\}/);
assertNoBroadBusinessFetches('app/overtime/page.tsx');
});
test('requirement pool fallback hydrates the selected relation scope', () => {
const text = source('app/requirements/page.tsx');
assert.match(text, /hydrateRequirementStoreFallback/);
assert.match(text, /fetchRequirements\(\{ productId: selectedScope\.productId \}\)/);
assert.match(text, /fetchRequirements\(\{ productId: project\.productId, projectId: selectedScope\.projectId \}\)/);
assert.match(text, /fetchRequirements\(\{ productId: product\.id \}\)/);
assert.match(text, /fetchDevTasks\(\{ versionId: version\.id \}\)/);
assertNoBroadBusinessFetches('app/requirements/page.tsx');
});