Spaces:
Running
Running
| /** | |
| * 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); | |
| }); | |
| }); | |