Files
Investidor-V2/tests/frontend/iva-fx-selection.test.mjs
T

172 lines
6.1 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import fs from 'node:fs';
import {
emptyFxSelection,
selectFx,
fxTempoSource,
} from '../../resources/js/lib/iva-fx-selection.ts';
import {
newCurrencyEvent,
evaluateCurrencyEvent,
recordCurrencyEvents,
saveCurrencyEventBook,
currencyEventBook,
currencyEventCurrent,
} from '../../resources/js/lib/iva-currency-events.ts';
const selection = () => ({
...emptyFxSelection(),
nature: 'commercial',
submissionDate: '2026-10-23',
bulletinDate: '2026-10-23',
bulletinRef: 'SYNTHETIC QA boletimBM de referência,23/10/2026',
source: 'https://www.bancomoc.mz/pt/areas-de-actuacao/mercados/mercado-cambial/',
sourceCurrency: 'USD',
direction: 'mzn_per_unit',
bulletinPerspective: 'SYNTHETIC QA bancos perante público',
relation: 'same',
buy: '63.27',
sell: '64.54',
mean: '63.91',
});
for (const [nature, relation, column, rate] of [
['purchase', 'same', 'buy', '63.27'],
['sale', 'same', 'sell', '64.54'],
['purchase', 'opposite', 'sell', '64.54'],
['sale', 'opposite', 'buy', '63.27'],
['commercial', '', 'mean', '63.91'],
])
void test(`P104 ${nature}/${relation || 'médiaBM'} escolhe ${column} sem inverter cliente`, () => {
const result = selectFx({ ...selection(), nature, relation });
assert.equal(result.column, column);
assert.equal(result.rate, rate);
assert.equal(result.date, '2026-10-23');
});
void test('P104 falta de boletim/data/perspectiva não autoriza taxa actual,média própria ou coluna oposta presumida', () => {
for (const patch of [
{ bulletinDate: '2026-10-22' },
{ mean: '' },
{ source: 'https://example.com/cambio' },
{ submissionDate: '2026-02-30' },
{ nature: '' },
{ nature: 'purchase', relation: '' },
{ nature: 'sale', bulletinPerspective: '' },
])
assert.throws(() => selectFx({ ...selection(), ...patch }));
assert.equal(
selectFx({
...selection(),
source: 'SYNTHETIC QA exemplar oficial arquivado',
issuer: 'Banco de Moçambique',
}).column,
'mean',
);
const exception = {
...selection(),
mode: 'exception',
exceptionRef: 'SYNTHETIC QA disposição fiscal específica',
exceptionScope: 'SYNTHETIC QA apenas operaçãoA',
exceptionDate: '2026-10-20',
exceptionRate: '62.12345678',
};
assert.equal(selectFx(exception).rate, '62.12345678');
assert.equal(selectFx(exception).date, '2026-10-20');
assert.throws(() => selectFx({ ...exception, exceptionRef: '' }));
});
const frozen = JSON.parse(
fs.readFileSync(
'tests/frontend/fixtures/iva-offer-workflow.json',
'utf8',
),
),
prior = JSON.parse(frozen.modelo_a_snapshot),
draft = {
...frozen,
context_opportunity: 'opportunity:365',
project_title: 'SYNTHETIC QA — P104 operacional / Manica',
};
function row() {
const s = selection(),
applied = selectFx(s);
return {
...newCurrencyEvent('commercial', 'MZN'),
treatment: 'foreign_scenario',
description: 'SYNTHETIC QA operação comercial',
period: '2026-10',
origin: 'effective',
operationRef: 'SYNTHETIC QA operação já declarada',
documentRef: 'SYNTHETIC QA factura',
documentDate: '2026-10-23',
scope: 'SYNTHETIC QA operaçãoA',
evidence: 'SYNTHETIC QA factos',
factsConfirmed: 'yes',
amount: '100.25',
sourceCurrency: 'USD',
fxRate: applied.rate,
fxDate: applied.date,
fxSource: applied.source,
fxRuleRef: fxTempoSource,
fxDirection: s.direction,
fxSelection: { ...s, applied },
};
}
void test('P104 original/convertido,direcção e taxa guardados;semIVA,nova dedução ou alteração automática', () => {
const r = row(),
v = evaluateCurrencyEvent(r);
assert.equal(v.status, 'determined');
assert.equal(v.scenarioAmount, '6406.9775');
assert.equal(v.baseAmount, null);
assert.equal(v.vat, null);
const reverse = {
...r,
fxDirection: 'units_per_mzn',
fxSelection: { ...r.fxSelection, direction: 'units_per_mzn' },
};
const applied = selectFx(reverse.fxSelection);
reverse.fxSelection.applied = applied;
reverse.fxSource = applied.source;
assert.equal(evaluateCurrencyEvent(reverse).scenarioAmount, '1.568612');
assert.equal(
evaluateCurrencyEvent({ ...r, fxRate: '999' }).status,
'indeterminate',
);
assert.equal(
evaluateCurrencyEvent({
...r,
fxSelection: { ...r.fxSelection, submissionDate: '2026-10-24' },
}).status,
'indeterminate',
);
});
void test('P104 versões antigas permanecem condicionadas e127fechos/P058/ofertas permanecem iguais', () => {
const r = row(),
legacy = { ...r };
delete legacy.fxSelection;
assert.equal(evaluateCurrencyEvent(legacy).status, 'conditional');
let b = recordCurrencyEvents(draft, { rows: [r] });
const saved = structuredClone(b.result);
const changed = { ...b, rows: [{ ...r, amount: '200' }] };
assert.equal(currencyEventCurrent(draft, changed), false);
assert.deepEqual(b.result, saved);
b = recordCurrencyEvents(draft, changed);
assert.deepEqual(b.history[0], saved);
draft.modelo_a_snapshot = saveCurrencyEventBook(draft, b);
assert.deepEqual(currencyEventBook(draft), b);
const next = JSON.parse(draft.modelo_a_snapshot);
assert.deepEqual(
Object.fromEntries(
Object.entries(prior).filter(([k]) => k !== 'currencyEvents'),
),
Object.fromEntries(
Object.entries(next).filter(([k]) => k !== 'currencyEvents'),
),
);
for (const [k, v] of Object.entries(prior.currencyEvents ?? {}))
assert.deepEqual(next.currencyEvents[k], v);
if (process.env.IVA_FX_SELECTION_FIXTURE)
fs.writeFileSync(
'tests/frontend/fixtures/iva-fx-selection.json',
JSON.stringify(draft),
);
});