865 lines
27 KiB
JavaScript
865 lines
27 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import {
|
|
getSimulationSections,
|
|
simulationFieldGroups,
|
|
getAutosaveSnapshot,
|
|
advancedSimulationFields,
|
|
partitionSimulationDraft,
|
|
replaceSimulationValue,
|
|
restoreSimulationDraft,
|
|
simulationOptions,
|
|
validateCustomFiscalPlanning,
|
|
simulationDraftFingerprint,
|
|
validateSimulationDraft,
|
|
exportCompletedArithmeticCsv,
|
|
readArithmeticSnapshot,
|
|
readVersionedArithmeticReportReference,
|
|
moveSearchChoiceIndex,
|
|
getFiscalPeriodRange,
|
|
} from '../../resources/js/lib/simulation-form.ts';
|
|
|
|
void test('fiscal period range requires and respects exact annual, monthly, quarterly, and one-off dates', () => {
|
|
assert.deepEqual(
|
|
getFiscalPeriodRange({
|
|
fiscal_period: '2024',
|
|
fiscal_periodicity: 'annual',
|
|
}),
|
|
{ start: '2024-01-01', end: '2024-12-31' },
|
|
);
|
|
assert.deepEqual(
|
|
getFiscalPeriodRange({
|
|
fiscal_period: '2024',
|
|
fiscal_periodicity: 'monthly',
|
|
fiscal_month: '02',
|
|
}),
|
|
{ start: '2024-02-01', end: '2024-02-29' },
|
|
);
|
|
assert.deepEqual(
|
|
getFiscalPeriodRange({
|
|
fiscal_period: '2026',
|
|
fiscal_periodicity: 'monthly',
|
|
}),
|
|
null,
|
|
);
|
|
assert.deepEqual(
|
|
getFiscalPeriodRange({
|
|
fiscal_period: '2026',
|
|
fiscal_periodicity: 'quarterly',
|
|
fiscal_quarter: 'Q4',
|
|
}),
|
|
{ start: '2026-10-01', end: '2026-12-31' },
|
|
);
|
|
assert.deepEqual(
|
|
getFiscalPeriodRange({
|
|
fiscal_period: '2026',
|
|
fiscal_periodicity: 'one_off',
|
|
fiscal_reference_date: '2026-06-15',
|
|
}),
|
|
{ start: '2026-06-15', end: '2026-06-15' },
|
|
);
|
|
assert.deepEqual(
|
|
getFiscalPeriodRange({
|
|
fiscal_period: '2026',
|
|
fiscal_periodicity: 'one_off',
|
|
fiscal_reference_date: '2025-06-15',
|
|
}),
|
|
null,
|
|
);
|
|
});
|
|
|
|
void test('searchable choice keyboard navigation starts at the first result and stays in bounds', () => {
|
|
assert.equal(moveSearchChoiceIndex(-1, 1, 3), 0);
|
|
assert.equal(moveSearchChoiceIndex(-1, -1, 3), 2);
|
|
assert.equal(moveSearchChoiceIndex(0, -1, 3), 0);
|
|
assert.equal(moveSearchChoiceIndex(2, 1, 3), 2);
|
|
assert.equal(moveSearchChoiceIndex(0, 1, 0), -1);
|
|
assert.equal(moveSearchChoiceIndex(0, -1, -1), -1);
|
|
});
|
|
|
|
void test('anonymous arithmetic journey skips formal investor and project sections', () => {
|
|
assert.deepEqual(
|
|
getSimulationSections({ purpose: 'simulate_only' }).map(
|
|
(section) => section.id,
|
|
),
|
|
['purpose', 'tax', 'review'],
|
|
);
|
|
assert.equal(
|
|
getSimulationSections({ purpose: 'own_investment' }).length,
|
|
11,
|
|
);
|
|
});
|
|
|
|
void test('small business exploratory draft includes idea and finance without mandatory formal project data', () => {
|
|
const draft = {
|
|
journey_type: 'small_business',
|
|
purpose: 'simulate_only',
|
|
detail_level: 'simple',
|
|
};
|
|
assert.deepEqual(
|
|
getSimulationSections(draft).map((section) => section.id),
|
|
['purpose', 'market', 'location', 'team', 'financial', 'review'],
|
|
);
|
|
assert.deepEqual(validateSimulationDraft(draft, 'market'), {});
|
|
const ideaFields = [
|
|
...simulationFieldGroups.project,
|
|
...simulationFieldGroups.market,
|
|
]
|
|
.filter((field) => field.showWhen?.(draft))
|
|
.filter(
|
|
(field, index, all) =>
|
|
all.findIndex((candidate) => candidate.key === field.key) ===
|
|
index,
|
|
);
|
|
for (const key of [
|
|
'project_title',
|
|
'project_summary',
|
|
'project_problem',
|
|
'target_customers',
|
|
'value_proposition',
|
|
'market_assumption',
|
|
'market_source',
|
|
'project_type',
|
|
'sector_id',
|
|
'activity_id',
|
|
'products_services',
|
|
'maturity_stage',
|
|
'expected_start_date',
|
|
'project_duration_years',
|
|
])
|
|
assert.ok(
|
|
ideaFields.some((field) => field.key === key),
|
|
`missing ${key}`,
|
|
);
|
|
assert.equal(
|
|
ideaFields.some((field) =>
|
|
typeof field.required === 'function'
|
|
? field.required(draft)
|
|
: Boolean(field.required),
|
|
),
|
|
false,
|
|
);
|
|
assert.equal(
|
|
getSimulationSections(draft).some((section) =>
|
|
['identity', 'documents', 'tax'].includes(section.id),
|
|
),
|
|
false,
|
|
);
|
|
assert.deepEqual(validateSimulationDraft(draft, 'location'), {});
|
|
assert.deepEqual(validateSimulationDraft(draft, 'opportunity'), {});
|
|
});
|
|
|
|
void test('project investment evaluation opens a direct project, location, finance, tax and analysis journey', () => {
|
|
const selected = replaceSimulationValue({}, 'journey_type', 'investor');
|
|
assert.equal(selected.purpose, 'simulate_only');
|
|
const sections = getSimulationSections(selected).map(
|
|
(section) => section.id,
|
|
);
|
|
assert.deepEqual(sections, [
|
|
'purpose',
|
|
'project',
|
|
'location',
|
|
'financial',
|
|
'tax',
|
|
'scenarios',
|
|
'procedures',
|
|
'review',
|
|
]);
|
|
for (const sectionId of [
|
|
'project',
|
|
'location',
|
|
'financial',
|
|
'tax',
|
|
'procedures',
|
|
]) {
|
|
assert.deepEqual(validateSimulationDraft(selected, sectionId), {});
|
|
}
|
|
const fieldsVisible = (group) =>
|
|
simulationFieldGroups[group].filter(
|
|
(field) => field.showWhen?.(selected) ?? true,
|
|
);
|
|
assert.ok(
|
|
fieldsVisible('project').some((field) => field.key === 'activity_id'),
|
|
);
|
|
assert.ok(
|
|
fieldsVisible('location').some(
|
|
(field) => field.key === 'infrastructure_needs',
|
|
),
|
|
);
|
|
assert.ok(
|
|
fieldsVisible('financial').some(
|
|
(field) => field.key === 'total_investment',
|
|
),
|
|
);
|
|
assert.equal(simulationFieldGroups.procedures[0].showWhen(selected), true);
|
|
assert.ok(simulationOptions.infrastructure.includes('raw_materials'));
|
|
assert.ok(simulationOptions.infrastructure.includes('legal_requirements'));
|
|
});
|
|
|
|
void test('public journeys offer distinct section paths for personal, small business, company, and investor', () => {
|
|
assert.deepEqual(
|
|
getSimulationSections({
|
|
journey_type: 'personal',
|
|
purpose: 'own_investment',
|
|
}).map((section) => section.id),
|
|
['purpose', 'financial', 'tax', 'review'],
|
|
);
|
|
assert.deepEqual(
|
|
getSimulationSections({
|
|
journey_type: 'small_business',
|
|
purpose: 'own_investment',
|
|
}).map((section) => section.id),
|
|
[
|
|
'purpose',
|
|
'promoter',
|
|
'project',
|
|
'financial',
|
|
'impact',
|
|
'tax',
|
|
'review',
|
|
],
|
|
);
|
|
assert.equal(
|
|
getSimulationSections({
|
|
journey_type: 'company',
|
|
purpose: 'own_investment',
|
|
}).length,
|
|
11,
|
|
);
|
|
assert.deepEqual(
|
|
getSimulationSections({
|
|
journey_type: 'investor',
|
|
purpose: 'seek_investor',
|
|
}).map((section) => section.id),
|
|
[
|
|
'purpose',
|
|
'promoter',
|
|
'identity',
|
|
'project',
|
|
'location',
|
|
'financial',
|
|
'opportunity',
|
|
'documents',
|
|
'review',
|
|
],
|
|
);
|
|
assert.equal(advancedSimulationFields.has('deductible_costs'), true);
|
|
});
|
|
|
|
void test('switching to anonymous mode keeps non-sensitive journey and detail choices', () => {
|
|
assert.deepEqual(
|
|
replaceSimulationValue(
|
|
{
|
|
purpose: 'own_investment',
|
|
journey_type: 'personal',
|
|
detail_level: 'advanced',
|
|
legal_name: 'Nome',
|
|
email: 'a@b.co',
|
|
},
|
|
'purpose',
|
|
'simulate_only',
|
|
),
|
|
{
|
|
purpose: 'simulate_only',
|
|
journey_type: 'personal',
|
|
detail_level: 'advanced',
|
|
legal_name: 'Nome',
|
|
email: 'a@b.co',
|
|
},
|
|
);
|
|
});
|
|
|
|
void test('changing territorial or sector parents clears every dependent value', () => {
|
|
assert.deepEqual(
|
|
replaceSimulationValue(
|
|
{
|
|
province_id: 'p1',
|
|
district_id: 'd1',
|
|
admin_post_id: 'a1',
|
|
locality_id: 'l1',
|
|
},
|
|
'province_id',
|
|
'p2',
|
|
),
|
|
{ province_id: 'p2' },
|
|
);
|
|
assert.deepEqual(
|
|
replaceSimulationValue(
|
|
{ sector_id: 's1', activity_id: 'a1', activity_id_label: 'A' },
|
|
'sector_id',
|
|
's2',
|
|
),
|
|
{ sector_id: 's2' },
|
|
);
|
|
});
|
|
|
|
void test('changing the purpose preserves private and project answers for later correction', () => {
|
|
const next = replaceSimulationValue(
|
|
{
|
|
purpose: 'own_investment',
|
|
legal_name: 'Empresa',
|
|
email: 'a@b.co',
|
|
project_title: 'Projecto',
|
|
total_investment: '10',
|
|
total_investment_label: '10',
|
|
},
|
|
'purpose',
|
|
'simulate_only',
|
|
);
|
|
assert.deepEqual(next, {
|
|
purpose: 'simulate_only',
|
|
legal_name: 'Empresa',
|
|
email: 'a@b.co',
|
|
project_title: 'Projecto',
|
|
total_investment: '10',
|
|
total_investment_label: '10',
|
|
});
|
|
});
|
|
|
|
void test('approved-parameter fiscal mode does not ask users to enter a rate', () => {
|
|
const base = {
|
|
purpose: 'simulate_only',
|
|
fiscal_period: '2026',
|
|
fiscal_periodicity: 'monthly',
|
|
fiscal_month: '04',
|
|
fiscal_component: 'iva',
|
|
fiscal_residency_assessment: 'unknown',
|
|
fiscal_operation: 'domestic',
|
|
fiscal_establishments: '1',
|
|
fiscal_documentation: 'unknown',
|
|
fiscal_scenario_mode: 'approved_parameters',
|
|
};
|
|
assert.deepEqual(validateSimulationDraft(base, 'tax'), {});
|
|
});
|
|
|
|
void test('custom planning mode requires user assumptions and validates percentage limits', () => {
|
|
const base = {
|
|
purpose: 'simulate_only',
|
|
fiscal_period: '2026',
|
|
fiscal_periodicity: 'monthly',
|
|
fiscal_component: 'iva',
|
|
fiscal_residency_assessment: 'unknown',
|
|
fiscal_operation: 'domestic',
|
|
fiscal_establishments: '1',
|
|
fiscal_documentation: 'unknown',
|
|
fiscal_scenario_mode: 'custom_planning',
|
|
};
|
|
assert.deepEqual(validateSimulationDraft(base, 'tax'), {});
|
|
assert.deepEqual(validateCustomFiscalPlanning(base), {
|
|
base: 'Este pressuposto é necessário para executar este cenário.',
|
|
rate: 'Este pressuposto é necessário para executar este cenário.',
|
|
reduction: 'Este pressuposto é necessário para executar este cenário.',
|
|
});
|
|
assert.deepEqual(
|
|
validateSimulationDraft(
|
|
{ ...base, fiscal_documentation: 'unknown' },
|
|
'tax',
|
|
),
|
|
{},
|
|
);
|
|
const overLimit = {
|
|
...base,
|
|
fiscal_documentation: 'unknown',
|
|
base: '0',
|
|
rate: '101',
|
|
reduction: '0',
|
|
};
|
|
assert.deepEqual(validateSimulationDraft(overLimit, 'tax'), {});
|
|
assert.equal(
|
|
validateCustomFiscalPlanning(overLimit).rate,
|
|
'O valor não pode exceder 100.',
|
|
);
|
|
});
|
|
|
|
void test('fiscal intake asks only component-specific questions and leaves residence unresolved when unknown', () => {
|
|
assert.equal(
|
|
simulationOptions.fiscal_period.includes(
|
|
String(new Date().getFullYear() + 1),
|
|
),
|
|
true,
|
|
);
|
|
const ispcErrors = validateSimulationDraft(
|
|
{
|
|
purpose: 'simulate_only',
|
|
fiscal_period: '2026',
|
|
fiscal_periodicity: 'quarterly',
|
|
fiscal_component: 'ispc',
|
|
fiscal_scenario_mode: 'approved_parameters',
|
|
},
|
|
'tax',
|
|
);
|
|
assert.equal(ispcErrors.fiscal_activity, 'Este campo é obrigatório.');
|
|
assert.equal(ispcErrors.fiscal_establishments, 'Este campo é obrigatório.');
|
|
assert.equal(ispcErrors.fiscal_residency_assessment, undefined);
|
|
const irpsErrors = validateSimulationDraft(
|
|
{
|
|
purpose: 'simulate_only',
|
|
fiscal_period: '2026',
|
|
fiscal_periodicity: 'annual',
|
|
fiscal_component: 'irps',
|
|
fiscal_scenario_mode: 'approved_parameters',
|
|
},
|
|
'tax',
|
|
);
|
|
assert.equal(
|
|
irpsErrors.fiscal_residency_assessment,
|
|
'Este campo é obrigatório.',
|
|
);
|
|
assert.equal(
|
|
irpsErrors.fiscal_income_category,
|
|
'Este campo é obrigatório.',
|
|
);
|
|
assert.deepEqual(simulationOptions.irps_salary_view, [
|
|
'monthly_net_estimate',
|
|
'monthly_withholding',
|
|
'annual_assessment',
|
|
]);
|
|
const salaryViewErrors = validateSimulationDraft(
|
|
{
|
|
purpose: 'simulate_only',
|
|
fiscal_period: '2026',
|
|
fiscal_periodicity: 'monthly',
|
|
fiscal_component: 'irps',
|
|
fiscal_income_category: 'employment',
|
|
fiscal_residency_assessment: 'unknown',
|
|
fiscal_documentation: 'unknown',
|
|
fiscal_scenario_mode: 'approved_parameters',
|
|
},
|
|
'tax',
|
|
);
|
|
assert.equal(
|
|
salaryViewErrors.irps_salary_view,
|
|
'Este campo é obrigatório.',
|
|
);
|
|
const customsErrors = validateSimulationDraft(
|
|
{
|
|
purpose: 'simulate_only',
|
|
fiscal_period: '2026',
|
|
fiscal_periodicity: 'one_off',
|
|
fiscal_component: 'customs_ice',
|
|
fiscal_operation: 'import',
|
|
fiscal_scenario_mode: 'approved_parameters',
|
|
},
|
|
'tax',
|
|
);
|
|
assert.equal(
|
|
customsErrors.fiscal_customs_code,
|
|
'Este campo é obrigatório.',
|
|
);
|
|
});
|
|
|
|
void test('financial reconciliation reports a mismatch without inventing a fiscal value', () => {
|
|
const errors = validateSimulationDraft(
|
|
{
|
|
currency: 'USD',
|
|
total_investment: '100',
|
|
own_capital: '20',
|
|
domestic_debt: '20',
|
|
foreign_debt: '20',
|
|
grants: '20',
|
|
},
|
|
'financial',
|
|
);
|
|
assert.match(errors.total_investment, /corresponder ao investimento total/);
|
|
assert.equal(
|
|
validateSimulationDraft({}, 'financial').currency,
|
|
'Este campo é obrigatório.',
|
|
);
|
|
});
|
|
|
|
void test('autosave omits identity and contact fields from browser storage', () => {
|
|
assert.deepEqual(
|
|
getAutosaveSnapshot({
|
|
total_investment: '50',
|
|
legal_name: 'Nome',
|
|
email: 'person@example.com',
|
|
nuit: '123',
|
|
project_title: 'Projecto',
|
|
legal_name_label: 'Nome',
|
|
}),
|
|
{ total_investment: '50', project_title: 'Projecto' },
|
|
);
|
|
});
|
|
|
|
void test('server draft snapshots split private identity from economic values', () => {
|
|
assert.deepEqual(
|
|
partitionSimulationDraft({
|
|
journey_type: 'personal',
|
|
detail_level: 'simple',
|
|
purpose: 'simulate_only',
|
|
legal_name: 'Nome',
|
|
base: '10',
|
|
financial_tools_snapshot: '{"version":1}',
|
|
rate: '2',
|
|
province_id_label: 'Tete',
|
|
person_type: 'collective',
|
|
person_type_label: 'Pessoa colectiva',
|
|
}),
|
|
{
|
|
personal_snapshot: {
|
|
legal_name: 'Nome',
|
|
person_type: 'collective',
|
|
selection_labels: { person_type: 'Pessoa colectiva' },
|
|
},
|
|
economic_snapshot: {
|
|
journey_type: 'personal',
|
|
detail_level: 'simple',
|
|
purpose: 'simulate_only',
|
|
base: '10',
|
|
financial_tools_snapshot: '{"version":1}',
|
|
rate: '2',
|
|
selection_labels: { province_id: 'Tete' },
|
|
},
|
|
},
|
|
);
|
|
assert.deepEqual(
|
|
partitionSimulationDraft({
|
|
purpose: 'own_investment',
|
|
business_plan_snapshot: '{"version":1}',
|
|
financial_tools_snapshot: '{"version":1}',
|
|
}).economic_snapshot,
|
|
{
|
|
purpose: 'own_investment',
|
|
business_plan_snapshot: '{"version":1}',
|
|
financial_tools_snapshot: '{"version":1}',
|
|
},
|
|
);
|
|
assert.deepEqual(
|
|
restoreSimulationDraft(
|
|
{
|
|
activity_id: 'uuid',
|
|
infrastructure_needs: ['energia', 'água'],
|
|
selection_labels: {
|
|
activity_id: 'Energia',
|
|
infrastructure_needs: ['Energia', 'Água'],
|
|
},
|
|
},
|
|
{},
|
|
),
|
|
{
|
|
activity_id: 'uuid',
|
|
project_questionnaire_version: 'legacy-unversioned',
|
|
activity_id_label: 'Energia',
|
|
infrastructure_needs: ['energia', 'água'],
|
|
infrastructure_needs_label: ['Energia', 'Água'],
|
|
},
|
|
);
|
|
assert.equal(
|
|
simulationDraftFingerprint({ base: '10', purpose: 'simulate_only' }),
|
|
simulationDraftFingerprint({ purpose: 'simulate_only', base: '10' }),
|
|
);
|
|
});
|
|
|
|
void test('anonymous server snapshots omit personal values and labels before transmission', () => {
|
|
assert.deepEqual(
|
|
partitionSimulationDraft(
|
|
{
|
|
legal_name: 'Nome',
|
|
legal_name_label: 'Nome',
|
|
email: 'person@example.com',
|
|
fiscal_residency_country: 'mzn-country',
|
|
fiscal_residency_country_label: 'Moçambique',
|
|
purpose: 'simulate_only',
|
|
base: '10',
|
|
financial_tools_snapshot: '{"version":1}',
|
|
},
|
|
false,
|
|
),
|
|
{
|
|
personal_snapshot: {},
|
|
economic_snapshot: {
|
|
purpose: 'simulate_only',
|
|
base: '10',
|
|
financial_tools_snapshot: '{"version":1}',
|
|
},
|
|
},
|
|
);
|
|
});
|
|
|
|
void test('server-owned converted simulation snapshots are restored separately from editable form fields', () => {
|
|
const restored = restoreSimulationDraft(
|
|
{
|
|
purpose: 'own_investment',
|
|
simulation_sources: [{ simulation_id: 'abc' }],
|
|
base: '100',
|
|
},
|
|
{},
|
|
);
|
|
assert.equal(restored.base, '100');
|
|
assert.equal('simulation_sources' in restored, false);
|
|
assert.equal(
|
|
'simulation_sources' in
|
|
partitionSimulationDraft(restored).economic_snapshot,
|
|
false,
|
|
);
|
|
});
|
|
|
|
void test('completed arithmetic CSV preserves calculation details without exporting personal data', () => {
|
|
const simulation = {
|
|
id: '12345678-aaaa-bbbb-cccc-123456789abc',
|
|
status: 'completed',
|
|
version_no: 4,
|
|
updated_at: '2026-10-02T10:00:00Z',
|
|
legal_name: 'Pessoa Privada',
|
|
email: 'private@example.com',
|
|
reports: [
|
|
{
|
|
id: 'report-1',
|
|
version_no: 3,
|
|
status: 'ready',
|
|
created_at: '2026-10-02T10:00:00Z',
|
|
report_sha256: 'a'.repeat(64),
|
|
},
|
|
],
|
|
result_snapshot: {
|
|
base: '100000.00',
|
|
rate_percent: '10',
|
|
reduction_percent: '5',
|
|
indicative_total: '9500.000000',
|
|
currency: null,
|
|
legal_interpretation: false,
|
|
formula_version: 'portal-tax-arithmetic-v1',
|
|
contract_version: 1,
|
|
context: {
|
|
reference_date: '2026-10-02',
|
|
currency: null,
|
|
currency_fraction_digits: null,
|
|
scenario_id: '87654321-aaaa-bbbb-cccc-123456789abc',
|
|
scenario_version: 3,
|
|
engine_version: 'portal-tax-arithmetic-v1+simulation-v1',
|
|
ruleset_version: null,
|
|
},
|
|
result: {
|
|
status: 'calculated',
|
|
value: '9500.000000',
|
|
unit: null,
|
|
explanation: 'Resultado aritmético; sem conclusão fiscal.',
|
|
missing_inputs: [],
|
|
source_ids: [],
|
|
},
|
|
},
|
|
};
|
|
simulation.reports[0].report_snapshot = structuredClone(
|
|
simulation.result_snapshot,
|
|
);
|
|
const csv = exportCompletedArithmeticCsv(
|
|
simulation,
|
|
'2026-10-02T11:00:00Z',
|
|
);
|
|
assert.match(csv, /^\uFEFF/);
|
|
assert.match(csv, /Versão da execução,4/);
|
|
assert.match(csv, /Versão do contrato,1/);
|
|
assert.match(csv, /Data de referência,2026-10-02/);
|
|
assert.match(csv, /ID do cenário,87654321-aaaa-bbbb-cccc-123456789abc/);
|
|
assert.match(
|
|
csv,
|
|
/Versão do motor,portal-tax-arithmetic-v1\+simulation-v1/,
|
|
);
|
|
assert.match(csv, /Estado do resultado,calculated/);
|
|
assert.match(csv, /ID do relatório,report-1/);
|
|
assert.match(csv, /Versão do relatório\/cenário,3/);
|
|
assert.match(csv, /Hash SHA-256 do relatório,aaaaaaaa/);
|
|
assert.match(csv, /Período,cenário aritmético pontual/);
|
|
assert.match(csv, /IDs de fontes,nenhum/);
|
|
assert.match(csv, /portal-tax-arithmetic-v1/);
|
|
assert.match(csv, /Base introduzida,100000\.00/);
|
|
assert.match(csv, /Resultado aritmético indicativo,9500\.000000/);
|
|
assert.match(csv, /Moeda,não fornecida/);
|
|
assert.match(csv, /Fontes legais,nenhuma associada/);
|
|
assert.doesNotMatch(csv, /Pessoa Privada|private@example\.com/);
|
|
assert.throws(
|
|
() => exportCompletedArithmeticCsv({ status: 'running' }),
|
|
/concluída/,
|
|
);
|
|
assert.throws(
|
|
() =>
|
|
exportCompletedArithmeticCsv({
|
|
status: 'completed',
|
|
result_snapshot: { base: 1 },
|
|
}),
|
|
/base/,
|
|
);
|
|
});
|
|
|
|
void test('versioned arithmetic report reference requires the exact current snapshot and a valid hash', () => {
|
|
const snapshot = {
|
|
contract_version: 1,
|
|
context: { scenario_version: 4 },
|
|
indicative_total: '12.50',
|
|
};
|
|
const simulation = {
|
|
status: 'completed',
|
|
result_snapshot: snapshot,
|
|
reports: [
|
|
{
|
|
id: 'matching-report',
|
|
status: 'ready',
|
|
version_no: 4,
|
|
report_snapshot: structuredClone(snapshot),
|
|
report_sha256: 'b'.repeat(64),
|
|
created_at: '2026-10-03T10:00:00Z',
|
|
},
|
|
],
|
|
};
|
|
assert.deepEqual(readVersionedArithmeticReportReference(simulation), {
|
|
id: 'matching-report',
|
|
version_no: 4,
|
|
report_sha256: 'b'.repeat(64),
|
|
created_at: '2026-10-03T10:00:00Z',
|
|
});
|
|
assert.equal(
|
|
readVersionedArithmeticReportReference({
|
|
...simulation,
|
|
reports: [
|
|
{
|
|
...simulation.reports[0],
|
|
report_snapshot: { ...snapshot, indicative_total: '99' },
|
|
},
|
|
],
|
|
}),
|
|
null,
|
|
);
|
|
assert.equal(
|
|
readVersionedArithmeticReportReference({
|
|
...simulation,
|
|
reports: [{ ...simulation.reports[0], report_sha256: 'invalid' }],
|
|
}),
|
|
null,
|
|
);
|
|
});
|
|
|
|
void test('versioned arithmetic CSV refuses a result envelope that differs from the legacy total', () => {
|
|
assert.throws(
|
|
() =>
|
|
exportCompletedArithmeticCsv({
|
|
status: 'completed',
|
|
result_snapshot: {
|
|
base: '10',
|
|
rate_percent: '2',
|
|
reduction_percent: '0',
|
|
indicative_total: '0.2',
|
|
legal_interpretation: false,
|
|
contract_version: 1,
|
|
context: {
|
|
reference_date: '2026-10-02',
|
|
currency: null,
|
|
scenario_id: '87654321-aaaa-bbbb-cccc-123456789abc',
|
|
engine_version:
|
|
'portal-tax-arithmetic-v1+simulation-v1',
|
|
ruleset_version: null,
|
|
},
|
|
result: {
|
|
status: 'calculated',
|
|
value: '0.3',
|
|
explanation: 'Aritmética apenas.',
|
|
},
|
|
},
|
|
}),
|
|
/não reconcilia/,
|
|
);
|
|
});
|
|
|
|
void test('versioned arithmetic CSV refuses a report from another scenario revision', () => {
|
|
const snapshot = {
|
|
indicative_total: '10.00',
|
|
legal_interpretation: false,
|
|
contract_version: 1,
|
|
context: {
|
|
reference_date: '2026-10-02',
|
|
currency: null,
|
|
currency_fraction_digits: null,
|
|
scenario_id: '87654321-aaaa-bbbb-cccc-123456789abc',
|
|
scenario_version: 2,
|
|
engine_version: 'portal-tax-arithmetic-v1+simulation-v2',
|
|
ruleset_version: null,
|
|
},
|
|
result: {
|
|
status: 'calculated',
|
|
value: '10.00',
|
|
unit: null,
|
|
explanation: 'Aritmética apenas.',
|
|
missing_inputs: [],
|
|
source_ids: [],
|
|
},
|
|
};
|
|
assert.throws(
|
|
() =>
|
|
exportCompletedArithmeticCsv({
|
|
status: 'completed',
|
|
result_snapshot: snapshot,
|
|
reports: [
|
|
{
|
|
id: 'stale-report',
|
|
status: 'ready',
|
|
version_no: 1,
|
|
report_sha256: 'a'.repeat(64),
|
|
report_snapshot: structuredClone(snapshot),
|
|
},
|
|
],
|
|
}),
|
|
/nao corresponde a versao atual do resultado/,
|
|
);
|
|
});
|
|
|
|
void test('arithmetic result reader exposes only reconciled versioned or legacy values', () => {
|
|
const snapshot = {
|
|
indicative_total: '0.20',
|
|
legal_interpretation: false,
|
|
contract_version: 1,
|
|
formula_version: 'portal-tax-arithmetic-v1',
|
|
context: {
|
|
reference_date: '2026-10-02',
|
|
currency: null,
|
|
currency_fraction_digits: null,
|
|
scenario_id: '87654321-aaaa-bbbb-cccc-123456789abc',
|
|
scenario_version: 3,
|
|
engine_version: 'portal-tax-arithmetic-v1+simulation-v1',
|
|
ruleset_version: null,
|
|
},
|
|
result: {
|
|
status: 'calculated',
|
|
value: '0.20',
|
|
unit: null,
|
|
explanation: 'Aritmética apenas.',
|
|
missing_inputs: [],
|
|
source_ids: [],
|
|
},
|
|
};
|
|
assert.equal(readArithmeticSnapshot(snapshot).integrity, 'versioned');
|
|
assert.equal(readArithmeticSnapshot(snapshot).value, '0.20');
|
|
assert.equal(
|
|
readArithmeticSnapshot({ indicative_total: '0.20' }).integrity,
|
|
'legacy',
|
|
);
|
|
assert.equal(
|
|
readArithmeticSnapshot({
|
|
...snapshot,
|
|
result: { ...snapshot.result, value: '0.2' },
|
|
}).integrity,
|
|
'invalid',
|
|
);
|
|
assert.equal(
|
|
readArithmeticSnapshot({ ...snapshot, contract_version: 99 }).integrity,
|
|
'invalid',
|
|
);
|
|
});
|
|
|
|
void test('arithmetic result CSV neutralizes formula-like report metadata', () => {
|
|
const csv = exportCompletedArithmeticCsv({
|
|
id: '=HYPERLINK("https://example.invalid")',
|
|
status: 'completed',
|
|
version_no: 1,
|
|
result_snapshot: {
|
|
base: '10',
|
|
rate_percent: '2',
|
|
reduction_percent: '0',
|
|
indicative_total: '0.2',
|
|
formula_version: 'portal-tax-arithmetic-v1',
|
|
},
|
|
});
|
|
assert.match(
|
|
csv,
|
|
/ID da execução,"'=HYPERLINK\(""https:\/\/example\.invalid""\)"/,
|
|
);
|
|
});
|