/** * Node built-in test runner for pure strategy cascade model. * Mirrors strategyCascadeModel.ts logic (kept small + isolated). * Run: node --test src/components/company/strategyCascadeModel.node-test.mjs */ import { describe, it } from 'node:test'; import assert from 'node:assert/strict'; const CANONICAL_CASCADE_ORDER = [ 'tax_psi_ulgi', 'regional_fst', 'national_feng', 'debt_loan_guarantee', 'direct_eu_prep', ]; function normalizeStrategyResult(raw) { const empty = { paths: [], primaryPathId: null, recommendedPathIds: [], deMinimisExhausted: false, mspStatus: null, deMinimisUsedEur: null, notes: [], cascadeOrder: [...CANONICAL_CASCADE_ORDER], }; if (!raw || typeof raw !== 'object') return empty; const pathsIn = Array.isArray(raw.paths) ? raw.paths : []; const paths = pathsIn .filter((p) => p && typeof p === 'object') .map((p) => { const checklistIds = Array.isArray(p.checklist_ids) ? p.checklist_ids.map(String).filter(Boolean) : undefined; const view = { id: String(p.id || ''), label: String(p.label || p.id || 'Ścieżka'), score: Number(p.score) || 0, reasons: Array.isArray(p.reasons) ? p.reasons.map(String).filter(Boolean) : [], primary: Boolean(p.primary), de_minimis_blocked: Boolean(p.de_minimis_blocked), }; if (checklistIds && checklistIds.length > 0) { view.checklist_ids = checklistIds; } if (p.checklist_id != null && p.checklist_id !== '') { view.checklist_id = String(p.checklist_id); } else if (checklistIds && checklistIds.length > 0) { view.checklist_id = checklistIds[0]; } if (p.never_say_submit_to_parp != null) { view.never_say_submit_to_parp = Boolean(p.never_say_submit_to_parp); } return view; }) .filter((p) => p.id); paths.sort((a, b) => b.score - a.score); const primaryFromFlag = paths.find((p) => p.primary)?.id ?? null; const primaryPathId = (typeof raw.primary_path_id === 'string' && raw.primary_path_id) || primaryFromFlag; if (primaryPathId) { for (const p of paths) { p.primary = p.id === primaryPathId && !p.de_minimis_blocked; } if (!paths.some((p) => p.primary)) { const fallback = paths.find((p) => !p.de_minimis_blocked); if (fallback) fallback.primary = true; } } const rec = Array.isArray(raw.recommended_paths) ? raw.recommended_paths : []; const recommendedPathIds = rec .map((p) => (p && typeof p === 'object' ? String(p.id || '') : '')) .filter(Boolean); const elig = raw.eligibility_summary && typeof raw.eligibility_summary === 'object' ? raw.eligibility_summary : {}; return { paths, primaryPathId: paths.find((p) => p.primary)?.id ?? primaryPathId, recommendedPathIds: recommendedPathIds.length > 0 ? recommendedPathIds : paths.filter((p) => !p.de_minimis_blocked).slice(0, 4).map((p) => p.id), deMinimisExhausted: Boolean(elig.de_minimis_exhausted), mspStatus: elig.msp != null ? String(elig.msp) : null, deMinimisUsedEur: typeof elig.de_minimis_used_eur === 'number' ? elig.de_minimis_used_eur : null, notes: Array.isArray(raw.notes) ? raw.notes.map(String) : [], cascadeOrder: Array.isArray(raw.cascade_order) ? raw.cascade_order.map(String) : [...CANONICAL_CASCADE_ORDER], }; } function formatScore(score) { if (!Number.isFinite(score)) return '—'; return Math.round(score).toString(); } function isBlockedPath(path) { return Boolean(path.de_minimis_blocked); } const TAX_CHECKLIST_LABELS = { psi: 'PSI', ulga_br: 'Ulga B+R', ip_box: 'IP Box', }; function formatTaxChecklistId(id) { return TAX_CHECKLIST_LABELS[id] || id; } function hasTaxChecklists(path) { return Array.isArray(path.checklist_ids) && path.checklist_ids.length > 0; } /** * Mirror of strategyCascadeModel.resolveAutoSelectedPath */ function resolveAutoSelectedPath(view, currentSelected, userHasPicked) { if (userHasPicked && currentSelected) { const still = view.paths.find( (p) => p.id === currentSelected && !isBlockedPath(p), ); if (still) return null; } if (currentSelected) { const still = view.paths.find( (p) => p.id === currentSelected && !isBlockedPath(p), ); if (still && !userHasPicked) { return null; } } const primary = view.paths.find((p) => p.primary && !isBlockedPath(p)) || view.paths.find((p) => p.id === view.primaryPathId && !isBlockedPath(p)) || view.paths.find((p) => !isBlockedPath(p)); if (!primary) return null; if (primary.id === currentSelected) return null; return primary; } describe('normalizeStrategyResult', () => { it('returns empty for null', () => { const v = normalizeStrategyResult(null); assert.equal(v.paths.length, 0); assert.equal(v.primaryPathId, null); }); it('keeps ≥4 paths and marks primary non-blocked', () => { const v = normalizeStrategyResult({ paths: [ { id: 'tax_psi_ulgi', label: 'Tax', score: 55, primary: false }, { id: 'regional_fst', label: 'Region', score: 70, primary: true }, { id: 'national_feng', label: 'FENG', score: 60, primary: false }, { id: 'debt_loan_guarantee', label: 'Debt', score: 80, primary: false, de_minimis_blocked: true, }, { id: 'direct_eu_prep', label: 'EU', score: 40, primary: false }, ], primary_path_id: 'regional_fst', recommended_paths: [ { id: 'regional_fst' }, { id: 'national_feng' }, { id: 'tax_psi_ulgi' }, { id: 'direct_eu_prep' }, ], eligibility_summary: { de_minimis_exhausted: true, msp: 'srednia' }, }); assert.ok(v.paths.length >= 4); assert.equal(v.primaryPathId, 'regional_fst'); assert.equal(v.paths.find((p) => p.id === 'regional_fst')?.primary, true); assert.equal(v.paths.find((p) => p.id === 'debt_loan_guarantee')?.de_minimis_blocked, true); assert.equal(v.deMinimisExhausted, true); // blocked path still visible (sorted by score first) assert.equal(v.paths[0].id, 'debt_loan_guarantee'); assert.ok(v.recommendedPathIds.includes('regional_fst')); }); it('does not mark blocked path as primary even if API says so', () => { const v = normalizeStrategyResult({ paths: [ { id: 'debt_loan_guarantee', label: 'Debt', score: 90, primary: true, de_minimis_blocked: true }, { id: 'tax_psi_ulgi', label: 'Tax', score: 50, primary: false }, { id: 'regional_fst', label: 'R', score: 48 }, { id: 'national_feng', label: 'F', score: 47 }, ], primary_path_id: 'debt_loan_guarantee', }); assert.notEqual(v.primaryPathId, 'debt_loan_guarantee'); assert.equal(v.paths.find((p) => p.id === 'debt_loan_guarantee')?.primary, false); assert.ok(v.paths.some((p) => p.primary && !p.de_minimis_blocked)); }); it('passes through tax checklist_ids and never_say_submit_to_parp', () => { const v = normalizeStrategyResult({ paths: [ { id: 'tax_psi_ulgi', label: 'Podatki / PSI / ulgi', score: 75, primary: true, checklist_ids: ['psi', 'ulga_br', 'ip_box'], checklist_id: 'psi', never_say_submit_to_parp: true, }, { id: 'regional_fst', label: 'Region', score: 50 }, { id: 'national_feng', label: 'FENG', score: 48 }, { id: 'debt_loan_guarantee', label: 'Debt', score: 40 }, ], primary_path_id: 'tax_psi_ulgi', }); const tax = v.paths.find((p) => p.id === 'tax_psi_ulgi'); assert.ok(tax); assert.deepEqual(tax.checklist_ids, ['psi', 'ulga_br', 'ip_box']); assert.equal(tax.checklist_id, 'psi'); assert.equal(tax.never_say_submit_to_parp, true); assert.equal(hasTaxChecklists(tax), true); assert.equal(hasTaxChecklists(v.paths.find((p) => p.id === 'regional_fst')), false); assert.equal(formatTaxChecklistId('psi'), 'PSI'); assert.equal(formatTaxChecklistId('ulga_br'), 'Ulga B+R'); assert.equal(formatTaxChecklistId('ip_box'), 'IP Box'); }); it('defaults checklist_id from first checklist_ids entry', () => { const v = normalizeStrategyResult({ paths: [ { id: 'tax_psi_ulgi', label: 'Tax', score: 60, checklist_ids: ['psi', 'ulga_br', 'ip_box'], }, { id: 'regional_fst', label: 'R', score: 50 }, { id: 'national_feng', label: 'F', score: 48 }, { id: 'debt_loan_guarantee', label: 'D', score: 40 }, ], }); const tax = v.paths.find((p) => p.id === 'tax_psi_ulgi'); assert.equal(tax.checklist_id, 'psi'); }); }); describe('formatScore', () => { it('rounds finite scores', () => { assert.equal(formatScore(55.4), '55'); assert.equal(formatScore(55.6), '56'); }); it('handles non-finite', () => { assert.equal(formatScore(NaN), '—'); }); }); describe('resolveAutoSelectedPath', () => { const baseView = () => normalizeStrategyResult({ paths: [ { id: 'regional_fst', label: 'Region', score: 70, primary: true }, { id: 'national_feng', label: 'FENG', score: 60, primary: false }, { id: 'tax_psi_ulgi', label: 'Tax', score: 55, primary: false }, { id: 'debt_loan_guarantee', label: 'Debt', score: 80, de_minimis_blocked: true, }, { id: 'direct_eu_prep', label: 'EU', score: 40, primary: false }, ], primary_path_id: 'regional_fst', }); it('auto-picks primary when nothing selected', () => { const v = baseView(); const auto = resolveAutoSelectedPath(v, null, false); assert.ok(auto); assert.equal(auto.id, 'regional_fst'); assert.equal(auto.primary, true); }); it('does not re-notify when parent already holds primary', () => { const v = baseView(); const auto = resolveAutoSelectedPath(v, 'regional_fst', false); assert.equal(auto, null); }); it('keeps user pick when still valid', () => { const v = baseView(); const auto = resolveAutoSelectedPath(v, 'national_feng', true); assert.equal(auto, null); }); it('falls back when user pick is blocked', () => { const v = baseView(); const auto = resolveAutoSelectedPath(v, 'debt_loan_guarantee', true); assert.ok(auto); assert.equal(auto.id, 'regional_fst'); }); it('never auto-selects de_minimis_blocked path', () => { const v = normalizeStrategyResult({ paths: [ { id: 'debt_loan_guarantee', label: 'Debt', score: 99, primary: true, de_minimis_blocked: true, }, { id: 'tax_psi_ulgi', label: 'Tax', score: 50 }, { id: 'regional_fst', label: 'R', score: 48 }, { id: 'national_feng', label: 'F', score: 47 }, ], primary_path_id: 'debt_loan_guarantee', }); const auto = resolveAutoSelectedPath(v, null, false); assert.ok(auto); assert.notEqual(auto.id, 'debt_loan_guarantee'); assert.equal(auto.de_minimis_blocked, false); }); });