168 lines
6.4 KiB
JavaScript
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)),
|
|
]);
|
|
});
|