/*** IMPORT ------------------------------------------- ***/ import { expect, test } from "vitest"; /*** UTILITY ------------------------------------------ ***/ import { createApqFetcher } from "../source/library/fetcher/apq.ts"; /*** HELPERS ------------------------------------------ ***/ type Call = { body: Record; url: string }; function createStub(queue: Array>): { calls: Call[]; stub: typeof fetch; } { const calls: Call[] = []; const stub: typeof fetch = (input, init) => { const body = JSON.parse((init?.body as string) ?? "null") as Record; calls.push({ body, url: String(input) }); const next = queue.shift(); if (next === undefined) throw new Error("stub fetch exhausted"); return Promise.resolve(new Response(JSON.stringify(next), { status: 200 })); }; return { calls, stub }; } async function expectedHash(query: string): Promise { const buf = await globalThis.crypto.subtle.digest("SHA-256", new TextEncoder().encode(query)); return Array.from(new Uint8Array(buf)).map((b) => b.toString(16).padStart(2, "0")).join(""); } function readPersistedHash(body: Record): string { const extensions = body.extensions as Record; const persistedQuery = extensions.persistedQuery as Record; return persistedQuery.sha256Hash as string; } /*** TESTS -------------------------------------------- ***/ test("apq cache miss retries with the full query", async () => { const { calls, stub } = createStub([ { errors: [{ message: "PersistedQueryNotFound" }] }, { data: { hello: "world" } } ]); const fetcher = createApqFetcher({ fetch: stub, url: "https://example.test/graphql" }); const result = await fetcher({ query: "{ hello }" }); expect(result).toEqual({ data: { hello: "world" } }); expect(calls.length).toEqual(2); expect(calls[0].body.query).toEqual(undefined); expect(calls[1].body.query).toEqual("{ hello }"); }); test("apq cache hit sends a single request without the query", async () => { const { calls, stub } = createStub([{ data: { hello: "world" } }]); const fetcher = createApqFetcher({ fetch: stub, url: "https://example.test/graphql" }); const result = await fetcher({ query: "{ hello }" }); expect(result).toEqual({ data: { hello: "world" } }); expect(calls.length).toEqual(1); expect(calls[0].body.query).toEqual(undefined); expect(calls[0].body.extensions !== undefined).toBe(true); }); test("apq hashes are stable across calls with the same query", async () => { const { calls, stub } = createStub([ { data: { hello: "world" } }, { data: { hello: "world" } } ]); const fetcher = createApqFetcher({ fetch: stub, url: "https://example.test/graphql" }); await fetcher({ query: "{ hello }" }); await fetcher({ query: "{ hello }" }); expect(calls.length).toEqual(2); expect(readPersistedHash(calls[0].body)).toEqual(readPersistedHash(calls[1].body)); expect(readPersistedHash(calls[0].body)).toEqual(await expectedHash("{ hello }")); }); test("apq with disable sends the full query and the extension in one request", async () => { const { calls, stub } = createStub([{ data: { hello: "world" } }]); const fetcher = createApqFetcher({ disable: true, fetch: stub, url: "https://example.test/graphql" }); const result = await fetcher({ query: "{ hello }" }); expect(result).toEqual({ data: { hello: "world" } }); expect(calls.length).toEqual(1); expect(calls[0].body.query).toEqual("{ hello }"); expect(readPersistedHash(calls[0].body)).toEqual(await expectedHash("{ hello }")); }); test("apq hash is lowercase hex 64 chars", async () => { const { calls, stub } = createStub([{ data: { hello: "world" } }]); const fetcher = createApqFetcher({ fetch: stub, url: "https://example.test/graphql" }); await fetcher({ query: "{ hello }" }); expect(readPersistedHash(calls[0].body)).toMatch(/^[0-9a-f]{64}$/); }); test("apq accepts PERSISTED_QUERY_NOT_FOUND extension code", async () => { const { calls, stub } = createStub([ { errors: [{ extensions: { code: "PERSISTED_QUERY_NOT_FOUND" }, message: "nope" }] }, { data: { hello: "world" } } ]); const fetcher = createApqFetcher({ fetch: stub, url: "https://example.test/graphql" }); const result = await fetcher({ query: "{ hello }" }); expect(result).toEqual({ data: { hello: "world" } }); expect(calls.length).toEqual(2); expect(calls[1].body.query).toEqual("{ hello }"); });