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()); let pass=0,fail=0; const t=(n,ok)=>{ok?(pass++,console.log(' PASS',n)):(fail++,console.log(' FAIL',n))}; const kb0 = await get('/api/knowledge-base'); t('seeded KB loads', kb0.entries.length >= 630); const f5 = kb0.entries.find(e=>e.label==='F5 BIG-IP'); t('F5 BIG-IP pre-mapped to Avi', f5 && f5.productIds.includes('ans-avi')); t('seeded entry has no observations', f5.observations.length === 0); // Save the seeded assessment -> should learn from its 10 candidates. const a = await get('/api/assessments/northwind-financial'); await put('/api/assessments/northwind-financial', a); const kb1 = await get('/api/knowledge-base'); const learned = kb1.entries.filter(e=>e.observations.length>0); t('learned from all 10 candidates', learned.length === 10); const splunk = kb1.entries.find(e=>e.label==='Splunk Enterprise'); t('Splunk Enterprise captured', !!splunk); t('Splunk learned our real mapping', splunk.productIds.join()==='vcf-ops-logs,aod-dx-oi'); t('Splunk learned switchEase 2', splunk.switchEase === 2); t('Splunk learned savingsPct 45', splunk.savingsPct === 45); t('Splunk learned risks text', /SOC dashboards/.test(splunk.risks)); t('observation links back to assessment', splunk.observations[0].assessmentId === 'northwind-financial'); // Re-saving must update in place, not stack duplicates. await put('/api/assessments/northwind-financial', a); const kb2 = await get('/api/knowledge-base'); const splunk2 = kb2.entries.find(e=>e.label==='Splunk Enterprise'); t('re-save does not duplicate observations', splunk2.observations.length === 1); t('entry count stable after re-save', kb2.entries.length === kb1.entries.length); // A pinned entry must survive learning untouched. const pinTarget = structuredClone(kb2); const p = pinTarget.entries.find(e=>e.label==='Splunk Enterprise'); p.pinned = true; p.savingsPct = 99; p.switchEase = 5; await put('/api/knowledge-base', pinTarget); await put('/api/assessments/northwind-financial', a); const kb3 = await get('/api/knowledge-base'); const splunk3 = kb3.entries.find(e=>e.label==='Splunk Enterprise'); t('curated entry keeps savingsPct', splunk3.savingsPct === 99); t('curated entry keeps switchEase', splunk3.switchEase === 5); console.log(`\n${pass} passed, ${fail} failed`); process.exit(fail?1:0);