Files
Investidor-V2/tests/frontend/simulator-closure-scope.test.mjs
T

168 lines
6.4 KiB
JavaScript

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)),
]);
});