Files
buzz-sheet/lib/commission/slip-upload-payment.test.ts
T
gunshiz 7ba513c2bb
CI / Verify (push) Successful in 1m56s
CI / Build immutable images and deploy (push) Successful in 3m26s
feat : add slipok and switcher to switch between slip2go and slipok
2026-10-10 00:31:12 +07:00

55 lines
3.1 KiB
TypeScript

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<unknown>) => {
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();
});
});