const B='http://127.0.0.1:3500'; const get=(u)=>fetch(B+u).then(r=>r.json()); const put=(u,b)=>fetch(B+u,{method:'PUT',headers:{'Content-Type':'application/json'},body:JSON.stringify(b)}).then(r=>r.json()); const del=(u)=>fetch(B+u,{method:'DELETE'}).then(r=>r.json()); const post=(u,b)=>fetch(B+u,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(b)}).then(r=>r.json()); let pass=0,fail=0; const t=(n,ok)=>{ok?(pass++,console.log(' PASS',n)):(fail++,console.log(' FAIL',n))}; const entry=(kb,l)=>kb.entries.find(e=>e.label===l); const cand=(o)=>({id:'c-x',incumbentVendor:'',incumbentProduct:'',domain:'',productIds:[],annualSpend:0,units:0,unitLabel:'',renewalDate:'',capability:4,switchEase:3,timeToRoiMonths:12,savingsPct:50,migrationCost:0,strategicValue:3,risks:'',notes:'',included:true,...o}); // Self-contained: create an assessment, learn from it, then take things away. const a = await post('/api/assessments', { customerName: 'Prune Test Co' }); a.candidates = [ cand({ id:'c-1', incumbentVendor:'CyberArk', incumbentProduct:'PAM', domain:'iam', productIds:['esg-pam'], annualSpend:400000, savingsPct:55 }), cand({ id:'c-2', incumbentVendor:'Okta', incumbentProduct:'Workforce Identity', domain:'iam', productIds:['esg-siteminder'], annualSpend:300000, savingsPct:40 }), ]; await put('/api/assessments/'+a.id, a); const learned = await get('/api/knowledge-base'); t('both candidates learned', entry(learned,'CyberArk PAM').observations.some(o=>o.assessmentId===a.id) && entry(learned,'Okta Workforce Identity').observations.some(o=>o.assessmentId===a.id)); t('learned the real savings figure', entry(learned,'CyberArk PAM').savingsPct === 55); // Remove one candidate -> only its observation is pruned. a.candidates = a.candidates.filter(c => c.id !== 'c-1'); await put('/api/assessments/'+a.id, a); const pruned = await get('/api/knowledge-base'); t('removed candidate is forgotten', !entry(pruned,'CyberArk PAM').observations.some(o=>o.assessmentId===a.id)); t('defaults revert to seeded values', entry(pruned,'CyberArk PAM').savingsPct === 50); t('the entry survives for reuse', entry(pruned,'CyberArk PAM').productIds.includes('esg-pam')); t('the other candidate is untouched', entry(pruned,'Okta Workforce Identity').observations.some(o=>o.assessmentId===a.id)); // Delete the assessment -> everything it taught is forgotten. await del('/api/assessments/'+a.id); const gone = await get('/api/knowledge-base'); t('deleting the assessment forgets the rest', !entry(gone,'Okta Workforce Identity').observations.some(o=>o.assessmentId===a.id)); t('unrelated learning is preserved', entry(gone,'Splunk Enterprise').observations.length === 1); console.log(`\n${pass} passed, ${fail} failed`); process.exit(fail?1:0);