graphql-http 1.7.0 → 1.7.2
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 +9 -8
- package/lib/audits/server.js +152 -103
- package/lib/audits/server.mjs +152 -103
- package/lib/audits/utils.d.mts +1 -2
- package/lib/audits/utils.d.ts +1 -2
- package/lib/audits/utils.js +3 -8
- package/lib/audits/utils.mjs +3 -8
- package/lib/handler.js +5 -4
- package/lib/handler.mjs +5 -4
- package/package.json +2 -2
- package/umd/graphql-http.min.js.gz +0 -0
package/README.md
CHANGED
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
|
|
8
8
|
[](https://github.com/graphql/graphql-http/actions?query=workflow%3A%22Continuous+integration%22) [](https://www.npmjs.com/package/graphql-http)
|
|
9
9
|
|
|
10
|
-
<i>
|
|
10
|
+
<i>Want a full-featured server? See the <b>[servers section](#servers)</b>!</i>
|
|
11
11
|
|
|
12
|
-
<i>
|
|
12
|
+
<i>Need subscriptions? Try <b>[graphql-ws](https://github.com/enisdenjo/graphql-ws)</b> or <b>[graphql-sse](https://github.com/enisdenjo/graphql-sse)</b> instead!</i>
|
|
13
13
|
|
|
14
14
|
<br />
|
|
15
15
|
</div>
|
|
@@ -729,12 +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
|
|
733
|
-
|
|
|
734
|
-
| [graphql-yoga](https://www.the-guild.dev/graphql/yoga-server)
|
|
735
|
-
| [
|
|
736
|
-
| [
|
|
737
|
-
| [
|
|
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) |
|
|
738
739
|
|
|
739
740
|
## [Documentation](docs/)
|
|
740
741
|
|
package/lib/audits/server.js
CHANGED
|
@@ -19,53 +19,56 @@ 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
|
|
23
|
-
|
|
24
|
-
const res = await fetchFn(url.toString(), {
|
|
22
|
+
const res = await fetchFn(opts.url, {
|
|
23
|
+
method: 'POST',
|
|
25
24
|
headers: {
|
|
25
|
+
'content-type': 'application/json',
|
|
26
26
|
accept: 'application/graphql-response+json',
|
|
27
27
|
},
|
|
28
|
+
body: JSON.stringify({ query: '{ __typename }' }),
|
|
28
29
|
});
|
|
29
30
|
(0, utils_1.assert)('Status code', res.status).toBe(200);
|
|
30
31
|
(0, utils_1.assert)('Content-Type header', res.headers.get('content-type')).toContain('application/graphql-response+json');
|
|
31
32
|
}),
|
|
32
33
|
(0, utils_1.audit)('MUST accept application/json and match the content-type', async () => {
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
const res = await fetchFn(url.toString(), {
|
|
34
|
+
const res = await fetchFn(opts.url, {
|
|
35
|
+
method: 'POST',
|
|
36
36
|
headers: {
|
|
37
|
+
'content-type': 'application/json',
|
|
37
38
|
accept: 'application/json',
|
|
38
39
|
},
|
|
40
|
+
body: JSON.stringify({ query: '{ __typename }' }),
|
|
39
41
|
});
|
|
40
42
|
(0, utils_1.assert)('Status code', res.status).toBe(200);
|
|
41
43
|
(0, utils_1.assert)('Content-Type header', res.headers.get('content-type')).toContain('application/json');
|
|
42
44
|
}),
|
|
43
|
-
(0, utils_1.audit)(
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const url = new URL(opts.url);
|
|
47
|
-
url.searchParams.set('query', '{ __typename }');
|
|
48
|
-
const res = await fetchFn(url.toString(), {
|
|
45
|
+
(0, utils_1.audit)('SHOULD accept */* and use application/json for the content-type', async () => {
|
|
46
|
+
const res = await fetchFn(opts.url, {
|
|
47
|
+
method: 'POST',
|
|
49
48
|
headers: {
|
|
49
|
+
'content-type': 'application/json',
|
|
50
50
|
accept: '*/*',
|
|
51
51
|
},
|
|
52
|
+
body: JSON.stringify({ query: '{ __typename }' }),
|
|
52
53
|
});
|
|
53
54
|
(0, utils_1.assert)('Status code', res.status).toBe(200);
|
|
54
|
-
(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');
|
|
55
56
|
}),
|
|
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 () => {
|
|
57
|
+
(0, utils_1.audit)('SHOULD assume application/json content-type when accept is missing', async () => {
|
|
59
58
|
const url = new URL(opts.url);
|
|
60
59
|
url.searchParams.set('query', '{ __typename }');
|
|
61
60
|
const res = await fetchFn(url.toString());
|
|
62
61
|
(0, utils_1.assert)('Status code', res.status).toBe(200);
|
|
63
|
-
(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');
|
|
64
63
|
}),
|
|
65
64
|
(0, utils_1.audit)('MUST use utf-8 encoding when responding', async () => {
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
65
|
+
const res = await fetchFn(opts.url, {
|
|
66
|
+
method: 'POST',
|
|
67
|
+
headers: {
|
|
68
|
+
'content-type': 'application/json',
|
|
69
|
+
},
|
|
70
|
+
body: JSON.stringify({ query: '{ __typename }' }),
|
|
71
|
+
});
|
|
69
72
|
(0, utils_1.assert)('Status code', res.status).toBe(200);
|
|
70
73
|
// has charset set to utf-8
|
|
71
74
|
try {
|
|
@@ -88,23 +91,23 @@ function serverAudits(opts) {
|
|
|
88
91
|
}
|
|
89
92
|
}),
|
|
90
93
|
(0, utils_1.audit)('MUST accept utf-8 encoding', async () => {
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
const res = await fetchFn(url.toString(), {
|
|
94
|
+
const res = await fetchFn(opts.url, {
|
|
95
|
+
method: 'POST',
|
|
94
96
|
headers: {
|
|
95
97
|
'content-type': 'application/json; charset=utf-8',
|
|
96
98
|
},
|
|
99
|
+
body: JSON.stringify({ query: '{ __typename }' }),
|
|
97
100
|
});
|
|
98
101
|
(0, utils_1.assert)('Status code', res.status).toBe(200);
|
|
99
102
|
(0, utils_1.assert)('Content-Type header', res.headers.get('content-type')).toContain('utf-8');
|
|
100
103
|
}),
|
|
101
104
|
(0, utils_1.audit)('MUST assume utf-8 if encoding is unspecified', async () => {
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
const res = await fetchFn(url.toString(), {
|
|
105
|
+
const res = await fetchFn(opts.url, {
|
|
106
|
+
method: 'POST',
|
|
105
107
|
headers: {
|
|
106
108
|
'content-type': 'application/json',
|
|
107
109
|
},
|
|
110
|
+
body: JSON.stringify({ query: '{ __typename }' }),
|
|
108
111
|
});
|
|
109
112
|
(0, utils_1.assert)('Status code', res.status).toBe(200);
|
|
110
113
|
(0, utils_1.assert)('Content-Type header', res.headers.get('content-type')).toContain('utf-8');
|
|
@@ -125,7 +128,8 @@ function serverAudits(opts) {
|
|
|
125
128
|
(0, utils_1.assert)('Status code', res.status).toBe(200);
|
|
126
129
|
}),
|
|
127
130
|
// Request GET
|
|
128
|
-
|
|
131
|
+
// TODO: this is a MUST if the server supports GET requests
|
|
132
|
+
(0, utils_1.audit)('MAY NOT allow executing mutations on GET requests', async () => {
|
|
129
133
|
const url = new URL(opts.url);
|
|
130
134
|
url.searchParams.set('query', 'mutation { __typename }');
|
|
131
135
|
const res = await fetchFn(url.toString(), {
|
|
@@ -133,15 +137,14 @@ function serverAudits(opts) {
|
|
|
133
137
|
accept: 'application/graphql-response+json',
|
|
134
138
|
},
|
|
135
139
|
});
|
|
136
|
-
(0, utils_1.assert)('Status code', res.status).
|
|
140
|
+
(0, utils_1.assert)('Status code', res.status).toBeBetween(400, 499);
|
|
137
141
|
}),
|
|
138
142
|
// Request POST
|
|
139
143
|
(0, utils_1.audit)('SHOULD respond with 4xx status code if content-type is not supplied on POST requests', async () => {
|
|
140
144
|
const res = await fetchFn(opts.url, {
|
|
141
145
|
method: 'POST',
|
|
142
146
|
});
|
|
143
|
-
(0, utils_1.assert)('Status code', res.status).
|
|
144
|
-
(0, utils_1.assert)('Status code', res.status).toBeLessThanOrEqual(499);
|
|
147
|
+
(0, utils_1.assert)('Status code', res.status).toBeBetween(400, 499);
|
|
145
148
|
}),
|
|
146
149
|
(0, utils_1.audit)('MUST accept application/json POST requests', async () => {
|
|
147
150
|
const res = await fetchFn(opts.url, {
|
|
@@ -152,11 +155,17 @@ function serverAudits(opts) {
|
|
|
152
155
|
(0, utils_1.assert)('Status code', res.status).toBe(200);
|
|
153
156
|
}),
|
|
154
157
|
(0, utils_1.audit)('MUST require a request body on POST', async () => {
|
|
158
|
+
var _a;
|
|
155
159
|
const res = await fetchFn(opts.url, {
|
|
156
160
|
method: 'POST',
|
|
157
161
|
headers: { 'content-type': 'application/json' },
|
|
158
162
|
});
|
|
159
|
-
(
|
|
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
|
+
}
|
|
160
169
|
}),
|
|
161
170
|
// Request Parameters
|
|
162
171
|
(0, utils_1.audit)(
|
|
@@ -366,9 +375,7 @@ function serverAudits(opts) {
|
|
|
366
375
|
(0, utils_1.assert)('Status code', res.status).toBe(200);
|
|
367
376
|
(0, utils_1.assert)('Execution result', await (0, utils_1.assertBodyAsExecutionResult)(res)).notToHaveProperty('errors');
|
|
368
377
|
}),
|
|
369
|
-
(0, utils_1.audit)(
|
|
370
|
-
// TODO: convert to MUST after watershed
|
|
371
|
-
'SHOULD allow URL-encoded JSON string {variables} parameter in GETs when accepting application/graphql-response+json', async () => {
|
|
378
|
+
(0, utils_1.audit)('MAY allow URL-encoded JSON string {variables} parameter in GETs when accepting application/graphql-response+json', async () => {
|
|
372
379
|
const url = new URL(opts.url);
|
|
373
380
|
url.searchParams.set('query', 'query Type($name: String!) { __type(name: $name) { name } }');
|
|
374
381
|
url.searchParams.set('variables', JSON.stringify({ name: 'sometype' }));
|
|
@@ -380,7 +387,7 @@ function serverAudits(opts) {
|
|
|
380
387
|
});
|
|
381
388
|
(0, utils_1.assert)('Status code', res.status).toBe(200);
|
|
382
389
|
}),
|
|
383
|
-
(0, utils_1.audit)('
|
|
390
|
+
(0, utils_1.audit)('MAY allow URL-encoded JSON string {variables} parameter in GETs when accepting application/json', async () => {
|
|
384
391
|
const url = new URL(opts.url);
|
|
385
392
|
url.searchParams.set('query', 'query Type($name: String!) { __type(name: $name) { name } }');
|
|
386
393
|
url.searchParams.set('variables', JSON.stringify({ name: 'sometype' }));
|
|
@@ -469,29 +476,39 @@ function serverAudits(opts) {
|
|
|
469
476
|
(0, utils_1.assert)('Status code', res.status).toBe(200);
|
|
470
477
|
}),
|
|
471
478
|
(0, utils_1.audit)('SHOULD use 200 status code if parameters are invalid when accepting application/json', async () => {
|
|
472
|
-
const
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
479
|
+
const res = await fetchFn(opts.url, {
|
|
480
|
+
method: 'POST',
|
|
481
|
+
headers: {
|
|
482
|
+
'content-type': 'application/json',
|
|
483
|
+
accept: 'application/json',
|
|
484
|
+
},
|
|
485
|
+
body: JSON.stringify({
|
|
486
|
+
qeury: /* typo */ '{ __typename }',
|
|
487
|
+
}),
|
|
477
488
|
});
|
|
478
489
|
(0, utils_1.assert)('Status code', res.status).toBe(200);
|
|
479
490
|
}),
|
|
480
491
|
(0, utils_1.audit)('SHOULD use 200 status code on document parsing failure when accepting application/json', async () => {
|
|
481
|
-
const
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
492
|
+
const res = await fetchFn(opts.url, {
|
|
493
|
+
method: 'POST',
|
|
494
|
+
headers: {
|
|
495
|
+
'content-type': 'application/json',
|
|
496
|
+
accept: 'application/json',
|
|
497
|
+
},
|
|
498
|
+
body: JSON.stringify({ query: '{' }),
|
|
486
499
|
});
|
|
487
500
|
(0, utils_1.assert)('Status code', res.status).toBe(200);
|
|
488
501
|
}),
|
|
489
502
|
(0, utils_1.audit)('SHOULD use 200 status code on document validation failure when accepting application/json', async () => {
|
|
490
|
-
const
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
503
|
+
const res = await fetchFn(opts.url, {
|
|
504
|
+
method: 'POST',
|
|
505
|
+
headers: {
|
|
506
|
+
'content-type': 'application/json',
|
|
507
|
+
accept: 'application/json',
|
|
508
|
+
},
|
|
509
|
+
body: JSON.stringify({
|
|
510
|
+
query: '{ 8f31403dfe404bccbb0e835f2629c6a7 }', // making sure the field doesnt exist
|
|
511
|
+
}),
|
|
495
512
|
});
|
|
496
513
|
(0, utils_1.assert)('Status code', res.status).toBe(200);
|
|
497
514
|
}),
|
|
@@ -507,8 +524,7 @@ function serverAudits(opts) {
|
|
|
507
524
|
},
|
|
508
525
|
body: '{ "not a JSON',
|
|
509
526
|
});
|
|
510
|
-
(0, utils_1.assert)('Status code', res.status).
|
|
511
|
-
(0, utils_1.assert)('Status code', res.status).toBeLessThanOrEqual(599);
|
|
527
|
+
(0, utils_1.assert)('Status code', res.status).toBeBetween(400, 499);
|
|
512
528
|
}),
|
|
513
529
|
(0, utils_1.audit)('SHOULD use 400 status code on JSON parsing failure when accepting application/graphql-response+json', async () => {
|
|
514
530
|
const res = await fetchFn(opts.url, {
|
|
@@ -535,90 +551,123 @@ function serverAudits(opts) {
|
|
|
535
551
|
(0, utils_1.audit)(
|
|
536
552
|
// TODO: convert to MUST after watershed
|
|
537
553
|
'SHOULD use 4xx or 5xx status codes if parameters are invalid when accepting application/graphql-response+json', async () => {
|
|
538
|
-
const
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
554
|
+
const res = await fetchFn(opts.url, {
|
|
555
|
+
method: 'POST',
|
|
556
|
+
headers: {
|
|
557
|
+
'content-type': 'application/json',
|
|
558
|
+
accept: 'application/graphql-response+json',
|
|
559
|
+
},
|
|
560
|
+
body: JSON.stringify({
|
|
561
|
+
qeury /* typo */: '{ __typename }',
|
|
562
|
+
}),
|
|
543
563
|
});
|
|
544
|
-
(0, utils_1.assert)('Status code', res.status).
|
|
545
|
-
(0, utils_1.assert)('Status code', res.status).toBeLessThanOrEqual(599);
|
|
564
|
+
(0, utils_1.assert)('Status code', res.status).toBeBetween(400, 599);
|
|
546
565
|
}),
|
|
547
566
|
(0, utils_1.audit)('SHOULD use 400 status code if parameters are invalid when accepting application/graphql-response+json', async () => {
|
|
548
|
-
const
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
567
|
+
const res = await fetchFn(opts.url, {
|
|
568
|
+
method: 'POST',
|
|
569
|
+
headers: {
|
|
570
|
+
'content-type': 'application/json',
|
|
571
|
+
accept: 'application/graphql-response+json',
|
|
572
|
+
},
|
|
573
|
+
body: JSON.stringify({
|
|
574
|
+
qeury: /* typo */ '{ __typename }',
|
|
575
|
+
}),
|
|
553
576
|
});
|
|
554
577
|
(0, utils_1.assert)('Status code', res.status).toBe(400);
|
|
555
578
|
}),
|
|
556
579
|
(0, utils_1.audit)('SHOULD not contain the data entry if parameters are invalid when accepting application/graphql-response+json', async () => {
|
|
557
|
-
const
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
580
|
+
const res = await fetchFn(opts.url, {
|
|
581
|
+
method: 'POST',
|
|
582
|
+
headers: {
|
|
583
|
+
'content-type': 'application/json',
|
|
584
|
+
accept: 'application/graphql-response+json',
|
|
585
|
+
},
|
|
586
|
+
body: JSON.stringify({
|
|
587
|
+
qeury: /* typo */ '{ __typename }',
|
|
588
|
+
}),
|
|
562
589
|
});
|
|
563
590
|
(0, utils_1.assert)('Data entry', (await (0, utils_1.assertBodyAsExecutionResult)(res)).data).toBe(undefined);
|
|
564
591
|
}),
|
|
565
592
|
(0, utils_1.audit)(
|
|
566
593
|
// TODO: convert to MUST after watershed
|
|
567
594
|
'SHOULD use 4xx or 5xx status codes on document parsing failure when accepting application/graphql-response+json', async () => {
|
|
568
|
-
const
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
595
|
+
const res = await fetchFn(opts.url, {
|
|
596
|
+
method: 'POST',
|
|
597
|
+
headers: {
|
|
598
|
+
'content-type': 'application/json',
|
|
599
|
+
accept: 'application/graphql-response+json',
|
|
600
|
+
},
|
|
601
|
+
body: JSON.stringify({
|
|
602
|
+
query: '{',
|
|
603
|
+
}),
|
|
573
604
|
});
|
|
574
|
-
(0, utils_1.assert)('Status code', res.status).
|
|
575
|
-
(0, utils_1.assert)('Status code', res.status).toBeLessThanOrEqual(599);
|
|
605
|
+
(0, utils_1.assert)('Status code', res.status).toBeBetween(400, 599);
|
|
576
606
|
}),
|
|
577
607
|
(0, utils_1.audit)('SHOULD use 400 status code on document parsing failure when accepting application/graphql-response+json', async () => {
|
|
578
|
-
const
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
608
|
+
const res = await fetchFn(opts.url, {
|
|
609
|
+
method: 'POST',
|
|
610
|
+
headers: {
|
|
611
|
+
'content-type': 'application/json',
|
|
612
|
+
accept: 'application/graphql-response+json',
|
|
613
|
+
},
|
|
614
|
+
body: JSON.stringify({
|
|
615
|
+
query: '{',
|
|
616
|
+
}),
|
|
583
617
|
});
|
|
584
618
|
(0, utils_1.assert)('Status code', res.status).toBe(400);
|
|
585
619
|
}),
|
|
586
620
|
(0, utils_1.audit)('SHOULD not contain the data entry on document parsing failure when accepting application/graphql-response+json', async () => {
|
|
587
|
-
const
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
621
|
+
const res = await fetchFn(opts.url, {
|
|
622
|
+
method: 'POST',
|
|
623
|
+
headers: {
|
|
624
|
+
'content-type': 'application/json',
|
|
625
|
+
accept: 'application/graphql-response+json',
|
|
626
|
+
},
|
|
627
|
+
body: JSON.stringify({
|
|
628
|
+
query: '{',
|
|
629
|
+
}),
|
|
592
630
|
});
|
|
593
631
|
(0, utils_1.assert)('Data entry', (await (0, utils_1.assertBodyAsExecutionResult)(res)).data).toBe(undefined);
|
|
594
632
|
}),
|
|
595
633
|
(0, utils_1.audit)(
|
|
596
634
|
// TODO: convert to MUST after watershed
|
|
597
635
|
'SHOULD use 4xx or 5xx status codes on document validation failure when accepting application/graphql-response+json', async () => {
|
|
598
|
-
const
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
636
|
+
const res = await fetchFn(opts.url, {
|
|
637
|
+
method: 'POST',
|
|
638
|
+
headers: {
|
|
639
|
+
'content-type': 'application/json',
|
|
640
|
+
accept: 'application/graphql-response+json',
|
|
641
|
+
},
|
|
642
|
+
body: JSON.stringify({
|
|
643
|
+
query: '{ 8f31403dfe404bccbb0e835f2629c6a7 }', // making sure the field doesnt exist
|
|
644
|
+
}),
|
|
603
645
|
});
|
|
604
|
-
(0, utils_1.assert)('Status code', res.status).
|
|
605
|
-
(0, utils_1.assert)('Status code', res.status).toBeLessThanOrEqual(599);
|
|
646
|
+
(0, utils_1.assert)('Status code', res.status).toBeBetween(400, 599);
|
|
606
647
|
}),
|
|
607
648
|
(0, utils_1.audit)('SHOULD use 400 status code on document validation failure when accepting application/graphql-response+json', async () => {
|
|
608
|
-
const
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
649
|
+
const res = await fetchFn(opts.url, {
|
|
650
|
+
method: 'POST',
|
|
651
|
+
headers: {
|
|
652
|
+
'content-type': 'application/json',
|
|
653
|
+
accept: 'application/graphql-response+json',
|
|
654
|
+
},
|
|
655
|
+
body: JSON.stringify({
|
|
656
|
+
query: '{ 8f31403dfe404bccbb0e835f2629c6a7 }', // making sure the field doesnt exist
|
|
657
|
+
}),
|
|
613
658
|
});
|
|
614
659
|
(0, utils_1.assert)('Status code', res.status).toBe(400);
|
|
615
660
|
}),
|
|
616
661
|
(0, utils_1.audit)('SHOULD not contain the data entry on document validation failure when accepting application/graphql-response+json', async () => {
|
|
617
|
-
const
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
662
|
+
const res = await fetchFn(opts.url, {
|
|
663
|
+
method: 'POST',
|
|
664
|
+
headers: {
|
|
665
|
+
'content-type': 'application/json',
|
|
666
|
+
accept: 'application/graphql-response+json',
|
|
667
|
+
},
|
|
668
|
+
body: JSON.stringify({
|
|
669
|
+
query: '{ 8f31403dfe404bccbb0e835f2629c6a7 }', // making sure the field doesnt exist
|
|
670
|
+
}),
|
|
622
671
|
});
|
|
623
672
|
(0, utils_1.assert)('Data entry', (await (0, utils_1.assertBodyAsExecutionResult)(res)).data).toBe(undefined);
|
|
624
673
|
}),
|
package/lib/audits/server.mjs
CHANGED
|
@@ -16,53 +16,56 @@ 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
|
|
20
|
-
|
|
21
|
-
const res = await fetchFn(url.toString(), {
|
|
19
|
+
const res = await fetchFn(opts.url, {
|
|
20
|
+
method: 'POST',
|
|
22
21
|
headers: {
|
|
22
|
+
'content-type': 'application/json',
|
|
23
23
|
accept: 'application/graphql-response+json',
|
|
24
24
|
},
|
|
25
|
+
body: JSON.stringify({ query: '{ __typename }' }),
|
|
25
26
|
});
|
|
26
27
|
assert('Status code', res.status).toBe(200);
|
|
27
28
|
assert('Content-Type header', res.headers.get('content-type')).toContain('application/graphql-response+json');
|
|
28
29
|
}),
|
|
29
30
|
audit('MUST accept application/json and match the content-type', async () => {
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
const res = await fetchFn(url.toString(), {
|
|
31
|
+
const res = await fetchFn(opts.url, {
|
|
32
|
+
method: 'POST',
|
|
33
33
|
headers: {
|
|
34
|
+
'content-type': 'application/json',
|
|
34
35
|
accept: 'application/json',
|
|
35
36
|
},
|
|
37
|
+
body: JSON.stringify({ query: '{ __typename }' }),
|
|
36
38
|
});
|
|
37
39
|
assert('Status code', res.status).toBe(200);
|
|
38
40
|
assert('Content-Type header', res.headers.get('content-type')).toContain('application/json');
|
|
39
41
|
}),
|
|
40
|
-
audit(
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const url = new URL(opts.url);
|
|
44
|
-
url.searchParams.set('query', '{ __typename }');
|
|
45
|
-
const res = await fetchFn(url.toString(), {
|
|
42
|
+
audit('SHOULD accept */* and use application/json for the content-type', async () => {
|
|
43
|
+
const res = await fetchFn(opts.url, {
|
|
44
|
+
method: 'POST',
|
|
46
45
|
headers: {
|
|
46
|
+
'content-type': 'application/json',
|
|
47
47
|
accept: '*/*',
|
|
48
48
|
},
|
|
49
|
+
body: JSON.stringify({ query: '{ __typename }' }),
|
|
49
50
|
});
|
|
50
51
|
assert('Status code', res.status).toBe(200);
|
|
51
|
-
assert('Content-Type header', res.headers.get('content-type')).toContain('application/
|
|
52
|
+
assert('Content-Type header', res.headers.get('content-type')).toContain('application/json');
|
|
52
53
|
}),
|
|
53
|
-
audit(
|
|
54
|
-
// TODO: convert to MUST after watershed
|
|
55
|
-
'SHOULD assume application/graphql-response+json content-type when accept is missing', async () => {
|
|
54
|
+
audit('SHOULD assume application/json content-type when accept is missing', async () => {
|
|
56
55
|
const url = new URL(opts.url);
|
|
57
56
|
url.searchParams.set('query', '{ __typename }');
|
|
58
57
|
const res = await fetchFn(url.toString());
|
|
59
58
|
assert('Status code', res.status).toBe(200);
|
|
60
|
-
assert('Content-Type header', res.headers.get('content-type')).toContain('application/
|
|
59
|
+
assert('Content-Type header', res.headers.get('content-type')).toContain('application/json');
|
|
61
60
|
}),
|
|
62
61
|
audit('MUST use utf-8 encoding when responding', async () => {
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
62
|
+
const res = await fetchFn(opts.url, {
|
|
63
|
+
method: 'POST',
|
|
64
|
+
headers: {
|
|
65
|
+
'content-type': 'application/json',
|
|
66
|
+
},
|
|
67
|
+
body: JSON.stringify({ query: '{ __typename }' }),
|
|
68
|
+
});
|
|
66
69
|
assert('Status code', res.status).toBe(200);
|
|
67
70
|
// has charset set to utf-8
|
|
68
71
|
try {
|
|
@@ -85,23 +88,23 @@ export function serverAudits(opts) {
|
|
|
85
88
|
}
|
|
86
89
|
}),
|
|
87
90
|
audit('MUST accept utf-8 encoding', async () => {
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
const res = await fetchFn(url.toString(), {
|
|
91
|
+
const res = await fetchFn(opts.url, {
|
|
92
|
+
method: 'POST',
|
|
91
93
|
headers: {
|
|
92
94
|
'content-type': 'application/json; charset=utf-8',
|
|
93
95
|
},
|
|
96
|
+
body: JSON.stringify({ query: '{ __typename }' }),
|
|
94
97
|
});
|
|
95
98
|
assert('Status code', res.status).toBe(200);
|
|
96
99
|
assert('Content-Type header', res.headers.get('content-type')).toContain('utf-8');
|
|
97
100
|
}),
|
|
98
101
|
audit('MUST assume utf-8 if encoding is unspecified', async () => {
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
const res = await fetchFn(url.toString(), {
|
|
102
|
+
const res = await fetchFn(opts.url, {
|
|
103
|
+
method: 'POST',
|
|
102
104
|
headers: {
|
|
103
105
|
'content-type': 'application/json',
|
|
104
106
|
},
|
|
107
|
+
body: JSON.stringify({ query: '{ __typename }' }),
|
|
105
108
|
});
|
|
106
109
|
assert('Status code', res.status).toBe(200);
|
|
107
110
|
assert('Content-Type header', res.headers.get('content-type')).toContain('utf-8');
|
|
@@ -122,7 +125,8 @@ export function serverAudits(opts) {
|
|
|
122
125
|
assert('Status code', res.status).toBe(200);
|
|
123
126
|
}),
|
|
124
127
|
// Request GET
|
|
125
|
-
|
|
128
|
+
// TODO: this is a MUST if the server supports GET requests
|
|
129
|
+
audit('MAY NOT allow executing mutations on GET requests', async () => {
|
|
126
130
|
const url = new URL(opts.url);
|
|
127
131
|
url.searchParams.set('query', 'mutation { __typename }');
|
|
128
132
|
const res = await fetchFn(url.toString(), {
|
|
@@ -130,15 +134,14 @@ export function serverAudits(opts) {
|
|
|
130
134
|
accept: 'application/graphql-response+json',
|
|
131
135
|
},
|
|
132
136
|
});
|
|
133
|
-
assert('Status code', res.status).
|
|
137
|
+
assert('Status code', res.status).toBeBetween(400, 499);
|
|
134
138
|
}),
|
|
135
139
|
// Request POST
|
|
136
140
|
audit('SHOULD respond with 4xx status code if content-type is not supplied on POST requests', async () => {
|
|
137
141
|
const res = await fetchFn(opts.url, {
|
|
138
142
|
method: 'POST',
|
|
139
143
|
});
|
|
140
|
-
assert('Status code', res.status).
|
|
141
|
-
assert('Status code', res.status).toBeLessThanOrEqual(499);
|
|
144
|
+
assert('Status code', res.status).toBeBetween(400, 499);
|
|
142
145
|
}),
|
|
143
146
|
audit('MUST accept application/json POST requests', async () => {
|
|
144
147
|
const res = await fetchFn(opts.url, {
|
|
@@ -149,11 +152,17 @@ export function serverAudits(opts) {
|
|
|
149
152
|
assert('Status code', res.status).toBe(200);
|
|
150
153
|
}),
|
|
151
154
|
audit('MUST require a request body on POST', async () => {
|
|
155
|
+
var _a;
|
|
152
156
|
const res = await fetchFn(opts.url, {
|
|
153
157
|
method: 'POST',
|
|
154
158
|
headers: { 'content-type': 'application/json' },
|
|
155
159
|
});
|
|
156
|
-
|
|
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
|
+
}
|
|
157
166
|
}),
|
|
158
167
|
// Request Parameters
|
|
159
168
|
audit(
|
|
@@ -363,9 +372,7 @@ export function serverAudits(opts) {
|
|
|
363
372
|
assert('Status code', res.status).toBe(200);
|
|
364
373
|
assert('Execution result', await assertBodyAsExecutionResult(res)).notToHaveProperty('errors');
|
|
365
374
|
}),
|
|
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 () => {
|
|
375
|
+
audit('MAY allow URL-encoded JSON string {variables} parameter in GETs when accepting application/graphql-response+json', async () => {
|
|
369
376
|
const url = new URL(opts.url);
|
|
370
377
|
url.searchParams.set('query', 'query Type($name: String!) { __type(name: $name) { name } }');
|
|
371
378
|
url.searchParams.set('variables', JSON.stringify({ name: 'sometype' }));
|
|
@@ -377,7 +384,7 @@ export function serverAudits(opts) {
|
|
|
377
384
|
});
|
|
378
385
|
assert('Status code', res.status).toBe(200);
|
|
379
386
|
}),
|
|
380
|
-
audit('
|
|
387
|
+
audit('MAY allow URL-encoded JSON string {variables} parameter in GETs when accepting application/json', async () => {
|
|
381
388
|
const url = new URL(opts.url);
|
|
382
389
|
url.searchParams.set('query', 'query Type($name: String!) { __type(name: $name) { name } }');
|
|
383
390
|
url.searchParams.set('variables', JSON.stringify({ name: 'sometype' }));
|
|
@@ -466,29 +473,39 @@ export function serverAudits(opts) {
|
|
|
466
473
|
assert('Status code', res.status).toBe(200);
|
|
467
474
|
}),
|
|
468
475
|
audit('SHOULD use 200 status code if parameters are invalid when accepting application/json', async () => {
|
|
469
|
-
const
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
476
|
+
const res = await fetchFn(opts.url, {
|
|
477
|
+
method: 'POST',
|
|
478
|
+
headers: {
|
|
479
|
+
'content-type': 'application/json',
|
|
480
|
+
accept: 'application/json',
|
|
481
|
+
},
|
|
482
|
+
body: JSON.stringify({
|
|
483
|
+
qeury: /* typo */ '{ __typename }',
|
|
484
|
+
}),
|
|
474
485
|
});
|
|
475
486
|
assert('Status code', res.status).toBe(200);
|
|
476
487
|
}),
|
|
477
488
|
audit('SHOULD use 200 status code on document parsing failure when accepting application/json', async () => {
|
|
478
|
-
const
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
489
|
+
const res = await fetchFn(opts.url, {
|
|
490
|
+
method: 'POST',
|
|
491
|
+
headers: {
|
|
492
|
+
'content-type': 'application/json',
|
|
493
|
+
accept: 'application/json',
|
|
494
|
+
},
|
|
495
|
+
body: JSON.stringify({ query: '{' }),
|
|
483
496
|
});
|
|
484
497
|
assert('Status code', res.status).toBe(200);
|
|
485
498
|
}),
|
|
486
499
|
audit('SHOULD use 200 status code on document validation failure when accepting application/json', async () => {
|
|
487
|
-
const
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
500
|
+
const res = await fetchFn(opts.url, {
|
|
501
|
+
method: 'POST',
|
|
502
|
+
headers: {
|
|
503
|
+
'content-type': 'application/json',
|
|
504
|
+
accept: 'application/json',
|
|
505
|
+
},
|
|
506
|
+
body: JSON.stringify({
|
|
507
|
+
query: '{ 8f31403dfe404bccbb0e835f2629c6a7 }', // making sure the field doesnt exist
|
|
508
|
+
}),
|
|
492
509
|
});
|
|
493
510
|
assert('Status code', res.status).toBe(200);
|
|
494
511
|
}),
|
|
@@ -504,8 +521,7 @@ export function serverAudits(opts) {
|
|
|
504
521
|
},
|
|
505
522
|
body: '{ "not a JSON',
|
|
506
523
|
});
|
|
507
|
-
assert('Status code', res.status).
|
|
508
|
-
assert('Status code', res.status).toBeLessThanOrEqual(599);
|
|
524
|
+
assert('Status code', res.status).toBeBetween(400, 499);
|
|
509
525
|
}),
|
|
510
526
|
audit('SHOULD use 400 status code on JSON parsing failure when accepting application/graphql-response+json', async () => {
|
|
511
527
|
const res = await fetchFn(opts.url, {
|
|
@@ -532,90 +548,123 @@ export function serverAudits(opts) {
|
|
|
532
548
|
audit(
|
|
533
549
|
// TODO: convert to MUST after watershed
|
|
534
550
|
'SHOULD use 4xx or 5xx status codes if parameters are invalid when accepting application/graphql-response+json', async () => {
|
|
535
|
-
const
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
551
|
+
const res = await fetchFn(opts.url, {
|
|
552
|
+
method: 'POST',
|
|
553
|
+
headers: {
|
|
554
|
+
'content-type': 'application/json',
|
|
555
|
+
accept: 'application/graphql-response+json',
|
|
556
|
+
},
|
|
557
|
+
body: JSON.stringify({
|
|
558
|
+
qeury /* typo */: '{ __typename }',
|
|
559
|
+
}),
|
|
540
560
|
});
|
|
541
|
-
assert('Status code', res.status).
|
|
542
|
-
assert('Status code', res.status).toBeLessThanOrEqual(599);
|
|
561
|
+
assert('Status code', res.status).toBeBetween(400, 599);
|
|
543
562
|
}),
|
|
544
563
|
audit('SHOULD use 400 status code if parameters are invalid when accepting application/graphql-response+json', async () => {
|
|
545
|
-
const
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
564
|
+
const res = await fetchFn(opts.url, {
|
|
565
|
+
method: 'POST',
|
|
566
|
+
headers: {
|
|
567
|
+
'content-type': 'application/json',
|
|
568
|
+
accept: 'application/graphql-response+json',
|
|
569
|
+
},
|
|
570
|
+
body: JSON.stringify({
|
|
571
|
+
qeury: /* typo */ '{ __typename }',
|
|
572
|
+
}),
|
|
550
573
|
});
|
|
551
574
|
assert('Status code', res.status).toBe(400);
|
|
552
575
|
}),
|
|
553
576
|
audit('SHOULD not contain the data entry if parameters are invalid when accepting application/graphql-response+json', async () => {
|
|
554
|
-
const
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
577
|
+
const res = await fetchFn(opts.url, {
|
|
578
|
+
method: 'POST',
|
|
579
|
+
headers: {
|
|
580
|
+
'content-type': 'application/json',
|
|
581
|
+
accept: 'application/graphql-response+json',
|
|
582
|
+
},
|
|
583
|
+
body: JSON.stringify({
|
|
584
|
+
qeury: /* typo */ '{ __typename }',
|
|
585
|
+
}),
|
|
559
586
|
});
|
|
560
587
|
assert('Data entry', (await assertBodyAsExecutionResult(res)).data).toBe(undefined);
|
|
561
588
|
}),
|
|
562
589
|
audit(
|
|
563
590
|
// TODO: convert to MUST after watershed
|
|
564
591
|
'SHOULD use 4xx or 5xx status codes on document parsing failure when accepting application/graphql-response+json', async () => {
|
|
565
|
-
const
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
592
|
+
const res = await fetchFn(opts.url, {
|
|
593
|
+
method: 'POST',
|
|
594
|
+
headers: {
|
|
595
|
+
'content-type': 'application/json',
|
|
596
|
+
accept: 'application/graphql-response+json',
|
|
597
|
+
},
|
|
598
|
+
body: JSON.stringify({
|
|
599
|
+
query: '{',
|
|
600
|
+
}),
|
|
570
601
|
});
|
|
571
|
-
assert('Status code', res.status).
|
|
572
|
-
assert('Status code', res.status).toBeLessThanOrEqual(599);
|
|
602
|
+
assert('Status code', res.status).toBeBetween(400, 599);
|
|
573
603
|
}),
|
|
574
604
|
audit('SHOULD use 400 status code on document parsing failure when accepting application/graphql-response+json', async () => {
|
|
575
|
-
const
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
605
|
+
const res = await fetchFn(opts.url, {
|
|
606
|
+
method: 'POST',
|
|
607
|
+
headers: {
|
|
608
|
+
'content-type': 'application/json',
|
|
609
|
+
accept: 'application/graphql-response+json',
|
|
610
|
+
},
|
|
611
|
+
body: JSON.stringify({
|
|
612
|
+
query: '{',
|
|
613
|
+
}),
|
|
580
614
|
});
|
|
581
615
|
assert('Status code', res.status).toBe(400);
|
|
582
616
|
}),
|
|
583
617
|
audit('SHOULD not contain the data entry on document parsing failure when accepting application/graphql-response+json', async () => {
|
|
584
|
-
const
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
618
|
+
const res = await fetchFn(opts.url, {
|
|
619
|
+
method: 'POST',
|
|
620
|
+
headers: {
|
|
621
|
+
'content-type': 'application/json',
|
|
622
|
+
accept: 'application/graphql-response+json',
|
|
623
|
+
},
|
|
624
|
+
body: JSON.stringify({
|
|
625
|
+
query: '{',
|
|
626
|
+
}),
|
|
589
627
|
});
|
|
590
628
|
assert('Data entry', (await assertBodyAsExecutionResult(res)).data).toBe(undefined);
|
|
591
629
|
}),
|
|
592
630
|
audit(
|
|
593
631
|
// TODO: convert to MUST after watershed
|
|
594
632
|
'SHOULD use 4xx or 5xx status codes on document validation failure when accepting application/graphql-response+json', async () => {
|
|
595
|
-
const
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
633
|
+
const res = await fetchFn(opts.url, {
|
|
634
|
+
method: 'POST',
|
|
635
|
+
headers: {
|
|
636
|
+
'content-type': 'application/json',
|
|
637
|
+
accept: 'application/graphql-response+json',
|
|
638
|
+
},
|
|
639
|
+
body: JSON.stringify({
|
|
640
|
+
query: '{ 8f31403dfe404bccbb0e835f2629c6a7 }', // making sure the field doesnt exist
|
|
641
|
+
}),
|
|
600
642
|
});
|
|
601
|
-
assert('Status code', res.status).
|
|
602
|
-
assert('Status code', res.status).toBeLessThanOrEqual(599);
|
|
643
|
+
assert('Status code', res.status).toBeBetween(400, 599);
|
|
603
644
|
}),
|
|
604
645
|
audit('SHOULD use 400 status code on document validation failure when accepting application/graphql-response+json', async () => {
|
|
605
|
-
const
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
646
|
+
const res = await fetchFn(opts.url, {
|
|
647
|
+
method: 'POST',
|
|
648
|
+
headers: {
|
|
649
|
+
'content-type': 'application/json',
|
|
650
|
+
accept: 'application/graphql-response+json',
|
|
651
|
+
},
|
|
652
|
+
body: JSON.stringify({
|
|
653
|
+
query: '{ 8f31403dfe404bccbb0e835f2629c6a7 }', // making sure the field doesnt exist
|
|
654
|
+
}),
|
|
610
655
|
});
|
|
611
656
|
assert('Status code', res.status).toBe(400);
|
|
612
657
|
}),
|
|
613
658
|
audit('SHOULD not contain the data entry on document validation failure when accepting application/graphql-response+json', async () => {
|
|
614
|
-
const
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
659
|
+
const res = await fetchFn(opts.url, {
|
|
660
|
+
method: 'POST',
|
|
661
|
+
headers: {
|
|
662
|
+
'content-type': 'application/json',
|
|
663
|
+
accept: 'application/graphql-response+json',
|
|
664
|
+
},
|
|
665
|
+
body: JSON.stringify({
|
|
666
|
+
query: '{ 8f31403dfe404bccbb0e835f2629c6a7 }', // making sure the field doesnt exist
|
|
667
|
+
}),
|
|
619
668
|
});
|
|
620
669
|
assert('Data entry', (await assertBodyAsExecutionResult(res)).data).toBe(undefined);
|
|
621
670
|
}),
|
package/lib/audits/utils.d.mts
CHANGED
|
@@ -21,8 +21,7 @@ export declare function audit(name: AuditName, fn: () => Promise<void>): Audit;
|
|
|
21
21
|
*/
|
|
22
22
|
export declare function assert<T = unknown>(name: string, actual: T): {
|
|
23
23
|
toBe: (expected: T) => void;
|
|
24
|
-
|
|
25
|
-
toBeGreaterThanOrEqual: (expected: T extends number ? T : never) => void;
|
|
24
|
+
toBeBetween: (min: T extends number ? T : never, max: T extends number ? T : never) => void;
|
|
26
25
|
toContain: (expected: T extends (infer U)[] ? U : T extends string ? T : never) => void;
|
|
27
26
|
notToContain: (expected: T extends (infer U_1)[] ? U_1 : T extends string ? T : never) => void;
|
|
28
27
|
toHaveProperty: (prop: T extends Record<any, any> ? PropertyKey : never) => void;
|
package/lib/audits/utils.d.ts
CHANGED
|
@@ -21,8 +21,7 @@ export declare function audit(name: AuditName, fn: () => Promise<void>): Audit;
|
|
|
21
21
|
*/
|
|
22
22
|
export declare function assert<T = unknown>(name: string, actual: T): {
|
|
23
23
|
toBe: (expected: T) => void;
|
|
24
|
-
|
|
25
|
-
toBeGreaterThanOrEqual: (expected: T extends number ? T : never) => void;
|
|
24
|
+
toBeBetween: (min: T extends number ? T : never, max: T extends number ? T : never) => void;
|
|
26
25
|
toContain: (expected: T extends (infer U)[] ? U : T extends string ? T : never) => void;
|
|
27
26
|
notToContain: (expected: T extends (infer U_1)[] ? U_1 : T extends string ? T : never) => void;
|
|
28
27
|
toHaveProperty: (prop: T extends Record<any, any> ? PropertyKey : never) => void;
|
package/lib/audits/utils.js
CHANGED
|
@@ -70,14 +70,9 @@ function assert(name, actual) {
|
|
|
70
70
|
throw `${name} ${actual} is not ${expected}`;
|
|
71
71
|
}
|
|
72
72
|
},
|
|
73
|
-
|
|
74
|
-
if (!(actual <=
|
|
75
|
-
throw `${name} ${actual} is not
|
|
76
|
-
}
|
|
77
|
-
},
|
|
78
|
-
toBeGreaterThanOrEqual: (expected) => {
|
|
79
|
-
if (!(actual >= expected)) {
|
|
80
|
-
throw `${name} ${actual} is not greater than or equal to ${expected}`;
|
|
73
|
+
toBeBetween: (min, max) => {
|
|
74
|
+
if (!(min <= actual && actual <= max)) {
|
|
75
|
+
throw `${name} ${actual} is not between ${min} and ${max}`;
|
|
81
76
|
}
|
|
82
77
|
},
|
|
83
78
|
toContain: (expected) => {
|
package/lib/audits/utils.mjs
CHANGED
|
@@ -52,14 +52,9 @@ export function assert(name, actual) {
|
|
|
52
52
|
throw `${name} ${actual} is not ${expected}`;
|
|
53
53
|
}
|
|
54
54
|
},
|
|
55
|
-
|
|
56
|
-
if (!(actual <=
|
|
57
|
-
throw `${name} ${actual} is not
|
|
58
|
-
}
|
|
59
|
-
},
|
|
60
|
-
toBeGreaterThanOrEqual: (expected) => {
|
|
61
|
-
if (!(actual >= expected)) {
|
|
62
|
-
throw `${name} ${actual} is not greater than or equal to ${expected}`;
|
|
55
|
+
toBeBetween: (min, max) => {
|
|
56
|
+
if (!(min <= actual && actual <= max)) {
|
|
57
|
+
throw `${name} ${actual} is not between ${min} and ${max}`;
|
|
63
58
|
}
|
|
64
59
|
},
|
|
65
60
|
toContain: (expected) => {
|
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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "graphql-http",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.2",
|
|
4
4
|
"description": "Simple, pluggable, zero-dependency, GraphQL over HTTP Protocol compliant server and client",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"graphql",
|
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
"release": "semantic-release"
|
|
83
83
|
},
|
|
84
84
|
"workspaces": [
|
|
85
|
-
"implementations
|
|
85
|
+
"implementations/**/*"
|
|
86
86
|
],
|
|
87
87
|
"peerDependencies": {
|
|
88
88
|
"graphql": ">=0.11 <=16"
|
|
Binary file
|