Files
suaveplan/packages/persistence/storage/storage-cdn-bunnycdn/src/bunny-cdn-adapter/bunny-cdn-adapter.coverage.test.ts
2026-05-27 23:47:53 +01:00

200 lines
6.5 KiB
TypeScript

import {
afterEach,
beforeEach,
describe,
expect,
it,
vi,
} from "@suaveplan/testing/runner";
import type { FetchLike } from "@suaveplan/types";
import {
STORAGE_CDN_BUNNYCDN_CODES,
StorageCdnBunnycdnError,
} from "../error-codes/error-codes.js";
import { setBunnyMeterProvider } from "../otel/otel.js";
import { BunnyCdnAdapter } from "./bunny-cdn-adapter.js";
function makeFetch(
...responses: Response[]
): FetchLike & { calls: { url: string; init: RequestInit | undefined }[] } {
let i = 0;
const calls: { url: string; init: RequestInit | undefined }[] = [];
const fn: FetchLike = async (input, init) => {
const url = typeof input === "string" ? input : input.toString();
calls.push({ url, init });
return responses[i++] ?? new Response(null, { status: 200 });
};
(fn as FetchLike & { calls: typeof calls }).calls = calls;
return fn as FetchLike & { calls: typeof calls };
}
function makeAdapter(
fetch: FetchLike,
overrides: Record<string, unknown> = {}
) {
return new BunnyCdnAdapter({
storageZone: "myz",
storageAccessKey: "ak",
pullZoneHostname: "mycdn.b-cdn.net",
storageRegion: "de",
tokenAuthKey: "secret",
purgeApiKey: "purgek",
fetch,
...overrides,
});
}
describe("BunnyCdnAdapter extended coverage", () => {
beforeEach(() => setBunnyMeterProvider(undefined));
afterEach(() => setBunnyMeterProvider(undefined));
it("accepts custom timers in retry path", { timeout: 5000 }, async () => {
const setSpy = vi.fn((handler: () => void, ms: number) =>
Number(globalThis.setTimeout(handler, ms))
);
const clearSpy = vi.fn((id: number) => globalThis.clearTimeout(id));
const fetchFn = makeFetch(
new Response(null, { status: 503 }),
new Response(new Uint8Array([1]), { status: 200 })
);
const storage = makeAdapter(fetchFn, {
retry: { maxAttempts: 2, baseDelayMs: 1, honourRetryAfter: false },
timers: { setTimer: setSpy, clearTimer: clearSpy },
});
const bytes = await storage.get("k");
expect(Array.from(bytes as Uint8Array)).toEqual([1]);
expect(setSpy).toHaveBeenCalled();
});
it("accepts originBaseUrl and edgeBaseUrl options", {
timeout: 5000,
}, () => {
const fetchFn = makeFetch();
const storage = new BunnyCdnAdapter({
storageZone: "myz",
storageAccessKey: "ak",
pullZoneHostname: "mycdn.b-cdn.net",
originBaseUrl: "https://example.com/",
edgeBaseUrl: "https://e.example.com/",
fetch: fetchFn,
});
expect(storage).toBeInstanceOf(BunnyCdnAdapter);
});
it("rejects originBaseUrl without trailing slash", {
timeout: 5000,
}, () => {
try {
new BunnyCdnAdapter({
storageZone: "myz",
storageAccessKey: "ak",
pullZoneHostname: "mycdn.b-cdn.net",
originBaseUrl: "https://example.com",
});
expect.fail("expected throw");
} catch (err) {
expect((err as StorageCdnBunnycdnError).code).toBe(
STORAGE_CDN_BUNNYCDN_CODES.INVALID_OPTIONS
);
}
});
it("threads signal into successful get fetch init", {
timeout: 5000,
}, async () => {
const fetchFn = makeFetch(
new Response(new Uint8Array([1]), { status: 200 })
);
const c = new AbortController();
await makeAdapter(fetchFn).get("k", c.signal);
expect(fetchFn.calls[0]?.init?.signal).toBe(c.signal);
});
it("rejects edgeBaseUrl without trailing slash", { timeout: 5000 }, () => {
try {
new BunnyCdnAdapter({
storageZone: "myz",
storageAccessKey: "ak",
pullZoneHostname: "mycdn.b-cdn.net",
edgeBaseUrl: "https://e.example.com",
});
expect.fail("expected throw");
} catch (err) {
expect((err as StorageCdnBunnycdnError).code).toBe(
STORAGE_CDN_BUNNYCDN_CODES.INVALID_OPTIONS
);
}
});
it("PUT emits writes_total with provider and storage_region attrs", {
timeout: 5000,
}, async () => {
const fetchFn = makeFetch(new Response(null, { status: 201 }));
await makeAdapter(fetchFn).set("k", new Uint8Array([1]));
expect(
fetchFn.calls.some(
(c) =>
c.init?.method === "PUT" &&
c.url.startsWith("https://storage.bunnycdn.com")
)
).toBe(true);
});
it("createReadStream errors mid-stream on non-ok", {
timeout: 5000,
}, async () => {
const fetchFn = makeFetch(new Response(null, { status: 500 }));
const reader = makeAdapter(fetchFn).createReadStream("k").getReader();
try {
await reader.read();
expect.fail("expected throw");
} catch (err) {
expect(err).toBeInstanceOf(StorageCdnBunnycdnError);
}
});
it("createReadStream closes when body is null", {
timeout: 5000,
}, async () => {
const fetchFn = makeFetch(new Response(null, { status: 200 }));
const reader = makeAdapter(fetchFn).createReadStream("k").getReader();
expect((await reader.read()).done).toBe(true);
});
it("default fetch resolves via globalThis.fetch when omitted", {
timeout: 5000,
}, async () => {
const spy = vi
.spyOn(globalThis, "fetch")
.mockResolvedValue(
new Response(new Uint8Array([1]), { status: 200 })
);
try {
const storage = new BunnyCdnAdapter({
storageZone: "myz",
storageAccessKey: "ak",
pullZoneHostname: "mycdn.b-cdn.net",
});
await storage.get("k");
expect(spy).toHaveBeenCalled();
} finally {
spy.mockRestore();
}
});
it("createBunnyCdnAdapter factory returns an instance", {
timeout: 5000,
}, async () => {
const { createBunnyCdnAdapter } = await import(
"./bunny-cdn-adapter.js"
);
const storage = createBunnyCdnAdapter({
storageZone: "myz",
storageAccessKey: "ak",
pullZoneHostname: "mycdn.b-cdn.net",
fetch: makeFetch(),
});
expect(storage).toBeInstanceOf(BunnyCdnAdapter);
});
});