126 lines
3.8 KiB
TypeScript
126 lines
3.8 KiB
TypeScript
/// <reference types="jest" />
|
|
|
|
import { Prisma } from "@prisma/client";
|
|
import { BillPayService } from "../src/bill-pay/bill-pay.service";
|
|
import { createPrismaMock } from "./utils/mock-prisma";
|
|
|
|
describe("BillPayService", () => {
|
|
const userId = "user_1";
|
|
let prisma: ReturnType<typeof createPrismaMock>;
|
|
let notifications: { notifyUser: jest.Mock };
|
|
let service: BillPayService;
|
|
|
|
beforeEach(() => {
|
|
prisma = createPrismaMock();
|
|
notifications = { notifyUser: jest.fn().mockResolvedValue({}) };
|
|
service = new BillPayService(prisma as never, notifications as never);
|
|
prisma.auditLog.create.mockResolvedValue({});
|
|
});
|
|
|
|
it("creates a bill and sends a notification", async () => {
|
|
const dueDate = new Date("2026-08-01T00:00:00.000Z");
|
|
prisma.bill.create.mockResolvedValue({
|
|
id: "bill_1",
|
|
userId,
|
|
name: "Electric",
|
|
amount: new Prisma.Decimal(125),
|
|
currency: "USD",
|
|
dueDate,
|
|
status: "pending",
|
|
recurrence: "monthly",
|
|
autopay: false,
|
|
reminderDays: 3,
|
|
payee: null,
|
|
payments: [],
|
|
createdAt: dueDate,
|
|
updatedAt: dueDate,
|
|
});
|
|
|
|
const bill = await service.createBill(userId, {
|
|
name: "Electric",
|
|
amount: 125,
|
|
dueDate: "2026-08-01",
|
|
recurrence: "monthly",
|
|
});
|
|
|
|
expect(prisma.bill.create).toHaveBeenCalledWith(expect.objectContaining({
|
|
data: expect.objectContaining({
|
|
userId,
|
|
name: "Electric",
|
|
amount: expect.any(Prisma.Decimal),
|
|
recurrence: "monthly",
|
|
}),
|
|
}));
|
|
expect(notifications.notifyUser).toHaveBeenCalledWith(userId, expect.objectContaining({
|
|
type: "bill.created",
|
|
title: "Bill added: Electric",
|
|
}));
|
|
expect(bill.computedStatus).toBe("pending");
|
|
});
|
|
|
|
it("marks a bill paid and records a payment", async () => {
|
|
const dueDate = new Date("2026-08-01T00:00:00.000Z");
|
|
prisma.bill.findFirst.mockResolvedValue({
|
|
id: "bill_1",
|
|
userId,
|
|
name: "Internet",
|
|
amount: new Prisma.Decimal(80),
|
|
currency: "USD",
|
|
dueDate,
|
|
status: "pending",
|
|
});
|
|
prisma.billPayment.create.mockResolvedValue({
|
|
id: "payment_1",
|
|
billId: "bill_1",
|
|
userId,
|
|
amount: new Prisma.Decimal(80),
|
|
method: "bank_bill_pay",
|
|
});
|
|
prisma.bill.update.mockResolvedValue({
|
|
id: "bill_1",
|
|
userId,
|
|
name: "Internet",
|
|
amount: new Prisma.Decimal(80),
|
|
currency: "USD",
|
|
dueDate,
|
|
status: "paid",
|
|
paidAt: new Date("2026-08-02T00:00:00.000Z"),
|
|
payee: null,
|
|
payments: [],
|
|
});
|
|
|
|
const result = await service.markPaid(userId, "bill_1", {
|
|
method: "bank_bill_pay",
|
|
confirmationNumber: "ABC123",
|
|
paidAt: "2026-08-02",
|
|
});
|
|
|
|
expect(prisma.billPayment.create).toHaveBeenCalledWith(expect.objectContaining({
|
|
data: expect.objectContaining({
|
|
billId: "bill_1",
|
|
method: "bank_bill_pay",
|
|
confirmationNumber: "ABC123",
|
|
}),
|
|
}));
|
|
expect(prisma.bill.update).toHaveBeenCalledWith(expect.objectContaining({
|
|
data: expect.objectContaining({ status: "paid" }),
|
|
}));
|
|
expect(result.bill.computedStatus).toBe("paid");
|
|
});
|
|
|
|
it("summarizes upcoming and overdue bills", async () => {
|
|
const yesterday = new Date(Date.now() - 24 * 60 * 60 * 1000);
|
|
const nextWeek = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000);
|
|
prisma.bill.findMany.mockResolvedValue([
|
|
{ id: "b1", userId, name: "Overdue", amount: new Prisma.Decimal(10), dueDate: yesterday, status: "pending", payments: [] },
|
|
{ id: "b2", userId, name: "Soon", amount: new Prisma.Decimal(20), dueDate: nextWeek, status: "scheduled", payments: [] },
|
|
]);
|
|
|
|
const result = await service.summary(userId);
|
|
|
|
expect(result.activeCount).toBe(2);
|
|
expect(result.overdueCount).toBe(1);
|
|
expect(result.totalDueNext30).toBe("30");
|
|
});
|
|
});
|