Files
retire-planner/tests/ps-auto-service.test.js
T

56 lines
3.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// PS-AUTO-SERVICE tests — auto-derivation of "years of pensionable service"
// (join year + retirement age + current age). Run: node tests/ps-auto-service.test.js
// Derivation (canonical, PS-AUTO-SERVICE plan):
// worked = currentYear − joinYear ; joinAge = age − worked ; s = retireAge − joinAge ; clamp [0, 60]
'use strict';
const P = require("../site/engine/pspension.js");
let pass = 0, fail = 0;
function ok(name, cond, detail) {
if (cond) { pass++; console.log(" PASS " + name + (detail !== undefined ? " [" + detail + "]" : "")); }
else { fail++; console.log(" FAIL " + name + (detail !== undefined ? " [" + detail + "]" : "")); }
}
const A = P.autoServiceYears;
console.log("PS auto-service tests (PS.autoServiceYears)");
console.log("---- plan cases ----");
ok("age 42, joined 2000, retire 65, year 2026 → joined at 16 → 49", A(42, 2000, 65, 2026) === 49, A(42, 2000, 65, 2026));
ok("age 30, joined 2020, retire 65, year 2026 → joined at 24 → 41", A(30, 2020, 65, 2026) === 41, A(30, 2020, 65, 2026));
ok("join year in the future (2030 @ age 40) → clamp to 0", A(40, 2030, 40, 2026) === 0, A(40, 2030, 40, 2026));
ok("retireAge < ageAtJoin (age 60, join 2020 → joined at 54, retire 50) → 0", A(60, 2020, 50, 2026) === 0, A(60, 2020, 50, 2026));
ok("retireAge = ageAtJoin (joined at 54, retire 54) → 0", A(60, 2020, 54, 2026) === 0, A(60, 2020, 54, 2026));
console.log("---- missing / invalid → null ----");
ok("age missing → null", A(null, 2000, 65, 2026) === null);
ok("age '' → null", A("", 2000, 65, 2026) === null);
ok("age 'abc' → null", A("abc", 2000, 65, 2026) === null);
ok("joinYear missing → null", A(42, null, 65, 2026) === null);
ok("joinYear '' → null", A(42, "", 65, 2026) === null);
ok("joinYear 999 (not a year) → null", A(42, 999, 65, 2026) === null);
ok("retireAge missing → null", A(42, 2000, null, 2026) === null);
ok("retireAge 0 → null", A(42, 2000, 0, 2026) === null);
ok("age 0 → null", A(0, 2000, 65, 2026) === null);
console.log("---- clamps + shapes ----");
ok("upper clamp 60 (age 20, joined 1960, retire 65 → raw 111 → 60)", A(20, 1960, 65, 2026) === 60, A(20, 1960, 65, 2026));
ok("whole years (age 43, join 2001, retire 65 → worked 25, joined at 18 → 47)", A(43, 2001, 65, 2026) === 47, A(43, 2001, 65, 2026));
ok("string inputs accepted ('42','2000','65','2026' → 49)", A("42", "2000", "65", "2026") === 49, A("42", "2000", "65", "2026"));
ok("currentYear defaults to the calendar year (joined this year, retire 65, age 25 → 40±1)",
Math.abs(A(25, new Date().getFullYear(), 65) - 40) <= 1, A(25, new Date().getFullYear(), 65));
console.log("---- engine contract (35-yr cap stays the engine's job) ----");
{
const s = A(42, 2000, 65, 2026); // 49
const r = P.computePSPension({ joinYear: 2000, retireAge: 65, serviceYears: s, avgSalary: 100000, ampe: 74600 });
ok("auto 49 feeds the engine → serviceCapped 35", r.serviceCapped === 35, r.serviceCapped);
ok("auto 49 unreduced at 65 (G1, S≥2): atRetirement = 0.01375·74600·35 + 0.02·25400·35 = 53,681.25",
Math.abs(r.pensionAtRetirement - (0.01375 * 74600 * 35 + 0.02 * 25400 * 35)) < 0.005, r.pensionAtRetirement);
const s2 = A(30, 2020, 65, 2026); // 41 → capped 35
const r2 = P.computePSPension({ joinYear: 2020, retireAge: 65, serviceYears: s2, avgSalary: 100000, ampe: 74600 });
ok("auto 41 → serviceCapped 35 (G2)", r2.serviceCapped === 35 && r2.group === 2, r2.serviceCapped + " g" + r2.group);
}
console.log("\nRESULT: " + pass + " pass, " + fail + " fail");
process.exit(fail ? 1 : 0);