Next.js app for mapping a customer's competitive tool landscape onto our portfolio and building the consolidation business case: assessment workbench, customer report and Word export, product catalog, competitive knowledge base, dossier import, shareable landscape questionnaires with a review inbox, tool-footprint and business-unit value analysis, and an implementation timeline. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
65 lines
3.9 KiB
JavaScript
65 lines
3.9 KiB
JavaScript
// Parser tests run against the compiled lib so they exercise the real source.
|
|
const { parseDossier, buildImportResult } = await import('../.test-build/dossier.js');
|
|
import { readFileSync } from 'node:fs';
|
|
|
|
const raw = readFileSync(new URL('./fixtures/dossier-sample.txt', import.meta.url), 'utf8');
|
|
const kb = JSON.parse(readFileSync(new URL('../data/knowledge-base.json', import.meta.url), 'utf8'));
|
|
const catalog = JSON.parse(readFileSync(new URL('../data/catalog.json', import.meta.url), 'utf8'));
|
|
|
|
let pass=0, fail=0;
|
|
const t=(n,ok,extra='')=>{ok?(pass++,console.log(' PASS',n)):(fail++,console.log(' FAIL',n,extra))};
|
|
|
|
const intel = parseDossier(raw, 'https://docs.google.com/document/d/ABC/edit');
|
|
|
|
t('company', intel.company === 'GUIDEWELL BLUE CROSS BLUE SHIELD OF FLORIDA', intel.company);
|
|
t('industry', intel.industry === 'Health Care Equipment & Services', intel.industry);
|
|
t('classification', intel.classification === 'Tier 1 / Strategic');
|
|
t('renewal timeframe', intel.renewalTimeframe === '2028 Q 2');
|
|
t('"Not available" is dropped, not stored', !JSON.stringify(intel).includes('Not available'));
|
|
t('source url retained', intel.sourceUrl.includes('docs.google.com'));
|
|
|
|
t('infrastructure vendors', intel.infrastructure.length === 5, JSON.stringify(intel.infrastructure));
|
|
t('load balancer is F5', intel.infrastructure.some(f=>f.label==='Load Balancer Vendor' && f.value==='F5'));
|
|
t('cloud facts', intel.cloud.length === 3);
|
|
t('vcf facts captured', intel.vcf.some(f=>f.label==='VCF Cores Entitled' && f.value==='35768'));
|
|
|
|
t('consumption rows', intel.consumption.length === 7, String(intel.consumption.length));
|
|
const vsphere = intel.consumption.find(c=>c.product==='VSPHERE');
|
|
t('vsphere entitled/deployed parsed', vsphere.entitled===35768 && vsphere.deployed===33400 && vsphere.adoption==='High');
|
|
t('deployment rate', intel.deploymentRate.startsWith('76%'));
|
|
|
|
t('apps parsed across categories', intel.applications.length === 19, String(intel.applications.length));
|
|
t('app category assigned', intel.applications.find(a=>a.name==='SAP Finance')?.category === 'ERP');
|
|
t('bullet chars stripped', intel.applications.every(a=>!/^[•\-\s]/.test(a.name)));
|
|
|
|
t('narratives captured', intel.narratives.length === 3, JSON.stringify(intel.narratives.map(n=>n.heading)));
|
|
t('narrative body', /Mainframe at 15%/.test(intel.narratives.find(n=>n.heading==='Tech Debt Signals').body));
|
|
|
|
// ---- proposals ----
|
|
const res = buildImportResult(raw, '', kb, catalog);
|
|
const by = (l) => res.proposals.find(p=>p.label.toLowerCase().includes(l));
|
|
|
|
t('F5 proposed', !!by('f5'), res.proposals.map(p=>p.label).join(' | '));
|
|
t('F5 maps to Avi', by('f5')?.productIds.includes('ans-avi'));
|
|
t('Zscaler proposed', !!by('zscaler'));
|
|
t('IBM API Connect proposed', !!by('api connect') || !!by('ibm api'));
|
|
t('no duplicate KB entries', new Set(res.proposals.map(p=>p.kbEntryId)).size === res.proposals.length);
|
|
t('all mapped product ids are real', res.proposals.every(p=>p.productIds.every(id=>catalog.products.some(x=>x.id===id))));
|
|
t('proposals carry evidence', res.proposals.every(p=>p.evidence.includes(':')));
|
|
t('sorted by confidence', res.proposals.map(p=>({high:0,medium:1,low:2})[p.confidence]).every((v,i,a)=>i===0||a[i-1]<=v));
|
|
|
|
t('shelfware = entitled>0 & deployed=0', res.shelfware.length===2 && res.shelfware.every(c=>c.entitled>0&&c.deployed===0), JSON.stringify(res.shelfware.map(c=>c.product)));
|
|
t('vsphere not shelfware', !res.shelfware.some(c=>c.product==='VSPHERE'));
|
|
|
|
// Garbage in must not throw.
|
|
const empty = buildImportResult('hello world', '', kb, catalog);
|
|
t('junk input degrades gracefully', empty.proposals.length===0 && empty.warnings.length>0);
|
|
|
|
console.log(`\n${pass} passed, ${fail} failed`);
|
|
console.log(`\nProposals from the sample dossier (${res.proposals.length}):`);
|
|
for (const p of res.proposals) {
|
|
const names = p.productIds.map(id=>catalog.products.find(x=>x.id===id)?.name).join(', ');
|
|
console.log(` [${p.confidence.padEnd(6)}] ${p.label.padEnd(26)} → ${names || '(unmapped)'}`);
|
|
}
|
|
process.exit(fail?1:0);
|