orbit-studio / src /modules /util /gpSource.test.ts
moncefem's picture
Deploy Orbit Studio propagator
9f21d0a
Raw
History Blame Contribute Delete
3.82 kB
import { afterEach, describe, expect, test, vi } from "vitest";
import { fetchGpGroup, fetchGpIndex, resetGpSource } from "./gpSource";
type RouteMap = Record<string, () => Response>;
function json(body: unknown): () => Response {
return () => new Response(JSON.stringify(body), { headers: { "Content-Type": "application/json" } });
}
function installFetch(routes: RouteMap): string[] {
const requested: string[] = [];
vi.stubGlobal(
"fetch",
vi.fn(async (input: RequestInfo | URL) => {
const url = String(input);
requested.push(url);
const route = routes[url];
if (!route) {
return new Response("Not Found", { status: 404 });
}
return route();
}),
);
return requested;
}
const WORKER_ROUTES: RouteMap = {
"/api/groups.json": json({ updated: "", groups: [{ name: "weather", count: 2 }] }),
};
const STATIC_ROUTES: RouteMap = {
"data/gp/index.json": json({ updated: "", groups: [{ name: "weather", count: 1 }] }),
};
afterEach(() => {
resetGpSource();
vi.unstubAllGlobals();
});
describe("GpSource probe", () => {
test("uses the worker API when the probe answers with JSON", async () => {
const requested = installFetch({
...WORKER_ROUTES,
"/api/gp/weather.json": () => new Response("[]"),
});
expect(await fetchGpIndex()).toEqual([{ name: "weather", count: 2 }]);
await fetchGpGroup("weather");
expect(requested).toContain("/api/gp/weather.json");
});
test("falls back to the static snapshot when the probe fails", async () => {
const requested = installFetch({
...STATIC_ROUTES,
"data/gp/weather.json": () => new Response("[]"),
});
expect(await fetchGpIndex()).toEqual([{ name: "weather", count: 1 }]);
await fetchGpGroup("weather");
expect(requested).toContain("data/gp/weather.json");
expect(requested).not.toContain("/api/gp/weather.json");
});
test("treats an HTML probe answer (worker-less SPA fallthrough) as no worker", async () => {
installFetch({
"/api/groups.json": () => new Response("<!doctype html><html></html>", { status: 200, headers: { "Content-Type": "text/html" } }),
...STATIC_ROUTES,
});
expect(await fetchGpIndex()).toEqual([{ name: "weather", count: 1 }]);
});
test("probes only once per session", async () => {
const requested = installFetch(WORKER_ROUTES);
await fetchGpIndex();
await fetchGpGroup("weather").catch(() => undefined);
await fetchGpGroup("stations").catch(() => undefined);
expect(requested.filter((url) => url === "/api/groups.json")).toHaveLength(1);
});
});
describe("fetchGpGroup", () => {
test("retries a failed API group against the static snapshot", async () => {
installFetch({
...WORKER_ROUTES,
"/api/gp/weather.json": () => new Response("KV miss", { status: 404 }),
"data/gp/weather.json": () => new Response('[{"OBJECT_NAME":"METEO-1","NORAD_CAT_ID":1}]'),
});
const payload = await fetchGpGroup("weather");
expect(JSON.parse(payload)).toHaveLength(1);
});
test("passes explicit URL sources through without a fallback", async () => {
const requested = installFetch({
...WORKER_ROUTES,
"data/tle/custom.txt": () => new Response("CUSTOM\n1 ...\n2 ..."),
});
const payload = await fetchGpGroup("data/tle/custom.txt");
expect(payload).toContain("CUSTOM");
expect(requested).not.toContain("/api/gp/data/tle/custom.txt.json");
});
test("rethrows when both the API and the static snapshot fail", async () => {
installFetch({
...WORKER_ROUTES,
"/api/gp/weather.json": () => new Response("boom", { status: 500 }),
"data/gp/weather.json": () => new Response("missing", { status: 404 }),
});
await expect(fetchGpGroup("weather")).rejects.toThrow();
});
});