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/README.md
CHANGED
|
@@ -739,6 +739,10 @@ for (const audit of serverAudits({
|
|
|
739
739
|
|
|
740
740
|
Check the [docs folder](docs/) out for [TypeDoc](https://typedoc.org) generated documentation.
|
|
741
741
|
|
|
742
|
+
## [Audits](implementations/)
|
|
743
|
+
|
|
744
|
+
Inspect audits of other implementations in the [implementations folder](implementations/). Adding your implementation is very welcome!
|
|
745
|
+
|
|
742
746
|
## [Want to help?](CONTRIBUTING.md)
|
|
743
747
|
|
|
744
748
|
File a bug, contribute with code, or improve documentation? Read up on our guidelines for [contributing](CONTRIBUTING.md) and drive development with `yarn test --watch` away!
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* audit/common
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Audit requirement levels as per [RFC2119](https://www.rfc-editor.org/rfc/rfc2119).
|
|
8
|
+
*
|
|
9
|
+
* @category Audits
|
|
10
|
+
*/
|
|
11
|
+
export declare type AuditRequirement = 'MUST' | 'SHOULD' | 'MAY';
|
|
12
|
+
/**
|
|
13
|
+
* Audit name starting with the audit requirement level.
|
|
14
|
+
*
|
|
15
|
+
* @category Audits
|
|
16
|
+
*/
|
|
17
|
+
export declare type AuditName = `${AuditRequirement} ${string}`;
|
|
18
|
+
/**
|
|
19
|
+
* Actual audit test returning an result.
|
|
20
|
+
*
|
|
21
|
+
* The test function will throw only if the error is fatal.
|
|
22
|
+
*
|
|
23
|
+
* @category Audits
|
|
24
|
+
*/
|
|
25
|
+
export interface Audit {
|
|
26
|
+
name: AuditName;
|
|
27
|
+
fn: () => Promise<AuditResult>;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Indicates that the audit was successful.
|
|
31
|
+
*
|
|
32
|
+
* @category Audits
|
|
33
|
+
*/
|
|
34
|
+
export interface AuditOk {
|
|
35
|
+
name: AuditName;
|
|
36
|
+
status: 'ok';
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Indicates that the audit failed.
|
|
40
|
+
*
|
|
41
|
+
* If the status is `warn`, the audit is not a requirement but rather a recommendation.
|
|
42
|
+
*
|
|
43
|
+
* On the other hand, if the status is `error`, the audit is a requirement and the source
|
|
44
|
+
* is therefore not compliant.
|
|
45
|
+
*
|
|
46
|
+
* @category Audits
|
|
47
|
+
*/
|
|
48
|
+
export interface AuditFail {
|
|
49
|
+
name: AuditName;
|
|
50
|
+
status: 'warn' | 'error';
|
|
51
|
+
reason: string;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Result of the performed audit. See `AuditOk` and `AuditFail` for more information.
|
|
55
|
+
*
|
|
56
|
+
* @category Audits
|
|
57
|
+
*/
|
|
58
|
+
export declare type AuditResult = AuditOk | AuditFail;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* audit/server
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
import { Audit, AuditResult } from './common';
|
|
7
|
+
/**
|
|
8
|
+
* Options for server audits required to check GraphQL over HTTP spec conformance.
|
|
9
|
+
*
|
|
10
|
+
* @category Audits
|
|
11
|
+
*/
|
|
12
|
+
export interface ServerAuditOptions {
|
|
13
|
+
/**
|
|
14
|
+
* The URL of the GraphQL server for the audit.
|
|
15
|
+
*/
|
|
16
|
+
url: string;
|
|
17
|
+
/**
|
|
18
|
+
* The Fetch function to use.
|
|
19
|
+
*
|
|
20
|
+
* For NodeJS environments consider using [`@whatwg-node/fetch`](https://github.com/ardatan/whatwg-node/tree/master/packages/fetch).
|
|
21
|
+
*
|
|
22
|
+
* @default global.fetch
|
|
23
|
+
*/
|
|
24
|
+
fetchFn?: unknown;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* List of server audits required to check GraphQL over HTTP spec conformance.
|
|
28
|
+
*
|
|
29
|
+
* @category Audits
|
|
30
|
+
*/
|
|
31
|
+
export declare function serverAudits(opts: ServerAuditOptions): Audit[];
|
|
32
|
+
/**
|
|
33
|
+
* Performs the full list of server audits required for GraphQL over HTTP spec conformance.
|
|
34
|
+
*
|
|
35
|
+
* Please consult the `AuditResult` for more information.
|
|
36
|
+
*
|
|
37
|
+
* @category Audits
|
|
38
|
+
*/
|
|
39
|
+
export declare function auditServer(opts: ServerAuditOptions): Promise<AuditResult[]>;
|
package/lib/audits/server.js
CHANGED
|
@@ -16,7 +16,9 @@ function serverAudits(opts) {
|
|
|
16
16
|
const fetchFn = (opts.fetchFn || fetch);
|
|
17
17
|
return [
|
|
18
18
|
// Media Types
|
|
19
|
-
(0, utils_1.audit)(
|
|
19
|
+
(0, utils_1.audit)(
|
|
20
|
+
// TODO: convert to MUST after watershed
|
|
21
|
+
'SHOULD accept application/graphql-response+json and match the content-type', async () => {
|
|
20
22
|
const url = new URL(opts.url);
|
|
21
23
|
url.searchParams.set('query', '{ __typename }');
|
|
22
24
|
const res = await fetchFn(url.toString(), {
|
|
@@ -38,7 +40,9 @@ function serverAudits(opts) {
|
|
|
38
40
|
(0, utils_1.assert)('Status code', res.status).toBe(200);
|
|
39
41
|
(0, utils_1.assert)('Content-Type header', res.headers.get('content-type')).toContain('application/json');
|
|
40
42
|
}),
|
|
41
|
-
(0, utils_1.audit)(
|
|
43
|
+
(0, utils_1.audit)(
|
|
44
|
+
// TODO: convert to MUST after watershed
|
|
45
|
+
'SHOULD accept */* and use application/graphql-response+json for the content-type', async () => {
|
|
42
46
|
const url = new URL(opts.url);
|
|
43
47
|
url.searchParams.set('query', '{ __typename }');
|
|
44
48
|
const res = await fetchFn(url.toString(), {
|
|
@@ -49,19 +53,39 @@ function serverAudits(opts) {
|
|
|
49
53
|
(0, utils_1.assert)('Status code', res.status).toBe(200);
|
|
50
54
|
(0, utils_1.assert)('Content-Type header', res.headers.get('content-type')).toContain('application/graphql-response+json');
|
|
51
55
|
}),
|
|
52
|
-
(0, utils_1.audit)(
|
|
56
|
+
(0, utils_1.audit)(
|
|
57
|
+
// TODO: convert to MUST after watershed
|
|
58
|
+
'SHOULD assume application/graphql-response+json content-type when accept is missing', async () => {
|
|
53
59
|
const url = new URL(opts.url);
|
|
54
60
|
url.searchParams.set('query', '{ __typename }');
|
|
55
61
|
const res = await fetchFn(url.toString());
|
|
56
62
|
(0, utils_1.assert)('Status code', res.status).toBe(200);
|
|
57
63
|
(0, utils_1.assert)('Content-Type header', res.headers.get('content-type')).toContain('application/graphql-response+json');
|
|
58
64
|
}),
|
|
59
|
-
(0, utils_1.audit)('MUST use utf-8
|
|
65
|
+
(0, utils_1.audit)('MUST use utf-8 encoding when responding', async () => {
|
|
60
66
|
const url = new URL(opts.url);
|
|
61
67
|
url.searchParams.set('query', '{ __typename }');
|
|
62
68
|
const res = await fetchFn(url.toString());
|
|
63
69
|
(0, utils_1.assert)('Status code', res.status).toBe(200);
|
|
64
|
-
|
|
70
|
+
// has charset set to utf-8
|
|
71
|
+
try {
|
|
72
|
+
(0, utils_1.assert)('Content-Type header', res.headers.get('content-type')).toContain('charset=utf-8');
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
catch (_a) {
|
|
76
|
+
// noop, continue
|
|
77
|
+
}
|
|
78
|
+
// has no charset specified
|
|
79
|
+
(0, utils_1.assert)('Content-Type header', res.headers.get('content-type')).notToContain('charset');
|
|
80
|
+
// and the content is utf-8 encoded
|
|
81
|
+
try {
|
|
82
|
+
const decoder = new TextDecoder('utf-8');
|
|
83
|
+
const decoded = decoder.decode(await res.arrayBuffer());
|
|
84
|
+
(0, utils_1.assert)('UTF-8 decoded body', decoded).toBe('{"data":{"__typename":"Query"}}');
|
|
85
|
+
}
|
|
86
|
+
catch (_b) {
|
|
87
|
+
throw 'Body is not UTF-8 encoded';
|
|
88
|
+
}
|
|
65
89
|
}),
|
|
66
90
|
(0, utils_1.audit)('MUST accept only utf-8 charset', async () => {
|
|
67
91
|
const url = new URL(opts.url);
|
|
@@ -71,8 +95,16 @@ function serverAudits(opts) {
|
|
|
71
95
|
accept: 'application/graphql-response+json; charset=iso-8859-1',
|
|
72
96
|
},
|
|
73
97
|
});
|
|
74
|
-
|
|
75
|
-
|
|
98
|
+
// application/json is 200 + errors
|
|
99
|
+
const contentType = res.headers.get('content-type') || '';
|
|
100
|
+
if (contentType.includes('application/json')) {
|
|
101
|
+
(0, utils_1.assert)(`Content-Type ${contentType} status code`, res.status).toBe(200);
|
|
102
|
+
(0, utils_1.assert)('Execution result', await (0, utils_1.assertBodyAsExecutionResult)(res)).toHaveProperty('errors');
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
// other content-types must be 4xx
|
|
106
|
+
(0, utils_1.assert)(`Content-Type ${contentType} status code`, res.status).toBeGreaterThanOrEqual(400);
|
|
107
|
+
(0, utils_1.assert)(`Content-Type ${contentType} status code`, res.status).toBeLessThanOrEqual(499);
|
|
76
108
|
}),
|
|
77
109
|
// Request
|
|
78
110
|
(0, utils_1.audit)('MUST accept POST requests', async () => {
|
|
@@ -106,7 +138,7 @@ function serverAudits(opts) {
|
|
|
106
138
|
method: 'POST',
|
|
107
139
|
});
|
|
108
140
|
(0, utils_1.assert)('Status code', res.status).toBeGreaterThanOrEqual(400);
|
|
109
|
-
(0, utils_1.assert)('Status code', res.status).toBeLessThanOrEqual(
|
|
141
|
+
(0, utils_1.assert)('Status code', res.status).toBeLessThanOrEqual(499);
|
|
110
142
|
}),
|
|
111
143
|
(0, utils_1.audit)('MUST accept application/json POST requests', async () => {
|
|
112
144
|
const res = await fetchFn(opts.url, {
|
|
@@ -124,7 +156,9 @@ function serverAudits(opts) {
|
|
|
124
156
|
(0, utils_1.assert)('Status code', res.status).toBe(400);
|
|
125
157
|
}),
|
|
126
158
|
// Request Parameters
|
|
127
|
-
(0, utils_1.audit)(
|
|
159
|
+
(0, utils_1.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 () => {
|
|
128
162
|
const res = await fetchFn(opts.url, {
|
|
129
163
|
method: 'POST',
|
|
130
164
|
headers: {
|
|
@@ -135,7 +169,21 @@ function serverAudits(opts) {
|
|
|
135
169
|
});
|
|
136
170
|
(0, utils_1.assert)('Status code', res.status).toBe(400);
|
|
137
171
|
}),
|
|
138
|
-
|
|
172
|
+
(0, utils_1.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
|
+
(0, utils_1.assert)('Status code', res.status).toBe(200);
|
|
182
|
+
(0, utils_1.assert)('Execution result', await (0, utils_1.assertBodyAsExecutionResult)(res)).toHaveProperty('errors');
|
|
183
|
+
}),
|
|
184
|
+
...[{ obj: 'ect' }, 0, false, ['array']].map((invalid) => (0, utils_1.audit)(
|
|
185
|
+
// TODO: convert to MUST after watershed
|
|
186
|
+
`SHOULD use 400 status code on ${(0, utils_1.extendedTypeof)(invalid)} {query} parameter when accepting application/graphql-response+json`, async () => {
|
|
139
187
|
const res = await fetchFn(opts.url, {
|
|
140
188
|
method: 'POST',
|
|
141
189
|
headers: {
|
|
@@ -148,7 +196,23 @@ function serverAudits(opts) {
|
|
|
148
196
|
});
|
|
149
197
|
(0, utils_1.assert)('Status code', res.status).toBe(400);
|
|
150
198
|
})),
|
|
151
|
-
(0, utils_1.audit)(
|
|
199
|
+
...[{ obj: 'ect' }, 0, false, ['array']].map((invalid) => (0, utils_1.audit)(`SHOULD use 200 status code with errors field on ${(0, utils_1.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
|
+
(0, utils_1.assert)('Status code', res.status).toBe(200);
|
|
211
|
+
(0, utils_1.assert)('Execution result', await (0, utils_1.assertBodyAsExecutionResult)(res)).toHaveProperty('errors');
|
|
212
|
+
})),
|
|
213
|
+
(0, utils_1.audit)(
|
|
214
|
+
// TODO: convert to MUST after watershed
|
|
215
|
+
'SHOULD allow string {query} parameter when accepting application/graphql-response+json', async () => {
|
|
152
216
|
const res = await fetchFn(opts.url, {
|
|
153
217
|
method: 'POST',
|
|
154
218
|
headers: {
|
|
@@ -161,7 +225,23 @@ function serverAudits(opts) {
|
|
|
161
225
|
});
|
|
162
226
|
(0, utils_1.assert)('Status code', res.status).toBe(200);
|
|
163
227
|
}),
|
|
164
|
-
|
|
228
|
+
(0, utils_1.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
|
+
(0, utils_1.assert)('Status code', res.status).toBe(200);
|
|
240
|
+
(0, utils_1.assert)('Execution result', await (0, utils_1.assertBodyAsExecutionResult)(res)).notToHaveProperty('errors');
|
|
241
|
+
}),
|
|
242
|
+
...[{ obj: 'ect' }, 0, false, ['array']].map((invalid) => (0, utils_1.audit)(
|
|
243
|
+
// TODO: convert to MUST after watershed
|
|
244
|
+
`SHOULD use 400 status code on ${(0, utils_1.extendedTypeof)(invalid)} {operationName} parameter when accepting application/graphql-response+json`, async () => {
|
|
165
245
|
const res = await fetchFn(opts.url, {
|
|
166
246
|
method: 'POST',
|
|
167
247
|
headers: {
|
|
@@ -175,7 +255,24 @@ function serverAudits(opts) {
|
|
|
175
255
|
});
|
|
176
256
|
(0, utils_1.assert)('Status code', res.status).toBe(400);
|
|
177
257
|
})),
|
|
178
|
-
(0, utils_1.audit)(
|
|
258
|
+
...[{ obj: 'ect' }, 0, false, ['array']].map((invalid) => (0, utils_1.audit)(`SHOULD use 200 status code with errors field on ${(0, utils_1.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
|
+
(0, utils_1.assert)('Status code', res.status).toBe(200);
|
|
271
|
+
(0, utils_1.assert)('Execution result', await (0, utils_1.assertBodyAsExecutionResult)(res)).toHaveProperty('errors');
|
|
272
|
+
})),
|
|
273
|
+
(0, utils_1.audit)(
|
|
274
|
+
// TODO: convert to MUST after watershed
|
|
275
|
+
'SHOULD allow string {operationName} parameter when accepting application/graphql-response+json', async () => {
|
|
179
276
|
const res = await fetchFn(opts.url, {
|
|
180
277
|
method: 'POST',
|
|
181
278
|
headers: {
|
|
@@ -189,7 +286,24 @@ function serverAudits(opts) {
|
|
|
189
286
|
});
|
|
190
287
|
(0, utils_1.assert)('Status code', res.status).toBe(200);
|
|
191
288
|
}),
|
|
192
|
-
|
|
289
|
+
(0, utils_1.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
|
+
(0, utils_1.assert)('Status code', res.status).toBe(200);
|
|
302
|
+
(0, utils_1.assert)('Execution result', await (0, utils_1.assertBodyAsExecutionResult)(res)).notToHaveProperty('errors');
|
|
303
|
+
}),
|
|
304
|
+
...['string', 0, false, ['array']].map((invalid) => (0, utils_1.audit)(
|
|
305
|
+
// TODO: convert to MUST after watershed
|
|
306
|
+
`SHOULD use 400 status code on ${(0, utils_1.extendedTypeof)(invalid)} {variables} parameter when accepting application/graphql-response+json`, async () => {
|
|
193
307
|
const res = await fetchFn(opts.url, {
|
|
194
308
|
method: 'POST',
|
|
195
309
|
headers: {
|
|
@@ -203,7 +317,24 @@ function serverAudits(opts) {
|
|
|
203
317
|
});
|
|
204
318
|
(0, utils_1.assert)('Status code', res.status).toBe(400);
|
|
205
319
|
})),
|
|
206
|
-
(0, utils_1.audit)(
|
|
320
|
+
...['string', 0, false, ['array']].map((invalid) => (0, utils_1.audit)(`SHOULD use 200 status code with errors field on ${(0, utils_1.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
|
+
(0, utils_1.assert)('Status code', res.status).toBe(200);
|
|
333
|
+
(0, utils_1.assert)('Execution result', await (0, utils_1.assertBodyAsExecutionResult)(res)).toHaveProperty('errors');
|
|
334
|
+
})),
|
|
335
|
+
(0, utils_1.audit)(
|
|
336
|
+
// TODO: convert to MUST after watershed
|
|
337
|
+
'SHOULD allow map {variables} parameter when accepting application/graphql-response+json', async () => {
|
|
207
338
|
const res = await fetchFn(opts.url, {
|
|
208
339
|
method: 'POST',
|
|
209
340
|
headers: {
|
|
@@ -216,21 +347,52 @@ function serverAudits(opts) {
|
|
|
216
347
|
}),
|
|
217
348
|
});
|
|
218
349
|
(0, utils_1.assert)('Status code', res.status).toBe(200);
|
|
219
|
-
const result = (await res.json());
|
|
220
|
-
(0, utils_1.assert)('Execution result', result).notToHaveProperty('errors');
|
|
221
350
|
}),
|
|
222
|
-
(0, utils_1.audit)('MUST
|
|
351
|
+
(0, utils_1.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
|
+
(0, utils_1.assert)('Status code', res.status).toBe(200);
|
|
364
|
+
(0, utils_1.assert)('Execution result', await (0, utils_1.assertBodyAsExecutionResult)(res)).notToHaveProperty('errors');
|
|
365
|
+
}),
|
|
366
|
+
(0, utils_1.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 () => {
|
|
223
369
|
const url = new URL(opts.url);
|
|
224
370
|
url.searchParams.set('query', 'query Type($name: String!) { __type(name: $name) { name } }');
|
|
225
371
|
url.searchParams.set('variables', JSON.stringify({ name: 'sometype' }));
|
|
226
372
|
const res = await fetchFn(url.toString(), {
|
|
227
373
|
method: 'GET',
|
|
374
|
+
headers: {
|
|
375
|
+
accept: 'application/graphql-response+json',
|
|
376
|
+
},
|
|
228
377
|
});
|
|
229
378
|
(0, utils_1.assert)('Status code', res.status).toBe(200);
|
|
230
|
-
const result = (await res.json());
|
|
231
|
-
(0, utils_1.assert)('Execution result', result).notToHaveProperty('errors');
|
|
232
379
|
}),
|
|
233
|
-
|
|
380
|
+
(0, utils_1.audit)('MUST allow URL-encoded JSON string {variables} parameter in GETs when accepting application/json', async () => {
|
|
381
|
+
const url = new URL(opts.url);
|
|
382
|
+
url.searchParams.set('query', 'query Type($name: String!) { __type(name: $name) { name } }');
|
|
383
|
+
url.searchParams.set('variables', JSON.stringify({ name: 'sometype' }));
|
|
384
|
+
const res = await fetchFn(url.toString(), {
|
|
385
|
+
method: 'GET',
|
|
386
|
+
headers: {
|
|
387
|
+
accept: 'application/json',
|
|
388
|
+
},
|
|
389
|
+
});
|
|
390
|
+
(0, utils_1.assert)('Status code', res.status).toBe(200);
|
|
391
|
+
(0, utils_1.assert)('Execution result', await (0, utils_1.assertBodyAsExecutionResult)(res)).notToHaveProperty('errors');
|
|
392
|
+
}),
|
|
393
|
+
...['string', 0, false, ['array']].map((invalid) => (0, utils_1.audit)(
|
|
394
|
+
// TODO: convert to MUST after watershed
|
|
395
|
+
`SHOULD use 400 status code on ${(0, utils_1.extendedTypeof)(invalid)} {extensions} parameter when accepting application/graphql-response+json`, async () => {
|
|
234
396
|
const res = await fetchFn(opts.url, {
|
|
235
397
|
method: 'POST',
|
|
236
398
|
headers: {
|
|
@@ -244,7 +406,24 @@ function serverAudits(opts) {
|
|
|
244
406
|
});
|
|
245
407
|
(0, utils_1.assert)('Status code', res.status).toBe(400);
|
|
246
408
|
})),
|
|
247
|
-
(0, utils_1.audit)(
|
|
409
|
+
...['string', 0, false, ['array']].map((invalid) => (0, utils_1.audit)(`SHOULD use 200 status code with errors field on ${(0, utils_1.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
|
+
(0, utils_1.assert)('Status code', res.status).toBe(200);
|
|
422
|
+
(0, utils_1.assert)('Execution result', await (0, utils_1.assertBodyAsExecutionResult)(res)).toHaveProperty('errors');
|
|
423
|
+
})),
|
|
424
|
+
(0, utils_1.audit)(
|
|
425
|
+
// TODO: convert to MUST after watershed
|
|
426
|
+
'SHOULD allow map {extensions} parameter when accepting application/graphql-response+json', async () => {
|
|
248
427
|
const res = await fetchFn(opts.url, {
|
|
249
428
|
method: 'POST',
|
|
250
429
|
headers: {
|
|
@@ -258,6 +437,21 @@ function serverAudits(opts) {
|
|
|
258
437
|
});
|
|
259
438
|
(0, utils_1.assert)('Status code', res.status).toBe(200);
|
|
260
439
|
}),
|
|
440
|
+
(0, utils_1.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
|
+
(0, utils_1.assert)('Status code', res.status).toBe(200);
|
|
453
|
+
(0, utils_1.assert)('Execution result', await (0, utils_1.assertBodyAsExecutionResult)(res)).notToHaveProperty('errors');
|
|
454
|
+
}),
|
|
261
455
|
// TODO: audit('MUST accept a map for the {extensions} parameter'),
|
|
262
456
|
// Response application/json
|
|
263
457
|
(0, utils_1.audit)('SHOULD use 200 status code on JSON parsing failure when accepting application/json', async () => {
|
|
@@ -299,7 +493,9 @@ function serverAudits(opts) {
|
|
|
299
493
|
(0, utils_1.assert)('Status code', res.status).toBe(200);
|
|
300
494
|
}),
|
|
301
495
|
// Response application/graphql-response+json
|
|
302
|
-
(0, utils_1.audit)(
|
|
496
|
+
(0, utils_1.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 () => {
|
|
303
499
|
const res = await fetchFn(opts.url, {
|
|
304
500
|
method: 'POST',
|
|
305
501
|
headers: {
|
|
@@ -331,9 +527,11 @@ function serverAudits(opts) {
|
|
|
331
527
|
},
|
|
332
528
|
body: '{ "not a JSON',
|
|
333
529
|
});
|
|
334
|
-
(0, utils_1.assert)('Data entry', (await
|
|
530
|
+
(0, utils_1.assert)('Data entry', (await (0, utils_1.assertBodyAsExecutionResult)(res)).data).toBe(undefined);
|
|
335
531
|
}),
|
|
336
|
-
(0, utils_1.audit)(
|
|
532
|
+
(0, utils_1.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 () => {
|
|
337
535
|
const url = new URL(opts.url);
|
|
338
536
|
url.searchParams.set('qeury' /* typo */, '{ __typename }');
|
|
339
537
|
const res = await fetchFn(url.toString(), {
|
|
@@ -359,9 +557,11 @@ function serverAudits(opts) {
|
|
|
359
557
|
method: 'GET',
|
|
360
558
|
headers: { accept: 'application/graphql-response+json' },
|
|
361
559
|
});
|
|
362
|
-
(0, utils_1.assert)('Data entry', (await
|
|
560
|
+
(0, utils_1.assert)('Data entry', (await (0, utils_1.assertBodyAsExecutionResult)(res)).data).toBe(undefined);
|
|
363
561
|
}),
|
|
364
|
-
(0, utils_1.audit)(
|
|
562
|
+
(0, utils_1.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 () => {
|
|
365
565
|
const url = new URL(opts.url);
|
|
366
566
|
url.searchParams.set('query', '{');
|
|
367
567
|
const res = await fetchFn(url.toString(), {
|
|
@@ -387,9 +587,11 @@ function serverAudits(opts) {
|
|
|
387
587
|
method: 'GET',
|
|
388
588
|
headers: { accept: 'application/graphql-response+json' },
|
|
389
589
|
});
|
|
390
|
-
(0, utils_1.assert)('Data entry', (await
|
|
590
|
+
(0, utils_1.assert)('Data entry', (await (0, utils_1.assertBodyAsExecutionResult)(res)).data).toBe(undefined);
|
|
391
591
|
}),
|
|
392
|
-
(0, utils_1.audit)(
|
|
592
|
+
(0, utils_1.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 () => {
|
|
393
595
|
const url = new URL(opts.url);
|
|
394
596
|
url.searchParams.set('query', '{ 8f31403dfe404bccbb0e835f2629c6a7 }'); // making sure the field doesnt exist
|
|
395
597
|
const res = await fetchFn(url.toString(), {
|
|
@@ -415,7 +617,7 @@ function serverAudits(opts) {
|
|
|
415
617
|
method: 'GET',
|
|
416
618
|
headers: { accept: 'application/graphql-response+json' },
|
|
417
619
|
});
|
|
418
|
-
(0, utils_1.assert)('Data entry', (await
|
|
620
|
+
(0, utils_1.assert)('Data entry', (await (0, utils_1.assertBodyAsExecutionResult)(res)).data).toBe(undefined);
|
|
419
621
|
}),
|
|
420
622
|
// TODO: how to fail and have the data entry?
|
|
421
623
|
// audit('MUST use 2xx status code if response contains the data entry and it is not null when accepting application/graphql-response+json'),
|