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>
80 lines
4.0 KiB
JavaScript
80 lines
4.0 KiB
JavaScript
/**
|
|
* The account dossier holds internal pricing strategy, competitive risk and
|
|
* personal data. None of it may reach a customer deliverable.
|
|
*
|
|
* Self-contained: builds its own assessment, imports the sample dossier through
|
|
* the real endpoint, asserts on the redacted projection and the generated Word
|
|
* file, then removes itself.
|
|
*
|
|
* (The rendered report page is verified in a browser; its server-rendered shell
|
|
* carries no data, so asserting on that HTML would pass vacuously.)
|
|
*/
|
|
import { readFileSync, writeFileSync } from 'node:fs';
|
|
import { execFileSync } from 'node:child_process';
|
|
|
|
const B = 'http://127.0.0.1:3500';
|
|
const json = (u, o) => fetch(B + u, o).then(r => r.json());
|
|
const send = (u, m, b) => json(u, { method: m, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(b) });
|
|
|
|
let pass = 0, fail = 0;
|
|
const t = (n, ok, x = '') => { ok ? (pass++, console.log(' PASS', n)) : (fail++, console.log(' FAIL', n, x)); };
|
|
|
|
const text = readFileSync(new URL('./fixtures/dossier-sample.txt', import.meta.url), 'utf8');
|
|
const SOURCE = 'https://docs.google.com/document/d/LEAKTEST/edit';
|
|
|
|
const a = await send('/api/assessments', 'POST', { customerName: 'Redaction Test Co' });
|
|
try {
|
|
const imported = await send('/api/assessments/x/intel', 'POST', { text, sourceUrl: SOURCE });
|
|
t('dossier parsed for the test', imported.proposals.length > 0 && !!imported.intel.company);
|
|
|
|
a.accountIntel = imported.intel;
|
|
a.candidates = imported.proposals.slice(0, 3).map((p, i) => ({
|
|
id: `c-${i}`, incumbentVendor: p.vendor, incumbentProduct: p.product, domain: p.domain,
|
|
productIds: p.productIds, annualSpend: 500000, units: 0, unitLabel: '', renewalDate: '',
|
|
capability: p.capability, switchEase: p.switchEase, timeToRoiMonths: p.timeToRoiMonths,
|
|
savingsPct: p.savingsPct, migrationCost: 50000, strategicValue: 3,
|
|
risks: '', notes: `From account dossier — ${p.evidence}`, included: true,
|
|
}));
|
|
await send(`/api/assessments/${a.id}`, 'PUT', a);
|
|
|
|
const full = await json(`/api/assessments/${a.id}`);
|
|
t('stored record keeps the dossier', !!full.accountIntel);
|
|
t('stored record keeps internal notes', full.candidates.every(c => c.notes));
|
|
|
|
const safe = await json(`/api/assessments/${a.id}/report`);
|
|
t('projection drops the dossier', safe.accountIntel === null);
|
|
t('projection drops internal notes', safe.candidates.every(c => c.notes === ''));
|
|
t('projection keeps every candidate', safe.candidates.length === full.candidates.length);
|
|
t('projection keeps the mappings', safe.candidates.every((c, i) => c.productIds.join() === full.candidates[i].productIds.join()));
|
|
t('projection keeps the spend figures', safe.candidates.every((c, i) => c.annualSpend === full.candidates[i].annualSpend));
|
|
|
|
// A .docx is a ZIP: the bytes must be inflated or a text search passes vacuously.
|
|
const buf = Buffer.from(await (await fetch(`${B}/api/assessments/${a.id}/docx`)).arrayBuffer());
|
|
const tmp = new URL('./.leak-test.docx', import.meta.url);
|
|
writeFileSync(tmp, buf);
|
|
const docx = execFileSync('unzip', ['-p', tmp.pathname, 'word/document.xml'], { maxBuffer: 32e6 }).toString('utf8');
|
|
t('Word file is a readable docx', docx.includes('<w:document') && docx.length > 2000);
|
|
|
|
const safeText = JSON.stringify(safe);
|
|
const secrets = [
|
|
SOURCE, 'docs.google.com', 'Tier 1 / Strategic', 'Renewal Risk', 'Intelligence Score',
|
|
'TechDirector', 'Shelfware', 'Tech Debt Signals', 'AM-SOUTHEAST', 'From account dossier',
|
|
'Everpure', 'Workload Distribution',
|
|
];
|
|
for (const s of secrets) {
|
|
const label = s.length > 30 ? s.slice(0, 30) + '…' : s;
|
|
t(`projection omits "${label}"`, !safeText.includes(s));
|
|
t(`Word omits "${label}"`, !docx.includes(s));
|
|
}
|
|
|
|
t('Word still names the customer', docx.includes('Redaction Test Co'));
|
|
t('Word still carries a mapped product', /Avi Load Balancer|VCF Networking|Layer7/.test(docx));
|
|
|
|
execFileSync('rm', ['-f', tmp.pathname]);
|
|
} finally {
|
|
await fetch(`${B}/api/assessments/${a.id}`, { method: 'DELETE' });
|
|
}
|
|
|
|
console.log(`\n${pass} passed, ${fail} failed`);
|
|
process.exit(fail ? 1 : 0);
|