Files
Investidor-V2/tests/frontend/simulation-contract.test.mjs
nala 1106509116
tests / ci (push) Canceled after 0s
vrsao 2
2026-10-08 14:02:01 +02:00

621 lines
18 KiB
JavaScript

import assert from 'node:assert/strict';
import { readFile } from 'node:fs/promises';
import test from 'node:test';
import {
isApiErrorV1,
isApiQuestionnaireReadResponseV1,
isApiSimulationJobsReadResponseV1,
isApiSimulationReportReadResponseV1,
isApiSimulationDraftAttachResponseV1,
isApiSimulationDraftReadResponseV1,
isApiSimulationDraftWriteResponseV1,
isApiSimulationHistoryResponseV1,
fiscalComponentCodes,
indeterminateFiscalComponent,
isApiSimulationCreateResponseV1,
isApiSimulationReadResponseV1,
isApiSimulationResultSnapshotV1,
isSimulationCurrency,
simulationCurrencyFractionDigits,
simulationCurrencyOptions,
validateSimulationResult,
} from '../../resources/js/lib/simulation-contract.ts';
const apiSnapshot = {
contract_version: 1,
context: {
reference_date: '2026-10-03',
currency: null,
currency_fraction_digits: null,
scenario_id: '87654321-aaaa-bbbb-cccc-123456789abc',
scenario_version: 1,
engine_version: 'portal-arithmetic-v1',
ruleset_version: null,
},
result: {
status: 'calculated',
value: '0.000000',
unit: null,
explanation: 'Arithmetic indicative.',
missing_inputs: [],
source_ids: [],
},
indicative_total: '0.000000',
legal_interpretation: false,
formula_version: 'portal-arithmetic-v1',
};
const apiExecution = {
id: '87654321-aaaa-bbbb-cccc-123456789abc',
status: 'queued',
version_no: 1,
result_snapshot: null,
};
const apiDraft = {
id: '87654321-aaaa-bbbb-cccc-123456789abc',
version_no: 2,
snapshot_sha256: 'a'.repeat(64),
personal_snapshot: [],
economic_snapshot: { purpose: 'simulate_only' },
simulation_sources: [],
updated_at: '2026-10-03T12:30:00.000000Z',
};
const apiHistory = {
data: [
{
id: apiExecution.id,
scope: 'personal',
organization_name: null,
status: 'completed',
version_no: 1,
created_at: '2026-10-03T12:00:00.000000Z',
updated_at: '2026-10-03T12:00:01.000000Z',
has_ready_report: true,
},
],
meta: { current_page: 1, last_page: 1, per_page: 10, total: 1 },
};
const apiJob = {
id: '87654321-aaaa-bbbb-cccc-123456789abc',
simulation_id: '87654321-aaaa-bbbb-cccc-123456789abc',
job_type: 'execute',
status: 'succeeded',
attempts: 1,
available_at: '2026-10-03T12:00:00.000000Z',
started_at: '2026-10-03T12:00:01.000000Z',
finished_at: '2026-10-03T12:00:02.000000Z',
error_code: null,
};
void test('OpenAPI matches decimal-string inputs and execution response guards', async () => {
const openapi = JSON.parse(
await readFile(
new URL(
'../../docs/investmoz/simulations/openapi.json',
import.meta.url,
),
'utf8',
),
);
const schemas = openapi.components.schemas;
assert.ok(schemas.DecimalInputString.pattern.includes(String.raw`\.`));
assert.equal(
schemas.SimulationInput.properties.base.allOf[0].$ref,
'#/components/schemas/DecimalInputString',
);
assert.equal(
schemas.SimulationInput.properties.rate.allOf[0].$ref,
'#/components/schemas/PercentageInputString',
);
assert.equal(
schemas.UpdateScenarioInput.properties.base.allOf[0].$ref,
'#/components/schemas/DecimalInputString',
);
assert.deepEqual(schemas.SimulationExecutionV1.properties.status.enum, [
'queued',
'running',
'completed',
'failed',
'cancelled',
]);
assert.equal(
openapi.paths['/simulations'].post.responses['202'].content[
'application/json'
].schema.$ref,
'#/components/schemas/SimulationCreateResponseV1',
);
assert.equal(
openapi.paths['/simulations/{id}'].get.responses['200'].content[
'application/json'
].schema.$ref,
'#/components/schemas/SimulationReadResponseV1',
);
assert.equal(
openapi.paths['/simulations/history'].get.responses['200'].content[
'application/json'
].schema.$ref,
'#/components/schemas/SimulationHistoryResponseV1',
);
assert.equal(
openapi.paths['/simulation-drafts/current'].get.responses['200']
.content['application/json'].schema.$ref,
'#/components/schemas/SimulationDraftReadResponseV1',
);
assert.equal(
openapi.paths['/questionnaires/{code}'].get.responses['200'].content[
'application/json'
].schema.$ref,
'#/components/schemas/QuestionnaireReadResponseV1',
);
assert.equal(
openapi.paths['/simulations/{id}/jobs'].get.responses['200'].content[
'application/json'
].schema.$ref,
'#/components/schemas/SimulationJobsReadResponseV1',
);
assert.equal(
openapi.paths['/simulations/{id}/scenarios/{scenarioId}'].patch
.responses['202'].content['application/json'].schema.$ref,
'#/components/schemas/SimulationReadResponseV1',
);
assert.equal(
openapi.paths['/simulations/{id}/scenarios/{scenarioId}'].patch
.responses['422'].content['application/json'].schema.$ref,
'#/components/schemas/ApiErrorV1',
);
assert.equal(
schemas.SimulationHistoryResponseV1.properties.meta.properties.per_page
.maximum,
50,
);
for (const pathItem of Object.values(openapi.paths)) {
for (const operation of Object.values(pathItem)) {
for (const [status, response] of Object.entries(
operation.responses ?? {},
)) {
if (!/^4\d\d$/.test(status)) continue;
assert.equal(
response.content?.['application/json']?.schema?.$ref,
'#/components/schemas/ApiErrorV1',
`HTTP ${status} must use the shared API error contract`,
);
}
}
}
});
void test('currency codes are explicit and must be present in the supported currency catalog', () => {
assert.equal(isSimulationCurrency('MZN'), true);
assert.equal(isSimulationCurrency('USD'), true);
assert.equal(isSimulationCurrency('mzn'), false);
assert.equal(isSimulationCurrency('XYZ'), false);
assert.equal(simulationCurrencyFractionDigits('JPY'), 0);
assert.equal(simulationCurrencyFractionDigits('KWD'), 3);
assert.equal(simulationCurrencyOptions.includes('JPY'), false);
assert.equal(simulationCurrencyOptions.includes('KWD'), false);
});
void test('calculated values preserve exact decimal strings, including zero', () => {
assert.deepEqual(
validateSimulationResult({
status: 'calculated',
value: '0.00',
unit: 'MZN',
explanation: 'Cenário aritmético.',
missingInputs: [],
sourceIds: [],
}),
[],
);
});
void test('API v1 wire envelope preserves exact zero and validates indeterminate states', () => {
assert.equal(isApiSimulationResultSnapshotV1(apiSnapshot), true);
assert.equal(
isApiSimulationResultSnapshotV1({
...apiSnapshot,
context: { ...apiSnapshot.context, reference_date: '2026-02-31' },
}),
false,
);
assert.equal(
isApiSimulationResultSnapshotV1({
...apiSnapshot,
context: { ...apiSnapshot.context, reference_date: '2024-02-29' },
}),
true,
);
assert.equal(
isApiSimulationResultSnapshotV1({
...apiSnapshot,
indicative_total: null,
result: {
...apiSnapshot.result,
status: 'indeterminate',
value: null,
missing_inputs: ['approved_rule'],
},
}),
true,
);
assert.equal(
isApiSimulationResultSnapshotV1({
...apiSnapshot,
indicative_total: '0.000001',
}),
false,
);
assert.equal(
isApiSimulationResultSnapshotV1({
...apiSnapshot,
result: { ...apiSnapshot.result, value: '0' },
}),
false,
);
assert.equal(
isApiSimulationResultSnapshotV1({
...apiSnapshot,
result: {
...apiSnapshot.result,
status: 'indeterminate',
missing_inputs: [],
},
}),
false,
);
});
void test('simulation creation and polling responses require a valid execution view', () => {
const queuedExecution = { ...apiExecution };
delete queuedExecution.result_snapshot;
assert.equal(
isApiSimulationCreateResponseV1({
data: queuedExecution,
replayed: false,
}),
true,
);
assert.equal(isApiSimulationReadResponseV1({ data: apiExecution }), true);
const completedExecution = {
...apiExecution,
status: 'completed',
version_no: 2,
result_snapshot: apiSnapshot,
};
assert.equal(
isApiSimulationReadResponseV1({ data: completedExecution }),
true,
);
assert.equal(
isApiSimulationReadResponseV1({
data: { ...completedExecution, result_snapshot: { broken: true } },
}),
false,
);
assert.equal(
isApiSimulationReadResponseV1({
data: { ...completedExecution, result_snapshot: null },
}),
false,
);
assert.equal(
isApiSimulationReadResponseV1({
data: {
...apiExecution,
result_snapshot: {
contract_version: 1,
indicative_total: '12',
},
},
}),
false,
);
assert.equal(
isApiSimulationReadResponseV1({
data: {
...completedExecution,
result_snapshot: { indicative_total: '12.50' },
},
}),
true,
);
assert.equal(
isApiSimulationCreateResponseV1({
data: apiExecution,
replayed: false,
}),
true,
);
assert.equal(
isApiSimulationCreateResponseV1({
data: apiExecution,
replayed: true,
access_token: 'x'.repeat(48),
}),
true,
);
assert.equal(
isApiSimulationReadResponseV1({
data: { ...apiExecution, status: 'unknown' },
}),
false,
);
assert.equal(
isApiSimulationReadResponseV1({
data: { id: apiExecution.id, status: 'completed', version_no: 2 },
}),
false,
);
assert.equal(
isApiSimulationCreateResponseV1({
data: apiExecution,
replayed: 'false',
}),
false,
);
});
void test('history and encrypted-draft endpoints reject malformed envelopes', () => {
assert.equal(isApiSimulationHistoryResponseV1(apiHistory), true);
assert.equal(
isApiSimulationHistoryResponseV1({
...apiHistory,
data: [{ ...apiHistory.data[0], status: 'unrecognized' }],
}),
false,
);
assert.equal(
isApiSimulationHistoryResponseV1({ ...apiHistory, meta: {} }),
false,
);
assert.equal(isApiSimulationDraftReadResponseV1({ data: apiDraft }), true);
assert.equal(isApiSimulationDraftReadResponseV1({ data: null }), true);
assert.equal(
isApiSimulationDraftReadResponseV1({
data: { ...apiDraft, personal_snapshot: ['private'] },
}),
false,
);
assert.equal(
isApiSimulationDraftWriteResponseV1({
data: { id: apiDraft.id, version_no: 3 },
}),
true,
);
assert.equal(
isApiSimulationDraftWriteResponseV1({
data: { id: 'bad', version_no: 0 },
}),
false,
);
assert.equal(
isApiSimulationDraftAttachResponseV1({
data: apiDraft,
replayed: false,
}),
true,
);
assert.equal(
isApiSimulationDraftAttachResponseV1({
data: apiDraft,
replayed: 'false',
}),
false,
);
});
void test('questionnaire, report, job and error envelopes match their server contracts', () => {
assert.equal(
isApiQuestionnaireReadResponseV1({
data: {
id: apiExecution.id,
code: 'user-entered-arithmetic',
version_no: 1,
locale: 'pt-MZ',
schema_snapshot: { inputs: {} },
status: 'published',
valid_from: '2026-10-01',
valid_to: null,
},
}),
true,
);
assert.equal(
isApiQuestionnaireReadResponseV1({
data: {
id: apiExecution.id,
code: 'user-entered-arithmetic',
version_no: 1,
locale: 'pt-MZ',
schema_snapshot: { inputs: {} },
status: 'published',
valid_from: '2026-02-31',
valid_to: null,
},
}),
false,
);
assert.equal(
isApiQuestionnaireReadResponseV1({ data: { locale: 'en' } }),
false,
);
assert.equal(
isApiSimulationReportReadResponseV1({
data: {
id: apiExecution.id,
simulation_id: apiExecution.id,
version_no: 1,
status: 'ready',
report_snapshot: apiSnapshot,
report_sha256: 'b'.repeat(64),
},
}),
true,
);
assert.equal(
isApiSimulationReportReadResponseV1({
data: {
id: apiExecution.id,
simulation_id: apiExecution.id,
version_no: 1,
status: 'ready',
report_snapshot: [],
report_sha256: 'invalid',
},
}),
false,
);
assert.equal(
isApiSimulationReportReadResponseV1({
data: {
id: apiExecution.id,
simulation_id: apiExecution.id,
version_no: 1,
status: 'queued',
report_snapshot: apiSnapshot,
report_sha256: 'b'.repeat(64),
},
}),
false,
);
assert.equal(
isApiSimulationReportReadResponseV1({
data: {
id: apiExecution.id,
simulation_id: apiExecution.id,
version_no: 1,
status: 'ready',
report_snapshot: null,
report_sha256: null,
},
}),
false,
);
assert.equal(isApiSimulationJobsReadResponseV1({ data: [apiJob] }), true);
assert.equal(
isApiSimulationJobsReadResponseV1({
data: [{ ...apiJob, attempts: 4 }],
}),
false,
);
assert.equal(
isApiErrorV1({
message: 'Validation failed',
errors: { base: ['Invalid'] },
}),
true,
);
assert.equal(isApiErrorV1({ message: 42 }), false);
assert.equal(isApiErrorV1({ errors: { base: 'Invalid' } }), false);
});
void test('wire contract guards survive a real JSON encode/decode round trip', () => {
const wirePayloads = [
[isApiSimulationReadResponseV1, { data: apiExecution }],
[
isApiSimulationCreateResponseV1,
{
data: apiExecution,
replayed: false,
access_token: 'x'.repeat(48),
},
],
[isApiSimulationHistoryResponseV1, apiHistory],
[isApiSimulationDraftReadResponseV1, { data: apiDraft }],
[
isApiSimulationDraftAttachResponseV1,
{ data: apiDraft, replayed: false },
],
[isApiSimulationJobsReadResponseV1, { data: [apiJob] }],
[
isApiSimulationResultSnapshotV1,
{ ...apiSnapshot, result: { ...apiSnapshot.result } },
],
];
for (const [guard, payload] of wirePayloads) {
const decoded = JSON.parse(JSON.stringify(payload));
assert.equal(guard(decoded), true);
}
});
void test('indeterminate results require missing inputs and cannot carry a numeric value', () => {
assert.deepEqual(
validateSimulationResult({
status: 'indeterminate',
value: null,
unit: null,
explanation: 'Falta a classificação.',
missingInputs: ['tariff_code'],
sourceIds: [],
}),
[],
);
assert.deepEqual(
validateSimulationResult({
status: 'indeterminate',
value: '0',
unit: 'MZN',
explanation: 'Sem dados.',
missingInputs: [],
sourceIds: [],
}),
['indeterminate_inputs'],
);
});
void test('estimated and not-applicable states must be explicit', () => {
assert.deepEqual(
validateSimulationResult({
status: 'estimated',
value: '12.30',
unit: 'MZN',
explanation: 'Estimativa incompleta.',
missingInputs: ['tariff_code'],
sourceIds: [],
}),
[],
);
assert.deepEqual(
validateSimulationResult({
status: 'not_applicable',
value: null,
unit: null,
explanation: 'Fora do cenário.',
missingInputs: [],
sourceIds: [],
}),
[],
);
assert.deepEqual(
validateSimulationResult({
status: 'not_applicable',
value: '0',
unit: 'MZN',
explanation: 'Fora do cenário.',
missingInputs: [],
sourceIds: [],
}),
['not_applicable_value'],
);
});
void test('every tax component remains indeterminate until an approved effective rule exists', () => {
assert.deepEqual(fiscalComponentCodes, [
'IRPS',
'INSS',
'IVA',
'IRPC',
'ISPC',
'RETENCOES',
]);
for (const code of fiscalComponentCodes) {
const result = indeterminateFiscalComponent(code, '2026-10-02');
assert.deepEqual(validateSimulationResult(result), []);
assert.equal(result.value, null);
assert.ok(result.missingInputs.includes('approved_rule'));
assert.deepEqual(result.sourceIds, []);
}
});