/** * 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(' 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);