import { afterEach, describe, expect, it } from "bun:test"; import { ExternalInformationClient, geocodeInput } from "../../src/product/external-information-client.js"; import { bindSessionCredential, credentialRefreshCoordinator, refreshSessionCredential, releaseSessionCredential } from "../../src/product/credential-refresh.js"; const originalFetch = globalThis.fetch; const session = "external-information-test"; afterEach(() => { globalThis.fetch = originalFetch; releaseSessionCredential(session); }); describe("typed external information", () => { it("normalizes valid Tianditu coordinates for zoom_to_map and rejects fabricated coordinates", async () => { bindSessionCredential(session, { authorization: "Bearer TEST", projectId: "trusted" }); globalThis.fetch = (async (url: Parameters[0], init?: RequestInit) => { expect(new URL(String(url)).pathname).toBe("/api/v1/geocoding-requests"); expect(new Headers(init?.headers).get("Authorization")).toBe("Bearer TEST"); expect(new Headers(init?.headers).get("X-Project-Id")).toBe("trusted"); expect(JSON.parse(String(init?.body))).toEqual({ keyword: "北京市人民政府" }); return Response.json({ status: "0", location: { lon: "116.4", lat: "39.9", level: 1 } }); }) as unknown as typeof fetch; expect(await new ExternalInformationClient("http://server.test").geocode(session, { keyword: "北京市人民政府" })).toMatchObject({ source_crs: "EPSG:4326", center: [116.4, 39.9], location: { lon: 116.4, lat: 39.9 } }); for (const data of [null, { status: "0", location: { lon: "", lat: " " } }, { status: "1" }, { status: "0", location: { lon: 181, lat: 39 } }, { status: "0", location: { lon: null, lat: null } }]) { globalThis.fetch = (async () => Response.json(data)) as unknown as typeof fetch; await expect(new ExternalInformationClient("http://server.test").geocode(session, { keyword: "北京市人民政府" })).rejects.toThrow("no valid coordinate"); } }); it("renews a rejected credential once and never turns a 403 into a renewal", async () => { bindSessionCredential(session, { authorization: "Bearer OLD", projectId: "trusted" }); let requests = 0; const unsubscribe = credentialRefreshCoordinator.subscribe(session, (event) => { if (event.type === "credential_refresh_required") refreshSessionCredential(session, { authorization: "Bearer NEW", projectId: "trusted" }, event.requestId); }); try { globalThis.fetch = (async (_url: Parameters[0], init?: RequestInit) => { requests++; return new Headers(init?.headers).get("Authorization") === "Bearer OLD" ? new Response(null, { status: 401 }) : Response.json({ status: "0", location: { lon: 116, lat: 39 } }); }) as unknown as typeof fetch; await new ExternalInformationClient("http://server.test").geocode(session, { keyword: "北京" }); expect(requests).toBe(2); globalThis.fetch = (async () => { requests++; return new Response(null, { status: 403 }); }) as unknown as typeof fetch; await expect(new ExternalInformationClient("http://server.test").geocode(session, { keyword: "北京" })).rejects.toMatchObject({ status: 403 }); expect(requests).toBe(3); } finally { unsubscribe(); } }); it("rejects scope, URL overrides and empty keywords before dispatch", async () => { expect(geocodeInput.safeParse({ keyword: "北京", url: "http://untrusted" }).success).toBeFalse(); expect(geocodeInput.safeParse({ keyword: "北京", project_id: "other" }).success).toBeFalse(); expect(geocodeInput.safeParse({ keyword: " " }).success).toBeFalse(); await expect(new ExternalInformationClient().geocode(session, { keyword: "北京" })).rejects.toMatchObject({ status: 401 }); }); });