aboutsummaryrefslogtreecommitdiff
path: root/tests/apq.test.ts
diff options
context:
space:
mode:
Diffstat (limited to 'tests/apq.test.ts')
-rw-r--r--tests/apq.test.ts151
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 }");
+});