import {test} from 'node:test'; import assert from 'node:assert/strict'; import fs from 'node:fs'; import {validateDevelopmentReferences,researchRecords,directionReferences,materialGroup, standardDate,standardUse,developmentBrief,DEVELOPMENT_DIRECTIONS} from '../src/core/development-references.js'; import {readDevelopmentReferences} from '../src/services/development-service.js'; const published=JSON.parse(fs.readFileSync(new URL('../src/data/development-references.json',import.meta.url))); const copy=()=>structuredClone(published); test('研发资料来源、品类关系和五维记录可回溯,统计保留候选分母',()=>{ assert.equal(validateDevelopmentReferences(published),published); const counts=Object.fromEntries(['KfCategory','KfCategoryRelation','KfMaterial','KfProcess','KfStandard','KfSupplier'].map(c=>[c,researchRecords(published,c).length])); assert.deepEqual(counts,{KfCategory:16,KfCategoryRelation:15,KfMaterial:14,KfProcess:3,KfStandard:9,KfSupplier:1}); assert.equal(published.sources.length,14); assert.equal(published.scope.products+published.scope.pending+published.scope.excluded,4674); assert.equal(published.scope.coverage.productsWithReviews,155); assert.equal(published.scope.coverage.verifiedUnitPriceObservations,0); assert.ok(published.records.every(r=>r.reviewState==='pending')); }); test('来源丢失、重复ID或关系断开拒绝展示;错误计数不能变成零',()=>{ for(const corrupt of [ d=>d.sources.pop(),d=>d.records.push(d.records[0]), d=>d.records.find(r=>r.className==='KfCategoryRelation').toCategoryRef.objectId='missing', d=>d.scope.coverage.products=100,d=>d.scope.coverage.productsWithReviews=10000, d=>delete d.scope.coverage.productsWithReviews, ]){const d=copy();corrupt(d);assert.throws(()=>validateDevelopmentReferences(d));} }); test('方向假设保留原评与完整定位,不把待核配方称为已验证结果',()=>{ for(const d of DEVELOPMENT_DIRECTIONS){ const records=directionReferences(published,d); assert.equal(records.length,d.keys.length,d.id); const text=developmentBrief(published,d); assert.ok(text.includes('待验证设想')&&text.includes(published.libraryGeneration)); assert.ok(text.includes('快照')&&text.includes('不代表需求比例')); for(const r of records)assert.ok(text.includes(r.className+'/'+r.objectId)&&text.includes('KfSource/'+r.sourceId)); for(const rid of d.reviews){const r=published.reviewExamples.find(r=>r.reviewId===rid);assert.ok(r);assert.ok(text.includes(r.text)&&text.includes(r.reviewId)&&text.includes(r.sourceRecordId));} } assert.equal(DEVELOPMENT_DIRECTIONS.find(d=>d.id==='nutrition').reviews.length,0); }); test('驳回资料不再作为可用研发参考,原料角色和标准版次分开',()=>{ const d=copy(),row=d.records.find(r=>r.key==='material-ye');row.reviewState='rejected'; assert.ok(!directionReferences(d,DEVELOPMENT_DIRECTIONS[0]).some(r=>r.objectId===row.objectId)); assert.ok(!researchRecords(d,'KfMaterial').some(r=>r.objectId===row.objectId)); const groups=researchRecords(published,'KfMaterial').reduce((o,r)=>(o[materialGroup(r)]=(o[materialGroup(r)]||0)+1,o),{}); assert.deepEqual(groups,{'营养强化':2,'面体原料':3,'添加剂候选':6,'风味配料':3}); const future=published.records.find(r=>r.key==='standard-GB7718-2025'); assert.equal(standardDate(future),'2027-03-16');assert.match(standardUse(future),/过渡/); }); test('研发读取请求不带凭据,网络和格式错误直接报告',async()=>{ const originalDocument=globalThis.document; globalThis.document={querySelector:()=>({getAttribute:()=>'/'}),baseURI:'http://localhost/'}; const originalLocation=globalThis.location; globalThis.location={origin:'http://localhost',pathname:'/'}; try{ let request; await readDevelopmentReferences({fetchImpl:async(url,options)=>{request={url,options};return {ok:true,json:async()=>published};}}); assert.equal(request.url,'http://localhost/src/data/development-references.json');assert.equal(request.options.cache,'no-store'); assert.equal(request.options.headers,undefined); await assert.rejects(()=>readDevelopmentReferences({fetchImpl:async()=>({ok:false})}),/尚未发布/); await assert.rejects(()=>readDevelopmentReferences({fetchImpl:async()=>({ok:true,json:async()=>({})})}),/不完整/); }finally{globalThis.document=originalDocument;globalThis.location=originalLocation;} });