graphql-http 1.6.0 → 1.7.0

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,30 +50,61 @@ 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
+ }
86
+ }),
87
+ audit('MUST accept utf-8 encoding', async () => {
88
+ const url = new URL(opts.url);
89
+ url.searchParams.set('query', '{ __typename }');
90
+ const res = await fetchFn(url.toString(), {
91
+ headers: {
92
+ 'content-type': 'application/json; charset=utf-8',
93
+ },
94
+ });
95
+ assert('Status code', res.status).toBe(200);
96
+ assert('Content-Type header', res.headers.get('content-type')).toContain('utf-8');
62
97
  }),
63
- audit('MUST accept only utf-8 charset', async () => {
98
+ audit('MUST assume utf-8 if encoding is unspecified', async () => {
64
99
  const url = new URL(opts.url);
65
100
  url.searchParams.set('query', '{ __typename }');
66
101
  const res = await fetchFn(url.toString(), {
67
102
  headers: {
68
- accept: 'application/graphql-response+json; charset=iso-8859-1',
103
+ 'content-type': 'application/json',
69
104
  },
70
105
  });
71
- assert('Status code', res.status).toBe(406);
72
- assert('Accept header', res.headers.get('accept')).toContain('charset=utf-8');
106
+ assert('Status code', res.status).toBe(200);
107
+ assert('Content-Type header', res.headers.get('content-type')).toContain('utf-8');
73
108
  }),
74
109
  // Request
75
110
  audit('MUST accept POST requests', async () => {
@@ -103,7 +138,7 @@ export function serverAudits(opts) {
103
138
  method: 'POST',
104
139
  });
105
140
  assert('Status code', res.status).toBeGreaterThanOrEqual(400);
106
- assert('Status code', res.status).toBeLessThanOrEqual(599);
141
+ assert('Status code', res.status).toBeLessThanOrEqual(499);
107
142
  }),
108
143
  audit('MUST accept application/json POST requests', async () => {
109
144
  const res = await fetchFn(opts.url, {
@@ -121,7 +156,9 @@ export function serverAudits(opts) {
121
156
  assert('Status code', res.status).toBe(400);
122
157
  }),
123
158
  // Request Parameters
124
- audit('MUST require the {query} parameter', async () => {
159
+ audit(
160
+ // TODO: convert to MUST after watershed
161
+ 'SHOULD use 400 status code on missing {query} parameter when accepting application/graphql-response+json', async () => {
125
162
  const res = await fetchFn(opts.url, {
126
163
  method: 'POST',
127
164
  headers: {
@@ -132,7 +169,21 @@ export function serverAudits(opts) {
132
169
  });
133
170
  assert('Status code', res.status).toBe(400);
134
171
  }),
135
- ...[{ obj: 'ect' }, 0, false, ['array']].map((invalid) => audit(`MUST NOT allow ${extendedTypeof(invalid)} for the {query} parameter`, async () => {
172
+ audit('SHOULD use 200 status code with errors field on missing {query} parameter when accepting application/json', async () => {
173
+ const res = await fetchFn(opts.url, {
174
+ method: 'POST',
175
+ headers: {
176
+ 'content-type': 'application/json',
177
+ accept: 'application/json',
178
+ },
179
+ body: JSON.stringify({ notquery: '{ __typename }' }),
180
+ });
181
+ assert('Status code', res.status).toBe(200);
182
+ assert('Execution result', await assertBodyAsExecutionResult(res)).toHaveProperty('errors');
183
+ }),
184
+ ...[{ obj: 'ect' }, 0, false, ['array']].map((invalid) => audit(
185
+ // TODO: convert to MUST after watershed
186
+ `SHOULD use 400 status code on ${extendedTypeof(invalid)} {query} parameter when accepting application/graphql-response+json`, async () => {
136
187
  const res = await fetchFn(opts.url, {
137
188
  method: 'POST',
138
189
  headers: {
@@ -145,7 +196,23 @@ export function serverAudits(opts) {
145
196
  });
146
197
  assert('Status code', res.status).toBe(400);
147
198
  })),
148
- audit('MUST accept a string for the {query} parameter', async () => {
199
+ ...[{ 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 () => {
200
+ const res = await fetchFn(opts.url, {
201
+ method: 'POST',
202
+ headers: {
203
+ 'content-type': 'application/json',
204
+ accept: 'application/json',
205
+ },
206
+ body: JSON.stringify({
207
+ query: invalid,
208
+ }),
209
+ });
210
+ assert('Status code', res.status).toBe(200);
211
+ assert('Execution result', await assertBodyAsExecutionResult(res)).toHaveProperty('errors');
212
+ })),
213
+ audit(
214
+ // TODO: convert to MUST after watershed
215
+ 'SHOULD allow string {query} parameter when accepting application/graphql-response+json', async () => {
149
216
  const res = await fetchFn(opts.url, {
150
217
  method: 'POST',
151
218
  headers: {
@@ -158,7 +225,23 @@ export function serverAudits(opts) {
158
225
  });
159
226
  assert('Status code', res.status).toBe(200);
160
227
  }),
161
- ...[{ obj: 'ect' }, 0, false, ['array']].map((invalid) => audit(`MUST NOT allow ${extendedTypeof(invalid)} for the {operationName} parameter`, async () => {
228
+ audit('MUST allow string {query} parameter when accepting application/json', async () => {
229
+ const res = await fetchFn(opts.url, {
230
+ method: 'POST',
231
+ headers: {
232
+ 'content-type': 'application/json',
233
+ accept: 'application/json',
234
+ },
235
+ body: JSON.stringify({
236
+ query: '{ __typename }',
237
+ }),
238
+ });
239
+ assert('Status code', res.status).toBe(200);
240
+ assert('Execution result', await assertBodyAsExecutionResult(res)).notToHaveProperty('errors');
241
+ }),
242
+ ...[{ obj: 'ect' }, 0, false, ['array']].map((invalid) => audit(
243
+ // TODO: convert to MUST after watershed
244
+ `SHOULD use 400 status code on ${extendedTypeof(invalid)} {operationName} parameter when accepting application/graphql-response+json`, async () => {
162
245
  const res = await fetchFn(opts.url, {
163
246
  method: 'POST',
164
247
  headers: {
@@ -172,7 +255,24 @@ export function serverAudits(opts) {
172
255
  });
173
256
  assert('Status code', res.status).toBe(400);
174
257
  })),
175
- audit('MUST accept a string for the {operationName} parameter', async () => {
258
+ ...[{ 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 () => {
259
+ const res = await fetchFn(opts.url, {
260
+ method: 'POST',
261
+ headers: {
262
+ 'content-type': 'application/json',
263
+ accept: 'application/json',
264
+ },
265
+ body: JSON.stringify({
266
+ operationName: invalid,
267
+ query: '{ __typename }',
268
+ }),
269
+ });
270
+ assert('Status code', res.status).toBe(200);
271
+ assert('Execution result', await assertBodyAsExecutionResult(res)).toHaveProperty('errors');
272
+ })),
273
+ audit(
274
+ // TODO: convert to MUST after watershed
275
+ 'SHOULD allow string {operationName} parameter when accepting application/graphql-response+json', async () => {
176
276
  const res = await fetchFn(opts.url, {
177
277
  method: 'POST',
178
278
  headers: {
@@ -186,7 +286,24 @@ export function serverAudits(opts) {
186
286
  });
187
287
  assert('Status code', res.status).toBe(200);
188
288
  }),
189
- ...['string', 0, false, ['array']].map((invalid) => audit(`MUST NOT allow ${extendedTypeof(invalid)} for the {variables} parameter`, async () => {
289
+ audit('MUST allow string {operationName} parameter when accepting application/json', async () => {
290
+ const res = await fetchFn(opts.url, {
291
+ method: 'POST',
292
+ headers: {
293
+ 'content-type': 'application/json',
294
+ accept: 'application/json',
295
+ },
296
+ body: JSON.stringify({
297
+ operationName: 'Query',
298
+ query: 'query Query { __typename }',
299
+ }),
300
+ });
301
+ assert('Status code', res.status).toBe(200);
302
+ assert('Execution result', await assertBodyAsExecutionResult(res)).notToHaveProperty('errors');
303
+ }),
304
+ ...['string', 0, false, ['array']].map((invalid) => audit(
305
+ // TODO: convert to MUST after watershed
306
+ `SHOULD use 400 status code on ${extendedTypeof(invalid)} {variables} parameter when accepting application/graphql-response+json`, async () => {
190
307
  const res = await fetchFn(opts.url, {
191
308
  method: 'POST',
192
309
  headers: {
@@ -200,7 +317,24 @@ export function serverAudits(opts) {
200
317
  });
201
318
  assert('Status code', res.status).toBe(400);
202
319
  })),
203
- audit('MUST accept a map for the {variables} parameter', async () => {
320
+ ...['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 () => {
321
+ const res = await fetchFn(opts.url, {
322
+ method: 'POST',
323
+ headers: {
324
+ 'content-type': 'application/json',
325
+ accept: 'application/json',
326
+ },
327
+ body: JSON.stringify({
328
+ query: '{ __typename }',
329
+ variables: invalid,
330
+ }),
331
+ });
332
+ assert('Status code', res.status).toBe(200);
333
+ assert('Execution result', await assertBodyAsExecutionResult(res)).toHaveProperty('errors');
334
+ })),
335
+ audit(
336
+ // TODO: convert to MUST after watershed
337
+ 'SHOULD allow map {variables} parameter when accepting application/graphql-response+json', async () => {
204
338
  const res = await fetchFn(opts.url, {
205
339
  method: 'POST',
206
340
  headers: {
@@ -213,21 +347,52 @@ export function serverAudits(opts) {
213
347
  }),
214
348
  });
215
349
  assert('Status code', res.status).toBe(200);
216
- const result = (await res.json());
217
- assert('Execution result', result).notToHaveProperty('errors');
218
350
  }),
219
- audit('MUST accept a URL-encoded JSON string for the {variables} parameter in GETs', async () => {
351
+ audit('MUST allow map {variables} parameter when accepting application/json', async () => {
352
+ const res = await fetchFn(opts.url, {
353
+ method: 'POST',
354
+ headers: {
355
+ 'content-type': 'application/json',
356
+ accept: 'application/json',
357
+ },
358
+ body: JSON.stringify({
359
+ query: 'query Type($name: String!) { __type(name: $name) { name } }',
360
+ variables: { name: 'sometype' },
361
+ }),
362
+ });
363
+ assert('Status code', res.status).toBe(200);
364
+ assert('Execution result', await assertBodyAsExecutionResult(res)).notToHaveProperty('errors');
365
+ }),
366
+ audit(
367
+ // TODO: convert to MUST after watershed
368
+ 'SHOULD allow URL-encoded JSON string {variables} parameter in GETs when accepting application/graphql-response+json', async () => {
369
+ const url = new URL(opts.url);
370
+ url.searchParams.set('query', 'query Type($name: String!) { __type(name: $name) { name } }');
371
+ url.searchParams.set('variables', JSON.stringify({ name: 'sometype' }));
372
+ const res = await fetchFn(url.toString(), {
373
+ method: 'GET',
374
+ headers: {
375
+ accept: 'application/graphql-response+json',
376
+ },
377
+ });
378
+ assert('Status code', res.status).toBe(200);
379
+ }),
380
+ audit('MUST allow URL-encoded JSON string {variables} parameter in GETs when accepting application/json', async () => {
220
381
  const url = new URL(opts.url);
221
382
  url.searchParams.set('query', 'query Type($name: String!) { __type(name: $name) { name } }');
222
383
  url.searchParams.set('variables', JSON.stringify({ name: 'sometype' }));
223
384
  const res = await fetchFn(url.toString(), {
224
385
  method: 'GET',
386
+ headers: {
387
+ accept: 'application/json',
388
+ },
225
389
  });
226
390
  assert('Status code', res.status).toBe(200);
227
- const result = (await res.json());
228
- assert('Execution result', result).notToHaveProperty('errors');
391
+ assert('Execution result', await assertBodyAsExecutionResult(res)).notToHaveProperty('errors');
229
392
  }),
230
- ...['string', 0, false, ['array']].map((invalid) => audit(`MUST NOT allow ${extendedTypeof(invalid)} for the {extensions} parameter`, async () => {
393
+ ...['string', 0, false, ['array']].map((invalid) => audit(
394
+ // TODO: convert to MUST after watershed
395
+ `SHOULD use 400 status code on ${extendedTypeof(invalid)} {extensions} parameter when accepting application/graphql-response+json`, async () => {
231
396
  const res = await fetchFn(opts.url, {
232
397
  method: 'POST',
233
398
  headers: {
@@ -241,7 +406,24 @@ export function serverAudits(opts) {
241
406
  });
242
407
  assert('Status code', res.status).toBe(400);
243
408
  })),
244
- audit('MUST accept a map for the {extensions} parameter', async () => {
409
+ ...['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 () => {
410
+ const res = await fetchFn(opts.url, {
411
+ method: 'POST',
412
+ headers: {
413
+ 'content-type': 'application/json',
414
+ accept: 'application/json',
415
+ },
416
+ body: JSON.stringify({
417
+ query: '{ __typename }',
418
+ extensions: invalid,
419
+ }),
420
+ });
421
+ assert('Status code', res.status).toBe(200);
422
+ assert('Execution result', await assertBodyAsExecutionResult(res)).toHaveProperty('errors');
423
+ })),
424
+ audit(
425
+ // TODO: convert to MUST after watershed
426
+ 'SHOULD allow map {extensions} parameter when accepting application/graphql-response+json', async () => {
245
427
  const res = await fetchFn(opts.url, {
246
428
  method: 'POST',
247
429
  headers: {
@@ -255,6 +437,21 @@ export function serverAudits(opts) {
255
437
  });
256
438
  assert('Status code', res.status).toBe(200);
257
439
  }),
440
+ audit('MUST allow map {extensions} parameter when accepting application/json', async () => {
441
+ const res = await fetchFn(opts.url, {
442
+ method: 'POST',
443
+ headers: {
444
+ 'content-type': 'application/json',
445
+ accept: 'application/json',
446
+ },
447
+ body: JSON.stringify({
448
+ query: '{ __typename }',
449
+ extensions: { some: 'value' },
450
+ }),
451
+ });
452
+ assert('Status code', res.status).toBe(200);
453
+ assert('Execution result', await assertBodyAsExecutionResult(res)).notToHaveProperty('errors');
454
+ }),
258
455
  // TODO: audit('MUST accept a map for the {extensions} parameter'),
259
456
  // Response application/json
260
457
  audit('SHOULD use 200 status code on JSON parsing failure when accepting application/json', async () => {
@@ -296,7 +493,9 @@ export function serverAudits(opts) {
296
493
  assert('Status code', res.status).toBe(200);
297
494
  }),
298
495
  // 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 () => {
496
+ audit(
497
+ // TODO: convert to MUST after watershed
498
+ 'SHOULD use 4xx or 5xx status codes on JSON parsing failure when accepting application/graphql-response+json', async () => {
300
499
  const res = await fetchFn(opts.url, {
301
500
  method: 'POST',
302
501
  headers: {
@@ -328,9 +527,11 @@ export function serverAudits(opts) {
328
527
  },
329
528
  body: '{ "not a JSON',
330
529
  });
331
- assert('Data entry', (await res.json()).data).toBe(undefined);
530
+ assert('Data entry', (await assertBodyAsExecutionResult(res)).data).toBe(undefined);
332
531
  }),
333
- audit('MUST use 4xx or 5xx status codes if parameters are invalid when accepting application/graphql-response+json', async () => {
532
+ audit(
533
+ // TODO: convert to MUST after watershed
534
+ 'SHOULD use 4xx or 5xx status codes if parameters are invalid when accepting application/graphql-response+json', async () => {
334
535
  const url = new URL(opts.url);
335
536
  url.searchParams.set('qeury' /* typo */, '{ __typename }');
336
537
  const res = await fetchFn(url.toString(), {
@@ -356,9 +557,11 @@ export function serverAudits(opts) {
356
557
  method: 'GET',
357
558
  headers: { accept: 'application/graphql-response+json' },
358
559
  });
359
- assert('Data entry', (await res.json()).data).toBe(undefined);
560
+ assert('Data entry', (await assertBodyAsExecutionResult(res)).data).toBe(undefined);
360
561
  }),
361
- audit('MUST use 4xx or 5xx status codes on document parsing failure when accepting application/graphql-response+json', async () => {
562
+ audit(
563
+ // TODO: convert to MUST after watershed
564
+ 'SHOULD use 4xx or 5xx status codes on document parsing failure when accepting application/graphql-response+json', async () => {
362
565
  const url = new URL(opts.url);
363
566
  url.searchParams.set('query', '{');
364
567
  const res = await fetchFn(url.toString(), {
@@ -384,9 +587,11 @@ export function serverAudits(opts) {
384
587
  method: 'GET',
385
588
  headers: { accept: 'application/graphql-response+json' },
386
589
  });
387
- assert('Data entry', (await res.json()).data).toBe(undefined);
590
+ assert('Data entry', (await assertBodyAsExecutionResult(res)).data).toBe(undefined);
388
591
  }),
389
- audit('MUST use 4xx or 5xx status codes on document validation failure when accepting application/graphql-response+json', async () => {
592
+ audit(
593
+ // TODO: convert to MUST after watershed
594
+ 'SHOULD use 4xx or 5xx status codes on document validation failure when accepting application/graphql-response+json', async () => {
390
595
  const url = new URL(opts.url);
391
596
  url.searchParams.set('query', '{ 8f31403dfe404bccbb0e835f2629c6a7 }'); // making sure the field doesnt exist
392
597
  const res = await fetchFn(url.toString(), {
@@ -412,7 +617,7 @@ export function serverAudits(opts) {
412
617
  method: 'GET',
413
618
  headers: { accept: 'application/graphql-response+json' },
414
619
  });
415
- assert('Data entry', (await res.json()).data).toBe(undefined);
620
+ assert('Data entry', (await assertBodyAsExecutionResult(res)).data).toBe(undefined);
416
621
  }),
417
622
  // TODO: how to fail and have the data entry?
418
623
  // 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 type { 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 type { 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
+ }