feat: add party-size queueing and call modes

This commit is contained in:
wangxuming
2026-07-16 11:40:34 +08:00
parent 7f751bebae
commit 66951b4dc3
53 changed files with 3348 additions and 533 deletions

View File

@@ -15,17 +15,18 @@ afterEach(() => {
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" },
ticket: { id: "ticket-1", display_number: "00001", status: "WAITING", party_size: 3 },
revision: 2,
}));
vi.stubGlobal("fetch", fetchMock);
const response = await api.createTicket("project-1", { phone: "13800138000" }, "ticket-intent-key");
const response = await api.createTicket("project-1", { phone: "13800138000", party_size: 3 }, "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");
expect(response.ticket.party_size).toBe(3);
});
it("sends the current revision and an idempotency key when calling next", async () => {
@@ -36,12 +37,12 @@ describe("mutating queue requests", () => {
}));
vi.stubGlobal("fetch", fetchMock);
await api.callNext("project-1", 7, 3, "call-intent-key");
await api.callNext("project-1", 7, "PEOPLE", 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 });
expect(JSON.parse(String(init.body))).toEqual({ expected_revision: 7, mode: "PEOPLE", count: 3 });
});
it("exposes nested API error code and details for explicit duplicate confirmation", async () => {
@@ -57,7 +58,7 @@ describe("mutating queue requests", () => {
}));
vi.stubGlobal("fetch", fetchMock);
await expect(api.createTicket("project-1", { phone: "13800138000" })).rejects.toMatchObject({
await expect(api.createTicket("project-1", { phone: "13800138000", party_size: 2 })).rejects.toMatchObject({
code: "DUPLICATE_PHONE",
status: 409,
details: { existing_ticket: "00001" },
@@ -80,6 +81,31 @@ describe("public visitor lookup", () => {
expect(JSON.parse(String(init.body))).toEqual({ phone: "13800138000" });
expect(response.tickets[0].ticket_number).toBe("00012");
});
it("loads public projects and creates a ticket through the public endpoint", async () => {
const fetchMock = vi.fn()
.mockResolvedValueOnce(jsonResponse({ projects: [{ id: "project-1", name: "云岭漂流", status: "RUNNING", min_party_size: 1, max_party_size: 8 }] }))
.mockResolvedValueOnce(jsonResponse({
ticket: { display_number: "00013", status: "WAITING", phone_last4: "8000", party_size: 4 },
public_token: "visitor-token",
public_url: "/visitor/visitor-token",
revision: 3,
}));
vi.stubGlobal("fetch", fetchMock);
await expect(api.publicProjects()).resolves.toEqual({
projects: [{ id: "project-1", name: "云岭漂流", status: "RUNNING", min_party_size: 1, max_party_size: 8 }],
});
const response = await api.publicCreateTicket("project-1", { phone: "13800138000", party_size: 4 }, "visitor-ticket-key");
const [url, init] = fetchMock.mock.calls[1] as [string, RequestInit];
expect(url).toBe("/api/public/projects/project-1/tickets");
expect(init.method).toBe("POST");
expect((init.headers as Headers).get("Idempotency-Key")).toBe("visitor-ticket-key");
expect(response.ticket.ticket_number).toBe("00013");
expect(response.ticket.party_size).toBe(4);
expect(response.public_token).toBe("visitor-token");
});
});
describe("isolated portal authentication", () => {