Initial commit
This commit is contained in:
83
web/src/api.test.ts
Normal file
83
web/src/api.test.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
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",
|
||||
]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user