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 = {} ) { 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); }); });