59 lines
1.9 KiB
JavaScript
59 lines
1.9 KiB
JavaScript
'use strict';
|
|
|
|
// Property-based proof of the money invariants. A financial app must not sample
|
|
// its cent math — it must hold for all inputs. Fuzzes utils/money.mts.
|
|
const test = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const fc = require('fast-check');
|
|
|
|
let money;
|
|
test.before(async () => {
|
|
money = await import('../utils/money.mts'); // ESM .mts
|
|
});
|
|
|
|
// Realistic dollar amounts for this app (up to ±$100M), finite only.
|
|
const dollars = () => fc.double({ min: -1e8, max: 1e8, noNaN: true });
|
|
|
|
test('toCents always yields an integer for finite input', () => {
|
|
fc.assert(fc.property(dollars(), (d) => Number.isInteger(money.toCents(d))));
|
|
});
|
|
|
|
test('dollars → cents → dollars equals roundMoney (no float drift)', () => {
|
|
fc.assert(
|
|
fc.property(dollars(), (d) => money.fromCents(money.toCents(d)) === money.roundMoney(d)),
|
|
);
|
|
});
|
|
|
|
test('sumMoney is cent-exact (equals the sum taken in integer cents)', () => {
|
|
fc.assert(
|
|
fc.property(fc.array(dollars(), { maxLength: 60 }), (arr) => {
|
|
const cents = arr.reduce((s, d) => s + money.toCents(d), 0);
|
|
return money.sumMoney(arr) === money.fromCents(cents);
|
|
}),
|
|
);
|
|
});
|
|
|
|
test('integer cents round-trip back to themselves', () => {
|
|
fc.assert(
|
|
fc.property(
|
|
fc.integer({ min: -1e9, max: 1e9 }),
|
|
(c) => money.toCents(money.fromCents(c)) === c,
|
|
),
|
|
);
|
|
});
|
|
|
|
test('classic float-drift cases are exact', () => {
|
|
assert.equal(money.sumMoney([0.1, 0.2]), 0.3);
|
|
assert.equal(money.sumMoney([0.1, 0.1, 0.1]), 0.3);
|
|
assert.equal(money.toCents(1.005), 101); // half-up on the shortest round-tripping decimal
|
|
assert.equal(money.toCents('$1,234.56'), 123456);
|
|
});
|
|
|
|
test('invalid inputs never produce a bogus number', () => {
|
|
assert.equal(money.toCents(null), null);
|
|
assert.equal(money.toCents(''), null);
|
|
assert.equal(money.fromCents(null), null);
|
|
assert.equal(money.fromCents(undefined), null);
|
|
assert.ok(Number.isNaN(money.toCents('not-money')));
|
|
});
|