import test from 'node:test'; import assert from 'node:assert/strict'; import { mkdir, writeFile } from 'node:fs/promises'; import JSZip from 'jszip'; import { defaultCashflowDraftInputs, serializeCashflowDraftSnapshot, restoreCashflowDraftSnapshot, cashflowCurrency, } from '../../resources/js/lib/monthly-cashflow.ts'; import { monthlyIvaInput, recoverMonthlyIvaRecord, } from '../../resources/js/lib/monthly-iva.ts'; import { exportProjectDossierCsv, buildProjectDossierRows, } from '../../resources/js/lib/project-dossier.ts'; import { createProjectDossierReport, generateProjectDossierDocx, createProjectDossierPrintHtml, } from '../../resources/js/lib/project-dossier-report.ts'; import { closureFixture } from './fixtures/simulator-closure-fixture.mjs'; void test('S1 saved USD stays USD in report and cannot be reused as MZN for IVA', () => { const inputs = { ...defaultCashflowDraftInputs(), monthlyRevenue: '123', monthlyTaxAssessed: '0', }; const snapshot = serializeCashflowDraftSnapshot('fixed', inputs, 'USD'); const draft = { currency: 'MZN', cashflow_snapshot: snapshot }; const saved = restoreCashflowDraftSnapshot(snapshot); assert.equal(saved.currency, 'USD'); assert.deepEqual(saved.inputs, inputs); assert.equal(cashflowCurrency(saved, 'MZN').matchesProject, false); assert.throws( () => monthlyIvaInput(draft, { start: '2026-01', basis: 'net', declared: true, auto: false, }), /continuam em USD/, ); const rows = buildProjectDossierRows(draft).filter( (r) => r[0] === 'Tesouraria', ); assert.match( rows.find((r) => r[1] === 'Pressupostos e período')[2], /moeda USD/, ); assert.match( rows.find((r) => r[1] === 'Divergência de moeda')[2], /não foram convertidos/, ); const restoredProject = { ...draft, currency: 'USD' }; assert.equal( cashflowCurrency( restoreCashflowDraftSnapshot(restoredProject.cashflow_snapshot), restoredProject.currency, ).matchesProject, true, ); assert.equal(restoredProject.cashflow_snapshot, snapshot); }); void test('S1 legacy amounts remain readable but require explicit origin currency before reuse', () => { const inputs = { ...defaultCashflowDraftInputs(), monthlyRevenue: '123' }; const legacy = serializeCashflowDraftSnapshot('fixed', inputs); const saved = restoreCashflowDraftSnapshot(legacy); assert.equal(saved.version, 1); assert.throws(() => cashflowCurrency(saved, 'MZN'), /por confirmar/); const confirmed = restoreCashflowDraftSnapshot( serializeCashflowDraftSnapshot(saved.taxMode, saved.inputs, 'USD'), ); assert.deepEqual(confirmed.inputs, saved.inputs); assert.equal(confirmed.currency, 'USD'); assert.equal(restoreCashflowDraftSnapshot(legacy).version, 1); }); void test('S2 recovery is a same-ID authenticated GET without new admission and preserves denied records', async () => { const pending = closureFixture('queued'); const completed = closureFixture(); const calls = []; const record = { input: pending.input, execution: pending.execution }; const before = JSON.stringify(record); const request = async (url, options) => { calls.push({ url, options }); return new Response(JSON.stringify({ data: completed.execution }), { status: 200, }); }; const recovered = await recoverMonthlyIvaRecord(record, request); assert.equal(recovered.execution.id, pending.execution.id); assert.equal(recovered.execution.status, 'completed'); assert.deepEqual(recovered.input, pending.input); assert.equal(calls.length, 1); assert.equal(calls[0].options.credentials, 'same-origin'); assert.equal(calls[0].options.method, undefined); assert.match(calls[0].url, /simulations\/06566910/); await assert.rejects( () => recoverMonthlyIvaRecord( record, async () => new Response('{}', { status: 403 }), ), /preservados/, ); await assert.rejects( () => recoverMonthlyIvaRecord( record, async () => new Response( JSON.stringify({ data: { ...completed.execution, id: '00000000-0000-4000-8000-000000000000', }, }), { status: 200 }, ), ), /preservados/, ); assert.equal(JSON.stringify(record), before); }); void test('S3 final CSV Word and print source retain frozen monthly values context source and version', async () => { const { draft } = closureFixture(); const report = createProjectDossierReport(draft, { selectedSections: new Set(['sources', 'finance']), version: 'SYNTHETIC QA S3', }); const csv = exportProjectDossierCsv(draft); const html = await createProjectDossierPrintHtml(report, [], false); const docx = await generateProjectDossierDocx(report, [], true); const bytes = new Uint8Array(await docx.arrayBuffer()); const word = await JSZip.loadAsync(bytes); const xml = await word.file('word/document.xml').async('string'); for (const text of [csv, html, xml]) for (const expected of [ '160.0048', '320.0096', '1160.0348', 'MZN', '2026-01', '2026-04', 'Fonte sintética QA', 'IVA-GERAL-BASE-LIQUIDA', 'segunda oportunidade Manica', 'opportunity:309', ]) assert.ok(text.includes(expected), expected); for (const text of [csv, html, xml]) assert.match(text, /IVA-GERAL-BASE-LIQUIDA[^\n<]{0,30}4/); assert.match(html, /segunda oportunidade Manica/); assert.match(xml, /segunda oportunidade Manica/); const out = 'outputs/simulator-closure-qa-20261024'; await mkdir(out, { recursive: true }); await Promise.all([ writeFile(`${out}/monthly-final.csv`, csv), writeFile(`${out}/monthly-final.docx`, bytes), writeFile(`${out}/monthly-final.html`, html), writeFile(`${out}/frozen-fixture.json`, JSON.stringify(draft, null, 2)), ]); });