import assert from 'node:assert/strict'; import { readFileSync } from 'node:fs'; import { join } from 'node:path'; import test from 'node:test'; const storeSource = () => readFileSync(join(process.cwd(), 'stores/useRequirementStore.ts'), 'utf8'); const pageSource = () => readFileSync(join(process.cwd(), 'app/requirements/page.tsx'), 'utf8'); const productDetailPage = () => readFileSync(join(process.cwd(), 'app/products/[id]/page.tsx'), 'utf8'); const projectDetailPage = () => readFileSync(join(process.cwd(), 'app/projects/[id]/page.tsx'), 'utf8'); function storeMethodBody(text: string, name: string) { const start = text.indexOf(` ${name}:`); assert.notEqual(start, -1, `missing store method ${name}`); let depth = 0; let sawFirstBrace = false; for (let i = start; i < text.length; i += 1) { const char = text[i]; if (char === '{') { depth += 1; sawFirstBrace = true; } if (char === '}') { depth -= 1; if (sawFirstBrace && depth === 0) { return text.slice(start, i + 1); } } } throw new Error(`could not extract store method ${name}`); } test('requirement store imports domain requirement write helpers', () => { const text = storeSource(); assert.match(text, /from '@\/lib\/domain-api'/); assert.match(text, /createRequirementByProductId/); assert.match(text, /updateRequirementByProductId/); assert.match(text, /deleteRequirementByProductId/); }); test('requirement mutations use domain APIs instead of AppData requirements saves', () => { const text = storeSource(); const createRequirement = storeMethodBody(text, 'createRequirement'); const updateRequirement = storeMethodBody(text, 'updateRequirement'); const deleteRequirement = storeMethodBody(text, 'deleteRequirement'); assert.match(createRequirement, /createRequirementByProductId\(newReq\.productId,/); assert.match(updateRequirement, /updateRequirementByProductId\(productId, id,/); assert.match(deleteRequirement, /deleteRequirementByProductId\(productId, id\)/); assert.doesNotMatch(createRequirement, /saveServerData\('requirements'/); assert.doesNotMatch(updateRequirement, /saveServerData\('requirements'/); assert.doesNotMatch(deleteRequirement, /saveServerData\('requirements'/); }); test('requirement dictionaries use governance dictionaries instead of AppData requirement saves', () => { const text = storeSource(); const fetchRequirements = storeMethodBody(text, 'fetchRequirements'); const addType = storeMethodBody(text, 'addType'); const updateType = storeMethodBody(text, 'updateType'); const deleteType = storeMethodBody(text, 'deleteType'); const addSourceTarget = storeMethodBody(text, 'addSourceTarget'); const addPlatform = storeMethodBody(text, 'addPlatform'); assert.match(fetchRequirements, /loadRequirementGovernanceDictionaries/); assert.match(addType, /createGovernanceDictionary\('requirement_type'/); assert.match(updateType, /updateGovernanceDictionary\('requirement_type'/); assert.match(deleteType, /deleteGovernanceDictionary\('requirement_type'/); assert.match(addSourceTarget, /createGovernanceDictionary\('requirement_source'/); assert.match(addPlatform, /createGovernanceDictionary\('requirement_platform'/); for (const body of [addType, updateType, deleteType, addSourceTarget, addPlatform]) { assert.doesNotMatch(body, /saveServerData\('requirements'/); } }); test('requirement pool no longer exposes local dictionary management drawers', () => { const page = pageSource(); const modal = readFileSync(join(process.cwd(), 'components/requirement/RequirementModal.tsx'), 'utf8'); assert.doesNotMatch(page, /SourceDrawer|DictDrawer/); assert.doesNotMatch(page, /setDrawerType/); assert.doesNotMatch(modal, /onOpenDrawer/); }); test('requirement pool uses server pagination without full AppData load for scoped list queries', () => { const text = pageSource(); assert.match(text, /loadV22RequirementsPage\(v22RequirementQuery\)/); assert.match(text, /if \(v22RequirementQuery && !v22RequirementsFailed\) return;\s+void hydrateRequirementStoreFallback\(\);/); assert.doesNotMatch(text, /fetchRequirements\(\);/); assert.doesNotMatch(text, /fetchDevTasks\(\);/); }); test('requirement store can hydrate scoped relation rows for product and project pages', () => { const text = storeSource(); const fetchRequirements = storeMethodBody(text, 'fetchRequirements'); assert.match(text, /loadV22RequirementsPage/); assert.match(fetchRequirements, /options\?\.productId/); assert.match(fetchRequirements, /mergeRequirementsForScope/); assert.doesNotMatch(fetchRequirements, /loadServerData<[^>]+>\('requirements'/); }); test('product and project detail pages request scoped requirement relation rows', () => { const productPage = productDetailPage(); const projectPage = projectDetailPage(); assert.match(productPage, /fetchRequirements\(\{ productId \}\)/); assert.doesNotMatch(productPage, /fetchRequirements\(\);/); assert.match(projectPage, /fetchRequirements\(\{ productId: project\.productId, projectId \}\)/); assert.doesNotMatch(projectPage, /fetchRequirements\(\);/); }); test('relation-backed requirement rows remain mutable without AppData identity', () => { const text = pageSource(); assert.match(text, /const canMutateRequirement = \(req: Requirement\) => Boolean\(req\.productId\);/); assert.doesNotMatch(text, /appDataRequirementIds\.has\(req\.id\)/); });