import { afterEach, describe, expect, it, vi } from "vitest"; import { findOwnedWorkbenchProject } from "./project-client"; import { requestWorkbenchJson } from "./request-json"; vi.mock("./project-client", async (original) => ({ ...await original(), findOwnedWorkbenchProject: vi.fn() })); afterEach(() => { vi.restoreAllMocks(); vi.unstubAllGlobals(); vi.clearAllMocks(); }); describe("project JSON transport", () => { it("reuses resolved credentials and preserves request headers without choosing another project", async () => { const fetcher = vi.fn().mockResolvedValue(Response.json({ ok: true })); vi.stubGlobal("fetch", fetcher); await expect(requestWorkbenchJson("/api/v1/probe", "查询", { credentials: { projectId: "project-a", accessToken: "token-a" }, init: { headers: { "X-Request-Id": "request-a" } } })).resolves.toEqual({ ok: true }); const headers = new Headers(fetcher.mock.calls[0][1].headers); expect(headers.get("Authorization")).toBe("Bearer token-a"); expect(headers.get("X-Project-Id")).toBe("project-a"); expect(headers.get("X-Request-Id")).toBe("request-a"); expect(findOwnedWorkbenchProject).not.toHaveBeenCalled(); }); it("rejects an explicitly empty project instead of falling back to a default", async () => { const fetcher = vi.fn(); vi.stubGlobal("fetch", fetcher); await expect(requestWorkbenchJson("/api/v1/probe", "查询", { projectId: "" })).rejects.toThrow("所属项目不能为空"); expect(findOwnedWorkbenchProject).not.toHaveBeenCalled(); expect(fetcher).not.toHaveBeenCalled(); }); it("preserves caller cancellation before fetching and while reading the body", async () => { const controller = new AbortController(); const reason = new DOMException("changed project", "AbortError"); const fetcher = vi.fn().mockImplementation(async () => ({ ok: true, json: async () => { controller.abort(reason); throw reason; } })); vi.stubGlobal("fetch", fetcher); const options = { credentials: { projectId: "project-a", accessToken: null }, init: { signal: controller.signal } }; await expect(requestWorkbenchJson("/api/v1/probe", "查询", options)).rejects.toBe(reason); await expect(requestWorkbenchJson("/api/v1/probe", "查询", options)).rejects.toBe(reason); expect(fetcher).toHaveBeenCalledTimes(1); }); it("keeps deadlines opt-in and never retries a failed write", async () => { const deadline = vi.spyOn(AbortSignal, "timeout"); const fetcher = vi.fn().mockResolvedValue(Response.json({ detail: "服务繁忙" }, { status: 503 })); vi.stubGlobal("fetch", fetcher); await expect(requestWorkbenchJson("/api/v1/probe", "提交", { credentials: { projectId: "project-a", accessToken: null }, init: { method: "POST", body: "{}" } })).rejects.toThrow("服务繁忙"); expect(fetcher).toHaveBeenCalledTimes(1); expect(deadline).not.toHaveBeenCalled(); fetcher.mockResolvedValue(Response.json({})); await requestWorkbenchJson("/api/v1/probe", "地名搜索", { credentials: { projectId: "project-a", accessToken: null }, timeoutMs: 20_000 }); expect(deadline).toHaveBeenCalledWith(20_000); }); });