341 lines
12 KiB
JavaScript
341 lines
12 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import fs from 'node:fs';
|
|
import {
|
|
newIspcEligibilityInput,
|
|
ispcEligibilityBook,
|
|
saveIspcEligibilityBook,
|
|
recordIspcEligibility,
|
|
ispcEligibilityCurrent,
|
|
ispcEligibilityReportRows,
|
|
ispcReuseValues,
|
|
ispcReuseDiff,
|
|
} from '../../resources/js/lib/ispc-eligibility.ts';
|
|
const seed = JSON.parse(
|
|
fs.readFileSync(
|
|
new URL('./fixtures/iva-l3e-preserved.json', import.meta.url),
|
|
'utf8',
|
|
),
|
|
);
|
|
function fixture(patch = {}) {
|
|
const d = {
|
|
...structuredClone(seed),
|
|
context_opportunity: 'opportunity:375',
|
|
fiscal_component: 'ispc',
|
|
person_type: 'collective',
|
|
fiscal_activity: 'SYNTHETIC QA comércio',
|
|
fiscal_establishments: '1',
|
|
project_title: 'SYNTHETIC QA — L4-A ISPC',
|
|
},
|
|
input = {
|
|
...newIspcEligibilityInput(d),
|
|
activities: ['commerce'],
|
|
territory: 'yes',
|
|
basis: 'previous_year',
|
|
referenceYear: '2025',
|
|
annualReference: '4000000',
|
|
annualReferenceRef: 'SYNTHETIC QA referência2025',
|
|
organizedRequired: 'no',
|
|
sharesInIspc: 'no',
|
|
sharesAnonymous: 'no',
|
|
otherActivities: 'no',
|
|
generalRegimeChosen: 'no',
|
|
...patch,
|
|
};
|
|
return { d, b: { version: 1, input } };
|
|
}
|
|
const evaluate = (patch) => {
|
|
const { d, b } = fixture(patch);
|
|
return recordIspcEligibility(d, b).result;
|
|
};
|
|
void test('reuses only valid person/activity/establishments;does not invent activity category,prior annual volume or option', () => {
|
|
const d = {
|
|
person_type: 'sole_trader',
|
|
fiscal_activity: 'Actividade conhecida',
|
|
fiscal_establishments: 2,
|
|
currency: 'MZN',
|
|
annual_sales: '3000000',
|
|
fiscal_turnover: '3000000',
|
|
};
|
|
const i = newIspcEligibilityInput(d);
|
|
assert.equal(i.personType, 'singular');
|
|
assert.equal(i.establishments, '2');
|
|
assert.equal(i.activityDescription, 'Actividade conhecida');
|
|
assert.deepEqual(i.activities, []);
|
|
assert.equal(i.annualReference, '');
|
|
assert.equal(i.option, 'preparation');
|
|
assert.equal(
|
|
newIspcEligibilityInput({ ...d, person_type: 'consortium' }).personType,
|
|
'',
|
|
);
|
|
});
|
|
void test('2025reference4000000inclusive is compatible;optative choice remains preparation;foreign investor does not imply foreign operation', () => {
|
|
const { d, b } = fixture();
|
|
d.nationality = 'estrangeira';
|
|
const r = recordIspcEligibility(d, b).result;
|
|
assert.equal(r.status, 'compatible', r.reason);
|
|
assert.equal(r.annualReference.numerator, '4000000');
|
|
assert.equal(r.optionStatus, 'preparation');
|
|
assert.match(r.optionReason, /não é adesão/);
|
|
assert.equal(
|
|
evaluate({ annualReference: '4000000.000001' }).status,
|
|
'incompatible',
|
|
);
|
|
assert.equal(evaluate({ annualReference: '' }).status, 'indeterminate');
|
|
assert.equal(evaluate({ annualReference: '0' }).status, 'compatible');
|
|
});
|
|
void test('known statutory exclusions are specific and do not require filling irrelevant remaining inputs', () => {
|
|
for (const patch of [
|
|
{ organizedRequired: 'yes' },
|
|
{ sharesInIspc: 'yes' },
|
|
{ sharesAnonymous: 'yes' },
|
|
{ otherActivities: 'yes' },
|
|
{ activities: ['other'] },
|
|
{ establishments: '2' },
|
|
{ territory: 'no' },
|
|
])
|
|
assert.equal(
|
|
evaluate({ ...patch, annualReference: '' }).status,
|
|
'incompatible',
|
|
JSON.stringify(patch),
|
|
);
|
|
for (const field of [
|
|
'organizedRequired',
|
|
'sharesInIspc',
|
|
'sharesAnonymous',
|
|
'otherActivities',
|
|
'generalRegimeChosen',
|
|
])
|
|
assert.equal(evaluate({ [field]: '' }).status, 'indeterminate', field);
|
|
});
|
|
void test('liberal183days versus184;8b services do not inherit the8c duration rule;inapplicable retained facts do not become exclusions', () => {
|
|
const p = {
|
|
activities: ['liberal15'],
|
|
unlistedServices: 'no',
|
|
liberalDaysSameEntity: '183',
|
|
liberalClientRef: 'SYNTHETIC QA maior duração do ano',
|
|
};
|
|
assert.equal(evaluate(p).status, 'compatible');
|
|
assert.equal(
|
|
evaluate({ ...p, liberalDaysSameEntity: '184' }).status,
|
|
'incompatible',
|
|
);
|
|
assert.equal(
|
|
evaluate({ ...p, liberalDaysSameEntity: '' }).status,
|
|
'indeterminate',
|
|
);
|
|
assert.equal(
|
|
evaluate({ ...p, liberalDaysSameEntity: '367' }).status,
|
|
'indeterminate',
|
|
);
|
|
assert.equal(
|
|
evaluate({
|
|
...p,
|
|
activities: ['services12'],
|
|
liberalDaysSameEntity: '184',
|
|
}).status,
|
|
'compatible',
|
|
);
|
|
assert.equal(
|
|
evaluate({
|
|
...p,
|
|
activities: ['commerce'],
|
|
unlistedServices: 'yes',
|
|
liberalDaysSameEntity: '184',
|
|
}).status,
|
|
'compatible',
|
|
);
|
|
assert.equal(
|
|
evaluate({
|
|
...p,
|
|
activities: ['commerce', 'liberal15'],
|
|
liberalDaysSameEntity: '184',
|
|
}).status,
|
|
'incompatible',
|
|
);
|
|
});
|
|
void test('startup forecast has its own2026basis and AT confirmation;projected assumption and effective reference stay distinct', () => {
|
|
const p = {
|
|
basis: 'start_forecast',
|
|
referenceYear: '2026',
|
|
annualReference: '1000000',
|
|
};
|
|
assert.equal(evaluate(p).status, 'indeterminate');
|
|
assert.equal(
|
|
evaluate({ ...p, forecastConfirmed: 'yes' }).status,
|
|
'compatible',
|
|
);
|
|
assert.equal(
|
|
evaluate({ ...p, origin: 'effective', forecastConfirmed: 'yes' })
|
|
.status,
|
|
'indeterminate',
|
|
);
|
|
assert.equal(
|
|
evaluate({
|
|
...p,
|
|
origin: 'effective',
|
|
forecastConfirmed: 'yes',
|
|
forecastConfirmationRef: 'SYNTHETIC QA confirmaçãoAT',
|
|
}).status,
|
|
'compatible',
|
|
);
|
|
assert.equal(
|
|
evaluate({
|
|
...p,
|
|
referenceYear: '2025',
|
|
forecastConfirmed: 'yes',
|
|
annualReference: '5000000',
|
|
}).status,
|
|
'indeterminate',
|
|
);
|
|
assert.equal(
|
|
evaluate({ basis: '', referenceYear: '', annualReference: '5000000' })
|
|
.status,
|
|
'indeterminate',
|
|
);
|
|
});
|
|
void test('three-year general-regime commitment is explicit;no new approver or assumption from an empty field', () => {
|
|
const p = { generalRegimeChosen: 'yes', generalThreeYearsCompleted: 'no' };
|
|
assert.equal(evaluate(p).status, 'incompatible');
|
|
assert.equal(
|
|
evaluate({ ...p, generalThreeYearsCompleted: 'yes' }).status,
|
|
'indeterminate',
|
|
);
|
|
assert.equal(
|
|
evaluate({
|
|
...p,
|
|
generalThreeYearsCompleted: 'yes',
|
|
generalRegimeRef: 'SYNTHETIC QA opção e período cumprido',
|
|
}).status,
|
|
'compatible',
|
|
);
|
|
assert.equal(
|
|
evaluate({
|
|
generalRegimeChosen: 'no',
|
|
generalThreeYearsCompleted: 'no',
|
|
}).status,
|
|
'compatible',
|
|
);
|
|
});
|
|
void test('documented option is separate from eligibility and cannot be inferred from selectingISPC', () => {
|
|
const p = { option: 'opted' };
|
|
let r = evaluate(p);
|
|
assert.equal(r.status, 'compatible');
|
|
assert.equal(r.optionStatus, 'indeterminate');
|
|
r = evaluate({
|
|
...p,
|
|
optionReference: 'SYNTHETIC QA opção',
|
|
optionDate: '2025-12-31',
|
|
});
|
|
assert.equal(r.optionStatus, 'recorded');
|
|
assert.equal(
|
|
evaluate({ ...p, optionReference: 'x', optionDate: '2026-02-01' })
|
|
.optionStatus,
|
|
'indeterminate',
|
|
);
|
|
assert.equal(evaluate({ option: 'not_opted' }).optionStatus, 'not_opted');
|
|
});
|
|
void test('wrong currency/year and occasional scope remain indeterminate rather than zero or invented ineligibility', () => {
|
|
for (const patch of [
|
|
{ currency: 'USD' },
|
|
{ currency: '' },
|
|
{ assessmentDate: '2025-12-31' },
|
|
{ assessmentDate: '2026-02-30' },
|
|
{ assessmentDate: '2027-01-01' },
|
|
{ mode: 'occasional' },
|
|
{ personType: '' },
|
|
{ referenceYear: '2026' },
|
|
{ establishments: '' },
|
|
])
|
|
assert.equal(
|
|
evaluate(patch).status,
|
|
'indeterminate',
|
|
JSON.stringify(patch),
|
|
);
|
|
});
|
|
void test('save/recovery preserves all prior fiscal maps;deliberate edits keep frozen report and append history only on calculation', () => {
|
|
const { d, b } = fixture(),
|
|
before = JSON.parse(d.modelo_a_snapshot);
|
|
let book = recordIspcEligibility(d, b, '2026-10-25T14:00:00Z');
|
|
d.modelo_a_snapshot = saveIspcEligibilityBook(d, book);
|
|
for (const [k, v] of Object.entries(before))
|
|
assert.deepEqual(JSON.parse(d.modelo_a_snapshot)[k], v, k);
|
|
assert.ok(ispcEligibilityCurrent(d, book));
|
|
const first = structuredClone(book.result);
|
|
book.input.activityDescription = 'Deliberate future edit';
|
|
d.modelo_a_snapshot = saveIspcEligibilityBook(d, book);
|
|
assert.equal(ispcEligibilityCurrent(d, book), false);
|
|
assert.doesNotMatch(
|
|
ispcEligibilityReportRows(d).flat().join(' '),
|
|
/Deliberate future edit/,
|
|
);
|
|
book = recordIspcEligibility(d, book);
|
|
assert.deepEqual(book.history.at(-1), first);
|
|
d.modelo_a_snapshot = saveIspcEligibilityBook(d, book);
|
|
assert.deepEqual(ispcEligibilityBook(JSON.parse(JSON.stringify(d))), book);
|
|
});
|
|
void test('reuse differences are reviewable;new profile values do not overwrite annual amounts,option or deliberate edits', () => {
|
|
const { d, b } = fixture();
|
|
b.input.activityDescription = 'Deliberate activity';
|
|
b.input.option = 'not_opted';
|
|
const diff = ispcReuseDiff(
|
|
{
|
|
...d,
|
|
fiscal_establishments: '2',
|
|
fiscal_activity: 'New profile activity',
|
|
},
|
|
b,
|
|
);
|
|
assert.equal(b.input.establishments, '1');
|
|
assert.equal(b.input.activityDescription, 'Deliberate activity');
|
|
assert.ok(
|
|
diff.some((v) => v.field === 'establishments' && v.after === '2'),
|
|
);
|
|
assert.ok(diff.some((v) => v.field === 'activityDescription'));
|
|
assert.ok(
|
|
!(
|
|
'annualReference' in
|
|
ispcReuseValues({ ...d, annual_sales: '9000000' })
|
|
),
|
|
);
|
|
assert.equal(b.input.option, 'not_opted');
|
|
});
|
|
void test('malformed books and ambiguous opportunity are preserved errors,not silently empty preparations', () => {
|
|
const { d, b } = fixture();
|
|
b.input.activities = ['commerce', 'commerce'];
|
|
assert.throws(() => saveIspcEligibilityBook(d, b), /não recuperáveis/);
|
|
assert.throws(
|
|
() =>
|
|
ispcEligibilityBook({
|
|
...d,
|
|
modelo_a_snapshot: JSON.stringify({
|
|
version: 1,
|
|
books: {},
|
|
ispcEligibility: [],
|
|
}),
|
|
}),
|
|
/não recuperável/,
|
|
);
|
|
assert.throws(
|
|
() =>
|
|
ispcEligibilityBook({
|
|
...d,
|
|
context_opportunity: '',
|
|
context_applicability_snapshot: JSON.stringify({
|
|
opportunity_choices: ['a', 'b'],
|
|
}),
|
|
}),
|
|
/Escolha/,
|
|
);
|
|
});
|
|
void test('export synthetic eligibility book for isolated private persistence and actual component proof', () => {
|
|
const { d, b } = fixture({ annualReference: '2500000' });
|
|
let book = recordIspcEligibility(d, b, '2026-10-25T14:00:00Z');
|
|
book = recordIspcEligibility(d, book, '2026-10-25T14:01:00Z');
|
|
d.modelo_a_snapshot = saveIspcEligibilityBook(d, book);
|
|
assert.equal(book.result.status, 'compatible');
|
|
assert.equal(book.result.optionStatus, 'preparation');
|
|
if (process.env.L4A_ISPC_OUTPUT)
|
|
fs.writeFileSync(process.env.L4A_ISPC_OUTPUT, JSON.stringify(d));
|
|
});
|