import { PgDialect } from "drizzle-orm/pg-core"; import type { SQL } from "drizzle-orm"; import { beforeEach, describe, expect, it, vi } from "vitest"; const authorizeTicket = vi.fn(); const selectLimit = vi.fn(); const selectWhere = vi.fn((condition: SQL) => { void condition; return { limit: selectLimit }; }); const insertReturning = vi.fn(); const insertValues = vi.fn(() => ({ returning: insertReturning })); const updateWhere = vi.fn(); const tx = { insert: vi.fn(() => ({ values: insertValues })), update: vi.fn(() => ({ set: () => ({ where: updateWhere }) })) }; const notifyCommission = vi.fn(); vi.mock("@/lib/commission/tickets", () => ({ authorizeTicket })); vi.mock("@/lib/commission/server", () => ({ notifyCommission })); vi.mock("@/lib/commission/push", () => ({ sendCommissionMessagePush: vi.fn() })); vi.mock("@/lib/security/rate-limit", () => ({ limitRequest: async () => undefined })); vi.mock("next/server", () => ({ after: vi.fn() })); vi.mock("@/db", () => ({ getDb: () => ({ select: () => ({ from: () => ({ where: selectWhere }) }), transaction: async (run: (value: typeof tx) => Promise) => run(tx), }) })); const { POST } = await import("./route"); const ticketId = "11111111-1111-4111-8111-111111111111"; const parentId = "22222222-2222-4222-8222-222222222222"; const messageId = "33333333-3333-4333-8333-333333333333"; function request(replyToId: string) { const body = new FormData(); body.set("text", "A reply"); body.set("replyToId", replyToId); return new Request(`https://guide.sudloh.com/api/commission/tickets/${ticketId}/messages`, { method: "POST", headers: { Origin: "https://guide.sudloh.com" }, body, }); } function context() { return { params: Promise.resolve({ id: ticketId }) } as RouteContext<"/api/commission/tickets/[id]/messages">; } describe("commission message replies", () => { beforeEach(() => { process.env.BETTER_AUTH_URL = "https://guide.sudloh.com"; vi.clearAllMocks(); authorizeTicket.mockResolvedValue({ ticket: { status: "open", userId: "user-1", title: "A ticket" }, user: { id: "user-1", name: "User" } }); selectLimit.mockResolvedValue([{ id: parentId }]); insertReturning.mockResolvedValue([{ id: messageId }]); }); it("accepts a reply only after looking up its parent in the same ticket", async () => { const response = await POST(request(parentId), context()); expect(response.status).toBe(201); const condition = new PgDialect().sqlToQuery(selectWhere.mock.calls[0][0]); expect(condition.sql).toContain('"ticket_id"'); expect(condition.params).toContain(ticketId); expect(condition.params).toContain(parentId); expect(insertValues).toHaveBeenCalledWith(expect.objectContaining({ replyToId: parentId })); }); it("rejects a parent that is absent from this ticket", async () => { selectLimit.mockResolvedValue([]); const response = await POST(request(parentId), context()); expect(response.status).toBe(404); expect(insertValues).not.toHaveBeenCalled(); }); });