diff options
Diffstat (limited to 'tests/apq.test.ts')
| -rw-r--r-- | tests/apq.test.ts | 151 |
1 files changed, 151 insertions, 0 deletions
diff --git a/tests/apq.test.ts b/tests/apq.test.ts new file mode 100644 index 0000000..66607cd --- /dev/null +++ b/tests/apq.test.ts @@ -0,0 +1,151 @@ + + + + +/*** IMPORT ------------------------------------------- ***/ + +import { expect, test } from "vitest"; + +/*** UTILITY ------------------------------------------ ***/ + +import { createApqFetcher } from "../source/library/fetcher/apq.ts"; + +/*** HELPERS ------------------------------------------ ***/ + +type Call = { body: Record<string, unknown>; url: string }; + +function createStub(queue: Array<Record<string, unknown>>): { + 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<string, unknown>; + 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<string> { + 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, unknown>): string { + const extensions = body.extensions as Record<string, unknown>; + const persistedQuery = extensions.persistedQuery as Record<string, unknown>; + 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 }"); +}); |