import { beforeEach, describe, expect, it, vi } from "vitest"; const requireCommissionUser = vi.fn(); const limitRequest = vi.fn(); const validateCommissionCatalog = vi.fn(); const promptPayConfig = vi.fn(); const transaction = vi.fn(); const forUpdate = vi.fn(); const returning = vi.fn(); const insert = vi.fn(); const values = vi.fn(); vi.mock("@/lib/commission/server", () => ({ requireCommissionUser })); vi.mock("@/lib/security/rate-limit", () => ({ limitRequest })); vi.mock("@/lib/commission/catalog", () => ({ validateCommissionCatalog })); vi.mock("@/lib/commission/payment", () => ({ promptPayConfig })); vi.mock("@/db", () => ({ getDb: () => ({ transaction }) })); const { POST } = await import("./route"); function request(customStats?: string) { return new Request("https://guide.sudloh.com/api/commission/checkouts", { method: "POST", headers: { Origin: "https://guide.sudloh.com", "Content-Type": "application/json" }, body: JSON.stringify({ teams: customStats === undefined ? [] : [{ members: ["a", "b", "c", "d"].map((characterKey, index) => ({ characterKey, weaponKey: "w1", artifactKey: "artifact", customStats: index === 0 ? customStats : undefined, })) }], weapons: [{ characterKey: "c", weaponKeys: ["w1", "w2"] }], constellations: [] }), }); } describe("commission checkout availability", () => { beforeEach(() => { process.env.BETTER_AUTH_URL = "https://guide.sudloh.com"; vi.clearAllMocks(); requireCommissionUser.mockResolvedValue({ id: "customer-1" }); limitRequest.mockResolvedValue(undefined); validateCommissionCatalog.mockResolvedValue(true); promptPayConfig.mockReturnValue({ identifier: "test" }); transaction.mockImplementation((callback) => callback({ select: () => ({ from: () => ({ where: () => ({ for: forUpdate }) }) }), insert, })); insert.mockReturnValue({ values }); values.mockReturnValue({ returning }); returning.mockResolvedValue([{ id: "checkout-1" }]); }); it("rejects a stale form submission after closure", async () => { forUpdate.mockResolvedValue([{ isOpen: false }]); const response = await POST(request()); expect(response.status).toBe(409); expect(await response.json()).toEqual({ error: "commission-closed" }); expect(insert).not.toHaveBeenCalled(); }); it("creates a checkout while open", async () => { forUpdate.mockResolvedValue([{ isOpen: true }]); const response = await POST(request()); expect(response.status).toBe(201); expect(await response.json()).toEqual({ checkoutId: "checkout-1" }); expect(insert).toHaveBeenCalledOnce(); expect(values).toHaveBeenCalledWith(expect.objectContaining({ amountBaht: 140 })); }); it("stores custom stats and charges twice the total on the server", async () => { forUpdate.mockResolvedValue([{ isOpen: true }]); const response = await POST(request("ATK 2000")); expect(response.status).toBe(201); expect(values).toHaveBeenCalledWith(expect.objectContaining({ amountBaht: 280, request: expect.objectContaining({ teams: [{ members: [ expect.objectContaining({ characterKey: "a", customStats: "ATK 2000" }), expect.objectContaining({ characterKey: "b" }), expect.objectContaining({ characterKey: "c" }), expect.objectContaining({ characterKey: "d" }), ] }] }), })); }); it("charges twice the total for custom stats on either comparison type without a team", async () => { forUpdate.mockResolvedValue([{ isOpen: true }]); for (const kind of ["weapons", "constellations"]) { values.mockClear(); const body = { teams: [], weapons: [], constellations: [], [kind]: kind === "weapons" ? [{ characterKey: "a", weaponKeys: ["one", "two"], customStats: "ATK 2000" }] : [{ characterKey: "a", levels: [0, 1], customStats: "ER 180%" }] }; const response = await POST(new Request("https://guide.sudloh.com/api/commission/checkouts", { method: "POST", headers: { Origin: "https://guide.sudloh.com", "Content-Type": "application/json" }, body: JSON.stringify(body), })); expect(response.status).toBe(201); expect(values).toHaveBeenCalledWith(expect.objectContaining({ amountBaht: 280, request: expect.objectContaining({ [kind]: expect.arrayContaining([expect.objectContaining({ customStats: body[kind][0].customStats })]) }), })); } }); });