import { beforeEach, describe, expect, it, vi } from "vitest"; import { HttpError } from "@/lib/security/http"; const requireAdmin = vi.fn(); const update = vi.fn(); const set = vi.fn(); const where = vi.fn(); const returning = vi.fn(); const notifyCommission = vi.fn(); vi.mock("@/lib/auth/server", () => ({ requireAdmin })); vi.mock("@/db", () => ({ getDb: () => ({ update }) })); vi.mock("@/lib/commission/server", () => ({ notifyCommission })); const { PATCH } = await import("./route"); const ticketId = "11111111-1111-4111-8111-111111111111"; const userId = "customer-1"; function request(title: unknown, origin = "https://guide.sudloh.com") { return new Request(`https://guide.sudloh.com/api/commission/tickets/${ticketId}/title`, { method: "PATCH", headers: { Origin: origin, "Content-Type": "application/json" }, body: JSON.stringify({ title }), }); } function context() { return { params: Promise.resolve({ id: ticketId }) } as RouteContext<"/api/commission/tickets/[id]/title">; } describe("commission ticket rename", () => { beforeEach(() => { process.env.BETTER_AUTH_URL = "https://guide.sudloh.com"; vi.clearAllMocks(); requireAdmin.mockResolvedValue({ user: { id: "admin-1" } }); update.mockReturnValue({ set }); set.mockReturnValue({ where }); where.mockReturnValue({ returning }); returning.mockResolvedValue([{ title: "New name", userId }]); notifyCommission.mockResolvedValue(undefined); }); it("rejects cross-origin and non-admin requests", async () => { expect((await PATCH(request("New name", "https://example.com"), context())).status).toBe(403); expect(requireAdmin).not.toHaveBeenCalled(); requireAdmin.mockRejectedValueOnce(new HttpError(401, "unauthorized")); expect((await PATCH(request("New name"), context())).status).toBe(401); expect(update).not.toHaveBeenCalled(); }); it("normalizes and saves a name, then notifies ticket viewers", async () => { const response = await PATCH(request(" New name "), context()); expect(response.status).toBe(200); expect(set).toHaveBeenCalledWith({ title: "New name", updatedAt: expect.any(Date) }); expect(notifyCommission).toHaveBeenCalledWith(ticketId, userId); }); it("allows clearing the name and rejects names over 80 characters", async () => { returning.mockResolvedValueOnce([{ title: null, userId }]); expect((await PATCH(request(" "), context())).status).toBe(200); expect(set).toHaveBeenCalledWith({ title: null, updatedAt: expect.any(Date) }); vi.clearAllMocks(); expect((await PATCH(request("x".repeat(81)), context())).status).toBe(400); expect(update).not.toHaveBeenCalled(); }); });