mirror of
https://gitlab.com/MisterBiggs/secure-act-2.0.git
synced 2025-08-17 16:24:43 +00:00
201 lines
5.4 KiB
JavaScript
201 lines
5.4 KiB
JavaScript
/**
|
|
* Tests for Student Loan Matching Calculator
|
|
* Run with: node calculations.test.js
|
|
*/
|
|
|
|
const {
|
|
calculatePayoffYears,
|
|
calculateRemainingBalance,
|
|
calculateRetirementValue,
|
|
calculateNetWorthComparison
|
|
} = require('./calculations.js');
|
|
|
|
// Test utilities
|
|
let testsPassed = 0;
|
|
let testsFailed = 0;
|
|
|
|
function assert(condition, message) {
|
|
if (condition) {
|
|
console.log(`✓ ${message}`);
|
|
testsPassed++;
|
|
} else {
|
|
console.error(`✗ ${message}`);
|
|
testsFailed++;
|
|
}
|
|
}
|
|
|
|
function assertClose(actual, expected, tolerance = 0.01, message = '') {
|
|
const diff = Math.abs(actual - expected);
|
|
const pass = diff <= tolerance;
|
|
if (pass) {
|
|
console.log(`✓ ${message} (${actual} ≈ ${expected})`);
|
|
testsPassed++;
|
|
} else {
|
|
console.error(`✗ ${message} (${actual} != ${expected}, diff: ${diff})`);
|
|
testsFailed++;
|
|
}
|
|
}
|
|
|
|
// Test calculatePayoffYears
|
|
console.log('\n=== Testing calculatePayoffYears ===');
|
|
|
|
assert(
|
|
calculatePayoffYears(30000, 500) === 6,
|
|
'Should calculate ~6 years for $30k debt at $500/month (5% interest)'
|
|
);
|
|
|
|
assert(
|
|
calculatePayoffYears(30000, 0) === 0,
|
|
'Should return 0 for zero payment'
|
|
);
|
|
|
|
assert(
|
|
calculatePayoffYears(30000, 100) === 50,
|
|
'Should return 50 (max) for payment below interest'
|
|
);
|
|
|
|
// Test calculateRemainingBalance
|
|
console.log('\n=== Testing calculateRemainingBalance ===');
|
|
|
|
const balance5Years = calculateRemainingBalance(30000, 500, 60, 84);
|
|
assertClose(
|
|
calculateRemainingBalance(30000, 500, 60, 6 * 12),
|
|
4498,
|
|
100,
|
|
'After 5 years of $500 payments on $30k, should have ~$4.5k remaining (5% interest)'
|
|
);
|
|
|
|
assert(
|
|
calculateRemainingBalance(30000, 500, 0, 84) === 30000,
|
|
'At month 0, balance should equal original'
|
|
);
|
|
|
|
assert(
|
|
calculateRemainingBalance(30000, 500, 84, 84) === 0,
|
|
'At final month, balance should be 0'
|
|
);
|
|
|
|
assert(
|
|
calculateRemainingBalance(30000, 500, 100, 84) === 0,
|
|
'After loan term, balance should be 0'
|
|
);
|
|
|
|
// Test calculateRetirementValue
|
|
console.log('\n=== Testing calculateRetirementValue ===');
|
|
|
|
const value10Years = calculateRetirementValue(5000, 10, 10);
|
|
assertClose(
|
|
value10Years,
|
|
73918,
|
|
100,
|
|
'$5k/year for 10 years at 7% should grow to ~$74k'
|
|
);
|
|
|
|
const value30Years = calculateRetirementValue(5000, 10, 30);
|
|
assertClose(
|
|
value30Years,
|
|
286039,
|
|
100,
|
|
'$5k/year for 10 years, growing for 30 total years should be ~$286k'
|
|
);
|
|
|
|
// Test full calculation scenario
|
|
console.log('\n=== Testing Full Scenario ===');
|
|
|
|
const scenario1 = calculateNetWorthComparison({
|
|
salary: 60000,
|
|
totalDebt: 30000,
|
|
monthlyPayment: 500,
|
|
matchRate: 5,
|
|
currentAge: 25
|
|
});
|
|
|
|
console.log('\nScenario 1 Results:');
|
|
console.log(` Payoff years: ${scenario1.payoffYears}`);
|
|
console.log(` Annual match: $${scenario1.annualMatch}`);
|
|
console.log(` Retirement with program: $${scenario1.totalRetirementWith.toLocaleString()}`);
|
|
console.log(` Retirement without program: $${scenario1.totalRetirementWithout.toLocaleString()}`);
|
|
console.log(` Difference: $${scenario1.retirementDifference.toLocaleString()}`);
|
|
|
|
assert(
|
|
scenario1.payoffYears === 6,
|
|
'Should take 6 years to pay off $30k at $500/month (5% interest)'
|
|
);
|
|
|
|
assert(
|
|
scenario1.annualMatch === 3000,
|
|
'Annual match should be $3000 (5% of $60k salary)'
|
|
);
|
|
|
|
assert(
|
|
scenario1.retirementDifference > 0,
|
|
'With program should result in more retirement savings'
|
|
);
|
|
|
|
// Test edge case: very high payment
|
|
const scenario2 = calculateNetWorthComparison({
|
|
salary: 100000,
|
|
totalDebt: 20000,
|
|
monthlyPayment: 2000,
|
|
matchRate: 4,
|
|
currentAge: 30
|
|
});
|
|
|
|
console.log('\nScenario 2 Results (High Payment):');
|
|
console.log(` Payoff years: ${scenario2.payoffYears}`);
|
|
console.log(` Annual match: $${scenario2.annualMatch}`);
|
|
|
|
assert(
|
|
scenario2.payoffYears <= 2,
|
|
'High payment should result in quick payoff'
|
|
);
|
|
|
|
assert(
|
|
scenario2.annualMatch === 4000,
|
|
'Annual match should be capped at 4% of salary'
|
|
);
|
|
|
|
// Test the growth rates after payoff
|
|
console.log('\n=== Testing Growth Rates After Payoff ===');
|
|
|
|
const scenario3 = calculateNetWorthComparison({
|
|
salary: 70000,
|
|
totalDebt: 40000,
|
|
monthlyPayment: 600,
|
|
matchRate: 5,
|
|
currentAge: 28
|
|
});
|
|
|
|
// Find the index right after payoff
|
|
const payoffIndex = Math.ceil((scenario3.payoffYears / 2));
|
|
if (payoffIndex < scenario3.withProgram.length - 1) {
|
|
const withGrowth = scenario3.withProgram[payoffIndex + 1] - scenario3.withProgram[payoffIndex];
|
|
const withoutGrowth = scenario3.withoutProgram[payoffIndex + 1] - scenario3.withoutProgram[payoffIndex];
|
|
|
|
console.log(`\nGrowth rate comparison after payoff:`);
|
|
console.log(` With program growth: $${withGrowth}`);
|
|
console.log(` Without program growth: $${withoutGrowth}`);
|
|
|
|
// After payoff, both should be contributing the same amount, so growth should be similar
|
|
// The "with" program will have a larger base, but the rate of change should be close
|
|
const growthRatio = withoutGrowth > 0 ? withGrowth / withoutGrowth : 0;
|
|
assertClose(
|
|
growthRatio,
|
|
1.5,
|
|
1.0,
|
|
'Growth rates after payoff should be relatively similar'
|
|
);
|
|
}
|
|
|
|
// Summary
|
|
console.log('\n=== Test Summary ===');
|
|
console.log(`Tests passed: ${testsPassed}`);
|
|
console.log(`Tests failed: ${testsFailed}`);
|
|
|
|
if (testsFailed === 0) {
|
|
console.log('\n✅ All tests passed!');
|
|
process.exit(0);
|
|
} else {
|
|
console.log('\n❌ Some tests failed');
|
|
process.exit(1);
|
|
} |