1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
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 }");
});
|