import { describe, expect, it, vi } from "vitest"; const set = vi.fn().mockResolvedValue(null); const getRedisClient = vi.fn().mockResolvedValue({ set }); const inspectImage = vi.fn(); const verifyCommissionSlip = vi.fn(); const limit = vi.fn(); const evalRedis = vi.fn().mockResolvedValue(0); vi.mock("server-only", () => ({})); vi.mock("@/lib/redis/client", () => ({ getRedisClient })); vi.mock("@/db", () => ({ getDb: () => ({ select: () => ({ from: () => ({ where: () => ({ limit }) }) }) }) })); vi.mock("@/lib/media/inspect", () => ({ inspectImage })); vi.mock("@/lib/commission/payment", () => ({ verifyCommissionSlip })); const { verifyAndCreateTicket } = await import("./slip-upload"); describe("commission slip submission lock", () => { it("rejects a second verification for the same checkout before calling Slip2Go", async () => { const checkout = { id: "checkout-1", userId: "customer-1", amountBaht: 150 }; const file = new File(["image"], "slip.png", { type: "image/png" }); await expect(verifyAndCreateTicket(checkout as Parameters[0], file)) .rejects.toMatchObject({ status: 409, message: "slip-verification-in-progress" }); expect(set).toHaveBeenCalledWith(expect.stringContaining(":commission-slip-lock:checkout-1"), expect.any(String), "EX", 60, "NX"); expect(inspectImage).not.toHaveBeenCalled(); expect(verifyCommissionSlip).not.toHaveBeenCalled(); }); it("rejects an unpaid expired checkout before verifying its slip", async () => { set.mockResolvedValueOnce("OK"); getRedisClient.mockResolvedValueOnce({ set, eval: evalRedis }); limit.mockResolvedValueOnce([]); const checkout = { id: "checkout-2", userId: "customer-1", amountBaht: 150, createdAt: new Date(Date.now() - 3_600_000) }; const file = new File(["image"], "slip.png", { type: "image/png" }); await expect(verifyAndCreateTicket(checkout as Parameters[0], file)) .rejects.toMatchObject({ status: 410, message: "checkout-expired" }); expect(inspectImage).not.toHaveBeenCalled(); expect(verifyCommissionSlip).not.toHaveBeenCalled(); }); it("still returns an existing ticket after the checkout deadline", async () => { set.mockResolvedValueOnce("OK"); getRedisClient.mockResolvedValueOnce({ set, eval: evalRedis }); limit.mockResolvedValueOnce([{ id: "ticket-1" }]); const checkout = { id: "checkout-3", userId: "customer-1", amountBaht: 150, createdAt: new Date(Date.now() - 3_600_000) }; const file = new File(["image"], "slip.png", { type: "image/png" }); expect(await verifyAndCreateTicket(checkout as Parameters[0], file)) .toEqual({ ticketId: "ticket-1", created: false }); expect(verifyCommissionSlip).not.toHaveBeenCalled(); }); });