graphql-http 1.7.1 → 1.8.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.
- package/README.md +7 -7
- package/lib/audits/server.d.mts +4 -1
- package/lib/audits/server.d.ts +4 -1
- package/lib/audits/server.js +69 -64
- package/lib/audits/server.mjs +69 -64
- package/lib/handler.js +5 -4
- package/lib/handler.mjs +5 -4
- package/package.json +1 -1
- package/umd/graphql-http.min.js.gz +0 -0
package/README.md
CHANGED
|
@@ -729,13 +729,13 @@ Having said this, graphql-http is mostly aimed for library authors and simple se
|
|
|
729
729
|
|
|
730
730
|
If you want a feature-full server with bleeding edge technologies, you're recommended to use one of the following.
|
|
731
731
|
|
|
732
|
-
| Name | Audit
|
|
733
|
-
| ------------------------------------------------------------------ |
|
|
734
|
-
| [graphql-yoga](https://www.the-guild.dev/graphql/yoga-server) | [✅ Compliant
|
|
735
|
-
| [hotchocolate](https://chillicream.com/docs/hotchocolate) | [✅ Compliant
|
|
736
|
-
| [postgraphile](https://www.graphile.org/postgraphile/) | [✅ Compliant](/implementations/postgraphile/README.md)
|
|
737
|
-
| [apollo-server](https://www.apollographql.com/docs/apollo-server/) | [✅ Compliant](/implementations/apollo-server/README.md)
|
|
738
|
-
| [mercurius](https://mercurius.dev) | [✅ Compliant](/implementations/mercurius/README.md)
|
|
732
|
+
| Name | Audit |
|
|
733
|
+
| ------------------------------------------------------------------ | -------------------------------------------------------- |
|
|
734
|
+
| [graphql-yoga](https://www.the-guild.dev/graphql/yoga-server) | [✅ Compliant](/implementations/graphql-yoga/README.md) |
|
|
735
|
+
| [hotchocolate](https://chillicream.com/docs/hotchocolate) | [✅ Compliant](/implementations/hotchocolate/README.md) |
|
|
736
|
+
| [postgraphile](https://www.graphile.org/postgraphile/) | [✅ Compliant](/implementations/postgraphile/README.md) |
|
|
737
|
+
| [apollo-server](https://www.apollographql.com/docs/apollo-server/) | [✅ Compliant](/implementations/apollo-server/README.md) |
|
|
738
|
+
| [mercurius](https://mercurius.dev) | [✅ Compliant](/implementations/mercurius/README.md) |
|
|
739
739
|
|
|
740
740
|
## [Documentation](docs/)
|
|
741
741
|
|
package/lib/audits/server.d.mts
CHANGED
|
@@ -12,8 +12,11 @@ import { Audit, AuditResult } from './common';
|
|
|
12
12
|
export interface ServerAuditOptions {
|
|
13
13
|
/**
|
|
14
14
|
* The URL of the GraphQL server for the audit.
|
|
15
|
+
*
|
|
16
|
+
* A function can be also supplied, in this case -
|
|
17
|
+
* every audit will invoke the function to get the URL.
|
|
15
18
|
*/
|
|
16
|
-
url: string;
|
|
19
|
+
url: string | (() => string | Promise<string>);
|
|
17
20
|
/**
|
|
18
21
|
* The Fetch function to use.
|
|
19
22
|
*
|
package/lib/audits/server.d.ts
CHANGED
|
@@ -12,8 +12,11 @@ import { Audit, AuditResult } from './common';
|
|
|
12
12
|
export interface ServerAuditOptions {
|
|
13
13
|
/**
|
|
14
14
|
* The URL of the GraphQL server for the audit.
|
|
15
|
+
*
|
|
16
|
+
* A function can be also supplied, in this case -
|
|
17
|
+
* every audit will invoke the function to get the URL.
|
|
15
18
|
*/
|
|
16
|
-
url: string;
|
|
19
|
+
url: string | (() => string | Promise<string>);
|
|
17
20
|
/**
|
|
18
21
|
* The Fetch function to use.
|
|
19
22
|
*
|
package/lib/audits/server.js
CHANGED
|
@@ -19,7 +19,7 @@ function serverAudits(opts) {
|
|
|
19
19
|
(0, utils_1.audit)(
|
|
20
20
|
// TODO: convert to MUST after watershed
|
|
21
21
|
'SHOULD accept application/graphql-response+json and match the content-type', async () => {
|
|
22
|
-
const res = await fetchFn(opts.url, {
|
|
22
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
23
23
|
method: 'POST',
|
|
24
24
|
headers: {
|
|
25
25
|
'content-type': 'application/json',
|
|
@@ -31,7 +31,7 @@ function serverAudits(opts) {
|
|
|
31
31
|
(0, utils_1.assert)('Content-Type header', res.headers.get('content-type')).toContain('application/graphql-response+json');
|
|
32
32
|
}),
|
|
33
33
|
(0, utils_1.audit)('MUST accept application/json and match the content-type', async () => {
|
|
34
|
-
const res = await fetchFn(opts.url, {
|
|
34
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
35
35
|
method: 'POST',
|
|
36
36
|
headers: {
|
|
37
37
|
'content-type': 'application/json',
|
|
@@ -42,10 +42,8 @@ function serverAudits(opts) {
|
|
|
42
42
|
(0, utils_1.assert)('Status code', res.status).toBe(200);
|
|
43
43
|
(0, utils_1.assert)('Content-Type header', res.headers.get('content-type')).toContain('application/json');
|
|
44
44
|
}),
|
|
45
|
-
(0, utils_1.audit)(
|
|
46
|
-
|
|
47
|
-
'SHOULD accept */* and use application/graphql-response+json for the content-type', async () => {
|
|
48
|
-
const res = await fetchFn(opts.url, {
|
|
45
|
+
(0, utils_1.audit)('SHOULD accept */* and use application/json for the content-type', async () => {
|
|
46
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
49
47
|
method: 'POST',
|
|
50
48
|
headers: {
|
|
51
49
|
'content-type': 'application/json',
|
|
@@ -54,23 +52,17 @@ function serverAudits(opts) {
|
|
|
54
52
|
body: JSON.stringify({ query: '{ __typename }' }),
|
|
55
53
|
});
|
|
56
54
|
(0, utils_1.assert)('Status code', res.status).toBe(200);
|
|
57
|
-
(0, utils_1.assert)('Content-Type header', res.headers.get('content-type')).toContain('application/
|
|
55
|
+
(0, utils_1.assert)('Content-Type header', res.headers.get('content-type')).toContain('application/json');
|
|
58
56
|
}),
|
|
59
|
-
(0, utils_1.audit)(
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const res = await fetchFn(
|
|
63
|
-
method: 'POST',
|
|
64
|
-
headers: {
|
|
65
|
-
'content-type': 'application/json',
|
|
66
|
-
},
|
|
67
|
-
body: JSON.stringify({ query: '{ __typename }' }),
|
|
68
|
-
});
|
|
57
|
+
(0, utils_1.audit)('SHOULD assume application/json content-type when accept is missing', async () => {
|
|
58
|
+
const url = new URL(await getUrl(opts.url));
|
|
59
|
+
url.searchParams.set('query', '{ __typename }');
|
|
60
|
+
const res = await fetchFn(url.toString());
|
|
69
61
|
(0, utils_1.assert)('Status code', res.status).toBe(200);
|
|
70
|
-
(0, utils_1.assert)('Content-Type header', res.headers.get('content-type')).toContain('application/
|
|
62
|
+
(0, utils_1.assert)('Content-Type header', res.headers.get('content-type')).toContain('application/json');
|
|
71
63
|
}),
|
|
72
64
|
(0, utils_1.audit)('MUST use utf-8 encoding when responding', async () => {
|
|
73
|
-
const res = await fetchFn(opts.url, {
|
|
65
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
74
66
|
method: 'POST',
|
|
75
67
|
headers: {
|
|
76
68
|
'content-type': 'application/json',
|
|
@@ -99,7 +91,7 @@ function serverAudits(opts) {
|
|
|
99
91
|
}
|
|
100
92
|
}),
|
|
101
93
|
(0, utils_1.audit)('MUST accept utf-8 encoding', async () => {
|
|
102
|
-
const res = await fetchFn(opts.url, {
|
|
94
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
103
95
|
method: 'POST',
|
|
104
96
|
headers: {
|
|
105
97
|
'content-type': 'application/json; charset=utf-8',
|
|
@@ -110,7 +102,7 @@ function serverAudits(opts) {
|
|
|
110
102
|
(0, utils_1.assert)('Content-Type header', res.headers.get('content-type')).toContain('utf-8');
|
|
111
103
|
}),
|
|
112
104
|
(0, utils_1.audit)('MUST assume utf-8 if encoding is unspecified', async () => {
|
|
113
|
-
const res = await fetchFn(opts.url, {
|
|
105
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
114
106
|
method: 'POST',
|
|
115
107
|
headers: {
|
|
116
108
|
'content-type': 'application/json',
|
|
@@ -122,7 +114,7 @@ function serverAudits(opts) {
|
|
|
122
114
|
}),
|
|
123
115
|
// Request
|
|
124
116
|
(0, utils_1.audit)('MUST accept POST requests', async () => {
|
|
125
|
-
const res = await fetchFn(opts.url, {
|
|
117
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
126
118
|
method: 'POST',
|
|
127
119
|
headers: { 'content-type': 'application/json' },
|
|
128
120
|
body: JSON.stringify({ query: '{ __typename }' }),
|
|
@@ -130,7 +122,7 @@ function serverAudits(opts) {
|
|
|
130
122
|
(0, utils_1.assert)('Status code', res.status).toBe(200);
|
|
131
123
|
}),
|
|
132
124
|
(0, utils_1.audit)('MAY accept application/x-www-form-urlencoded formatted GET requests', async () => {
|
|
133
|
-
const url = new URL(opts.url);
|
|
125
|
+
const url = new URL(await getUrl(opts.url));
|
|
134
126
|
url.searchParams.set('query', '{ __typename }');
|
|
135
127
|
const res = await fetchFn(url.toString());
|
|
136
128
|
(0, utils_1.assert)('Status code', res.status).toBe(200);
|
|
@@ -138,7 +130,7 @@ function serverAudits(opts) {
|
|
|
138
130
|
// Request GET
|
|
139
131
|
// TODO: this is a MUST if the server supports GET requests
|
|
140
132
|
(0, utils_1.audit)('MAY NOT allow executing mutations on GET requests', async () => {
|
|
141
|
-
const url = new URL(opts.url);
|
|
133
|
+
const url = new URL(await getUrl(opts.url));
|
|
142
134
|
url.searchParams.set('query', 'mutation { __typename }');
|
|
143
135
|
const res = await fetchFn(url.toString(), {
|
|
144
136
|
headers: {
|
|
@@ -149,13 +141,13 @@ function serverAudits(opts) {
|
|
|
149
141
|
}),
|
|
150
142
|
// Request POST
|
|
151
143
|
(0, utils_1.audit)('SHOULD respond with 4xx status code if content-type is not supplied on POST requests', async () => {
|
|
152
|
-
const res = await fetchFn(opts.url, {
|
|
144
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
153
145
|
method: 'POST',
|
|
154
146
|
});
|
|
155
147
|
(0, utils_1.assert)('Status code', res.status).toBeBetween(400, 499);
|
|
156
148
|
}),
|
|
157
149
|
(0, utils_1.audit)('MUST accept application/json POST requests', async () => {
|
|
158
|
-
const res = await fetchFn(opts.url, {
|
|
150
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
159
151
|
method: 'POST',
|
|
160
152
|
headers: { 'content-type': 'application/json' },
|
|
161
153
|
body: JSON.stringify({ query: '{ __typename }' }),
|
|
@@ -163,17 +155,23 @@ function serverAudits(opts) {
|
|
|
163
155
|
(0, utils_1.assert)('Status code', res.status).toBe(200);
|
|
164
156
|
}),
|
|
165
157
|
(0, utils_1.audit)('MUST require a request body on POST', async () => {
|
|
166
|
-
|
|
158
|
+
var _a;
|
|
159
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
167
160
|
method: 'POST',
|
|
168
161
|
headers: { 'content-type': 'application/json' },
|
|
169
162
|
});
|
|
170
|
-
(
|
|
163
|
+
if ((_a = res.headers.get('content-type')) === null || _a === void 0 ? void 0 : _a.includes('application/json')) {
|
|
164
|
+
(0, utils_1.assert)('Execution result', await (0, utils_1.assertBodyAsExecutionResult)(res)).toHaveProperty('errors');
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
(0, utils_1.assert)('Status code', res.status).toBe(400);
|
|
168
|
+
}
|
|
171
169
|
}),
|
|
172
170
|
// Request Parameters
|
|
173
171
|
(0, utils_1.audit)(
|
|
174
172
|
// TODO: convert to MUST after watershed
|
|
175
173
|
'SHOULD use 400 status code on missing {query} parameter when accepting application/graphql-response+json', async () => {
|
|
176
|
-
const res = await fetchFn(opts.url, {
|
|
174
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
177
175
|
method: 'POST',
|
|
178
176
|
headers: {
|
|
179
177
|
'content-type': 'application/json',
|
|
@@ -184,7 +182,7 @@ function serverAudits(opts) {
|
|
|
184
182
|
(0, utils_1.assert)('Status code', res.status).toBe(400);
|
|
185
183
|
}),
|
|
186
184
|
(0, utils_1.audit)('SHOULD use 200 status code with errors field on missing {query} parameter when accepting application/json', async () => {
|
|
187
|
-
const res = await fetchFn(opts.url, {
|
|
185
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
188
186
|
method: 'POST',
|
|
189
187
|
headers: {
|
|
190
188
|
'content-type': 'application/json',
|
|
@@ -198,7 +196,7 @@ function serverAudits(opts) {
|
|
|
198
196
|
...[{ obj: 'ect' }, 0, false, ['array']].map((invalid) => (0, utils_1.audit)(
|
|
199
197
|
// TODO: convert to MUST after watershed
|
|
200
198
|
`SHOULD use 400 status code on ${(0, utils_1.extendedTypeof)(invalid)} {query} parameter when accepting application/graphql-response+json`, async () => {
|
|
201
|
-
const res = await fetchFn(opts.url, {
|
|
199
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
202
200
|
method: 'POST',
|
|
203
201
|
headers: {
|
|
204
202
|
'content-type': 'application/json',
|
|
@@ -211,7 +209,7 @@ function serverAudits(opts) {
|
|
|
211
209
|
(0, utils_1.assert)('Status code', res.status).toBe(400);
|
|
212
210
|
})),
|
|
213
211
|
...[{ 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 () => {
|
|
214
|
-
const res = await fetchFn(opts.url, {
|
|
212
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
215
213
|
method: 'POST',
|
|
216
214
|
headers: {
|
|
217
215
|
'content-type': 'application/json',
|
|
@@ -227,7 +225,7 @@ function serverAudits(opts) {
|
|
|
227
225
|
(0, utils_1.audit)(
|
|
228
226
|
// TODO: convert to MUST after watershed
|
|
229
227
|
'SHOULD allow string {query} parameter when accepting application/graphql-response+json', async () => {
|
|
230
|
-
const res = await fetchFn(opts.url, {
|
|
228
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
231
229
|
method: 'POST',
|
|
232
230
|
headers: {
|
|
233
231
|
'content-type': 'application/json',
|
|
@@ -240,7 +238,7 @@ function serverAudits(opts) {
|
|
|
240
238
|
(0, utils_1.assert)('Status code', res.status).toBe(200);
|
|
241
239
|
}),
|
|
242
240
|
(0, utils_1.audit)('MUST allow string {query} parameter when accepting application/json', async () => {
|
|
243
|
-
const res = await fetchFn(opts.url, {
|
|
241
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
244
242
|
method: 'POST',
|
|
245
243
|
headers: {
|
|
246
244
|
'content-type': 'application/json',
|
|
@@ -256,7 +254,7 @@ function serverAudits(opts) {
|
|
|
256
254
|
...[{ obj: 'ect' }, 0, false, ['array']].map((invalid) => (0, utils_1.audit)(
|
|
257
255
|
// TODO: convert to MUST after watershed
|
|
258
256
|
`SHOULD use 400 status code on ${(0, utils_1.extendedTypeof)(invalid)} {operationName} parameter when accepting application/graphql-response+json`, async () => {
|
|
259
|
-
const res = await fetchFn(opts.url, {
|
|
257
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
260
258
|
method: 'POST',
|
|
261
259
|
headers: {
|
|
262
260
|
'content-type': 'application/json',
|
|
@@ -270,7 +268,7 @@ function serverAudits(opts) {
|
|
|
270
268
|
(0, utils_1.assert)('Status code', res.status).toBe(400);
|
|
271
269
|
})),
|
|
272
270
|
...[{ 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 () => {
|
|
273
|
-
const res = await fetchFn(opts.url, {
|
|
271
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
274
272
|
method: 'POST',
|
|
275
273
|
headers: {
|
|
276
274
|
'content-type': 'application/json',
|
|
@@ -287,7 +285,7 @@ function serverAudits(opts) {
|
|
|
287
285
|
(0, utils_1.audit)(
|
|
288
286
|
// TODO: convert to MUST after watershed
|
|
289
287
|
'SHOULD allow string {operationName} parameter when accepting application/graphql-response+json', async () => {
|
|
290
|
-
const res = await fetchFn(opts.url, {
|
|
288
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
291
289
|
method: 'POST',
|
|
292
290
|
headers: {
|
|
293
291
|
'content-type': 'application/json',
|
|
@@ -301,7 +299,7 @@ function serverAudits(opts) {
|
|
|
301
299
|
(0, utils_1.assert)('Status code', res.status).toBe(200);
|
|
302
300
|
}),
|
|
303
301
|
(0, utils_1.audit)('MUST allow string {operationName} parameter when accepting application/json', async () => {
|
|
304
|
-
const res = await fetchFn(opts.url, {
|
|
302
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
305
303
|
method: 'POST',
|
|
306
304
|
headers: {
|
|
307
305
|
'content-type': 'application/json',
|
|
@@ -318,7 +316,7 @@ function serverAudits(opts) {
|
|
|
318
316
|
...['string', 0, false, ['array']].map((invalid) => (0, utils_1.audit)(
|
|
319
317
|
// TODO: convert to MUST after watershed
|
|
320
318
|
`SHOULD use 400 status code on ${(0, utils_1.extendedTypeof)(invalid)} {variables} parameter when accepting application/graphql-response+json`, async () => {
|
|
321
|
-
const res = await fetchFn(opts.url, {
|
|
319
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
322
320
|
method: 'POST',
|
|
323
321
|
headers: {
|
|
324
322
|
'content-type': 'application/json',
|
|
@@ -332,7 +330,7 @@ function serverAudits(opts) {
|
|
|
332
330
|
(0, utils_1.assert)('Status code', res.status).toBe(400);
|
|
333
331
|
})),
|
|
334
332
|
...['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 () => {
|
|
335
|
-
const res = await fetchFn(opts.url, {
|
|
333
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
336
334
|
method: 'POST',
|
|
337
335
|
headers: {
|
|
338
336
|
'content-type': 'application/json',
|
|
@@ -349,7 +347,7 @@ function serverAudits(opts) {
|
|
|
349
347
|
(0, utils_1.audit)(
|
|
350
348
|
// TODO: convert to MUST after watershed
|
|
351
349
|
'SHOULD allow map {variables} parameter when accepting application/graphql-response+json', async () => {
|
|
352
|
-
const res = await fetchFn(opts.url, {
|
|
350
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
353
351
|
method: 'POST',
|
|
354
352
|
headers: {
|
|
355
353
|
'content-type': 'application/json',
|
|
@@ -363,7 +361,7 @@ function serverAudits(opts) {
|
|
|
363
361
|
(0, utils_1.assert)('Status code', res.status).toBe(200);
|
|
364
362
|
}),
|
|
365
363
|
(0, utils_1.audit)('MUST allow map {variables} parameter when accepting application/json', async () => {
|
|
366
|
-
const res = await fetchFn(opts.url, {
|
|
364
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
367
365
|
method: 'POST',
|
|
368
366
|
headers: {
|
|
369
367
|
'content-type': 'application/json',
|
|
@@ -378,7 +376,7 @@ function serverAudits(opts) {
|
|
|
378
376
|
(0, utils_1.assert)('Execution result', await (0, utils_1.assertBodyAsExecutionResult)(res)).notToHaveProperty('errors');
|
|
379
377
|
}),
|
|
380
378
|
(0, utils_1.audit)('MAY allow URL-encoded JSON string {variables} parameter in GETs when accepting application/graphql-response+json', async () => {
|
|
381
|
-
const url = new URL(opts.url);
|
|
379
|
+
const url = new URL(await getUrl(opts.url));
|
|
382
380
|
url.searchParams.set('query', 'query Type($name: String!) { __type(name: $name) { name } }');
|
|
383
381
|
url.searchParams.set('variables', JSON.stringify({ name: 'sometype' }));
|
|
384
382
|
const res = await fetchFn(url.toString(), {
|
|
@@ -390,7 +388,7 @@ function serverAudits(opts) {
|
|
|
390
388
|
(0, utils_1.assert)('Status code', res.status).toBe(200);
|
|
391
389
|
}),
|
|
392
390
|
(0, utils_1.audit)('MAY allow URL-encoded JSON string {variables} parameter in GETs when accepting application/json', async () => {
|
|
393
|
-
const url = new URL(opts.url);
|
|
391
|
+
const url = new URL(await getUrl(opts.url));
|
|
394
392
|
url.searchParams.set('query', 'query Type($name: String!) { __type(name: $name) { name } }');
|
|
395
393
|
url.searchParams.set('variables', JSON.stringify({ name: 'sometype' }));
|
|
396
394
|
const res = await fetchFn(url.toString(), {
|
|
@@ -405,7 +403,7 @@ function serverAudits(opts) {
|
|
|
405
403
|
...['string', 0, false, ['array']].map((invalid) => (0, utils_1.audit)(
|
|
406
404
|
// TODO: convert to MUST after watershed
|
|
407
405
|
`SHOULD use 400 status code on ${(0, utils_1.extendedTypeof)(invalid)} {extensions} parameter when accepting application/graphql-response+json`, async () => {
|
|
408
|
-
const res = await fetchFn(opts.url, {
|
|
406
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
409
407
|
method: 'POST',
|
|
410
408
|
headers: {
|
|
411
409
|
'content-type': 'application/json',
|
|
@@ -419,7 +417,7 @@ function serverAudits(opts) {
|
|
|
419
417
|
(0, utils_1.assert)('Status code', res.status).toBe(400);
|
|
420
418
|
})),
|
|
421
419
|
...['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 () => {
|
|
422
|
-
const res = await fetchFn(opts.url, {
|
|
420
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
423
421
|
method: 'POST',
|
|
424
422
|
headers: {
|
|
425
423
|
'content-type': 'application/json',
|
|
@@ -436,7 +434,7 @@ function serverAudits(opts) {
|
|
|
436
434
|
(0, utils_1.audit)(
|
|
437
435
|
// TODO: convert to MUST after watershed
|
|
438
436
|
'SHOULD allow map {extensions} parameter when accepting application/graphql-response+json', async () => {
|
|
439
|
-
const res = await fetchFn(opts.url, {
|
|
437
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
440
438
|
method: 'POST',
|
|
441
439
|
headers: {
|
|
442
440
|
'content-type': 'application/json',
|
|
@@ -450,7 +448,7 @@ function serverAudits(opts) {
|
|
|
450
448
|
(0, utils_1.assert)('Status code', res.status).toBe(200);
|
|
451
449
|
}),
|
|
452
450
|
(0, utils_1.audit)('MUST allow map {extensions} parameter when accepting application/json', async () => {
|
|
453
|
-
const res = await fetchFn(opts.url, {
|
|
451
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
454
452
|
method: 'POST',
|
|
455
453
|
headers: {
|
|
456
454
|
'content-type': 'application/json',
|
|
@@ -467,7 +465,7 @@ function serverAudits(opts) {
|
|
|
467
465
|
// TODO: audit('MUST accept a map for the {extensions} parameter'),
|
|
468
466
|
// Response application/json
|
|
469
467
|
(0, utils_1.audit)('SHOULD use 200 status code on JSON parsing failure when accepting application/json', async () => {
|
|
470
|
-
const res = await fetchFn(opts.url, {
|
|
468
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
471
469
|
method: 'POST',
|
|
472
470
|
headers: {
|
|
473
471
|
'content-type': 'application/json',
|
|
@@ -478,7 +476,7 @@ function serverAudits(opts) {
|
|
|
478
476
|
(0, utils_1.assert)('Status code', res.status).toBe(200);
|
|
479
477
|
}),
|
|
480
478
|
(0, utils_1.audit)('SHOULD use 200 status code if parameters are invalid when accepting application/json', async () => {
|
|
481
|
-
const res = await fetchFn(opts.url, {
|
|
479
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
482
480
|
method: 'POST',
|
|
483
481
|
headers: {
|
|
484
482
|
'content-type': 'application/json',
|
|
@@ -491,7 +489,7 @@ function serverAudits(opts) {
|
|
|
491
489
|
(0, utils_1.assert)('Status code', res.status).toBe(200);
|
|
492
490
|
}),
|
|
493
491
|
(0, utils_1.audit)('SHOULD use 200 status code on document parsing failure when accepting application/json', async () => {
|
|
494
|
-
const res = await fetchFn(opts.url, {
|
|
492
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
495
493
|
method: 'POST',
|
|
496
494
|
headers: {
|
|
497
495
|
'content-type': 'application/json',
|
|
@@ -502,7 +500,7 @@ function serverAudits(opts) {
|
|
|
502
500
|
(0, utils_1.assert)('Status code', res.status).toBe(200);
|
|
503
501
|
}),
|
|
504
502
|
(0, utils_1.audit)('SHOULD use 200 status code on document validation failure when accepting application/json', async () => {
|
|
505
|
-
const res = await fetchFn(opts.url, {
|
|
503
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
506
504
|
method: 'POST',
|
|
507
505
|
headers: {
|
|
508
506
|
'content-type': 'application/json',
|
|
@@ -518,7 +516,7 @@ function serverAudits(opts) {
|
|
|
518
516
|
(0, utils_1.audit)(
|
|
519
517
|
// TODO: convert to MUST after watershed
|
|
520
518
|
'SHOULD use 4xx or 5xx status codes on JSON parsing failure when accepting application/graphql-response+json', async () => {
|
|
521
|
-
const res = await fetchFn(opts.url, {
|
|
519
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
522
520
|
method: 'POST',
|
|
523
521
|
headers: {
|
|
524
522
|
'content-type': 'application/json',
|
|
@@ -529,7 +527,7 @@ function serverAudits(opts) {
|
|
|
529
527
|
(0, utils_1.assert)('Status code', res.status).toBeBetween(400, 499);
|
|
530
528
|
}),
|
|
531
529
|
(0, utils_1.audit)('SHOULD use 400 status code on JSON parsing failure when accepting application/graphql-response+json', async () => {
|
|
532
|
-
const res = await fetchFn(opts.url, {
|
|
530
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
533
531
|
method: 'POST',
|
|
534
532
|
headers: {
|
|
535
533
|
'content-type': 'application/json',
|
|
@@ -540,7 +538,7 @@ function serverAudits(opts) {
|
|
|
540
538
|
(0, utils_1.assert)('Status code', res.status).toBe(400);
|
|
541
539
|
}),
|
|
542
540
|
(0, utils_1.audit)('SHOULD not contain the data entry on JSON parsing failure when accepting application/graphql-response+json', async () => {
|
|
543
|
-
const res = await fetchFn(opts.url, {
|
|
541
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
544
542
|
method: 'POST',
|
|
545
543
|
headers: {
|
|
546
544
|
'content-type': 'application/json',
|
|
@@ -553,7 +551,7 @@ function serverAudits(opts) {
|
|
|
553
551
|
(0, utils_1.audit)(
|
|
554
552
|
// TODO: convert to MUST after watershed
|
|
555
553
|
'SHOULD use 4xx or 5xx status codes if parameters are invalid when accepting application/graphql-response+json', async () => {
|
|
556
|
-
const res = await fetchFn(opts.url, {
|
|
554
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
557
555
|
method: 'POST',
|
|
558
556
|
headers: {
|
|
559
557
|
'content-type': 'application/json',
|
|
@@ -566,7 +564,7 @@ function serverAudits(opts) {
|
|
|
566
564
|
(0, utils_1.assert)('Status code', res.status).toBeBetween(400, 599);
|
|
567
565
|
}),
|
|
568
566
|
(0, utils_1.audit)('SHOULD use 400 status code if parameters are invalid when accepting application/graphql-response+json', async () => {
|
|
569
|
-
const res = await fetchFn(opts.url, {
|
|
567
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
570
568
|
method: 'POST',
|
|
571
569
|
headers: {
|
|
572
570
|
'content-type': 'application/json',
|
|
@@ -579,7 +577,7 @@ function serverAudits(opts) {
|
|
|
579
577
|
(0, utils_1.assert)('Status code', res.status).toBe(400);
|
|
580
578
|
}),
|
|
581
579
|
(0, utils_1.audit)('SHOULD not contain the data entry if parameters are invalid when accepting application/graphql-response+json', async () => {
|
|
582
|
-
const res = await fetchFn(opts.url, {
|
|
580
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
583
581
|
method: 'POST',
|
|
584
582
|
headers: {
|
|
585
583
|
'content-type': 'application/json',
|
|
@@ -594,7 +592,7 @@ function serverAudits(opts) {
|
|
|
594
592
|
(0, utils_1.audit)(
|
|
595
593
|
// TODO: convert to MUST after watershed
|
|
596
594
|
'SHOULD use 4xx or 5xx status codes on document parsing failure when accepting application/graphql-response+json', async () => {
|
|
597
|
-
const res = await fetchFn(opts.url, {
|
|
595
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
598
596
|
method: 'POST',
|
|
599
597
|
headers: {
|
|
600
598
|
'content-type': 'application/json',
|
|
@@ -607,7 +605,7 @@ function serverAudits(opts) {
|
|
|
607
605
|
(0, utils_1.assert)('Status code', res.status).toBeBetween(400, 599);
|
|
608
606
|
}),
|
|
609
607
|
(0, utils_1.audit)('SHOULD use 400 status code on document parsing failure when accepting application/graphql-response+json', async () => {
|
|
610
|
-
const res = await fetchFn(opts.url, {
|
|
608
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
611
609
|
method: 'POST',
|
|
612
610
|
headers: {
|
|
613
611
|
'content-type': 'application/json',
|
|
@@ -620,7 +618,7 @@ function serverAudits(opts) {
|
|
|
620
618
|
(0, utils_1.assert)('Status code', res.status).toBe(400);
|
|
621
619
|
}),
|
|
622
620
|
(0, utils_1.audit)('SHOULD not contain the data entry on document parsing failure when accepting application/graphql-response+json', async () => {
|
|
623
|
-
const res = await fetchFn(opts.url, {
|
|
621
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
624
622
|
method: 'POST',
|
|
625
623
|
headers: {
|
|
626
624
|
'content-type': 'application/json',
|
|
@@ -635,7 +633,7 @@ function serverAudits(opts) {
|
|
|
635
633
|
(0, utils_1.audit)(
|
|
636
634
|
// TODO: convert to MUST after watershed
|
|
637
635
|
'SHOULD use 4xx or 5xx status codes on document validation failure when accepting application/graphql-response+json', async () => {
|
|
638
|
-
const res = await fetchFn(opts.url, {
|
|
636
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
639
637
|
method: 'POST',
|
|
640
638
|
headers: {
|
|
641
639
|
'content-type': 'application/json',
|
|
@@ -648,7 +646,7 @@ function serverAudits(opts) {
|
|
|
648
646
|
(0, utils_1.assert)('Status code', res.status).toBeBetween(400, 599);
|
|
649
647
|
}),
|
|
650
648
|
(0, utils_1.audit)('SHOULD use 400 status code on document validation failure when accepting application/graphql-response+json', async () => {
|
|
651
|
-
const res = await fetchFn(opts.url, {
|
|
649
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
652
650
|
method: 'POST',
|
|
653
651
|
headers: {
|
|
654
652
|
'content-type': 'application/json',
|
|
@@ -661,7 +659,7 @@ function serverAudits(opts) {
|
|
|
661
659
|
(0, utils_1.assert)('Status code', res.status).toBe(400);
|
|
662
660
|
}),
|
|
663
661
|
(0, utils_1.audit)('SHOULD not contain the data entry on document validation failure when accepting application/graphql-response+json', async () => {
|
|
664
|
-
const res = await fetchFn(opts.url, {
|
|
662
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
665
663
|
method: 'POST',
|
|
666
664
|
headers: {
|
|
667
665
|
'content-type': 'application/json',
|
|
@@ -694,3 +692,10 @@ async function auditServer(opts) {
|
|
|
694
692
|
return await Promise.all(audits.map(({ fn }) => fn()));
|
|
695
693
|
}
|
|
696
694
|
exports.auditServer = auditServer;
|
|
695
|
+
/** @private */
|
|
696
|
+
async function getUrl(url) {
|
|
697
|
+
if (typeof url === 'string') {
|
|
698
|
+
return url;
|
|
699
|
+
}
|
|
700
|
+
return await url();
|
|
701
|
+
}
|
package/lib/audits/server.mjs
CHANGED
|
@@ -16,7 +16,7 @@ export function serverAudits(opts) {
|
|
|
16
16
|
audit(
|
|
17
17
|
// TODO: convert to MUST after watershed
|
|
18
18
|
'SHOULD accept application/graphql-response+json and match the content-type', async () => {
|
|
19
|
-
const res = await fetchFn(opts.url, {
|
|
19
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
20
20
|
method: 'POST',
|
|
21
21
|
headers: {
|
|
22
22
|
'content-type': 'application/json',
|
|
@@ -28,7 +28,7 @@ export function serverAudits(opts) {
|
|
|
28
28
|
assert('Content-Type header', res.headers.get('content-type')).toContain('application/graphql-response+json');
|
|
29
29
|
}),
|
|
30
30
|
audit('MUST accept application/json and match the content-type', async () => {
|
|
31
|
-
const res = await fetchFn(opts.url, {
|
|
31
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
32
32
|
method: 'POST',
|
|
33
33
|
headers: {
|
|
34
34
|
'content-type': 'application/json',
|
|
@@ -39,10 +39,8 @@ export function serverAudits(opts) {
|
|
|
39
39
|
assert('Status code', res.status).toBe(200);
|
|
40
40
|
assert('Content-Type header', res.headers.get('content-type')).toContain('application/json');
|
|
41
41
|
}),
|
|
42
|
-
audit(
|
|
43
|
-
|
|
44
|
-
'SHOULD accept */* and use application/graphql-response+json for the content-type', async () => {
|
|
45
|
-
const res = await fetchFn(opts.url, {
|
|
42
|
+
audit('SHOULD accept */* and use application/json for the content-type', async () => {
|
|
43
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
46
44
|
method: 'POST',
|
|
47
45
|
headers: {
|
|
48
46
|
'content-type': 'application/json',
|
|
@@ -51,23 +49,17 @@ export function serverAudits(opts) {
|
|
|
51
49
|
body: JSON.stringify({ query: '{ __typename }' }),
|
|
52
50
|
});
|
|
53
51
|
assert('Status code', res.status).toBe(200);
|
|
54
|
-
assert('Content-Type header', res.headers.get('content-type')).toContain('application/
|
|
52
|
+
assert('Content-Type header', res.headers.get('content-type')).toContain('application/json');
|
|
55
53
|
}),
|
|
56
|
-
audit(
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const res = await fetchFn(
|
|
60
|
-
method: 'POST',
|
|
61
|
-
headers: {
|
|
62
|
-
'content-type': 'application/json',
|
|
63
|
-
},
|
|
64
|
-
body: JSON.stringify({ query: '{ __typename }' }),
|
|
65
|
-
});
|
|
54
|
+
audit('SHOULD assume application/json content-type when accept is missing', async () => {
|
|
55
|
+
const url = new URL(await getUrl(opts.url));
|
|
56
|
+
url.searchParams.set('query', '{ __typename }');
|
|
57
|
+
const res = await fetchFn(url.toString());
|
|
66
58
|
assert('Status code', res.status).toBe(200);
|
|
67
|
-
assert('Content-Type header', res.headers.get('content-type')).toContain('application/
|
|
59
|
+
assert('Content-Type header', res.headers.get('content-type')).toContain('application/json');
|
|
68
60
|
}),
|
|
69
61
|
audit('MUST use utf-8 encoding when responding', async () => {
|
|
70
|
-
const res = await fetchFn(opts.url, {
|
|
62
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
71
63
|
method: 'POST',
|
|
72
64
|
headers: {
|
|
73
65
|
'content-type': 'application/json',
|
|
@@ -96,7 +88,7 @@ export function serverAudits(opts) {
|
|
|
96
88
|
}
|
|
97
89
|
}),
|
|
98
90
|
audit('MUST accept utf-8 encoding', async () => {
|
|
99
|
-
const res = await fetchFn(opts.url, {
|
|
91
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
100
92
|
method: 'POST',
|
|
101
93
|
headers: {
|
|
102
94
|
'content-type': 'application/json; charset=utf-8',
|
|
@@ -107,7 +99,7 @@ export function serverAudits(opts) {
|
|
|
107
99
|
assert('Content-Type header', res.headers.get('content-type')).toContain('utf-8');
|
|
108
100
|
}),
|
|
109
101
|
audit('MUST assume utf-8 if encoding is unspecified', async () => {
|
|
110
|
-
const res = await fetchFn(opts.url, {
|
|
102
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
111
103
|
method: 'POST',
|
|
112
104
|
headers: {
|
|
113
105
|
'content-type': 'application/json',
|
|
@@ -119,7 +111,7 @@ export function serverAudits(opts) {
|
|
|
119
111
|
}),
|
|
120
112
|
// Request
|
|
121
113
|
audit('MUST accept POST requests', async () => {
|
|
122
|
-
const res = await fetchFn(opts.url, {
|
|
114
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
123
115
|
method: 'POST',
|
|
124
116
|
headers: { 'content-type': 'application/json' },
|
|
125
117
|
body: JSON.stringify({ query: '{ __typename }' }),
|
|
@@ -127,7 +119,7 @@ export function serverAudits(opts) {
|
|
|
127
119
|
assert('Status code', res.status).toBe(200);
|
|
128
120
|
}),
|
|
129
121
|
audit('MAY accept application/x-www-form-urlencoded formatted GET requests', async () => {
|
|
130
|
-
const url = new URL(opts.url);
|
|
122
|
+
const url = new URL(await getUrl(opts.url));
|
|
131
123
|
url.searchParams.set('query', '{ __typename }');
|
|
132
124
|
const res = await fetchFn(url.toString());
|
|
133
125
|
assert('Status code', res.status).toBe(200);
|
|
@@ -135,7 +127,7 @@ export function serverAudits(opts) {
|
|
|
135
127
|
// Request GET
|
|
136
128
|
// TODO: this is a MUST if the server supports GET requests
|
|
137
129
|
audit('MAY NOT allow executing mutations on GET requests', async () => {
|
|
138
|
-
const url = new URL(opts.url);
|
|
130
|
+
const url = new URL(await getUrl(opts.url));
|
|
139
131
|
url.searchParams.set('query', 'mutation { __typename }');
|
|
140
132
|
const res = await fetchFn(url.toString(), {
|
|
141
133
|
headers: {
|
|
@@ -146,13 +138,13 @@ export function serverAudits(opts) {
|
|
|
146
138
|
}),
|
|
147
139
|
// Request POST
|
|
148
140
|
audit('SHOULD respond with 4xx status code if content-type is not supplied on POST requests', async () => {
|
|
149
|
-
const res = await fetchFn(opts.url, {
|
|
141
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
150
142
|
method: 'POST',
|
|
151
143
|
});
|
|
152
144
|
assert('Status code', res.status).toBeBetween(400, 499);
|
|
153
145
|
}),
|
|
154
146
|
audit('MUST accept application/json POST requests', async () => {
|
|
155
|
-
const res = await fetchFn(opts.url, {
|
|
147
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
156
148
|
method: 'POST',
|
|
157
149
|
headers: { 'content-type': 'application/json' },
|
|
158
150
|
body: JSON.stringify({ query: '{ __typename }' }),
|
|
@@ -160,17 +152,23 @@ export function serverAudits(opts) {
|
|
|
160
152
|
assert('Status code', res.status).toBe(200);
|
|
161
153
|
}),
|
|
162
154
|
audit('MUST require a request body on POST', async () => {
|
|
163
|
-
|
|
155
|
+
var _a;
|
|
156
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
164
157
|
method: 'POST',
|
|
165
158
|
headers: { 'content-type': 'application/json' },
|
|
166
159
|
});
|
|
167
|
-
|
|
160
|
+
if ((_a = res.headers.get('content-type')) === null || _a === void 0 ? void 0 : _a.includes('application/json')) {
|
|
161
|
+
assert('Execution result', await assertBodyAsExecutionResult(res)).toHaveProperty('errors');
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
assert('Status code', res.status).toBe(400);
|
|
165
|
+
}
|
|
168
166
|
}),
|
|
169
167
|
// Request Parameters
|
|
170
168
|
audit(
|
|
171
169
|
// TODO: convert to MUST after watershed
|
|
172
170
|
'SHOULD use 400 status code on missing {query} parameter when accepting application/graphql-response+json', async () => {
|
|
173
|
-
const res = await fetchFn(opts.url, {
|
|
171
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
174
172
|
method: 'POST',
|
|
175
173
|
headers: {
|
|
176
174
|
'content-type': 'application/json',
|
|
@@ -181,7 +179,7 @@ export function serverAudits(opts) {
|
|
|
181
179
|
assert('Status code', res.status).toBe(400);
|
|
182
180
|
}),
|
|
183
181
|
audit('SHOULD use 200 status code with errors field on missing {query} parameter when accepting application/json', async () => {
|
|
184
|
-
const res = await fetchFn(opts.url, {
|
|
182
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
185
183
|
method: 'POST',
|
|
186
184
|
headers: {
|
|
187
185
|
'content-type': 'application/json',
|
|
@@ -195,7 +193,7 @@ export function serverAudits(opts) {
|
|
|
195
193
|
...[{ obj: 'ect' }, 0, false, ['array']].map((invalid) => audit(
|
|
196
194
|
// TODO: convert to MUST after watershed
|
|
197
195
|
`SHOULD use 400 status code on ${extendedTypeof(invalid)} {query} parameter when accepting application/graphql-response+json`, async () => {
|
|
198
|
-
const res = await fetchFn(opts.url, {
|
|
196
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
199
197
|
method: 'POST',
|
|
200
198
|
headers: {
|
|
201
199
|
'content-type': 'application/json',
|
|
@@ -208,7 +206,7 @@ export function serverAudits(opts) {
|
|
|
208
206
|
assert('Status code', res.status).toBe(400);
|
|
209
207
|
})),
|
|
210
208
|
...[{ 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 () => {
|
|
211
|
-
const res = await fetchFn(opts.url, {
|
|
209
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
212
210
|
method: 'POST',
|
|
213
211
|
headers: {
|
|
214
212
|
'content-type': 'application/json',
|
|
@@ -224,7 +222,7 @@ export function serverAudits(opts) {
|
|
|
224
222
|
audit(
|
|
225
223
|
// TODO: convert to MUST after watershed
|
|
226
224
|
'SHOULD allow string {query} parameter when accepting application/graphql-response+json', async () => {
|
|
227
|
-
const res = await fetchFn(opts.url, {
|
|
225
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
228
226
|
method: 'POST',
|
|
229
227
|
headers: {
|
|
230
228
|
'content-type': 'application/json',
|
|
@@ -237,7 +235,7 @@ export function serverAudits(opts) {
|
|
|
237
235
|
assert('Status code', res.status).toBe(200);
|
|
238
236
|
}),
|
|
239
237
|
audit('MUST allow string {query} parameter when accepting application/json', async () => {
|
|
240
|
-
const res = await fetchFn(opts.url, {
|
|
238
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
241
239
|
method: 'POST',
|
|
242
240
|
headers: {
|
|
243
241
|
'content-type': 'application/json',
|
|
@@ -253,7 +251,7 @@ export function serverAudits(opts) {
|
|
|
253
251
|
...[{ obj: 'ect' }, 0, false, ['array']].map((invalid) => audit(
|
|
254
252
|
// TODO: convert to MUST after watershed
|
|
255
253
|
`SHOULD use 400 status code on ${extendedTypeof(invalid)} {operationName} parameter when accepting application/graphql-response+json`, async () => {
|
|
256
|
-
const res = await fetchFn(opts.url, {
|
|
254
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
257
255
|
method: 'POST',
|
|
258
256
|
headers: {
|
|
259
257
|
'content-type': 'application/json',
|
|
@@ -267,7 +265,7 @@ export function serverAudits(opts) {
|
|
|
267
265
|
assert('Status code', res.status).toBe(400);
|
|
268
266
|
})),
|
|
269
267
|
...[{ 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 () => {
|
|
270
|
-
const res = await fetchFn(opts.url, {
|
|
268
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
271
269
|
method: 'POST',
|
|
272
270
|
headers: {
|
|
273
271
|
'content-type': 'application/json',
|
|
@@ -284,7 +282,7 @@ export function serverAudits(opts) {
|
|
|
284
282
|
audit(
|
|
285
283
|
// TODO: convert to MUST after watershed
|
|
286
284
|
'SHOULD allow string {operationName} parameter when accepting application/graphql-response+json', async () => {
|
|
287
|
-
const res = await fetchFn(opts.url, {
|
|
285
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
288
286
|
method: 'POST',
|
|
289
287
|
headers: {
|
|
290
288
|
'content-type': 'application/json',
|
|
@@ -298,7 +296,7 @@ export function serverAudits(opts) {
|
|
|
298
296
|
assert('Status code', res.status).toBe(200);
|
|
299
297
|
}),
|
|
300
298
|
audit('MUST allow string {operationName} parameter when accepting application/json', async () => {
|
|
301
|
-
const res = await fetchFn(opts.url, {
|
|
299
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
302
300
|
method: 'POST',
|
|
303
301
|
headers: {
|
|
304
302
|
'content-type': 'application/json',
|
|
@@ -315,7 +313,7 @@ export function serverAudits(opts) {
|
|
|
315
313
|
...['string', 0, false, ['array']].map((invalid) => audit(
|
|
316
314
|
// TODO: convert to MUST after watershed
|
|
317
315
|
`SHOULD use 400 status code on ${extendedTypeof(invalid)} {variables} parameter when accepting application/graphql-response+json`, async () => {
|
|
318
|
-
const res = await fetchFn(opts.url, {
|
|
316
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
319
317
|
method: 'POST',
|
|
320
318
|
headers: {
|
|
321
319
|
'content-type': 'application/json',
|
|
@@ -329,7 +327,7 @@ export function serverAudits(opts) {
|
|
|
329
327
|
assert('Status code', res.status).toBe(400);
|
|
330
328
|
})),
|
|
331
329
|
...['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 () => {
|
|
332
|
-
const res = await fetchFn(opts.url, {
|
|
330
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
333
331
|
method: 'POST',
|
|
334
332
|
headers: {
|
|
335
333
|
'content-type': 'application/json',
|
|
@@ -346,7 +344,7 @@ export function serverAudits(opts) {
|
|
|
346
344
|
audit(
|
|
347
345
|
// TODO: convert to MUST after watershed
|
|
348
346
|
'SHOULD allow map {variables} parameter when accepting application/graphql-response+json', async () => {
|
|
349
|
-
const res = await fetchFn(opts.url, {
|
|
347
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
350
348
|
method: 'POST',
|
|
351
349
|
headers: {
|
|
352
350
|
'content-type': 'application/json',
|
|
@@ -360,7 +358,7 @@ export function serverAudits(opts) {
|
|
|
360
358
|
assert('Status code', res.status).toBe(200);
|
|
361
359
|
}),
|
|
362
360
|
audit('MUST allow map {variables} parameter when accepting application/json', async () => {
|
|
363
|
-
const res = await fetchFn(opts.url, {
|
|
361
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
364
362
|
method: 'POST',
|
|
365
363
|
headers: {
|
|
366
364
|
'content-type': 'application/json',
|
|
@@ -375,7 +373,7 @@ export function serverAudits(opts) {
|
|
|
375
373
|
assert('Execution result', await assertBodyAsExecutionResult(res)).notToHaveProperty('errors');
|
|
376
374
|
}),
|
|
377
375
|
audit('MAY allow URL-encoded JSON string {variables} parameter in GETs when accepting application/graphql-response+json', async () => {
|
|
378
|
-
const url = new URL(opts.url);
|
|
376
|
+
const url = new URL(await getUrl(opts.url));
|
|
379
377
|
url.searchParams.set('query', 'query Type($name: String!) { __type(name: $name) { name } }');
|
|
380
378
|
url.searchParams.set('variables', JSON.stringify({ name: 'sometype' }));
|
|
381
379
|
const res = await fetchFn(url.toString(), {
|
|
@@ -387,7 +385,7 @@ export function serverAudits(opts) {
|
|
|
387
385
|
assert('Status code', res.status).toBe(200);
|
|
388
386
|
}),
|
|
389
387
|
audit('MAY allow URL-encoded JSON string {variables} parameter in GETs when accepting application/json', async () => {
|
|
390
|
-
const url = new URL(opts.url);
|
|
388
|
+
const url = new URL(await getUrl(opts.url));
|
|
391
389
|
url.searchParams.set('query', 'query Type($name: String!) { __type(name: $name) { name } }');
|
|
392
390
|
url.searchParams.set('variables', JSON.stringify({ name: 'sometype' }));
|
|
393
391
|
const res = await fetchFn(url.toString(), {
|
|
@@ -402,7 +400,7 @@ export function serverAudits(opts) {
|
|
|
402
400
|
...['string', 0, false, ['array']].map((invalid) => audit(
|
|
403
401
|
// TODO: convert to MUST after watershed
|
|
404
402
|
`SHOULD use 400 status code on ${extendedTypeof(invalid)} {extensions} parameter when accepting application/graphql-response+json`, async () => {
|
|
405
|
-
const res = await fetchFn(opts.url, {
|
|
403
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
406
404
|
method: 'POST',
|
|
407
405
|
headers: {
|
|
408
406
|
'content-type': 'application/json',
|
|
@@ -416,7 +414,7 @@ export function serverAudits(opts) {
|
|
|
416
414
|
assert('Status code', res.status).toBe(400);
|
|
417
415
|
})),
|
|
418
416
|
...['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 () => {
|
|
419
|
-
const res = await fetchFn(opts.url, {
|
|
417
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
420
418
|
method: 'POST',
|
|
421
419
|
headers: {
|
|
422
420
|
'content-type': 'application/json',
|
|
@@ -433,7 +431,7 @@ export function serverAudits(opts) {
|
|
|
433
431
|
audit(
|
|
434
432
|
// TODO: convert to MUST after watershed
|
|
435
433
|
'SHOULD allow map {extensions} parameter when accepting application/graphql-response+json', async () => {
|
|
436
|
-
const res = await fetchFn(opts.url, {
|
|
434
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
437
435
|
method: 'POST',
|
|
438
436
|
headers: {
|
|
439
437
|
'content-type': 'application/json',
|
|
@@ -447,7 +445,7 @@ export function serverAudits(opts) {
|
|
|
447
445
|
assert('Status code', res.status).toBe(200);
|
|
448
446
|
}),
|
|
449
447
|
audit('MUST allow map {extensions} parameter when accepting application/json', async () => {
|
|
450
|
-
const res = await fetchFn(opts.url, {
|
|
448
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
451
449
|
method: 'POST',
|
|
452
450
|
headers: {
|
|
453
451
|
'content-type': 'application/json',
|
|
@@ -464,7 +462,7 @@ export function serverAudits(opts) {
|
|
|
464
462
|
// TODO: audit('MUST accept a map for the {extensions} parameter'),
|
|
465
463
|
// Response application/json
|
|
466
464
|
audit('SHOULD use 200 status code on JSON parsing failure when accepting application/json', async () => {
|
|
467
|
-
const res = await fetchFn(opts.url, {
|
|
465
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
468
466
|
method: 'POST',
|
|
469
467
|
headers: {
|
|
470
468
|
'content-type': 'application/json',
|
|
@@ -475,7 +473,7 @@ export function serverAudits(opts) {
|
|
|
475
473
|
assert('Status code', res.status).toBe(200);
|
|
476
474
|
}),
|
|
477
475
|
audit('SHOULD use 200 status code if parameters are invalid when accepting application/json', async () => {
|
|
478
|
-
const res = await fetchFn(opts.url, {
|
|
476
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
479
477
|
method: 'POST',
|
|
480
478
|
headers: {
|
|
481
479
|
'content-type': 'application/json',
|
|
@@ -488,7 +486,7 @@ export function serverAudits(opts) {
|
|
|
488
486
|
assert('Status code', res.status).toBe(200);
|
|
489
487
|
}),
|
|
490
488
|
audit('SHOULD use 200 status code on document parsing failure when accepting application/json', async () => {
|
|
491
|
-
const res = await fetchFn(opts.url, {
|
|
489
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
492
490
|
method: 'POST',
|
|
493
491
|
headers: {
|
|
494
492
|
'content-type': 'application/json',
|
|
@@ -499,7 +497,7 @@ export function serverAudits(opts) {
|
|
|
499
497
|
assert('Status code', res.status).toBe(200);
|
|
500
498
|
}),
|
|
501
499
|
audit('SHOULD use 200 status code on document validation failure when accepting application/json', async () => {
|
|
502
|
-
const res = await fetchFn(opts.url, {
|
|
500
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
503
501
|
method: 'POST',
|
|
504
502
|
headers: {
|
|
505
503
|
'content-type': 'application/json',
|
|
@@ -515,7 +513,7 @@ export function serverAudits(opts) {
|
|
|
515
513
|
audit(
|
|
516
514
|
// TODO: convert to MUST after watershed
|
|
517
515
|
'SHOULD use 4xx or 5xx status codes on JSON parsing failure when accepting application/graphql-response+json', async () => {
|
|
518
|
-
const res = await fetchFn(opts.url, {
|
|
516
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
519
517
|
method: 'POST',
|
|
520
518
|
headers: {
|
|
521
519
|
'content-type': 'application/json',
|
|
@@ -526,7 +524,7 @@ export function serverAudits(opts) {
|
|
|
526
524
|
assert('Status code', res.status).toBeBetween(400, 499);
|
|
527
525
|
}),
|
|
528
526
|
audit('SHOULD use 400 status code on JSON parsing failure when accepting application/graphql-response+json', async () => {
|
|
529
|
-
const res = await fetchFn(opts.url, {
|
|
527
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
530
528
|
method: 'POST',
|
|
531
529
|
headers: {
|
|
532
530
|
'content-type': 'application/json',
|
|
@@ -537,7 +535,7 @@ export function serverAudits(opts) {
|
|
|
537
535
|
assert('Status code', res.status).toBe(400);
|
|
538
536
|
}),
|
|
539
537
|
audit('SHOULD not contain the data entry on JSON parsing failure when accepting application/graphql-response+json', async () => {
|
|
540
|
-
const res = await fetchFn(opts.url, {
|
|
538
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
541
539
|
method: 'POST',
|
|
542
540
|
headers: {
|
|
543
541
|
'content-type': 'application/json',
|
|
@@ -550,7 +548,7 @@ export function serverAudits(opts) {
|
|
|
550
548
|
audit(
|
|
551
549
|
// TODO: convert to MUST after watershed
|
|
552
550
|
'SHOULD use 4xx or 5xx status codes if parameters are invalid when accepting application/graphql-response+json', async () => {
|
|
553
|
-
const res = await fetchFn(opts.url, {
|
|
551
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
554
552
|
method: 'POST',
|
|
555
553
|
headers: {
|
|
556
554
|
'content-type': 'application/json',
|
|
@@ -563,7 +561,7 @@ export function serverAudits(opts) {
|
|
|
563
561
|
assert('Status code', res.status).toBeBetween(400, 599);
|
|
564
562
|
}),
|
|
565
563
|
audit('SHOULD use 400 status code if parameters are invalid when accepting application/graphql-response+json', async () => {
|
|
566
|
-
const res = await fetchFn(opts.url, {
|
|
564
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
567
565
|
method: 'POST',
|
|
568
566
|
headers: {
|
|
569
567
|
'content-type': 'application/json',
|
|
@@ -576,7 +574,7 @@ export function serverAudits(opts) {
|
|
|
576
574
|
assert('Status code', res.status).toBe(400);
|
|
577
575
|
}),
|
|
578
576
|
audit('SHOULD not contain the data entry if parameters are invalid when accepting application/graphql-response+json', async () => {
|
|
579
|
-
const res = await fetchFn(opts.url, {
|
|
577
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
580
578
|
method: 'POST',
|
|
581
579
|
headers: {
|
|
582
580
|
'content-type': 'application/json',
|
|
@@ -591,7 +589,7 @@ export function serverAudits(opts) {
|
|
|
591
589
|
audit(
|
|
592
590
|
// TODO: convert to MUST after watershed
|
|
593
591
|
'SHOULD use 4xx or 5xx status codes on document parsing failure when accepting application/graphql-response+json', async () => {
|
|
594
|
-
const res = await fetchFn(opts.url, {
|
|
592
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
595
593
|
method: 'POST',
|
|
596
594
|
headers: {
|
|
597
595
|
'content-type': 'application/json',
|
|
@@ -604,7 +602,7 @@ export function serverAudits(opts) {
|
|
|
604
602
|
assert('Status code', res.status).toBeBetween(400, 599);
|
|
605
603
|
}),
|
|
606
604
|
audit('SHOULD use 400 status code on document parsing failure when accepting application/graphql-response+json', async () => {
|
|
607
|
-
const res = await fetchFn(opts.url, {
|
|
605
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
608
606
|
method: 'POST',
|
|
609
607
|
headers: {
|
|
610
608
|
'content-type': 'application/json',
|
|
@@ -617,7 +615,7 @@ export function serverAudits(opts) {
|
|
|
617
615
|
assert('Status code', res.status).toBe(400);
|
|
618
616
|
}),
|
|
619
617
|
audit('SHOULD not contain the data entry on document parsing failure when accepting application/graphql-response+json', async () => {
|
|
620
|
-
const res = await fetchFn(opts.url, {
|
|
618
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
621
619
|
method: 'POST',
|
|
622
620
|
headers: {
|
|
623
621
|
'content-type': 'application/json',
|
|
@@ -632,7 +630,7 @@ export function serverAudits(opts) {
|
|
|
632
630
|
audit(
|
|
633
631
|
// TODO: convert to MUST after watershed
|
|
634
632
|
'SHOULD use 4xx or 5xx status codes on document validation failure when accepting application/graphql-response+json', async () => {
|
|
635
|
-
const res = await fetchFn(opts.url, {
|
|
633
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
636
634
|
method: 'POST',
|
|
637
635
|
headers: {
|
|
638
636
|
'content-type': 'application/json',
|
|
@@ -645,7 +643,7 @@ export function serverAudits(opts) {
|
|
|
645
643
|
assert('Status code', res.status).toBeBetween(400, 599);
|
|
646
644
|
}),
|
|
647
645
|
audit('SHOULD use 400 status code on document validation failure when accepting application/graphql-response+json', async () => {
|
|
648
|
-
const res = await fetchFn(opts.url, {
|
|
646
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
649
647
|
method: 'POST',
|
|
650
648
|
headers: {
|
|
651
649
|
'content-type': 'application/json',
|
|
@@ -658,7 +656,7 @@ export function serverAudits(opts) {
|
|
|
658
656
|
assert('Status code', res.status).toBe(400);
|
|
659
657
|
}),
|
|
660
658
|
audit('SHOULD not contain the data entry on document validation failure when accepting application/graphql-response+json', async () => {
|
|
661
|
-
const res = await fetchFn(opts.url, {
|
|
659
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
662
660
|
method: 'POST',
|
|
663
661
|
headers: {
|
|
664
662
|
'content-type': 'application/json',
|
|
@@ -689,3 +687,10 @@ export async function auditServer(opts) {
|
|
|
689
687
|
// audit tests will throw only on fatal errors, tests are contained within the AuditResult
|
|
690
688
|
return await Promise.all(audits.map(({ fn }) => fn()));
|
|
691
689
|
}
|
|
690
|
+
/** @private */
|
|
691
|
+
async function getUrl(url) {
|
|
692
|
+
if (typeof url === 'string') {
|
|
693
|
+
return url;
|
|
694
|
+
}
|
|
695
|
+
return await url();
|
|
696
|
+
}
|
package/lib/handler.js
CHANGED
|
@@ -296,15 +296,16 @@ function getAcceptableMediaType(acceptHeader) {
|
|
|
296
296
|
// TODO: handle the weight parameter "q"
|
|
297
297
|
const [mediaType, ...params] = accept.split(';');
|
|
298
298
|
const charset = (params === null || params === void 0 ? void 0 : params.find((param) => param.includes('charset='))) || 'charset=utf8'; // utf-8 is assumed when not specified;
|
|
299
|
-
if (mediaType === 'application/json' &&
|
|
300
|
-
|
|
299
|
+
if (mediaType === 'application/graphql-response+json' &&
|
|
300
|
+
charset === 'charset=utf8') {
|
|
301
|
+
acceptedMediaType = 'application/graphql-response+json';
|
|
301
302
|
break;
|
|
302
303
|
}
|
|
303
|
-
if ((mediaType === 'application/
|
|
304
|
+
if ((mediaType === 'application/json' ||
|
|
304
305
|
mediaType === 'application/*' ||
|
|
305
306
|
mediaType === '*/*') &&
|
|
306
307
|
charset === 'charset=utf8') {
|
|
307
|
-
acceptedMediaType = 'application/
|
|
308
|
+
acceptedMediaType = 'application/json';
|
|
308
309
|
break;
|
|
309
310
|
}
|
|
310
311
|
}
|
package/lib/handler.mjs
CHANGED
|
@@ -291,15 +291,16 @@ export function getAcceptableMediaType(acceptHeader) {
|
|
|
291
291
|
// TODO: handle the weight parameter "q"
|
|
292
292
|
const [mediaType, ...params] = accept.split(';');
|
|
293
293
|
const charset = (params === null || params === void 0 ? void 0 : params.find((param) => param.includes('charset='))) || 'charset=utf8'; // utf-8 is assumed when not specified;
|
|
294
|
-
if (mediaType === 'application/json' &&
|
|
295
|
-
|
|
294
|
+
if (mediaType === 'application/graphql-response+json' &&
|
|
295
|
+
charset === 'charset=utf8') {
|
|
296
|
+
acceptedMediaType = 'application/graphql-response+json';
|
|
296
297
|
break;
|
|
297
298
|
}
|
|
298
|
-
if ((mediaType === 'application/
|
|
299
|
+
if ((mediaType === 'application/json' ||
|
|
299
300
|
mediaType === 'application/*' ||
|
|
300
301
|
mediaType === '*/*') &&
|
|
301
302
|
charset === 'charset=utf8') {
|
|
302
|
-
acceptedMediaType = 'application/
|
|
303
|
+
acceptedMediaType = 'application/json';
|
|
303
304
|
break;
|
|
304
305
|
}
|
|
305
306
|
}
|
package/package.json
CHANGED
|
Binary file
|