payments-dashboard/tests/boaReconIntraday.test.js

143 lines
5.5 KiB
JavaScript
Raw Permalink Normal View History

import { test } from "node:test";
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import {
classifyTransaction,
extractBalances,
resolveDateRange,
resolveEndpoint,
} from "../src/boaRecon.js";
const fixture = (name) =>
JSON.parse(readFileSync(new URL(`./fixtures/${name}.json`, import.meta.url)))
.accountTransactions[0].transactions;
const histogram = (txns) => {
const h = {};
for (const t of txns) {
const e = classifyTransaction(t).event;
h[e] = (h[e] || 0) + 1;
}
return h;
};
// --- resolveEndpoint ---
test("resolveEndpoint: defaults to previous-day; accepts current-day", () => {
assert.equal(resolveEndpoint({}), "previous-day");
assert.equal(resolveEndpoint(undefined), "previous-day");
assert.equal(resolveEndpoint({ endpoint: "current-day" }), "current-day");
});
test("resolveEndpoint: rejects anything else (URL injection guard)", () => {
for (const bad of ["previous-day/../admin", "current", "", "PREVIOUS-DAY", 42]) {
assert.throws(() => resolveEndpoint({ endpoint: bad }), /endpoint must be one of/);
}
});
// --- endpoint-aware resolveDateRange ---
test("resolveDateRange: current-day default pins today..today", () => {
const r = resolveDateRange({}, new Date("2026-07-22T16:00:00Z"), "current-day");
assert.deepEqual(r, { fromDate: "2026-07-22", toDate: "2026-07-22" });
});
test("resolveDateRange: current-day explicit range passes through (API accepts ranges)", () => {
const r = resolveDateRange({ fromDate: "2026-07-21", toDate: "2026-07-22" }, new Date(), "current-day");
assert.deepEqual(r, { fromDate: "2026-07-21", toDate: "2026-07-22" });
});
test("resolveDateRange: previous-day default unchanged by endpoint param", () => {
const r = resolveDateRange({}, new Date("2026-07-21T13:00:00Z"), "previous-day");
assert.deepEqual(r, { fromDate: "2026-07-14", toDate: "2026-07-20" });
});
// --- extractBalances ---
test("extractBalances: current-day fixture yields one snapshot with named fields", () => {
const snaps = extractBalances(fixture("current-day"));
assert.equal(snaps.length, 1);
const s = snaps[0];
assert.equal(s.as_of_date, "2026-07-22");
assert.equal(s.opening_ledger, 209541.48);
assert.equal(s.current_ledger, 203890.83);
assert.equal(s.opening_available, 209541.48);
assert.equal(s.current_available, 203890.83);
assert.equal(s.total_credits, 15000);
assert.equal(s.total_credits_count, 1);
assert.equal(s.total_debits, 20650.65);
assert.equal(s.total_debits_count, 5);
assert.equal(s.total_ach_debits, 20650.65);
// Unrecognized summary codes captured, never dropped.
assert.ok(Object.keys(s.other).length > 0);
assert.equal(s.other["200"], 15000);
});
test("extractBalances: multi-day window groups one snapshot per asOfDate", () => {
const snaps = extractBalances(fixture("previous-day-week"));
const dates = snaps.map((s) => s.as_of_date).sort();
assert.deepEqual(dates, ["2026-07-15", "2026-07-16", "2026-07-17", "2026-07-20", "2026-07-21"]);
for (const s of snaps) assert.ok(typeof s.opening_ledger === "number");
});
test("extractBalances: Detail rows and garbage dates are excluded", () => {
const snaps = extractBalances([
{ transactionType: "Detail", transactionCode: "010", asOfDate: "2026-07-22", amount: "5" },
{ transactionType: "Summary", transactionCode: "010", asOfDate: "garbage", amount: "5" },
]);
assert.deepEqual(snaps, []);
});
// --- fixture-driven classification histograms (real captured responses) ---
test("fixture: previous-day returns window classifies exactly as the live replay did", () => {
const h = histogram(fixture("previous-day-returns"));
assert.deepEqual(h, {
summary: 100,
ignored: 3,
check_paid: 32,
check_return: 10,
electronic_return: 4,
ach_debit: 4,
});
});
test("fixture: quiet previous-day week has zero unknowns and full summary coverage", () => {
const h = histogram(fixture("previous-day-week"));
assert.equal(h.unknown ?? 0, 0);
assert.equal(h.summary, 250);
assert.equal(Object.values(h).reduce((a, b) => a + b, 0), 259);
});
test("fixture: current-day classifies the five live ACH settlements", () => {
const h = histogram(fixture("current-day"));
assert.equal(h.ach_debit, 5);
assert.equal(h.unknown ?? 0, 0);
assert.equal(h.summary, 46);
});
test("fixture: current-day range covers both days without unknowns", () => {
const h = histogram(fixture("current-day-range"));
assert.equal(h.unknown ?? 0, 0);
assert.equal(h.ach_debit, 5);
});
test("extractBalances: hostile responses hit caps and finite guards, counted not silent", () => {
const rows = [];
for (let i = 0; i < 60; i++)
rows.push({ transactionType: "Summary", transactionCode: String(500 + i), asOfDate: "2026-07-22", amount: "1" });
rows.push({ transactionType: "Summary", transactionCode: "010", asOfDate: "2026-07-22", amount: "Infinity" });
rows.push({ transactionType: "Summary", transactionCode: "100", asOfDate: "2026-07-22", amount: "5", itemCount: "Infinity" });
for (const month of ["05", "06"])
for (let d = 1; d <= 28; d++)
rows.push({ transactionType: "Summary", transactionCode: "030", asOfDate: `2026-${month}-${String(d).padStart(2, "0")}`, amount: "1" });
const snaps = extractBalances(rows);
const s = snaps.find((x) => x.as_of_date === "2026-07-22");
assert.ok(Object.keys(s.other).length <= 50);
assert.ok(s.other_truncated > 0);
assert.equal(s.opening_ledger, undefined); // Infinity amount skipped
assert.equal(s.total_credits_count, 0); // Infinity itemCount zeroed
assert.ok(snaps.length <= 31);
assert.ok(snaps[0].dates_truncated > 0);
});