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'); });