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.
@@ -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('MUST accept application/graphql-response+json and match the content-type', async () => {
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('MUST accept */* and use application/graphql-response+json for the content-type', async () => {
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('MUST assume application/graphql-response+json content-type when accept is missing', async () => {
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 charset in response', async () => {
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
- assert('Content-Type header', res.headers.get('content-type')).toContain('charset=utf-8');
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
- assert('Status code', res.status).toBe(406);
72
- assert('Accept header', res.headers.get('accept')).toContain('charset=utf-8');
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(599);
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('MUST require the {query} parameter', async () => {
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
- ...[{ obj: 'ect' }, 0, false, ['array']].map((invalid) => audit(`MUST NOT allow ${extendedTypeof(invalid)} for the {query} parameter`, async () => {
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('MUST accept a string for the {query} parameter', async () => {
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
- ...[{ obj: 'ect' }, 0, false, ['array']].map((invalid) => audit(`MUST NOT allow ${extendedTypeof(invalid)} for the {operationName} parameter`, async () => {
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('MUST accept a string for the {operationName} parameter', async () => {
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
- ...['string', 0, false, ['array']].map((invalid) => audit(`MUST NOT allow ${extendedTypeof(invalid)} for the {variables} parameter`, async () => {
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
- audit('MUST accept a map for the {variables} parameter', async () => {
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 accept a URL-encoded JSON string for the {variables} parameter in GETs', async () => {
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
- ...['string', 0, false, ['array']].map((invalid) => audit(`MUST NOT allow ${extendedTypeof(invalid)} for the {extensions} parameter`, async () => {
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
- audit('MUST accept a map for the {extensions} parameter', async () => {
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('MUST use 4xx or 5xx status codes on JSON parsing failure when accepting application/graphql-response+json', async () => {
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.json()).data).toBe(undefined);
527
+ assert('Data entry', (await assertBodyAsExecutionResult(res)).data).toBe(undefined);
332
528
  }),
333
- audit('MUST use 4xx or 5xx status codes if parameters are invalid when accepting application/graphql-response+json', async () => {
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.json()).data).toBe(undefined);
557
+ assert('Data entry', (await assertBodyAsExecutionResult(res)).data).toBe(undefined);
360
558
  }),
361
- audit('MUST use 4xx or 5xx status codes on document parsing failure when accepting application/graphql-response+json', async () => {
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.json()).data).toBe(undefined);
587
+ assert('Data entry', (await assertBodyAsExecutionResult(res)).data).toBe(undefined);
388
588
  }),
389
- audit('MUST use 4xx or 5xx status codes on document validation failure when accepting application/graphql-response+json', async () => {
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.json()).data).toBe(undefined);
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>>>;
@@ -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>>>;
@@ -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;
@@ -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
+ }