import { beforeEach, describe, expect, it, vi } from "vitest"; const mocks = vi.hoisted(() => ({ limit: vi.fn(), verifyCommissionSlip: vi.fn(), write: vi.fn(), delete: vi.fn(), paymentValues: vi.fn(), ticketReturning: vi.fn(), notify: vi.fn(), discord: vi.fn(), })); vi.mock("server-only", () => ({})); vi.mock("@/lib/redis/client", () => ({ getRedisClient: async () => ({ set: async () => "OK", eval: async () => 0 }) })); vi.mock("@/lib/media/inspect", () => ({ inspectImage: vi.fn() })); vi.mock("@/lib/media/storage", () => ({ getMediaStorage: async () => ({ write: mocks.write, delete: mocks.delete }) })); vi.mock("@/lib/commission/payment", () => ({ verifyCommissionSlip: mocks.verifyCommissionSlip })); vi.mock("@/lib/commission/server", () => ({ notifyCommission: mocks.notify })); vi.mock("@/lib/commission/discord", () => ({ notifyPaidTicketDiscord: mocks.discord })); vi.mock("@/db", () => ({ getDb: () => ({ select: () => ({ from: () => ({ where: () => ({ limit: mocks.limit }) }) }), transaction: async (fn: (tx: unknown) => Promise) => { let inserts = 0; return fn({ insert: () => ++inserts === 1 ? { values: () => ({ returning: mocks.ticketReturning }) } : { values: mocks.paymentValues } }); }, }) })); import { verifyAndCreateTicket } from "./slip-upload"; import type { Checkout } from "./slip-upload"; const checkout = { id: "checkout", userId: "customer", amountBaht: 100, createdAt: new Date() } as Checkout; const file = new File(["image"], "slip.png", { type: "image/png" }); beforeEach(() => { vi.clearAllMocks(); mocks.limit.mockResolvedValue([]); mocks.ticketReturning.mockResolvedValue([{ id: "ticket" }]); mocks.paymentValues.mockResolvedValue(undefined); mocks.write.mockResolvedValue(undefined); mocks.delete.mockResolvedValue(undefined); }); describe("verified commission payment persistence", () => { it.each(["slip2go", "slipok"])("persists the %s provider without changing the bank transaction key", async (provider) => { mocks.verifyCommissionSlip.mockResolvedValue({ provider, transRef: "bank-ref", referenceId: `${provider}:ref`, amountBaht: 100, transferredAt: new Date(), providerData: { success: true } }); expect(await verifyAndCreateTicket(checkout, file)).toEqual({ ticketId: "ticket", created: true }); expect(mocks.paymentValues).toHaveBeenCalledWith(expect.objectContaining({ provider, transRef: "bank-ref", checkoutId: "checkout", ticketId: "ticket" })); }); it("rejects a bank transaction already paid through another provider and cleans up the upload", async () => { mocks.verifyCommissionSlip.mockResolvedValue({ provider: "slipok", transRef: "bank-ref", referenceId: "slipok:bank-ref", amountBaht: 100, transferredAt: new Date(), providerData: {} }); mocks.paymentValues.mockRejectedValueOnce(Object.assign(new Error("duplicate bank reference"), { code: "23505" })); await expect(verifyAndCreateTicket(checkout, file)).rejects.toMatchObject({ status: 409, message: "payment-already-used" }); expect(mocks.delete).toHaveBeenCalledOnce(); expect(mocks.notify).not.toHaveBeenCalled(); expect(mocks.discord).not.toHaveBeenCalled(); }); });