Files
HiveGrid-Wireframes/hivegrid-cloud/scripts/cdn/bunny-purge.test.ts
T

155 lines
5.0 KiB
TypeScript

import { describe, expect, it, vi } from "vitest";
import { parsePurgeArgs, purgeBunnyCache, resolveAbsolutePurgeUrl } from "./bunny-purge";
describe("parsePurgeArgs", () => {
it("returns defaults for an empty argv", () => {
expect(parsePurgeArgs([])).toEqual({ paths: [], dryRun: false });
});
it("parses --dry-run", () => {
expect(parsePurgeArgs(["--dry-run"])).toEqual({ paths: [], dryRun: true });
});
it("parses a single --paths value", () => {
expect(parsePurgeArgs(["--paths", "/assets/app.js"])).toEqual({
paths: ["/assets/app.js"],
dryRun: false,
});
});
it("splits comma-separated --paths into multiple entries", () => {
expect(parsePurgeArgs(["--paths", "/a, /b,/c"])).toEqual({
paths: ["/a", "/b", "/c"],
dryRun: false,
});
});
it("accepts repeated --paths flags", () => {
expect(parsePurgeArgs(["--paths", "/a", "--paths", "/b"])).toEqual({
paths: ["/a", "/b"],
dryRun: false,
});
});
it("treats --path as an alias for --paths", () => {
expect(parsePurgeArgs(["--path", "/x"])).toEqual({ paths: ["/x"], dryRun: false });
});
it("throws on an unknown argument", () => {
expect(() => parsePurgeArgs(["--unknown"])).toThrow(/Unknown argument/);
});
it("throws when --paths is supplied without a value", () => {
expect(() => parsePurgeArgs(["--paths"])).toThrow(/expects a value/);
});
});
describe("resolveAbsolutePurgeUrl", () => {
it("returns the input unchanged when already absolute", () => {
expect(resolveAbsolutePurgeUrl("https://cdn.example.com/x.js")).toBe("https://cdn.example.com/x.js");
});
it("joins relative paths against the CDN base", () => {
expect(resolveAbsolutePurgeUrl("/assets/app.js", "https://cdn.example.com")).toBe(
"https://cdn.example.com/assets/app.js"
);
});
it("throws when relative paths are passed without a CDN base", () => {
expect(() => resolveAbsolutePurgeUrl("/assets/app.js")).toThrow(/relative/);
});
it("rejects empty paths", () => {
expect(() => resolveAbsolutePurgeUrl(" ")).toThrow(/Empty path/);
});
});
describe("purgeBunnyCache", () => {
function makeOkFetch() {
return vi.fn(async () => Promise.resolve({ ok: true, status: 200 } as Response));
}
it("calls the whole-zone purge endpoint by default", async () => {
const fetchImpl = makeOkFetch();
const result = await purgeBunnyCache({
pullZoneId: "12345",
apiKey: "secret",
fetchImpl,
});
expect(fetchImpl).toHaveBeenCalledOnce();
const [url, init] = fetchImpl.mock.calls[0];
expect(String(url)).toBe("https://api.bunny.net/pullzone/12345/purgeCache");
expect(init?.method).toBe("POST");
expect((init?.headers as Record<string, string>).AccessKey).toBe("secret");
expect(result).toEqual({
url: "https://api.bunny.net/pullzone/12345/purgeCache",
requests: 1,
ok: true,
failedStatus: undefined,
});
});
it("calls /purge?url=... once per path when paths are supplied", async () => {
const fetchImpl = makeOkFetch();
const result = await purgeBunnyCache({
pullZoneId: "12345",
apiKey: "secret",
paths: ["/assets/a.js", "/assets/b.js"],
cdnBaseUrl: "https://cdn.example.com",
fetchImpl,
});
expect(fetchImpl).toHaveBeenCalledTimes(2);
expect(String(fetchImpl.mock.calls[0][0])).toBe(
"https://api.bunny.net/purge?url=https%3A%2F%2Fcdn.example.com%2Fassets%2Fa.js"
);
expect(String(fetchImpl.mock.calls[1][0])).toBe(
"https://api.bunny.net/purge?url=https%3A%2F%2Fcdn.example.com%2Fassets%2Fb.js"
);
expect(result.ok).toBe(true);
expect(result.requests).toBe(2);
});
it("surfaces the first non-2xx status as a failure", async () => {
const fetchImpl = vi.fn(async () => Promise.resolve({ ok: false, status: 403 } as Response));
const result = await purgeBunnyCache({
pullZoneId: "12345",
apiKey: "secret",
fetchImpl,
});
expect(result.ok).toBe(false);
expect(result.failedStatus).toBe(403);
});
it("stops iterating paths after the first failure and reports the failed status", async () => {
let calls = 0;
const fetchImpl = vi.fn(async () => {
calls += 1;
if (calls === 2) return { ok: false, status: 502 } as Response;
return { ok: true, status: 200 } as Response;
});
const result = await purgeBunnyCache({
pullZoneId: "12345",
apiKey: "secret",
paths: ["/a", "/b", "/c"],
cdnBaseUrl: "https://cdn.example.com",
fetchImpl,
});
expect(result.ok).toBe(false);
expect(result.failedStatus).toBe(502);
expect(fetchImpl).toHaveBeenCalledTimes(2);
});
it("throws when pullZoneId is missing", async () => {
await expect(purgeBunnyCache({ pullZoneId: "", apiKey: "x", fetchImpl: makeOkFetch() })).rejects.toThrow(
/BUNNY_PULL_ZONE_ID/
);
});
it("throws when apiKey is missing", async () => {
await expect(purgeBunnyCache({ pullZoneId: "1", apiKey: "", fetchImpl: makeOkFetch() })).rejects.toThrow(
/BUNNY_API_KEY/
);
});
});