graphql-http 1.6.0 → 1.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/lib/audits/common.d.mts +58 -0
- package/lib/audits/index.d.mts +2 -0
- package/lib/audits/server.d.mts +39 -0
- package/lib/audits/server.js +232 -30
- package/lib/audits/server.mjs +233 -31
- package/lib/audits/utils.d.mts +38 -0
- package/lib/audits/utils.d.ts +11 -0
- package/lib/audits/utils.js +33 -1
- package/lib/audits/utils.mjs +31 -0
- package/lib/client.d.mts +155 -0
- package/lib/common.d.mts +31 -0
- package/lib/handler.d.mts +285 -0
- package/lib/handler.mjs +1 -1
- package/lib/index.d.mts +4 -0
- package/lib/index.mjs +1 -1
- package/lib/utils.d.mts +16 -0
- package/package.json +5 -3
- package/umd/graphql-http.min.js.gz +0 -0
package/lib/audits/server.mjs
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* audit/server
|
|
4
4
|
*
|
|
5
5
|
*/
|
|
6
|
-
import { assert, audit, extendedTypeof } from './utils.mjs';
|
|
6
|
+
import { assert, assertBodyAsExecutionResult, audit, extendedTypeof, } from './utils.mjs';
|
|
7
7
|
/**
|
|
8
8
|
* List of server audits required to check GraphQL over HTTP spec conformance.
|
|
9
9
|
*
|
|
@@ -13,7 +13,9 @@ export function serverAudits(opts) {
|
|
|
13
13
|
const fetchFn = (opts.fetchFn || fetch);
|
|
14
14
|
return [
|
|
15
15
|
// Media Types
|
|
16
|
-
audit(
|
|
16
|
+
audit(
|
|
17
|
+
// TODO: convert to MUST after watershed
|
|
18
|
+
'SHOULD accept application/graphql-response+json and match the content-type', async () => {
|
|
17
19
|
const url = new URL(opts.url);
|
|
18
20
|
url.searchParams.set('query', '{ __typename }');
|
|
19
21
|
const res = await fetchFn(url.toString(), {
|
|
@@ -35,7 +37,9 @@ export function serverAudits(opts) {
|
|
|
35
37
|
assert('Status code', res.status).toBe(200);
|
|
36
38
|
assert('Content-Type header', res.headers.get('content-type')).toContain('application/json');
|
|
37
39
|
}),
|
|
38
|
-
audit(
|
|
40
|
+
audit(
|
|
41
|
+
// TODO: convert to MUST after watershed
|
|
42
|
+
'SHOULD accept */* and use application/graphql-response+json for the content-type', async () => {
|
|
39
43
|
const url = new URL(opts.url);
|
|
40
44
|
url.searchParams.set('query', '{ __typename }');
|
|
41
45
|
const res = await fetchFn(url.toString(), {
|
|
@@ -46,19 +50,39 @@ export function serverAudits(opts) {
|
|
|
46
50
|
assert('Status code', res.status).toBe(200);
|
|
47
51
|
assert('Content-Type header', res.headers.get('content-type')).toContain('application/graphql-response+json');
|
|
48
52
|
}),
|
|
49
|
-
audit(
|
|
53
|
+
audit(
|
|
54
|
+
// TODO: convert to MUST after watershed
|
|
55
|
+
'SHOULD assume application/graphql-response+json content-type when accept is missing', async () => {
|
|
50
56
|
const url = new URL(opts.url);
|
|
51
57
|
url.searchParams.set('query', '{ __typename }');
|
|
52
58
|
const res = await fetchFn(url.toString());
|
|
53
59
|
assert('Status code', res.status).toBe(200);
|
|
54
60
|
assert('Content-Type header', res.headers.get('content-type')).toContain('application/graphql-response+json');
|
|
55
61
|
}),
|
|
56
|
-
audit('MUST use utf-8
|
|
62
|
+
audit('MUST use utf-8 encoding when responding', async () => {
|
|
57
63
|
const url = new URL(opts.url);
|
|
58
64
|
url.searchParams.set('query', '{ __typename }');
|
|
59
65
|
const res = await fetchFn(url.toString());
|
|
60
66
|
assert('Status code', res.status).toBe(200);
|
|
61
|
-
|
|
67
|
+
// has charset set to utf-8
|
|
68
|
+
try {
|
|
69
|
+
assert('Content-Type header', res.headers.get('content-type')).toContain('charset=utf-8');
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
catch (_a) {
|
|
73
|
+
// noop, continue
|
|
74
|
+
}
|
|
75
|
+
// has no charset specified
|
|
76
|
+
assert('Content-Type header', res.headers.get('content-type')).notToContain('charset');
|
|
77
|
+
// and the content is utf-8 encoded
|
|
78
|
+
try {
|
|
79
|
+
const decoder = new TextDecoder('utf-8');
|
|
80
|
+
const decoded = decoder.decode(await res.arrayBuffer());
|
|
81
|
+
assert('UTF-8 decoded body', decoded).toBe('{"data":{"__typename":"Query"}}');
|
|
82
|
+
}
|
|
83
|
+
catch (_b) {
|
|
84
|
+
throw 'Body is not UTF-8 encoded';
|
|
85
|
+
}
|
|
62
86
|
}),
|
|
63
87
|
audit('MUST accept only utf-8 charset', async () => {
|
|
64
88
|
const url = new URL(opts.url);
|
|
@@ -68,8 +92,16 @@ export function serverAudits(opts) {
|
|
|
68
92
|
accept: 'application/graphql-response+json; charset=iso-8859-1',
|
|
69
93
|
},
|
|
70
94
|
});
|
|
71
|
-
|
|
72
|
-
|
|
95
|
+
// application/json is 200 + errors
|
|
96
|
+
const contentType = res.headers.get('content-type') || '';
|
|
97
|
+
if (contentType.includes('application/json')) {
|
|
98
|
+
assert(`Content-Type ${contentType} status code`, res.status).toBe(200);
|
|
99
|
+
assert('Execution result', await assertBodyAsExecutionResult(res)).toHaveProperty('errors');
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
// other content-types must be 4xx
|
|
103
|
+
assert(`Content-Type ${contentType} status code`, res.status).toBeGreaterThanOrEqual(400);
|
|
104
|
+
assert(`Content-Type ${contentType} status code`, res.status).toBeLessThanOrEqual(499);
|
|
73
105
|
}),
|
|
74
106
|
// Request
|
|
75
107
|
audit('MUST accept POST requests', async () => {
|
|
@@ -103,7 +135,7 @@ export function serverAudits(opts) {
|
|
|
103
135
|
method: 'POST',
|
|
104
136
|
});
|
|
105
137
|
assert('Status code', res.status).toBeGreaterThanOrEqual(400);
|
|
106
|
-
assert('Status code', res.status).toBeLessThanOrEqual(
|
|
138
|
+
assert('Status code', res.status).toBeLessThanOrEqual(499);
|
|
107
139
|
}),
|
|
108
140
|
audit('MUST accept application/json POST requests', async () => {
|
|
109
141
|
const res = await fetchFn(opts.url, {
|
|
@@ -121,7 +153,9 @@ export function serverAudits(opts) {
|
|
|
121
153
|
assert('Status code', res.status).toBe(400);
|
|
122
154
|
}),
|
|
123
155
|
// Request Parameters
|
|
124
|
-
audit(
|
|
156
|
+
audit(
|
|
157
|
+
// TODO: convert to MUST after watershed
|
|
158
|
+
'SHOULD use 400 status code on missing {query} parameter when accepting application/graphql-response+json', async () => {
|
|
125
159
|
const res = await fetchFn(opts.url, {
|
|
126
160
|
method: 'POST',
|
|
127
161
|
headers: {
|
|
@@ -132,7 +166,21 @@ export function serverAudits(opts) {
|
|
|
132
166
|
});
|
|
133
167
|
assert('Status code', res.status).toBe(400);
|
|
134
168
|
}),
|
|
135
|
-
|
|
169
|
+
audit('SHOULD use 200 status code with errors field on missing {query} parameter when accepting application/json', async () => {
|
|
170
|
+
const res = await fetchFn(opts.url, {
|
|
171
|
+
method: 'POST',
|
|
172
|
+
headers: {
|
|
173
|
+
'content-type': 'application/json',
|
|
174
|
+
accept: 'application/json',
|
|
175
|
+
},
|
|
176
|
+
body: JSON.stringify({ notquery: '{ __typename }' }),
|
|
177
|
+
});
|
|
178
|
+
assert('Status code', res.status).toBe(200);
|
|
179
|
+
assert('Execution result', await assertBodyAsExecutionResult(res)).toHaveProperty('errors');
|
|
180
|
+
}),
|
|
181
|
+
...[{ obj: 'ect' }, 0, false, ['array']].map((invalid) => audit(
|
|
182
|
+
// TODO: convert to MUST after watershed
|
|
183
|
+
`SHOULD use 400 status code on ${extendedTypeof(invalid)} {query} parameter when accepting application/graphql-response+json`, async () => {
|
|
136
184
|
const res = await fetchFn(opts.url, {
|
|
137
185
|
method: 'POST',
|
|
138
186
|
headers: {
|
|
@@ -145,7 +193,23 @@ export function serverAudits(opts) {
|
|
|
145
193
|
});
|
|
146
194
|
assert('Status code', res.status).toBe(400);
|
|
147
195
|
})),
|
|
148
|
-
audit(
|
|
196
|
+
...[{ obj: 'ect' }, 0, false, ['array']].map((invalid) => audit(`SHOULD use 200 status code with errors field on ${extendedTypeof(invalid)} {query} parameter when accepting application/json`, async () => {
|
|
197
|
+
const res = await fetchFn(opts.url, {
|
|
198
|
+
method: 'POST',
|
|
199
|
+
headers: {
|
|
200
|
+
'content-type': 'application/json',
|
|
201
|
+
accept: 'application/json',
|
|
202
|
+
},
|
|
203
|
+
body: JSON.stringify({
|
|
204
|
+
query: invalid,
|
|
205
|
+
}),
|
|
206
|
+
});
|
|
207
|
+
assert('Status code', res.status).toBe(200);
|
|
208
|
+
assert('Execution result', await assertBodyAsExecutionResult(res)).toHaveProperty('errors');
|
|
209
|
+
})),
|
|
210
|
+
audit(
|
|
211
|
+
// TODO: convert to MUST after watershed
|
|
212
|
+
'SHOULD allow string {query} parameter when accepting application/graphql-response+json', async () => {
|
|
149
213
|
const res = await fetchFn(opts.url, {
|
|
150
214
|
method: 'POST',
|
|
151
215
|
headers: {
|
|
@@ -158,7 +222,23 @@ export function serverAudits(opts) {
|
|
|
158
222
|
});
|
|
159
223
|
assert('Status code', res.status).toBe(200);
|
|
160
224
|
}),
|
|
161
|
-
|
|
225
|
+
audit('MUST allow string {query} parameter when accepting application/json', async () => {
|
|
226
|
+
const res = await fetchFn(opts.url, {
|
|
227
|
+
method: 'POST',
|
|
228
|
+
headers: {
|
|
229
|
+
'content-type': 'application/json',
|
|
230
|
+
accept: 'application/json',
|
|
231
|
+
},
|
|
232
|
+
body: JSON.stringify({
|
|
233
|
+
query: '{ __typename }',
|
|
234
|
+
}),
|
|
235
|
+
});
|
|
236
|
+
assert('Status code', res.status).toBe(200);
|
|
237
|
+
assert('Execution result', await assertBodyAsExecutionResult(res)).notToHaveProperty('errors');
|
|
238
|
+
}),
|
|
239
|
+
...[{ obj: 'ect' }, 0, false, ['array']].map((invalid) => audit(
|
|
240
|
+
// TODO: convert to MUST after watershed
|
|
241
|
+
`SHOULD use 400 status code on ${extendedTypeof(invalid)} {operationName} parameter when accepting application/graphql-response+json`, async () => {
|
|
162
242
|
const res = await fetchFn(opts.url, {
|
|
163
243
|
method: 'POST',
|
|
164
244
|
headers: {
|
|
@@ -172,7 +252,24 @@ export function serverAudits(opts) {
|
|
|
172
252
|
});
|
|
173
253
|
assert('Status code', res.status).toBe(400);
|
|
174
254
|
})),
|
|
175
|
-
audit(
|
|
255
|
+
...[{ obj: 'ect' }, 0, false, ['array']].map((invalid) => audit(`SHOULD use 200 status code with errors field on ${extendedTypeof(invalid)} {operationName} parameter when accepting application/json`, async () => {
|
|
256
|
+
const res = await fetchFn(opts.url, {
|
|
257
|
+
method: 'POST',
|
|
258
|
+
headers: {
|
|
259
|
+
'content-type': 'application/json',
|
|
260
|
+
accept: 'application/json',
|
|
261
|
+
},
|
|
262
|
+
body: JSON.stringify({
|
|
263
|
+
operationName: invalid,
|
|
264
|
+
query: '{ __typename }',
|
|
265
|
+
}),
|
|
266
|
+
});
|
|
267
|
+
assert('Status code', res.status).toBe(200);
|
|
268
|
+
assert('Execution result', await assertBodyAsExecutionResult(res)).toHaveProperty('errors');
|
|
269
|
+
})),
|
|
270
|
+
audit(
|
|
271
|
+
// TODO: convert to MUST after watershed
|
|
272
|
+
'SHOULD allow string {operationName} parameter when accepting application/graphql-response+json', async () => {
|
|
176
273
|
const res = await fetchFn(opts.url, {
|
|
177
274
|
method: 'POST',
|
|
178
275
|
headers: {
|
|
@@ -186,7 +283,24 @@ export function serverAudits(opts) {
|
|
|
186
283
|
});
|
|
187
284
|
assert('Status code', res.status).toBe(200);
|
|
188
285
|
}),
|
|
189
|
-
|
|
286
|
+
audit('MUST allow string {operationName} parameter when accepting application/json', async () => {
|
|
287
|
+
const res = await fetchFn(opts.url, {
|
|
288
|
+
method: 'POST',
|
|
289
|
+
headers: {
|
|
290
|
+
'content-type': 'application/json',
|
|
291
|
+
accept: 'application/json',
|
|
292
|
+
},
|
|
293
|
+
body: JSON.stringify({
|
|
294
|
+
operationName: 'Query',
|
|
295
|
+
query: 'query Query { __typename }',
|
|
296
|
+
}),
|
|
297
|
+
});
|
|
298
|
+
assert('Status code', res.status).toBe(200);
|
|
299
|
+
assert('Execution result', await assertBodyAsExecutionResult(res)).notToHaveProperty('errors');
|
|
300
|
+
}),
|
|
301
|
+
...['string', 0, false, ['array']].map((invalid) => audit(
|
|
302
|
+
// TODO: convert to MUST after watershed
|
|
303
|
+
`SHOULD use 400 status code on ${extendedTypeof(invalid)} {variables} parameter when accepting application/graphql-response+json`, async () => {
|
|
190
304
|
const res = await fetchFn(opts.url, {
|
|
191
305
|
method: 'POST',
|
|
192
306
|
headers: {
|
|
@@ -200,7 +314,24 @@ export function serverAudits(opts) {
|
|
|
200
314
|
});
|
|
201
315
|
assert('Status code', res.status).toBe(400);
|
|
202
316
|
})),
|
|
203
|
-
|
|
317
|
+
...['string', 0, false, ['array']].map((invalid) => audit(`SHOULD use 200 status code with errors field on ${extendedTypeof(invalid)} {variables} parameter when accepting application/json`, async () => {
|
|
318
|
+
const res = await fetchFn(opts.url, {
|
|
319
|
+
method: 'POST',
|
|
320
|
+
headers: {
|
|
321
|
+
'content-type': 'application/json',
|
|
322
|
+
accept: 'application/json',
|
|
323
|
+
},
|
|
324
|
+
body: JSON.stringify({
|
|
325
|
+
query: '{ __typename }',
|
|
326
|
+
variables: invalid,
|
|
327
|
+
}),
|
|
328
|
+
});
|
|
329
|
+
assert('Status code', res.status).toBe(200);
|
|
330
|
+
assert('Execution result', await assertBodyAsExecutionResult(res)).toHaveProperty('errors');
|
|
331
|
+
})),
|
|
332
|
+
audit(
|
|
333
|
+
// TODO: convert to MUST after watershed
|
|
334
|
+
'SHOULD allow map {variables} parameter when accepting application/graphql-response+json', async () => {
|
|
204
335
|
const res = await fetchFn(opts.url, {
|
|
205
336
|
method: 'POST',
|
|
206
337
|
headers: {
|
|
@@ -213,21 +344,52 @@ export function serverAudits(opts) {
|
|
|
213
344
|
}),
|
|
214
345
|
});
|
|
215
346
|
assert('Status code', res.status).toBe(200);
|
|
216
|
-
const result = (await res.json());
|
|
217
|
-
assert('Execution result', result).notToHaveProperty('errors');
|
|
218
347
|
}),
|
|
219
|
-
audit('MUST
|
|
348
|
+
audit('MUST allow map {variables} parameter when accepting application/json', async () => {
|
|
349
|
+
const res = await fetchFn(opts.url, {
|
|
350
|
+
method: 'POST',
|
|
351
|
+
headers: {
|
|
352
|
+
'content-type': 'application/json',
|
|
353
|
+
accept: 'application/json',
|
|
354
|
+
},
|
|
355
|
+
body: JSON.stringify({
|
|
356
|
+
query: 'query Type($name: String!) { __type(name: $name) { name } }',
|
|
357
|
+
variables: { name: 'sometype' },
|
|
358
|
+
}),
|
|
359
|
+
});
|
|
360
|
+
assert('Status code', res.status).toBe(200);
|
|
361
|
+
assert('Execution result', await assertBodyAsExecutionResult(res)).notToHaveProperty('errors');
|
|
362
|
+
}),
|
|
363
|
+
audit(
|
|
364
|
+
// TODO: convert to MUST after watershed
|
|
365
|
+
'SHOULD allow URL-encoded JSON string {variables} parameter in GETs when accepting application/graphql-response+json', async () => {
|
|
220
366
|
const url = new URL(opts.url);
|
|
221
367
|
url.searchParams.set('query', 'query Type($name: String!) { __type(name: $name) { name } }');
|
|
222
368
|
url.searchParams.set('variables', JSON.stringify({ name: 'sometype' }));
|
|
223
369
|
const res = await fetchFn(url.toString(), {
|
|
224
370
|
method: 'GET',
|
|
371
|
+
headers: {
|
|
372
|
+
accept: 'application/graphql-response+json',
|
|
373
|
+
},
|
|
225
374
|
});
|
|
226
375
|
assert('Status code', res.status).toBe(200);
|
|
227
|
-
const result = (await res.json());
|
|
228
|
-
assert('Execution result', result).notToHaveProperty('errors');
|
|
229
376
|
}),
|
|
230
|
-
|
|
377
|
+
audit('MUST allow URL-encoded JSON string {variables} parameter in GETs when accepting application/json', async () => {
|
|
378
|
+
const url = new URL(opts.url);
|
|
379
|
+
url.searchParams.set('query', 'query Type($name: String!) { __type(name: $name) { name } }');
|
|
380
|
+
url.searchParams.set('variables', JSON.stringify({ name: 'sometype' }));
|
|
381
|
+
const res = await fetchFn(url.toString(), {
|
|
382
|
+
method: 'GET',
|
|
383
|
+
headers: {
|
|
384
|
+
accept: 'application/json',
|
|
385
|
+
},
|
|
386
|
+
});
|
|
387
|
+
assert('Status code', res.status).toBe(200);
|
|
388
|
+
assert('Execution result', await assertBodyAsExecutionResult(res)).notToHaveProperty('errors');
|
|
389
|
+
}),
|
|
390
|
+
...['string', 0, false, ['array']].map((invalid) => audit(
|
|
391
|
+
// TODO: convert to MUST after watershed
|
|
392
|
+
`SHOULD use 400 status code on ${extendedTypeof(invalid)} {extensions} parameter when accepting application/graphql-response+json`, async () => {
|
|
231
393
|
const res = await fetchFn(opts.url, {
|
|
232
394
|
method: 'POST',
|
|
233
395
|
headers: {
|
|
@@ -241,7 +403,24 @@ export function serverAudits(opts) {
|
|
|
241
403
|
});
|
|
242
404
|
assert('Status code', res.status).toBe(400);
|
|
243
405
|
})),
|
|
244
|
-
|
|
406
|
+
...['string', 0, false, ['array']].map((invalid) => audit(`SHOULD use 200 status code with errors field on ${extendedTypeof(invalid)} {extensions} parameter when accepting application/json`, async () => {
|
|
407
|
+
const res = await fetchFn(opts.url, {
|
|
408
|
+
method: 'POST',
|
|
409
|
+
headers: {
|
|
410
|
+
'content-type': 'application/json',
|
|
411
|
+
accept: 'application/json',
|
|
412
|
+
},
|
|
413
|
+
body: JSON.stringify({
|
|
414
|
+
query: '{ __typename }',
|
|
415
|
+
extensions: invalid,
|
|
416
|
+
}),
|
|
417
|
+
});
|
|
418
|
+
assert('Status code', res.status).toBe(200);
|
|
419
|
+
assert('Execution result', await assertBodyAsExecutionResult(res)).toHaveProperty('errors');
|
|
420
|
+
})),
|
|
421
|
+
audit(
|
|
422
|
+
// TODO: convert to MUST after watershed
|
|
423
|
+
'SHOULD allow map {extensions} parameter when accepting application/graphql-response+json', async () => {
|
|
245
424
|
const res = await fetchFn(opts.url, {
|
|
246
425
|
method: 'POST',
|
|
247
426
|
headers: {
|
|
@@ -255,6 +434,21 @@ export function serverAudits(opts) {
|
|
|
255
434
|
});
|
|
256
435
|
assert('Status code', res.status).toBe(200);
|
|
257
436
|
}),
|
|
437
|
+
audit('MUST allow map {extensions} parameter when accepting application/json', async () => {
|
|
438
|
+
const res = await fetchFn(opts.url, {
|
|
439
|
+
method: 'POST',
|
|
440
|
+
headers: {
|
|
441
|
+
'content-type': 'application/json',
|
|
442
|
+
accept: 'application/json',
|
|
443
|
+
},
|
|
444
|
+
body: JSON.stringify({
|
|
445
|
+
query: '{ __typename }',
|
|
446
|
+
extensions: { some: 'value' },
|
|
447
|
+
}),
|
|
448
|
+
});
|
|
449
|
+
assert('Status code', res.status).toBe(200);
|
|
450
|
+
assert('Execution result', await assertBodyAsExecutionResult(res)).notToHaveProperty('errors');
|
|
451
|
+
}),
|
|
258
452
|
// TODO: audit('MUST accept a map for the {extensions} parameter'),
|
|
259
453
|
// Response application/json
|
|
260
454
|
audit('SHOULD use 200 status code on JSON parsing failure when accepting application/json', async () => {
|
|
@@ -296,7 +490,9 @@ export function serverAudits(opts) {
|
|
|
296
490
|
assert('Status code', res.status).toBe(200);
|
|
297
491
|
}),
|
|
298
492
|
// Response application/graphql-response+json
|
|
299
|
-
audit(
|
|
493
|
+
audit(
|
|
494
|
+
// TODO: convert to MUST after watershed
|
|
495
|
+
'SHOULD use 4xx or 5xx status codes on JSON parsing failure when accepting application/graphql-response+json', async () => {
|
|
300
496
|
const res = await fetchFn(opts.url, {
|
|
301
497
|
method: 'POST',
|
|
302
498
|
headers: {
|
|
@@ -328,9 +524,11 @@ export function serverAudits(opts) {
|
|
|
328
524
|
},
|
|
329
525
|
body: '{ "not a JSON',
|
|
330
526
|
});
|
|
331
|
-
assert('Data entry', (await res
|
|
527
|
+
assert('Data entry', (await assertBodyAsExecutionResult(res)).data).toBe(undefined);
|
|
332
528
|
}),
|
|
333
|
-
audit(
|
|
529
|
+
audit(
|
|
530
|
+
// TODO: convert to MUST after watershed
|
|
531
|
+
'SHOULD use 4xx or 5xx status codes if parameters are invalid when accepting application/graphql-response+json', async () => {
|
|
334
532
|
const url = new URL(opts.url);
|
|
335
533
|
url.searchParams.set('qeury' /* typo */, '{ __typename }');
|
|
336
534
|
const res = await fetchFn(url.toString(), {
|
|
@@ -356,9 +554,11 @@ export function serverAudits(opts) {
|
|
|
356
554
|
method: 'GET',
|
|
357
555
|
headers: { accept: 'application/graphql-response+json' },
|
|
358
556
|
});
|
|
359
|
-
assert('Data entry', (await res
|
|
557
|
+
assert('Data entry', (await assertBodyAsExecutionResult(res)).data).toBe(undefined);
|
|
360
558
|
}),
|
|
361
|
-
audit(
|
|
559
|
+
audit(
|
|
560
|
+
// TODO: convert to MUST after watershed
|
|
561
|
+
'SHOULD use 4xx or 5xx status codes on document parsing failure when accepting application/graphql-response+json', async () => {
|
|
362
562
|
const url = new URL(opts.url);
|
|
363
563
|
url.searchParams.set('query', '{');
|
|
364
564
|
const res = await fetchFn(url.toString(), {
|
|
@@ -384,9 +584,11 @@ export function serverAudits(opts) {
|
|
|
384
584
|
method: 'GET',
|
|
385
585
|
headers: { accept: 'application/graphql-response+json' },
|
|
386
586
|
});
|
|
387
|
-
assert('Data entry', (await res
|
|
587
|
+
assert('Data entry', (await assertBodyAsExecutionResult(res)).data).toBe(undefined);
|
|
388
588
|
}),
|
|
389
|
-
audit(
|
|
589
|
+
audit(
|
|
590
|
+
// TODO: convert to MUST after watershed
|
|
591
|
+
'SHOULD use 4xx or 5xx status codes on document validation failure when accepting application/graphql-response+json', async () => {
|
|
390
592
|
const url = new URL(opts.url);
|
|
391
593
|
url.searchParams.set('query', '{ 8f31403dfe404bccbb0e835f2629c6a7 }'); // making sure the field doesnt exist
|
|
392
594
|
const res = await fetchFn(url.toString(), {
|
|
@@ -412,7 +614,7 @@ export function serverAudits(opts) {
|
|
|
412
614
|
method: 'GET',
|
|
413
615
|
headers: { accept: 'application/graphql-response+json' },
|
|
414
616
|
});
|
|
415
|
-
assert('Data entry', (await res
|
|
617
|
+
assert('Data entry', (await assertBodyAsExecutionResult(res)).data).toBe(undefined);
|
|
416
618
|
}),
|
|
417
619
|
// TODO: how to fail and have the data entry?
|
|
418
620
|
// audit('MUST use 2xx status code if response contains the data entry and it is not null when accepting application/graphql-response+json'),
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* audit/utils
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
import { ExecutionResult } from 'graphql';
|
|
7
|
+
import { Audit, AuditName } from './common';
|
|
8
|
+
export * from '../utils';
|
|
9
|
+
/**
|
|
10
|
+
* Wrap and prepare an audit for testing.
|
|
11
|
+
*
|
|
12
|
+
* @private
|
|
13
|
+
*/
|
|
14
|
+
export declare function audit(name: AuditName, fn: () => Promise<void>): Audit;
|
|
15
|
+
/**
|
|
16
|
+
* Will throw a string if the assertion fails.
|
|
17
|
+
*
|
|
18
|
+
* All fatal problems will throw an instance of Error.
|
|
19
|
+
*
|
|
20
|
+
* @private
|
|
21
|
+
*/
|
|
22
|
+
export declare function assert<T = unknown>(name: string, actual: T): {
|
|
23
|
+
toBe: (expected: T) => void;
|
|
24
|
+
toBeLessThanOrEqual: (expected: T extends number ? T : never) => void;
|
|
25
|
+
toBeGreaterThanOrEqual: (expected: T extends number ? T : never) => void;
|
|
26
|
+
toContain: (expected: T extends (infer U)[] ? U : T extends string ? T : never) => void;
|
|
27
|
+
notToContain: (expected: T extends (infer U_1)[] ? U_1 : T extends string ? T : never) => void;
|
|
28
|
+
toHaveProperty: (prop: T extends Record<any, any> ? PropertyKey : never) => void;
|
|
29
|
+
notToHaveProperty: (prop: T extends Record<any, any> ? PropertyKey : never) => void;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Parses the string as JSON and safely reports parsing issues for audits.
|
|
33
|
+
*
|
|
34
|
+
* Assumes the parsed JSON will be an `ExecutionResult`.
|
|
35
|
+
*
|
|
36
|
+
* @private
|
|
37
|
+
* */
|
|
38
|
+
export declare function assertBodyAsExecutionResult(res: Response): Promise<ExecutionResult<import("graphql/jsutils/ObjMap").ObjMap<unknown>, import("graphql/jsutils/ObjMap").ObjMap<unknown>>>;
|
package/lib/audits/utils.d.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* audit/utils
|
|
4
4
|
*
|
|
5
5
|
*/
|
|
6
|
+
import { ExecutionResult } from 'graphql';
|
|
6
7
|
import { Audit, AuditName } from './common';
|
|
7
8
|
export * from '../utils';
|
|
8
9
|
/**
|
|
@@ -23,5 +24,15 @@ export declare function assert<T = unknown>(name: string, actual: T): {
|
|
|
23
24
|
toBeLessThanOrEqual: (expected: T extends number ? T : never) => void;
|
|
24
25
|
toBeGreaterThanOrEqual: (expected: T extends number ? T : never) => void;
|
|
25
26
|
toContain: (expected: T extends (infer U)[] ? U : T extends string ? T : never) => void;
|
|
27
|
+
notToContain: (expected: T extends (infer U_1)[] ? U_1 : T extends string ? T : never) => void;
|
|
28
|
+
toHaveProperty: (prop: T extends Record<any, any> ? PropertyKey : never) => void;
|
|
26
29
|
notToHaveProperty: (prop: T extends Record<any, any> ? PropertyKey : never) => void;
|
|
27
30
|
};
|
|
31
|
+
/**
|
|
32
|
+
* Parses the string as JSON and safely reports parsing issues for audits.
|
|
33
|
+
*
|
|
34
|
+
* Assumes the parsed JSON will be an `ExecutionResult`.
|
|
35
|
+
*
|
|
36
|
+
* @private
|
|
37
|
+
* */
|
|
38
|
+
export declare function assertBodyAsExecutionResult(res: Response): Promise<ExecutionResult<import("graphql/jsutils/ObjMap").ObjMap<unknown>, import("graphql/jsutils/ObjMap").ObjMap<unknown>>>;
|
package/lib/audits/utils.js
CHANGED
|
@@ -19,7 +19,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
19
19
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
20
20
|
};
|
|
21
21
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
-
exports.assert = exports.audit = void 0;
|
|
22
|
+
exports.assertBodyAsExecutionResult = exports.assert = exports.audit = void 0;
|
|
23
23
|
__exportStar(require("../utils"), exports);
|
|
24
24
|
/**
|
|
25
25
|
* Wrap and prepare an audit for testing.
|
|
@@ -87,6 +87,21 @@ function assert(name, actual) {
|
|
|
87
87
|
throw `${name} ${JSON.stringify(actual)} does not contain ${JSON.stringify(expected)}`;
|
|
88
88
|
}
|
|
89
89
|
},
|
|
90
|
+
notToContain: (expected) => {
|
|
91
|
+
// @ts-expect-error types will match, otherwise never
|
|
92
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
93
|
+
if (actual.includes(expected)) {
|
|
94
|
+
throw `${name} ${JSON.stringify(actual)} contains ${JSON.stringify(expected)}`;
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
toHaveProperty: (
|
|
98
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
99
|
+
prop) => {
|
|
100
|
+
// @ts-expect-error types will match, otherwise never
|
|
101
|
+
if (!(prop in actual)) {
|
|
102
|
+
throw `${name} ${JSON.stringify(actual)} does not have a property '${String(prop)}'`;
|
|
103
|
+
}
|
|
104
|
+
},
|
|
90
105
|
notToHaveProperty: (
|
|
91
106
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
92
107
|
prop) => {
|
|
@@ -98,3 +113,20 @@ function assert(name, actual) {
|
|
|
98
113
|
};
|
|
99
114
|
}
|
|
100
115
|
exports.assert = assert;
|
|
116
|
+
/**
|
|
117
|
+
* Parses the string as JSON and safely reports parsing issues for audits.
|
|
118
|
+
*
|
|
119
|
+
* Assumes the parsed JSON will be an `ExecutionResult`.
|
|
120
|
+
*
|
|
121
|
+
* @private
|
|
122
|
+
* */
|
|
123
|
+
async function assertBodyAsExecutionResult(res) {
|
|
124
|
+
const str = await res.text();
|
|
125
|
+
try {
|
|
126
|
+
return JSON.parse(str);
|
|
127
|
+
}
|
|
128
|
+
catch (err) {
|
|
129
|
+
throw `Response body is not valid JSON. Got ${JSON.stringify(str)}`;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
exports.assertBodyAsExecutionResult = assertBodyAsExecutionResult;
|
package/lib/audits/utils.mjs
CHANGED
|
@@ -69,6 +69,21 @@ export function assert(name, actual) {
|
|
|
69
69
|
throw `${name} ${JSON.stringify(actual)} does not contain ${JSON.stringify(expected)}`;
|
|
70
70
|
}
|
|
71
71
|
},
|
|
72
|
+
notToContain: (expected) => {
|
|
73
|
+
// @ts-expect-error types will match, otherwise never
|
|
74
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
75
|
+
if (actual.includes(expected)) {
|
|
76
|
+
throw `${name} ${JSON.stringify(actual)} contains ${JSON.stringify(expected)}`;
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
toHaveProperty: (
|
|
80
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
81
|
+
prop) => {
|
|
82
|
+
// @ts-expect-error types will match, otherwise never
|
|
83
|
+
if (!(prop in actual)) {
|
|
84
|
+
throw `${name} ${JSON.stringify(actual)} does not have a property '${String(prop)}'`;
|
|
85
|
+
}
|
|
86
|
+
},
|
|
72
87
|
notToHaveProperty: (
|
|
73
88
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
74
89
|
prop) => {
|
|
@@ -79,3 +94,19 @@ export function assert(name, actual) {
|
|
|
79
94
|
},
|
|
80
95
|
};
|
|
81
96
|
}
|
|
97
|
+
/**
|
|
98
|
+
* Parses the string as JSON and safely reports parsing issues for audits.
|
|
99
|
+
*
|
|
100
|
+
* Assumes the parsed JSON will be an `ExecutionResult`.
|
|
101
|
+
*
|
|
102
|
+
* @private
|
|
103
|
+
* */
|
|
104
|
+
export async function assertBodyAsExecutionResult(res) {
|
|
105
|
+
const str = await res.text();
|
|
106
|
+
try {
|
|
107
|
+
return JSON.parse(str);
|
|
108
|
+
}
|
|
109
|
+
catch (err) {
|
|
110
|
+
throw `Response body is not valid JSON. Got ${JSON.stringify(str)}`;
|
|
111
|
+
}
|
|
112
|
+
}
|