fix(workbench): share sync reads and retry failed invalidations
This commit is contained in:
@@ -6,7 +6,10 @@ import {
|
||||
} from "./project-client";
|
||||
|
||||
describe("workbench project client", () => {
|
||||
afterEach(() => vi.unstubAllGlobals());
|
||||
afterEach(() => {
|
||||
vi.unstubAllGlobals();
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("selects the active project owned by the configured GeoServer workspace", async () => {
|
||||
const fetchMock = vi.fn(async (_input: RequestInfo | URL, _init?: RequestInit) => new Response(JSON.stringify({
|
||||
@@ -36,23 +39,20 @@ describe("workbench project client", () => {
|
||||
await expect(findOwnedWorkbenchProject()).rejects.toThrow("授权项目");
|
||||
});
|
||||
|
||||
it("does not share a caller-bound abort signal with other project consumers", async () => {
|
||||
it("shares project lookup while cancelling only the caller that aborts", async () => {
|
||||
const projectPage = JSON.stringify({
|
||||
items: [{ project_id: "supply", gs_workspace: "tjwater_next", status: "active" }],
|
||||
total: 1,
|
||||
limit: 1000,
|
||||
offset: 0
|
||||
});
|
||||
const fetchMock = vi.fn((_input: RequestInfo | URL, init?: RequestInit) => {
|
||||
if (!init?.signal) {
|
||||
return Promise.resolve(new Response(projectPage, { status: 200 }));
|
||||
}
|
||||
return new Promise<Response>((_resolve, reject) => {
|
||||
init.signal?.addEventListener("abort", () => {
|
||||
reject(new DOMException("Aborted", "AbortError"));
|
||||
}, { once: true });
|
||||
});
|
||||
});
|
||||
let resolveFetch!: (response: Response) => void;
|
||||
const fetchMock = vi.fn((_input: RequestInfo | URL, init?: RequestInit) =>
|
||||
new Promise<Response>((resolve, reject) => {
|
||||
resolveFetch = resolve;
|
||||
init?.signal?.addEventListener("abort", () => reject(new DOMException("Aborted", "AbortError")), { once: true });
|
||||
})
|
||||
);
|
||||
vi.stubGlobal("fetch", fetchMock);
|
||||
const getAccessToken = async () => null;
|
||||
const controller = new AbortController();
|
||||
@@ -63,8 +63,32 @@ describe("workbench project client", () => {
|
||||
controller.abort();
|
||||
|
||||
await expect(cancelled).rejects.toMatchObject({ name: "AbortError" });
|
||||
resolveFetch(new Response(projectPage, { status: 200 }));
|
||||
await expect(active).resolves.toMatchObject({ project: { project_id: "supply" } });
|
||||
expect(fetchMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("refreshes tokens, expires project metadata and evicts failed shared lookups", async () => {
|
||||
const clock = vi.spyOn(Date, "now").mockReturnValue(0);
|
||||
const page = { items: [{ project_id: "supply", gs_workspace: "tjwater_next", status: "active" }],
|
||||
total: 1, limit: 1000, offset: 0 };
|
||||
const fetchMock = vi.fn()
|
||||
.mockResolvedValueOnce(new Response(JSON.stringify({ detail: "temporary" }), { status: 503 }))
|
||||
.mockImplementation(async () => new Response(JSON.stringify(page), { status: 200 }));
|
||||
vi.stubGlobal("fetch", fetchMock);
|
||||
let token = "first";
|
||||
const provider = vi.fn(async () => token);
|
||||
await expect(findOwnedWorkbenchProject(provider)).rejects.toThrow("temporary");
|
||||
await findOwnedWorkbenchProject(provider);
|
||||
token = "renewed";
|
||||
expect((await findOwnedWorkbenchProject(provider)).accessToken).toBe("renewed");
|
||||
expect(fetchMock).toHaveBeenCalledTimes(2);
|
||||
clock.mockReturnValue(5 * 60_000);
|
||||
await findOwnedWorkbenchProject(provider);
|
||||
expect(fetchMock).toHaveBeenCalledTimes(3);
|
||||
expect(new Headers(fetchMock.mock.calls[2][1].headers).get("Authorization")).toBe("Bearer renewed");
|
||||
await findOwnedWorkbenchProject(async () => "another-identity");
|
||||
expect(fetchMock).toHaveBeenCalledTimes(4);
|
||||
});
|
||||
|
||||
it("loads the authoritative project extent and access identity independently", async () => {
|
||||
|
||||
Reference in New Issue
Block a user