Replaces the four legacy PHP scripts under email.notifications/send*.php with a single NestJS module. Four jobs (outstanding payments, payment confirmations, account-status alerts with day-of-week gates, trust payment confirmations) share one MailService modelled on StorageService: env-driven SES client, null fallback in dev with console logging, refuses to send in production when unconfigured. Schema adds email_notification_log (every attempt, sent/failed/skipped) and account_status_history (one row per threshold hit, Job 3). Enums encode the legacy wire shape so external log scrapers keep parsing notificationType keys verbatim. Web adds /notificaciones with four trigger cards, a flags panel, and a paginated log browser. New notification:send ability gates all four endpoints at MANAGER, matching the renewal:send trust tier.
126 lines
3.6 KiB
TypeScript
126 lines
3.6 KiB
TypeScript
import {
|
|
renderAccountStatus,
|
|
renderOutstanding,
|
|
renderPaymentConfirm,
|
|
renderTrustConfirm,
|
|
} from "./render";
|
|
|
|
/**
|
|
* Render-level tests. The legacy PHP scripts fetched these bodies by URL;
|
|
* we render server-side and inline. The tests assert the *shape* of each
|
|
* body — account id, name, subject, balance/tipo, color band — because
|
|
* the customer base has been seeing these letters for years and a visual
|
|
* regression costs trust faster than any backend change does.
|
|
*/
|
|
|
|
describe("renderOutstanding", () => {
|
|
it("includes the customer id, name, total, and per-row table", () => {
|
|
const html = renderOutstanding({
|
|
customerId: "C-001",
|
|
customerName: "Acme & Co.",
|
|
total: "1234.50",
|
|
rows: [
|
|
{
|
|
date: "2026-07-01",
|
|
reference: "INV-1",
|
|
period: "Jul-26",
|
|
type: "CHECK",
|
|
amount: "-500.00",
|
|
balance: "-500.00",
|
|
},
|
|
{
|
|
date: "2026-07-15",
|
|
reference: "INV-2",
|
|
period: "Jul-26",
|
|
type: "CASH",
|
|
amount: "-734.50",
|
|
balance: "-1234.50",
|
|
},
|
|
],
|
|
year: 2026,
|
|
});
|
|
expect(html).toContain("Acme & Co.");
|
|
expect(html).toContain("ACCOUNT #C-001");
|
|
expect(html).toContain("$ 1,234.50");
|
|
expect(html).toContain("INV-1");
|
|
expect(html).toContain("CHECK");
|
|
expect(html).toContain("IF YOU ALREADY SENT THE CHECK");
|
|
});
|
|
|
|
it("escapes HTML in the customer name", () => {
|
|
const html = renderOutstanding({
|
|
customerId: "x",
|
|
customerName: "<script>alert(1)</script>",
|
|
total: "0.00",
|
|
rows: [],
|
|
year: 2026,
|
|
});
|
|
expect(html).not.toContain("<script>alert(1)</script>");
|
|
expect(html).toContain("<script>alert(1)</script>");
|
|
});
|
|
});
|
|
|
|
describe("renderPaymentConfirm", () => {
|
|
it("uses the transaction type in the heading and the amount in the body", () => {
|
|
const html = renderPaymentConfirm({
|
|
customerId: "C-002",
|
|
customerName: "Bob",
|
|
typeOfTrx: "CHECK DEPOSIT",
|
|
reference: "DEP-99",
|
|
amount: "500.00",
|
|
year: 2026,
|
|
});
|
|
expect(html).toContain("CHECK DEPOSIT CONFIRMATION");
|
|
expect(html).toContain("HI, Bob");
|
|
expect(html).toContain("REFER# DEP-99");
|
|
expect(html).toContain("$ 500.00");
|
|
});
|
|
});
|
|
|
|
describe("renderAccountStatus", () => {
|
|
it("uses the yellow band and the under-minimum phrasing for level=0", () => {
|
|
const html = renderAccountStatus({
|
|
customerId: "C-003",
|
|
customerName: "Carol",
|
|
level: 0,
|
|
balance: "10.00",
|
|
tipo: "40.00",
|
|
year: 2026,
|
|
});
|
|
expect(html).toContain("#88D5EE");
|
|
expect(html).toContain("under our minimum");
|
|
expect(html).toContain("Carol");
|
|
expect(html).toContain("$ 10.00");
|
|
expect(html).toContain("$ 40.00");
|
|
});
|
|
|
|
it("uses the red band and the rush phrasing for level=1", () => {
|
|
const html = renderAccountStatus({
|
|
customerId: "C-003",
|
|
customerName: "Carol",
|
|
level: 1,
|
|
balance: "-25.50",
|
|
tipo: "25.50",
|
|
year: 2026,
|
|
});
|
|
expect(html).toContain("#FF8D71");
|
|
expect(html).toContain("overdrawn");
|
|
expect(html).toContain("reactivate your payments");
|
|
expect(html).toContain("$ 25.50");
|
|
});
|
|
});
|
|
|
|
describe("renderTrustConfirm", () => {
|
|
it("labels the trust annual fee and quotes the amount", () => {
|
|
const html = renderTrustConfirm({
|
|
customerId: "C-004",
|
|
customerName: "Dan",
|
|
amount: "350.00",
|
|
year: 2026,
|
|
});
|
|
expect(html).toContain("Annual Bank Fee Payment Confirmation");
|
|
expect(html).toContain("$ 350.00");
|
|
expect(html).toContain("Most banks always request");
|
|
});
|
|
});
|