const assert=require('node:assert/strict'), fs=require('node:fs'),E=require('./engine.js');const data=JSON.parse(fs.readFileSync(__dirname+'/evaluation_snapshots.json'));const rows=E.parseRows(data),manifest=JSON.parse(fs.readFileSync(__dirname+'/benchmark_manifest.json'));let tested=0;function check(title,fn){try{fn();tested++;console.log('PASS',title);}catch(e){console.error('FAIL',title,e);process.exitCode=1;}} check('reproducible model/evaluation rows',()=>assert.equal(rows.length,736)); check('baseline split lengths',()=>{for(const m of E.METHODS){assert.equal(E.select(rows,m,'iid').length,130);assert.equal(E.select(rows,m,'challenge').length,54);}}); check('manifest provenance',()=>{assert.equal(manifest.trainCount,842);assert.equal(manifest.sourceType,'synthetic_curated');}); check('validated two independent splits',()=>assert.equal(E.audit(rows).clean,true)); check('duplicate detection',()=>assert.throws(()=>E.parseRows([rows[0],rows[0]]),/Duplicate/)); check('input labels rejected',()=>assert.throws(()=>E.parseRows([{...rows[0],truth:'superiority'}]),/Invalid model/)); check('input empty rejected',()=>assert.throws(()=>E.parseRows([]),/Expected an array/)); check('structured object loading',()=>assert.equal(E.parseRows({rows:rows.slice(0,2)}).length,2)); check('model challenge NB 40/54',()=>{let m=E.metrics(E.select(rows,'nb','challenge'));assert.equal(m.correct,40);assert.equal(m.n,54);}); check('hybrid 42/54 challenge',()=>{const m=E.metrics(E.select(rows,'hybrid','challenge'));assert.equal(m.correct,42);}); check('NB IID 130/130',()=>assert.equal(E.metrics(E.select(rows,'nb','iid')).correct,130)); check('char IID 124/130',()=>assert.equal(E.metrics(E.select(rows,'char','iid')).correct,124)); check('empty metrics safe',()=>assert.equal(E.metrics([]).accuracy,null)); check('macro F1 bounded',()=>{for(const m of E.METHODS){const x=E.metrics(E.select(rows,m,'challenge'));assert(x.macroF1>=0&&x.macroF1<=1)}}); check('slice sample size exact',()=>{const sl=E.segments(E.select(rows,'hybrid','challenge'),'language');assert.equal(sl.length,3);assert.equal(sl.reduce((s,x)=>s+x.n,0),54);}); check('truth-class slice returns six',()=>assert.equal(E.segments(E.select(rows,'hybrid','iid'),'truth').length,6)); check('drift equal splits returns zeros',()=>{const a=E.select(rows,'hybrid','iid'),d=E.drift(a,a);assert.equal(d.languageJSD,0);assert.equal(d.lengthPSI,0);}); check('drift different splits has change',()=>{const d=E.compare(rows,'hybrid').drift;assert(d.lengthPSI>.25);assert(d.languageJSD<.01);}); check('JSD commutative',()=>assert(Math.abs(E.jsd([1,8,2],[6,2,3])-E.jsd([6,2,3],[1,8,2]))<1e-9)); check('JSD bounded by one',()=>assert(Math.abs(E.jsd([20,0],[0,20])-1)<1e-8)); check('PSI zero if equal',()=>assert.equal(E.psi([1,3,4],[1,3,4]),0)); check('bootstrap deterministic',()=>{const a=E.select(rows,'hybrid','challenge');assert.deepEqual(E.ciAccuracy(a,80,10),E.ciAccuracy(a,80,10));}); check('bootstrap confidence interval ordered',()=>{const x=E.ciAccuracy(E.select(rows,'hybrid','challenge'));assert(x.low<=x.high&&x.high<=1);}); check('paired comparison same method zero',()=>{const z=E.pairedComparison(rows,'hybrid','hybrid');assert.equal(z.delta,0);assert.equal(z.n,54);assert.equal(z.bWins,0);}); check('paired comparison signs reverse',()=>{const a=E.pairedComparison(rows,'nb','hybrid'),b=E.pairedComparison(rows,'hybrid','nb');assert(Math.abs(a.delta+b.delta)<1e-8);assert.equal(a.aWins,b.bWins);}); check('gate default fails on length shift',()=>assert.equal(E.gate(rows,'hybrid').pass,false)); check('gate threshold changes not metrics',()=>{const a=E.gate(rows,'hybrid'),b=E.gate(rows,'hybrid',{maxLengthPSI:1});assert.equal(a.pass,false);assert.equal(b.pass,true);assert.equal(a.checks.length,6);}); check('CSV roundtrip quoted comma and newline',()=>{const a=E.csv([{id:'a',note:'hello, "world"\n2'}],['id','note']);assert.deepEqual(E.parseCsv(a),[{id:'a',note:'hello, "world"\n2'}]);}); check('CSV formula injection prevented',()=>{const x=E.csv([{a:'=HYPERLINK("https://x")'}],['a']);assert(x.includes("'=HYPERLINK"));}); check('CSV malformed quotes rejected',()=>assert.throws(()=>E.parseCsv('id,txt\n1,"hi'),/Unclosed/)); check('eval records with exotic text remain safe structure',()=>{const x=E.parseRows([{...rows[0],text:'',id:'X'}]);assert(x[0].text.includes('{const x=rows.slice(0,2).map((r,i)=>({...r,id:'x',split:i?'challenge':'iid'}));assert.equal(E.audit(x).splitIdOverlap,1);}); check('empty sample intervals forbidden',()=>assert.throws(()=>E.ciAccuracy([]),/Empty/)); check('invalid bootstrap sample count forbidden',()=>assert.throws(()=>E.ciAccuracy(rows,3),/iterations/)); check('length-bin histogram PSI finite',()=>{const x=E.compare(rows,'word').drift.lengthPSI;assert(Number.isFinite(x));}); check('label recall values finite',()=>{const z=E.metrics(E.select(rows,'nb','challenge'));assert(z.perLabel.every(x=>Number.isFinite(x.recall)));}); check('reproducible report score',()=>assert(Math.abs(E.compare(rows,'hybrid').challenge.accuracy-.7777777777777778)<1e-12)); console.log('TESTS',tested,process.exitCode?'FAIL':'PASS');if(process.exitCode)process.exit(1);