import { afterEach, describe, expect, it, vi } from "vitest"; import { api } from "./api"; function jsonResponse(body: unknown): Response { return new Response(JSON.stringify(body), { status: 200, headers: { "Content-Type": "application/json" }, }); } afterEach(() => { vi.unstubAllGlobals(); }); describe("mutating queue requests", () => { it("sends an idempotency key when creating a ticket", async () => { const fetchMock = vi.fn().mockResolvedValue(jsonResponse({ ticket: { id: "ticket-1", display_number: "00001", status: "WAITING" }, revision: 2, })); vi.stubGlobal("fetch", fetchMock); const response = await api.createTicket("project-1", { phone: "13800138000" }, "ticket-intent-key"); const [, init] = fetchMock.mock.calls[0] as [string, RequestInit]; const headers = init.headers as Headers; expect(headers.get("Idempotency-Key")).toBe("ticket-intent-key"); expect(response.ticket.ticket_number).toBe("00001"); }); it("sends the current revision and an idempotency key when calling next", async () => { const fetchMock = vi.fn().mockResolvedValue(jsonResponse({ batch: { id: "batch-1", batch_number: 3, status: "ACTIVE", tickets: [], called_at: null }, revision: 8, device_results: [], })); vi.stubGlobal("fetch", fetchMock); await api.callNext("project-1", 7, 3, "call-intent-key"); const [, init] = fetchMock.mock.calls[0] as [string, RequestInit]; const headers = init.headers as Headers; expect(headers.get("Idempotency-Key")).toBe("call-intent-key"); expect(JSON.parse(String(init.body))).toEqual({ expected_revision: 7, count: 3 }); }); it("exposes nested API error code and details for explicit duplicate confirmation", async () => { const fetchMock = vi.fn().mockResolvedValue(new Response(JSON.stringify({ error: { code: "DUPLICATE_PHONE", message: "该手机号已有活动号码", details: { existing_ticket: "00001" }, }, }), { status: 409, headers: { "Content-Type": "application/json" }, })); vi.stubGlobal("fetch", fetchMock); await expect(api.createTicket("project-1", { phone: "13800138000" })).rejects.toMatchObject({ code: "DUPLICATE_PHONE", status: 409, details: { existing_ticket: "00001" }, }); }); }); describe("isolated portal authentication", () => { it("uses different endpoints for staff and admin sessions", async () => { const fetchMock = vi.fn().mockResolvedValue(jsonResponse({ user: { id: "u1", role: "STAFF" }, projects: [] })); vi.stubGlobal("fetch", fetchMock); await api.login("staff", "employee", "secret"); await api.me("admin"); await api.logout("staff"); expect(fetchMock.mock.calls.map(([url]) => url)).toEqual([ "/api/staff/auth/login", "/api/admin/auth/me", "/api/staff/auth/logout", ]); }); });