seahaven-ap/packages/shared/src/payment-status.test.ts
Adam Moussa 46b570352a
feat(shared): add payment ladder and CSV domain package (AP-13) (#11)
* feat(shared): add payment ladder and CSV domain package

Introduce @seahaven-ap/shared via npm workspaces so FE and future API share
payments-dashboard-compatible status ladders, pay-date helpers, and CSV headers.

* fix(web): build shared package before vite dev

Ensure fresh npm ci && npm run dev (and Playwright webServer) can resolve
@seahaven-ap/shared dist exports that are gitignored.
2026-08-10 19:28:02 -04:00

66 lines
2.7 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
CANCEL_STATUSES,
PAYMENT_STATUS_RANK,
canTransitionPaymentStatus,
isCancelStatus,
parsePaymentStatus,
toPaymentDisplayStatus,
} from "./payment-status.js";
describe("payment status ladder", () => {
it("ranks the ladder in consumer order", () => {
expect(PAYMENT_STATUS_RANK.scheduled).toBe(1);
expect(PAYMENT_STATUS_RANK.payment_submitted).toBe(2);
expect(PAYMENT_STATUS_RANK.issued).toBe(3);
expect(PAYMENT_STATUS_RANK.outstanding).toBe(4);
expect(PAYMENT_STATUS_RANK.cleared).toBe(5);
});
it("maps internal statuses to CSV display strings", () => {
expect(toPaymentDisplayStatus("payment_submitted")).toBe("Payment Submitted");
expect(toPaymentDisplayStatus("cleared")).toBe("Cleared");
});
it("parses display and internal status strings", () => {
expect(parsePaymentStatus("Payment Submitted")).toBe("payment_submitted");
expect(parsePaymentStatus("OUTSTANDING")).toBe("outstanding");
expect(parsePaymentStatus("scheduled")).toBe("scheduled");
expect(parsePaymentStatus("voided")).toBeNull();
});
it("allows forward transitions and same-rank no-ops", () => {
expect(canTransitionPaymentStatus("scheduled", "payment_submitted")).toBe(true);
expect(canTransitionPaymentStatus("issued", "Outstanding")).toBe(true);
expect(canTransitionPaymentStatus("outstanding", "outstanding")).toBe(true);
expect(canTransitionPaymentStatus("outstanding", "cleared")).toBe(true);
});
it("blocks non-cancel rank regression", () => {
expect(canTransitionPaymentStatus("issued", "scheduled")).toBe(false);
expect(canTransitionPaymentStatus("cleared", "outstanding")).toBe(false);
expect(canTransitionPaymentStatus("payment_submitted", "Scheduled")).toBe(false);
});
it("allows void from ranks below cleared", () => {
for (const status of ["scheduled", "payment_submitted", "issued", "outstanding"] as const) {
expect(canTransitionPaymentStatus(status, "voided")).toBe(true);
expect(canTransitionPaymentStatus(status, "Cancelled")).toBe(true);
}
});
it("blocks all transitions once cleared, including void", () => {
expect(canTransitionPaymentStatus("cleared", "voided")).toBe(false);
expect(canTransitionPaymentStatus("cleared", "cleared")).toBe(false);
expect(canTransitionPaymentStatus("cleared", "outstanding")).toBe(false);
});
it("recognizes cancel vocabulary case-insensitively", () => {
for (const status of CANCEL_STATUSES) {
expect(isCancelStatus(status)).toBe(true);
expect(isCancelStatus(status.toUpperCase())).toBe(true);
}
expect(isCancelStatus("Marked As Void")).toBe(true);
expect(isCancelStatus("Cleared")).toBe(false);
});
});