import { test } from "node:test"; import assert from "node:assert/strict"; import { BAI_CODE_EVENTS, STALE_LIST_CAP, amountsEqual, applyEvent, buildEventUpdate, classifyTransaction, digitsCorroborate, isCancelStatus, isValidISODate, matchAchTransaction, matchCheckTransaction, matchElectronicReturn, resolveDateRange, sweepStalePayments, vendorMatches, } from "../src/boaRecon.js"; // Description fixtures are real statement lines from the 2026-07-21 // reconciliation (amounts/refs anonymized where they don't matter). test("classifier: BAI code 475 is a check paid debit", () => { const c = classifyTransaction({ transactionCode: "475", customerReference: "0001122200030", bankReference: "813312345", amount: "1,892.62", }); assert.equal(c.event, "check_paid"); assert.equal(c.direction, "debit"); assert.equal(c.checkNumber, "1122200030"); assert.equal(c.amount, 1892.62); assert.equal(BAI_CODE_EVENTS["475"].event, "check_paid"); }); test("classifier: statement-style 'Check ' description is check paid", () => { const c = classifyTransaction({ text: "Check 3176", amount: "-350" }); assert.equal(c.event, "check_paid"); assert.equal(c.checkNumber, "3176"); }); test("classifier: ARP refer-to-maker return credit", () => { const c = classifyTransaction({ transactionCode: "354", text: "ARP RETURNED CHECK REFER TO MAKER CHECK # 1122200030 PAID DATE 02/19/26", amount: "1,892.62", }); assert.equal(c.event, "check_return"); assert.equal(c.direction, "credit"); assert.equal(c.checkNumber, "1122200030"); }); test("classifier: return of posted check, check-numbered variant", () => { const c = classifyTransaction({ text: "RETURN OF POSTED CHECK / ITEM (RECEIVED ON 03-02) CHECK #1122200236", amount: "3,350.00", }); assert.equal(c.event, "check_return"); assert.equal(c.checkNumber, "1122200236"); }); test("classifier: return of posted check, electronic variant has no check number", () => { const c = classifyTransaction({ text: "RETURN OF POSTED CHECK / ITEM (RECEIVED ON 03-02) ELECTRONIC TRANSACTION", amount: "3,992.15", }); assert.equal(c.event, "electronic_return"); assert.equal(c.checkNumber, null); }); test("classifier: ACH CCD debit without embedded payment number", () => { const c = classifyTransaction({ text: "FISK EXCAVATING DES:PAYMENTS ID:PMT 6814295 INDN:Sea Haven Industries, CO ID:1811679038 CCD PMT INFO:Fisk Excavating Inc", amount: "-650", }); assert.equal(c.event, "ach_debit"); assert.equal(c.pmtId, "6814295"); assert.equal(c.embeddedPaymentNumber, null); assert.equal(c.vendorText, "Fisk Excavating Inc"); }); test("classifier: ACH debit with embedded payment number", () => { const c = classifyTransaction({ text: "COBRA SEPTIC DES:PAYMENTS ID:PMT 7584198 INDN:Sea Haven Industries, CO ID:1811679038 CCD PMT INFO:Cobra Septic 21222000264", amount: "-8,300.00", }); assert.equal(c.event, "ach_debit"); assert.equal(c.pmtId, "7584198"); assert.equal(c.embeddedPaymentNumber, "21222000264"); assert.equal(c.vendorText, "Cobra Septic"); }); test("classifier: embedded payment number with internal space '21 222000273'", () => { const c = classifyTransaction({ text: "HALL PUMP SALES DES:PAYMENTS ID:PMT 7617407 INDN:Sea Haven Industries, CO ID:1811679038 CCD PMT INFO:Hall Pump Sales & Service Corporation 21 222000273", amount: "-5,495.00", }); assert.equal(c.embeddedPaymentNumber, "21222000273"); assert.equal(c.vendorText, "Hall Pump Sales & Service Corporation"); }); test("classifier: embedded payment number with internal space '2122200 0256'", () => { const c = classifyTransaction({ text: "STERLING SEPTIC DES:PAYMENTS ID:PMT 7603631 INDN:Sea Haven Industries, CO ID:1811679038 CCD PMT INFO:Sterling Septic & Plumbing, LLC. 2122200 0256", amount: "-1,407.59", }); assert.equal(c.embeddedPaymentNumber, "21222000256"); assert.equal(c.vendorText, "Sterling Septic & Plumbing, LLC."); }); test("classifier: ACH reversal credit reusing PMT id is ach_return", () => { const c = classifyTransaction({ text: "SEA HAVEN INDUST DES:PAYMENTS ID:PMT 7176466 INDN:Sea Haven Industries, CO ID:1811679038 CCD PMT INFO:Sea Haven Industries, Inc 21222000128", amount: "2,100.00", }); assert.equal(c.event, "ach_return"); assert.equal(c.pmtId, "7176466"); assert.equal(c.embeddedPaymentNumber, "21222000128"); }); test("classifier: short trailing digits are not an embedded payment number", () => { const c = classifyTransaction({ text: "SOME VENDOR DES:PAYMENTS ID:PMT 700001 INDN:Sea Haven Industries, CO ID:1811679038 CCD PMT INFO:Vendor Company 2000", amount: "-100.00", }); assert.equal(c.embeddedPaymentNumber, null); assert.equal(c.vendorText, "Vendor Company 2000"); }); test("classifier: unknown code on a check-shaped transaction is 'unknown', never dropped", () => { const c = classifyTransaction({ transactionCode: "699", customerReference: "3301", text: "SOMETHING NEW", amount: "-100.00", }); assert.equal(c.event, "unknown"); assert.equal(c.checkShaped, true); assert.equal(c.code, "699"); }); test("classifier: transfers and misc bank activity are ignored", () => { const c = classifyTransaction({ text: "ACCOUNT TRANSFER TRSF FROM 483096772516", amount: "15,000.00", }); assert.equal(c.event, "ignored"); // Not check-shaped (no code, no reference, no CHECK text) -> ignored too. const c2 = classifyTransaction({ text: "RETURN ITEM CHARGEBACK", amount: "-215" }); assert.equal(c2.event, "ignored"); }); test("amountsEqual: comma-tolerant, sign-insensitive, zero never matches", () => { assert.equal(amountsEqual("1,892.62", -1892.62), true); assert.equal(amountsEqual(0, 0), false); assert.equal(amountsEqual("", ""), false); }); // ---------------------------------------------------------------- matcher const payment = (over = {}) => ({ pk: `payment#${over.check_number ?? "1001"}`, check_number: "1001", method: "Check", amount_usd: 500, status: "Outstanding", send_payment_on: "07/01/2026", ...over, }); test("matcher: check number AND amount must both match", () => { const payments = [payment({ check_number: "1001", amount_usd: 500 })]; const m = matchCheckTransaction({ checkNumber: "1001", amount: 500 }, payments, "2026-07-20"); assert.equal(m.payment, payments[0]); assert.equal(m.matchedBy, "number+amount"); }); test("matcher: number match with wrong amount NEVER falls through — human review", () => { const payments = [ payment({ check_number: "1001", amount_usd: 500 }), payment({ check_number: "1002", amount_usd: 750.25, pk: "payment#1002" }), ]; // Altered-check / collapsed-posting signal: alert, no guessing. const m = matchCheckTransaction({ checkNumber: "1001", amount: 750.25 }, payments, "2026-07-20"); assert.equal(m.payment, undefined); assert.equal(m.unmatched, "number matched, amount mismatch"); }); test("matcher: digit-dropped number we never issued recovers via corroborated amount fallback", () => { // 1222000012 is a digit-subsequence of issued 11222000012. const payments = [payment({ check_number: "11222000012", amount_usd: 6413 })]; const m = matchCheckTransaction({ checkNumber: "1222000012", amount: 6413 }, payments, "2026-07-20"); assert.equal(m.payment.check_number, "11222000012"); assert.equal(m.matchedBy, "amount"); }); test("matcher: amount fallback without digit-subsequence corroboration is unmatched", () => { // Same amount, but 9876 shares no digit-subsequence relation with 11222000012. const payments = [payment({ check_number: "11222000012", amount_usd: 6413 })]; const m = matchCheckTransaction({ checkNumber: "9876", amount: 6413 }, payments, "2026-07-20"); assert.equal(m.payment, undefined); assert.match(m.unmatched, /no corroborated amount fallback/); }); test("matcher: check_return amount fallback requires a bank-confirmed candidate", () => { const unconfirmed = [payment({ check_number: "11222000012", amount_usd: 6413 })]; const r1 = matchCheckTransaction( { event: "check_return", checkNumber: "1222000012", amount: 6413 }, unconfirmed, "2026-07-20" ); assert.equal(r1.payment, undefined); const confirmed = [ payment({ check_number: "11222000012", amount_usd: 6413, clear_status: "Cleared" }), ]; const r2 = matchCheckTransaction( { event: "check_return", checkNumber: "1222000012", amount: 6413 }, confirmed, "2026-07-20" ); assert.equal(r2.payment.check_number, "11222000012"); }); test("digitsCorroborate: digit-subsequence in either direction, nothing else", () => { assert.equal(digitsCorroborate("1222000012", "11222000012"), true); // dropped digit assert.equal(digitsCorroborate("11222000012", "1222000012"), true); // symmetric assert.equal(digitsCorroborate("9876", "11222000012"), false); assert.equal(digitsCorroborate("", "1234"), false); }); test("matcher: ambiguous amount fallback is unmatched — no write", () => { // "101" digit-corroborates both 1001 and 1011; same amount -> ambiguous. const payments = [ payment({ check_number: "1001", amount_usd: 350 }), payment({ check_number: "1011", amount_usd: 350, pk: "payment#1011" }), ]; const m = matchCheckTransaction({ checkNumber: "101", amount: 350 }, payments, "2026-07-20"); assert.equal(m.payment, undefined); assert.match(m.unmatched, /ambiguous/); }); test("matcher: zero candidates is unmatched", () => { const m = matchCheckTransaction({ checkNumber: "9999", amount: 123.45 }, [payment()], "2026-07-20"); assert.equal(m.payment, undefined); }); test("matcher: amount fallback only considers checks issued in the last 120 days", () => { // Corroborated ("101" is a subsequence of "1001") but issued too long ago. const payments = [ payment({ check_number: "1001", amount_usd: 350, send_payment_on: "01/02/2026" }), ]; const m = matchCheckTransaction({ checkNumber: "101", amount: 350 }, payments, "2026-07-20"); assert.equal(m.payment, undefined); }); test("matcher: non-Check payments are never check-match candidates", () => { const payments = [payment({ method: "ACH", check_number: "21222000264", amount_usd: 350 })]; const m = matchCheckTransaction({ checkNumber: "21222000264", amount: 350 }, payments, "2026-07-20"); assert.equal(m.payment, undefined); }); test("electronic return: unique amount among bank-confirmed payments matches", () => { const payments = [ payment({ check_number: "1001", amount_usd: 3992.15, clear_status: "Cleared" }), payment({ check_number: "1002", amount_usd: 3992.15, pk: "payment#1002" }), // not bank-confirmed ]; const m = matchElectronicReturn({ amount: 3992.15 }, payments, "2026-07-20"); assert.equal(m.payment.check_number, "1001"); }); test("electronic return: never matches ACH payments", () => { const achCleared = payment({ method: "ACH", check_number: "21222000264", amount_usd: 3992.15, clear_status: "Cleared", pk: "payment#21222000264", }); const checkCleared = payment({ check_number: "1001", amount_usd: 3992.15, clear_status: "Cleared", }); // Only the Check should be a candidate — ACH is excluded. const m = matchElectronicReturn({ amount: 3992.15 }, [achCleared, checkCleared], "2026-07-20"); assert.equal(m.payment.check_number, "1001"); assert.equal(m.matchedBy, "amount+cleared"); }); test("electronic return: ambiguity is unmatched", () => { const payments = [ payment({ check_number: "1001", amount_usd: 2500, clear_status: "Cleared" }), payment({ check_number: "1002", amount_usd: 2500, clear_status: "Cleared", pk: "payment#1002" }), ]; const m = matchElectronicReturn({ amount: 2500 }, payments, "2026-07-20"); assert.equal(m.payment, undefined); assert.match(m.unmatched, /ambiguous/); }); // ------------------------------------------------------------ transitions const paidEvent = (over = {}) => ({ event: "check_paid", amount: 500, bankReference: "813300001", ...over, }); const returnEvent = (over = {}) => ({ event: "check_return", amount: 500, bankReference: "813300002", ...over, }); test("transitions: paid debit sets BOTH status and clear_status to Cleared with dates", () => { const p = payment(); const r = applyEvent(p, paidEvent(), "2026-07-19"); assert.equal(r.kind, "cleared"); assert.equal(r.updates.status, "Cleared"); assert.equal(r.updates.clear_status, "Cleared"); assert.equal(r.updates.paid_date, "2026-07-19"); assert.equal(r.updates.cleared_date, "2026-07-19"); assert.equal(r.updates.bank_reference, "813300001"); assert.deepEqual(r.historyEvent, { event: "check_paid", date: "2026-07-19", bankRef: "813300001", amount: 500, }); }); test("transitions: return applies EVEN IF currently Cleared; clear_status is bank truth", () => { const p = payment({ status: "Cleared", clear_status: "Cleared" }); const r = applyEvent(p, returnEvent(), "2026-07-20"); assert.equal(r.kind, "returned"); assert.equal(r.updates.clear_status, "Returned"); assert.equal(r.updates.returned_date, "2026-07-20"); // status has no "Returned" rung on the CSV ladder — re-written unchanged. assert.equal(r.updates.status, "Cleared"); }); test("transitions: second paid debit on a Returned check is a redeposit back to Cleared", () => { const p = payment({ status: "Cleared", clear_status: "Returned" }); const r = applyEvent(p, paidEvent({ bankReference: "813300003" }), "2026-07-21"); assert.equal(r.kind, "redeposit"); assert.equal(r.updates.status, "Cleared"); assert.equal(r.updates.clear_status, "Cleared"); assert.equal(r.updates.paid_date, "2026-07-21"); }); test("transitions: paid -> returned -> redeposit sequence accumulates history", () => { const p = payment(); p.history = p.history || []; const seq = [ [paidEvent(), "2026-07-18"], [returnEvent(), "2026-07-19"], [paidEvent({ bankReference: "813300004" }), "2026-07-20"], ]; const kinds = []; for (const [ev, date] of seq) { const r = applyEvent(p, ev, date); kinds.push(r.kind); Object.assign(p, r.updates); p.history.push(r.historyEvent); } assert.deepEqual(kinds, ["cleared", "returned", "redeposit"]); assert.equal(p.clear_status, "Cleared"); assert.equal(p.history.length, 3); assert.deepEqual( p.history.map((h) => h.event), ["check_paid", "check_return", "check_paid"] ); }); test("transitions: return on a canceled record is terminal voided-and-bounced", () => { const p = payment({ status: "Voided", clear_status: "Cleared" }); const r = applyEvent(p, returnEvent(), "2026-07-20"); assert.equal(r.kind, "voided_and_bounced"); assert.equal(r.updates.clear_status, "Returned"); assert.equal(r.updates.status, "Voided"); // cancel status stays visible }); test("transitions: paid debit on a canceled record keeps the cancel status", () => { const p = payment({ status: "Marked as Void" }); const r = applyEvent(p, paidEvent(), "2026-07-19"); assert.equal(r.kind, "cleared_on_canceled"); assert.equal(r.updates.status, "Marked as Void"); assert.equal(r.updates.clear_status, "Cleared"); }); test("transitions: identical replayed event is a noop (idempotent replays)", () => { const p = payment({ status: "Cleared", clear_status: "Cleared", history: [{ event: "check_paid", date: "2026-07-19", bankRef: "813300001", amount: 500 }], }); const r = applyEvent(p, paidEvent(), "2026-07-19"); assert.equal(r.kind, "noop"); assert.equal(r.updates, null); }); test("transitions: dedup identity is {event, date, amount} — bankRef differences are ignored", () => { const p = payment({ status: "Cleared", clear_status: "Cleared", history: [{ event: "check_paid", date: "2026-07-19", bankRef: "OLD-FORMAT-REF", amount: 500 }], }); // Same event replayed with a reformatted/omitted bankRef must still dedup. const r = applyEvent(p, paidEvent({ bankReference: "813399999" }), "2026-07-19"); assert.equal(r.kind, "noop"); }); test("transitions: a paid event dated on/before the latest return is a replayed original, not a redeposit", () => { const p = payment({ status: "Cleared", clear_status: "Returned", history: [ { event: "check_paid", date: "2026-07-18", bankRef: "a", amount: 500 }, { event: "check_return", date: "2026-07-19", bankRef: "b", amount: 500 }, ], }); // Replayed original paid (different bankRef, date <= return date): noop. const equalDate = applyEvent(p, paidEvent({ bankReference: "c" }), "2026-07-19"); assert.equal(equalDate.kind, "noop"); const beforeDate = applyEvent(p, paidEvent({ bankReference: "c" }), "2026-07-17"); assert.equal(beforeDate.kind, "noop"); // Strictly after the return: genuine redeposit. const after = applyEvent(p, paidEvent({ bankReference: "c" }), "2026-07-20"); assert.equal(after.kind, "redeposit"); }); test("transitions: every non-noop result sets both status and clear_status", () => { const cases = [ [payment(), paidEvent()], [payment({ status: "Cleared", clear_status: "Cleared" }), returnEvent()], [payment({ status: "Cleared", clear_status: "Returned" }), paidEvent()], [payment({ status: "Voided" }), returnEvent()], [payment({ clear_status: "Cleared" }), { event: "electronic_return", amount: 500, bankReference: "x" }], ]; for (const [p, ev] of cases) { const r = applyEvent(p, ev, "2026-07-20"); assert.notEqual(r.kind, "noop"); assert.ok("status" in r.updates, `${r.kind} must set status`); assert.ok("clear_status" in r.updates, `${r.kind} must set clear_status`); } }); test("isCancelStatus covers the Stampli cancel vocabulary", () => { for (const s of ["Voided", "cancelled", "Canceled", "Marked as Void"]) { assert.equal(isCancelStatus(s), true); } assert.equal(isCancelStatus("Cleared"), false); }); // ------------------------------------------------------------ ACH (#69) const achPayment = (over = {}) => ({ pk: `payment#${over.check_number ?? "21222000264"}`, check_number: "21222000264", method: "ACH", payee: "Cobra Septic", amount_usd: 8300, status: "Payment Submitted", send_payment_on: "06/02/2026", ...over, }); test("ach matcher: embedded payment number + amount wins", () => { const payments = [achPayment(), achPayment({ check_number: "21222000265", pk: "payment#21222000265" })]; const m = matchAchTransaction( { event: "ach_debit", pmtId: "7584198", embeddedPaymentNumber: "21222000264", vendorText: "Cobra Septic", amount: 8300 }, payments, "2026-06-08" ); assert.equal(m.payment.check_number, "21222000264"); assert.equal(m.matchedBy, "payment-number+amount"); }); test("ach matcher: embedded number with wrong amount falls through to vendor+amount", () => { const payments = [ achPayment({ check_number: "21222000264", amount_usd: 100 }), achPayment({ check_number: "21222000265", pk: "payment#21222000265", amount_usd: 8300 }), ]; const m = matchAchTransaction( { event: "ach_debit", pmtId: "7584198", embeddedPaymentNumber: "21222000264", vendorText: "Cobra Septic", amount: 8300 }, payments, "2026-06-08" ); assert.equal(m.payment.check_number, "21222000265"); assert.equal(m.matchedBy, "vendor+amount"); }); test("ach matcher: stored pmt_id attributes a reversal credit", () => { const payments = [ achPayment({ pmt_id: "7584198", clear_status: "Cleared", status: "Cleared" }), ]; const m = matchAchTransaction( { event: "ach_return", pmtId: "7584198", embeddedPaymentNumber: null, vendorText: "Sea Haven Industries, Inc", amount: 8300 }, payments, "2026-06-10" ); assert.equal(m.matchedBy, "pmt_id"); }); test("ach matcher: vendor+amount only within send_payment_on -2..+14 days", () => { // Reddi 21222000211: sent 5/27, debited 6/4 (8-day lag) must match... const inWindow = [achPayment({ payee: "Reddi Services", send_payment_on: "05/27/2026", amount_usd: 1958 })]; const m1 = matchAchTransaction( { event: "ach_debit", pmtId: "1", embeddedPaymentNumber: null, vendorText: "Reddi Services", amount: 1958 }, inWindow, "2026-06-04" ); assert.equal(m1.matchedBy, "vendor+amount"); // ...but a posting 30 days after the send date must not. const m2 = matchAchTransaction( { event: "ach_debit", pmtId: "1", embeddedPaymentNumber: null, vendorText: "Reddi Services", amount: 1958 }, inWindow, "2026-06-26" ); assert.equal(m2.payment, undefined); }); test("ach matcher: PMT-id-less same-amount return credit matches a recently cleared ACH", () => { const payments = [ achPayment({ payee: "Uline", clear_status: "Cleared", status: "Cleared", cleared_date: "2026-05-22", amount_usd: 19281.12, }), ]; const m = matchAchTransaction( { event: "ach_return", pmtId: null, embeddedPaymentNumber: null, vendorText: null, amount: 19281.12 }, payments, "2026-05-26" ); assert.equal(m.matchedBy, "amount+cleared"); }); test("ach matcher: amount+cleared fallback requires cleared_date within 45 days before the credit", () => { const base = { payee: "Uline", clear_status: "Cleared", status: "Cleared", amount_usd: 19281.12, }; const tooOld = [achPayment({ ...base, cleared_date: "2026-03-01" })]; const m1 = matchAchTransaction( { event: "ach_return", pmtId: null, embeddedPaymentNumber: null, vendorText: null, amount: 19281.12 }, tooOld, "2026-05-26" ); assert.equal(m1.payment, undefined); const noDate = [achPayment({ ...base })]; const m2 = matchAchTransaction( { event: "ach_return", pmtId: null, embeddedPaymentNumber: null, vendorText: null, amount: 19281.12 }, noDate, "2026-05-26" ); assert.equal(m2.payment, undefined); }); test("ach matcher: return credit with an unattributable PMT id is unmatched, never amount-guessed", () => { const payments = [ achPayment({ clear_status: "Cleared", status: "Cleared", cleared_date: "2026-05-22", amount_usd: 8300 }), ]; const m = matchAchTransaction( { event: "ach_return", pmtId: "999", embeddedPaymentNumber: null, vendorText: null, amount: 8300 }, payments, "2026-05-26" ); assert.equal(m.payment, undefined); assert.equal(m.unmatched, "unknown PMT id"); }); test("ach matcher: pmt_id match with amount mismatch is unmatched — partial-reversal human case", () => { const payments = [achPayment({ pmt_id: "7584198", amount_usd: 8300 })]; const m = matchAchTransaction( { event: "ach_return", pmtId: "7584198", embeddedPaymentNumber: null, vendorText: null, amount: 4150 }, payments, "2026-06-10" ); assert.equal(m.payment, undefined); assert.equal(m.unmatched, "pmt_id matched, amount mismatch"); }); test("ach matcher: candidates with a DIFFERENT stored pmt_id are excluded from vendor+amount", () => { const payments = [ achPayment({ pmt_id: "1111111", amount_usd: 8300 }), achPayment({ check_number: "21222000270", pk: "payment#21222000270", amount_usd: 8300 }), ]; // Both are Cobra Septic @ 8300 in-window; the pmt_id conflict on the // first disambiguates to the second instead of going ambiguous. const m = matchAchTransaction( { event: "ach_debit", pmtId: "2222222", embeddedPaymentNumber: null, vendorText: "Cobra Septic", amount: 8300 }, payments, "2026-06-08" ); assert.equal(m.payment.check_number, "21222000270"); assert.equal(m.matchedBy, "vendor+amount"); }); test("ach matcher: ambiguity is unmatched — no write", () => { const payments = [ achPayment({ pk: "payment#a", check_number: "a", clear_status: "Cleared", cleared_date: "2026-06-01", amount_usd: 500 }), achPayment({ pk: "payment#b", check_number: "b", clear_status: "Cleared", cleared_date: "2026-06-02", amount_usd: 500 }), ]; const m = matchAchTransaction( { event: "ach_return", pmtId: null, embeddedPaymentNumber: null, vendorText: null, amount: 500 }, payments, "2026-06-08" ); assert.equal(m.payment, undefined); assert.match(m.unmatched, /ambiguous/); }); test("vendorMatches: truncated bank originator vs full payee, punctuation-insensitive", () => { assert.equal(vendorMatches("Hugill's Septic Service LLC", "Hugill's Septic Service LLC"), true); assert.equal(vendorMatches("Alliance Sanitation LLC", "ALLIANCE SANITAT"), true); assert.equal(vendorMatches("Cobra Septic", "Reddi Services"), false); assert.equal(vendorMatches("", "Cobra Septic"), false); }); test("vendorMatches: prefix only counts at >= 10 normalized chars; short names need exact equality", () => { // "ACME" (4 chars) must not claim "ACME Plumbing Co" by prefix... assert.equal(vendorMatches("ACME Plumbing Co", "ACME"), false); // ...but exact short-name equality still matches. assert.equal(vendorMatches("ACME", "A.C.M.E."), true); // 10+ char prefix still matches (truncated originators). assert.equal(vendorMatches("Alliance Sanitation LLC", "ALLIANCESANITAT"), true); }); test("ach transitions: settled debit clears both fields and persists pmt_id", () => { const p = achPayment(); const r = applyEvent( { ...p }, { event: "ach_debit", pmtId: "7584198", amount: 8300, bankReference: "905512345" }, "2026-06-04" ); assert.equal(r.kind, "cleared"); assert.equal(r.updates.status, "Cleared"); assert.equal(r.updates.clear_status, "Cleared"); assert.equal(r.updates.paid_date, "2026-06-04"); assert.equal(r.updates.pmt_id, "7584198"); }); test("ach transitions: return credit then re-debit (bounce and re-settle)", () => { const p = achPayment({ status: "Cleared", clear_status: "Cleared", pmt_id: "7500000", history: [] }); const ret = applyEvent(p, { event: "ach_return", pmtId: "7500000", amount: 8300, bankReference: "r1" }, "2026-05-26"); assert.equal(ret.kind, "returned"); assert.equal(ret.updates.clear_status, "Returned"); assert.equal(ret.updates.returned_date, "2026-05-26"); Object.assign(p, ret.updates); p.history.push(ret.historyEvent); const redebit = applyEvent(p, { event: "ach_debit", pmtId: "7500000", amount: 8300, bankReference: "d2" }, "2026-06-01"); assert.equal(redebit.kind, "redeposit"); assert.equal(redebit.updates.status, "Cleared"); assert.equal(redebit.updates.clear_status, "Cleared"); }); test("ach transitions: electronic return on a voided-but-settled ACH is voided-and-bounced", () => { const p = achPayment({ status: "Voided", clear_status: "Cleared" }); const r = applyEvent(p, { event: "electronic_return", amount: 8300, bankReference: "r2" }, "2026-06-09"); assert.equal(r.kind, "voided_and_bounced"); assert.equal(r.updates.status, "Voided"); assert.equal(r.updates.clear_status, "Returned"); }); // -------------------------------------------------- staleness sweep (F6) test("stale sweep: unconfirmed ACH older than 16 days is listed; checks older than 60 days counted", () => { const payments = [ achPayment({ check_number: "21222000300", send_payment_on: "06/20/2026" }), // 31d, stale achPayment({ check_number: "21222000301", pk: "payment#21222000301", send_payment_on: "07/10/2026" }), // 11d, fresh achPayment({ check_number: "21222000302", pk: "payment#21222000302", send_payment_on: "06/01/2026", clear_status: "Cleared" }), // confirmed achPayment({ check_number: "21222000303", pk: "payment#21222000303", send_payment_on: "06/01/2026", status: "Voided" }), // cancel-exempt payment({ check_number: "3001", send_payment_on: "04/01/2026" }), // check, 111d, stale payment({ check_number: "3002", pk: "payment#3002", send_payment_on: "07/01/2026" }), // check, fresh ]; const s = sweepStalePayments(payments, "2026-07-21"); assert.equal(s.staleAchCount, 1); assert.deepEqual(s.staleAch, [ { check_number: "21222000300", amount: 8300, send_payment_on: "06/20/2026" }, ]); assert.equal(s.staleChecksCount, 1); }); test("stale sweep: ACH list is capped, count is not", () => { const payments = []; for (let i = 0; i < STALE_LIST_CAP + 5; i++) { payments.push( achPayment({ check_number: `2122200${1000 + i}`, pk: `payment#s${i}`, send_payment_on: "06/01/2026" }) ); } const s = sweepStalePayments(payments, "2026-07-21"); assert.equal(s.staleAchCount, STALE_LIST_CAP + 5); assert.equal(s.staleAch.length, STALE_LIST_CAP); }); // ---------------------------------------------- conditioned writes (F7) test("buildEventUpdate: condition asserts the snapshot's status and clear_status", () => { const p = payment({ status: "Outstanding", clear_status: "Cleared" }); const applied = applyEvent(p, returnEvent(), "2026-07-20"); const cmd = buildEventUpdate("Table", p, applied); assert.equal(cmd.TableName, "Table"); assert.deepEqual(cmd.Key, { pk: p.pk }); assert.equal(cmd.ConditionExpression, "#c0 = :c0 AND #c1 = :c1"); assert.equal(cmd.ExpressionAttributeNames["#c0"], "status"); assert.equal(cmd.ExpressionAttributeNames["#c1"], "clear_status"); assert.equal(cmd.ExpressionAttributeValues[":c0"], "Outstanding"); assert.equal(cmd.ExpressionAttributeValues[":c1"], "Cleared"); assert.match(cmd.UpdateExpression, /^SET #history = list_append/); // Every applied field is present in the SET clause. const setFields = Object.entries(cmd.ExpressionAttributeNames) .filter(([k]) => k.startsWith("#f")) .map(([, v]) => v); assert.deepEqual(setFields.sort(), Object.keys(applied.updates).sort()); }); test("buildEventUpdate: missing snapshot clear_status becomes attribute_not_exists", () => { const p = payment(); // no clear_status yet const applied = applyEvent(p, paidEvent(), "2026-07-19"); const cmd = buildEventUpdate("Table", p, applied); assert.equal(cmd.ConditionExpression, "#c0 = :c0 AND attribute_not_exists(#c1)"); assert.equal(":c1" in cmd.ExpressionAttributeValues, false); }); // ----------------------------------------------------- ReDoS hardening test("classifier: bounded regexes still parse zero-padded and normal check descriptions", () => { const c = classifyTransaction({ text: "CHECK # 0003176", amount: "-350" }); assert.equal(c.event, "check_paid"); assert.equal(c.checkNumber, "3176"); }); test("classifier: pathological long input is bounded, classified without hanging", () => { const junk = `CHECK ${" ".repeat(2000)}${"9".repeat(2000)}`; const start = Date.now(); const c = classifyTransaction({ text: junk, customerReference: "42", amount: "-1" }); assert.ok(Date.now() - start < 1000); // Not parseable as a paid check -> unknown (check-shaped), never dropped. assert.equal(c.event, "unknown"); assert.ok(c.description.length <= 500); }); // ------------------------------------------------------------- date range test("resolveDateRange: default is the trailing 7-day window ending yesterday", () => { const r = resolveDateRange({}, new Date("2026-07-21T13:00:00Z")); assert.deepEqual(r, { fromDate: "2026-07-14", toDate: "2026-07-20" }); }); test("resolveDateRange: explicit valid range passes through", () => { const r = resolveDateRange({ fromDate: "2026-06-16", toDate: "2026-06-23" }); assert.deepEqual(r, { fromDate: "2026-06-16", toDate: "2026-06-23" }); }); test("resolveDateRange: fromDate only replays a single day", () => { const r = resolveDateRange({ fromDate: "2026-06-08" }); assert.deepEqual(r, { fromDate: "2026-06-08", toDate: "2026-06-08" }); }); test("resolveDateRange: malformed and impossible dates throw, naming the offending value", () => { assert.throws(() => resolveDateRange({ fromDate: "06/08/2026" }), /06\/08\/2026/); assert.throws(() => resolveDateRange({ fromDate: "2026-02-30" }), /2026-02-30/); assert.throws( () => resolveDateRange({ fromDate: "2026-07-02", toDate: "2026-07-01" }), /fromDate="2026-07-02", toDate="2026-07-01"/ ); assert.throws(() => resolveDateRange({ fromDate: "2026-07-01; DROP", toDate: "2026-07-02" })); }); test("isValidISODate rejects non-strings and bad calendar dates", () => { assert.equal(isValidISODate("2026-07-20"), true); assert.equal(isValidISODate("2026-13-01"), false); assert.equal(isValidISODate(20260720), false); }); // --- Live-API field shapes (real captures, 2026-07-22; account/bank ids // --- not part of transaction rows, amounts/refs/detailText verbatim) --- test("live shape: 455 ACH debit classifies via detailText", () => { const c = classifyTransaction({ asOfDate: "2026-07-22", transactionCode: "455", transactionDescription: "Preauthorized ACH Debit", transactionType: "Detail", amount: "14516.84", creditDebitIndicator: "Debit", customerReference: "000000000000", bankReference: "02023379940", detailText: "ALLIANCE SANITAT DES:PAYMENTS ID:PMT 7854980 INDN:Sea Haven Industries CO ID:1811679038 CCD PMT INFO:Alliance Sanitation LLC 21222000280", }); assert.equal(c.event, "ach_debit"); assert.equal(c.pmtId, "7854980"); assert.equal(c.embeddedPaymentNumber, "21222000280"); assert.equal(c.vendorText, "Alliance Sanitation LLC"); assert.equal(c.checkNumber, null); }); test("live shape: DES-less 455 is a third-party autopay, ignored not unknown", () => { const c = classifyTransaction({ transactionCode: "455", transactionDescription: "Preauthorized ACH Debit", transactionType: "Detail", amount: "1850", creditDebitIndicator: "Debit", customerReference: "000000000000", detailText: "SOME UTILITY CO DES:AUTOPAY ID:12345 INDN:Sea Haven Industries CO ID:999 WEB", }); assert.equal(c.event, "ignored"); }); test("live shape: 255 return credit maps to check_return with number from ref", () => { const c = classifyTransaction({ asOfDate: "2026-02-20", transactionCode: "255", transactionDescription: "Check Posted and Returned CR", transactionType: "Detail", amount: "1892.62", creditDebitIndicator: "Credit", customerReference: "1122200030", }); assert.equal(c.event, "check_return"); assert.equal(c.checkNumber, "1122200030"); assert.equal(c.direction, "credit"); }); test("live shape: 252 debit reversal credit maps to check_return; text agrees", () => { const c = classifyTransaction({ asOfDate: "2026-06-16", transactionCode: "252", transactionDescription: "Debit Reversal Credit", transactionType: "Detail", amount: "6932.76", creditDebitIndicator: "Credit", customerReference: "001122200802", detailText: "061626 RETURN OF POSTED CHECK / ITEM (RECEIVED ON 06-16) CHECK #1122200802", }); assert.equal(c.event, "check_return"); assert.equal(c.checkNumber, "1122200802"); }); test("live shape: 266 with electronic-return text stays electronic_return, no fabricated check number", () => { const c = classifyTransaction({ asOfDate: "2026-06-16", transactionCode: "266", transactionDescription: "Return Item Credit", transactionType: "Detail", amount: "1555.13", creditDebitIndicator: "Credit", customerReference: "000000000000", detailText: "061626 RETURN OF POSTED CHECK / ITEM (RECEIVED ON 06-16) ELECTRONIC TRANSACTION", }); assert.equal(c.event, "electronic_return"); assert.equal(c.checkNumber, null); }); test("266: ACH PMT text wins over the electronic_return fallback", () => { const c = classifyTransaction({ transactionCode: "266", transactionType: "Detail", amount: "4364.95", creditDebitIndicator: "Credit", customerReference: "000000000000", detailText: "REDDI SERVICES DES:PAYMENTS ID:PMT 7712345 INDN:Sea Haven Industries CO ID:1811679038 CCD PMT INFO:Reddi Services 21222000211", }); assert.equal(c.event, "ach_return"); assert.equal(c.pmtId, "7712345"); }); test("266: bare row (code label only, no detailText) falls back to electronic_return", () => { const c = classifyTransaction({ transactionCode: "266", transactionDescription: "Return Item Credit", transactionType: "Detail", amount: "360.00", creditDebitIndicator: "Credit", customerReference: "000000000000", }); assert.equal(c.event, "electronic_return"); assert.equal(c.checkNumber, null); }); test("266: substantive text matching no classifier stays loud as unknown, never a write event", () => { // Security review F1: a write-capable fallback must not fire on a row the // classifiers could not read — amount-only Returned writes were reachable. const c = classifyTransaction({ transactionCode: "266", transactionDescription: "Return Item Credit", transactionType: "Detail", amount: "360.00", creditDebitIndicator: "Credit", customerReference: "000000000000", detailText: "DEPOSITED ITEM RETURNED 12345", }); assert.equal(c.event, "unknown"); assert.equal(c.checkShaped, true); }); test("live-format return text with leading MMDDYY token classifies via regex", () => { // Security review F2: the anchors must accept the live detailText prefix. const c = classifyTransaction({ transactionCode: "699", creditDebitIndicator: "Credit", amount: "6932.76", detailText: "061626 RETURN OF POSTED CHECK / ITEM (RECEIVED ON 06-16) CHECK #1122200802", }); assert.equal(c.event, "check_return"); assert.equal(c.checkNumber, "1122200802"); const e = classifyTransaction({ transactionCode: "699", creditDebitIndicator: "Credit", amount: "360.00", detailText: "061626 RETURN OF POSTED CHECK / ITEM (RECEIVED ON 06-16) ELECTRONIC TRANSACTION", }); assert.equal(e.event, "electronic_return"); }); test("unmapped code with all-zeros reference stays unknown, not silently ignored", () => { // Security review F4: the zero-guard must not remove the shape signal. const c = classifyTransaction({ transactionCode: "275", transactionType: "Detail", creditDebitIndicator: "Credit", customerReference: "000000000000", amount: "4364.95", detailText: "VENDOR DES:REVERSAL ID:123 CCD", }); assert.equal(c.event, "unknown"); assert.equal(c.checkNumber, null); }); test("reference fields are length-bounded", () => { const c = classifyTransaction({ transactionCode: "475", customerReference: "9".repeat(500), bankReference: "8".repeat(500), amount: "1.00", }); assert.ok(c.checkNumber.length <= 64); assert.ok(c.bankReference.length <= 64); }); test("zero-reference guard: all-zeros customerReference never becomes check number 0", () => { for (const ref of ["0", "0000", "000000000000"]) { const c = classifyTransaction({ transactionCode: "255", transactionType: "Detail", amount: "100", creditDebitIndicator: "Credit", customerReference: ref, }); assert.equal(c.checkNumber, null, `ref ${ref}`); assert.equal(c.customerReference, ""); } }); test("summary rows classify as summary before any text matching", () => { const c = classifyTransaction({ asOfDate: "2026-07-22", transactionCode: "470", transactionDescription: "Total Checks Paid Debit", transactionType: "Summary", amount: "4547.62", }); assert.equal(c.event, "summary"); }); test("noise codes are ignored even when check-shaped", () => { for (const [code, desc] of [ ["170", "Total Other Check Deposits CR"], ["201", "Individual Auto Transfer CR"], ["470", "Total Checks Paid Debit"], ["481", "Individual Loan Payment Debit"], ]) { const c = classifyTransaction({ transactionCode: code, transactionDescription: desc, transactionType: "Detail", amount: "100", customerReference: "197", }); assert.equal(c.event, "ignored", `code ${code}`); } }); test("live shape: 475 check paid has no detailText; number rides in customerReference", () => { const c = classifyTransaction({ asOfDate: "2026-06-16", transactionCode: "475", transactionDescription: "Check Paid", transactionType: "Detail", amount: "3350.00", creditDebitIndicator: "Debit", customerReference: "0001122200236", bankReference: "813312345", }); assert.equal(c.event, "check_paid"); assert.equal(c.checkNumber, "1122200236"); }); test("legacy statement-text fixtures still classify via the text fallback", () => { const c = classifyTransaction({ text: "ARP RETURNED CHECK REFER TO MAKER CHECK # 1122200030 PAID DATE 02/19/26", amount: "1,892.62", transactionCode: "354", }); assert.equal(c.event, "check_return"); assert.equal(c.checkNumber, "1122200030"); }); test("empty-string detailText does not shadow populated text (substantive chain)", () => { // Open SWE review: ?? alone would let detailText:"" hide the statement text. const c = classifyTransaction({ transactionCode: "699", detailText: "", text: "ARP RETURNED CHECK REFER TO MAKER CHECK # 1122200030 PAID DATE 02/19/26", amount: "1,892.62", }); assert.equal(c.event, "check_return"); assert.equal(c.checkNumber, "1122200030"); });