84 lines
2.9 KiB
TypeScript
84 lines
2.9 KiB
TypeScript
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",
|
|
]);
|
|
});
|
|
});
|