import test from 'node:test';import assert from 'node:assert/strict'; import {PROJECT_VERSION,INDICATORS,COUNTRIES,doiId,doiLink,validateUrl,parseCrossref,parseWorldBank,validGraph,normalizeSession,audit,nodeCounts,relevantLinks,shortestPath,graphCSV,linksCSV,graphML,reportMarkdown} from './engine.js'; import {DEMO} from './demo.js'; const g=DEMO.graph; function t(name,fn){test(name,fn)} t('version defined',()=>assert.equal(PROJECT_VERSION,'1.0.0')); t('demo has synthetic label',()=>assert.match(DEMO.label,/SYNTHETIC/)); t('demo nodes and links pass validation',()=>assert.deepEqual(validGraph(g),{nodes:9,links:8,publications:3})); t('no real DOIs in demo',()=>assert(g.nodes.every(n=>!n.doi))); t('country registry includes core corridor',()=>assert.equal(COUNTRIES.BOL,'Bolivia')); t('indicator registry has six entries',()=>assert.equal(Object.keys(INDICATORS).length,6)); t('DOI extracts from URL',()=>assert.equal(doiId('https://doi.org/10.1016/J.TEST.2023.01'),'10.1016/j.test.2023.01')); t('DOI extracts from doi prefix',()=>assert.equal(doiId('doi: 10.5555/example'),'10.5555/example')); t('DOI rejects malformed',()=>assert.equal(doiId('javascript:alert(1)'),'')); t('DOI link valid',()=>assert.equal(doiLink('10.1234/foo'),'https://doi.org/10.1234/foo')); t('unsafe URLs rejected',()=>assert.equal(validateUrl('javascript:alert(1)'),'')); t('https source accepted',()=>assert.match(validateUrl('https://www.example.org'),/^https:/)); t('crossref array title and names',()=>{const n=parseCrossref({title:['Test paper'],author:[{given:'Ana',family:'Test'}],published:{'date-parts':[[2022]]},DOI:'10.5678/Z'});assert.equal(n.year,2022);assert.equal(n.authors[0],'Ana Test');assert.equal(n.doi,'10.5678/z');}); t('crossref message wrapper parsed',()=>assert.equal(parseCrossref({message:{title:['A title']}}).title,'A title')); t('crossref missing title fails',()=>assert.throws(()=>parseCrossref({DOI:'10.1234/xxx'}),/title/)); t('crossref javascript URL blocked',()=>assert.equal(parseCrossref({title:['A'],URL:'javascript:alert(1)'}).url,'')); t('World Bank picks newest numeric observation',()=>{const n=parseWorldBank([{},[{date:'2022',value:1},{date:'2025',value:null},{date:'2024',value:6}]],{country:'BOL',indicator:'NY.GDP.PCAP.CD'});assert.equal(n.year,2024);assert.equal(n.value,6);}); t('World Bank ignores null values',()=>assert.throws(()=>parseWorldBank([{},[{date:'2024',value:null}]],{country:'ARG',indicator:'SE.PRM.CMPT.ZS'}),/No numeric/)); t('World Bank malformed response fails',()=>assert.throws(()=>parseWorldBank({}, {country:'ARG',indicator:'SE.PRM.CMPT.ZS'}),/No World Bank/)); t('World Bank invalid country fails',()=>assert.throws(()=>parseWorldBank([{},[]], {country:'XXX',indicator:'SE.PRM.CMPT.ZS'}),/Select/)); t('node counts',()=>assert.deepEqual(nodeCounts(g),{publication:3,claim:2,indicator:2,hypothesis:2})); t('report counts demo',()=>{const a=audit(g);assert.equal(a.linkTotal,8);assert.equal(a.completeness,100);assert.equal(a.nodeTotal,9)}); t('contested claim recognized',()=>assert(audit(g).claimSummaries.find(s=>s.id==='c_access').contested)); t('links to selected node',()=>assert.equal(relevantLinks(g,'h_training').length,2)); t('shortest connected path',()=>assert.deepEqual(shortestPath(g,'p_access_survey','h_training'),['p_access_survey','c_access','h_training'])); t('path to self',()=>assert.deepEqual(shortestPath(g,'c_access','c_access'),['c_access'])); t('path across disconnected nodes returns empty',()=>assert.deepEqual(shortestPath({nodes:[{id:'a',kind:'claim',title:'A'},{id:'b',kind:'claim',title:'B'}],links:[]},'a','b'),[])); t('valid graph rejects missing node array',()=>assert.throws(()=>validGraph({}),/nodes and links/)); t('valid graph rejects duplicate ids',()=>assert.throws(()=>validGraph({nodes:[g.nodes[0],g.nodes[0]],links:[]}),/duplicated/)); t('valid graph rejects unlisted relation',()=>assert.throws(()=>validGraph({nodes:g.nodes,links:[{id:'a',from:'p_education_review',to:'c_access',relation:'proves'}]}),/Invalid edge/)); t('valid graph rejects orphaned link',()=>assert.throws(()=>validGraph({nodes:g.nodes,links:[{id:'a',from:'missing',to:'c_access',relation:'supports'}]}),/Invalid edge/)); t('valid graph rejects self-link',()=>assert.throws(()=>validGraph({nodes:g.nodes,links:[{id:'a',from:'c_access',to:'c_access',relation:'supports'}]}),/Invalid edge/)); t('valid graph rejects unsafe DOI',()=>assert.throws(()=>validGraph({nodes:[{id:'p',kind:'publication',title:'Title',doi:'wrong'}],links:[]}),/DOI/)); t('normalization retains round trip',()=>{const x=normalizeSession({graph:g,notes:'test'});assert.deepEqual(x.graph,g);assert.equal(x.notes,'test')}); t('CSV escapes embedded quotes and commas',()=>{const x={nodes:[{id:'c',kind:'claim',title:'An "inquiry", now'}],links:[]};assert.match(graphCSV(x),/'?"An ""inquiry"", now"'?/)}); t('edge CSV contains locator',()=>assert.match(linksCSV(g),/Fictional section 3/)); t('GraphML escapes dangerous XML text',()=>{const xml=graphML({nodes:[{id:'x',kind:'claim',title:'A&B '}],links:[]});assert.match(xml,/A&B <C>/)}); t('report preserves caution',()=>assert.match(reportMarkdown(g),/does not establish factual truth/)); t('report includes source excerpts',()=>assert.match(reportMarkdown(g),/Fictional section 3/)); t('audit detects incompletely documented link',()=>{const x=structuredClone(g);x.links[0].locator='';const a=audit(x);assert.equal(a.unresolved.length,1);assert.equal(a.completeness,88)}); t('audit flags unsupported statement',()=>{const x=structuredClone(g);x.nodes.push({kind:'claim',id:'c_new',title:'Untested claim'});assert(audit(x).flags.some(f=>f.nodeId==='c_new'))}); t('audit catches unconnected record',()=>{const x=structuredClone(g);x.nodes.push({kind:'publication',id:'p_new',title:'New record'});assert(audit(x).orphans.some(n=>n.id==='p_new'))}); t('large payload rejected',()=>assert.throws(()=>validGraph({nodes:Array.from({length:301},(_,i)=>({id:`c${i}`,kind:'claim',title:'X'})),links:[]}),/maximum size/)); t('indicator negative allowed (balances etc)',()=>{const n=parseWorldBank([{},[{date:'2021',value:-3}]],{country:'ARG',indicator:'SL.UEM.TOTL.ZS'});assert.equal(n.value,-3)}); t('report does not fabricate external verification',()=>assert(!reportMarkdown(g).includes('scientifically verified')));