graphql-http 1.7.0 → 1.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -8
- package/lib/audits/server.js +148 -97
- package/lib/audits/server.mjs +148 -97
- 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/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 (0 warnings)](/implementations/graphql-yoga/README.md) |
|
|
735
|
+
| [hotchocolate](https://chillicream.com/docs/hotchocolate) | [✅ Compliant (0 warnings)](/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,23 +19,25 @@ 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');
|
|
@@ -43,12 +45,13 @@ function serverAudits(opts) {
|
|
|
43
45
|
(0, utils_1.audit)(
|
|
44
46
|
// TODO: convert to MUST after watershed
|
|
45
47
|
'SHOULD accept */* and use application/graphql-response+json for the content-type', async () => {
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
const res = await fetchFn(url.toString(), {
|
|
48
|
+
const res = await fetchFn(opts.url, {
|
|
49
|
+
method: 'POST',
|
|
49
50
|
headers: {
|
|
51
|
+
'content-type': 'application/json',
|
|
50
52
|
accept: '*/*',
|
|
51
53
|
},
|
|
54
|
+
body: JSON.stringify({ query: '{ __typename }' }),
|
|
52
55
|
});
|
|
53
56
|
(0, utils_1.assert)('Status code', res.status).toBe(200);
|
|
54
57
|
(0, utils_1.assert)('Content-Type header', res.headers.get('content-type')).toContain('application/graphql-response+json');
|
|
@@ -56,16 +59,24 @@ function serverAudits(opts) {
|
|
|
56
59
|
(0, utils_1.audit)(
|
|
57
60
|
// TODO: convert to MUST after watershed
|
|
58
61
|
'SHOULD assume application/graphql-response+json content-type when accept is missing', async () => {
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
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
|
+
});
|
|
62
69
|
(0, utils_1.assert)('Status code', res.status).toBe(200);
|
|
63
70
|
(0, utils_1.assert)('Content-Type header', res.headers.get('content-type')).toContain('application/graphql-response+json');
|
|
64
71
|
}),
|
|
65
72
|
(0, utils_1.audit)('MUST use utf-8 encoding when responding', async () => {
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
73
|
+
const res = await fetchFn(opts.url, {
|
|
74
|
+
method: 'POST',
|
|
75
|
+
headers: {
|
|
76
|
+
'content-type': 'application/json',
|
|
77
|
+
},
|
|
78
|
+
body: JSON.stringify({ query: '{ __typename }' }),
|
|
79
|
+
});
|
|
69
80
|
(0, utils_1.assert)('Status code', res.status).toBe(200);
|
|
70
81
|
// has charset set to utf-8
|
|
71
82
|
try {
|
|
@@ -88,23 +99,23 @@ function serverAudits(opts) {
|
|
|
88
99
|
}
|
|
89
100
|
}),
|
|
90
101
|
(0, utils_1.audit)('MUST accept utf-8 encoding', async () => {
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
const res = await fetchFn(url.toString(), {
|
|
102
|
+
const res = await fetchFn(opts.url, {
|
|
103
|
+
method: 'POST',
|
|
94
104
|
headers: {
|
|
95
105
|
'content-type': 'application/json; charset=utf-8',
|
|
96
106
|
},
|
|
107
|
+
body: JSON.stringify({ query: '{ __typename }' }),
|
|
97
108
|
});
|
|
98
109
|
(0, utils_1.assert)('Status code', res.status).toBe(200);
|
|
99
110
|
(0, utils_1.assert)('Content-Type header', res.headers.get('content-type')).toContain('utf-8');
|
|
100
111
|
}),
|
|
101
112
|
(0, utils_1.audit)('MUST assume utf-8 if encoding is unspecified', async () => {
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
const res = await fetchFn(url.toString(), {
|
|
113
|
+
const res = await fetchFn(opts.url, {
|
|
114
|
+
method: 'POST',
|
|
105
115
|
headers: {
|
|
106
116
|
'content-type': 'application/json',
|
|
107
117
|
},
|
|
118
|
+
body: JSON.stringify({ query: '{ __typename }' }),
|
|
108
119
|
});
|
|
109
120
|
(0, utils_1.assert)('Status code', res.status).toBe(200);
|
|
110
121
|
(0, utils_1.assert)('Content-Type header', res.headers.get('content-type')).toContain('utf-8');
|
|
@@ -125,7 +136,8 @@ function serverAudits(opts) {
|
|
|
125
136
|
(0, utils_1.assert)('Status code', res.status).toBe(200);
|
|
126
137
|
}),
|
|
127
138
|
// Request GET
|
|
128
|
-
|
|
139
|
+
// TODO: this is a MUST if the server supports GET requests
|
|
140
|
+
(0, utils_1.audit)('MAY NOT allow executing mutations on GET requests', async () => {
|
|
129
141
|
const url = new URL(opts.url);
|
|
130
142
|
url.searchParams.set('query', 'mutation { __typename }');
|
|
131
143
|
const res = await fetchFn(url.toString(), {
|
|
@@ -133,15 +145,14 @@ function serverAudits(opts) {
|
|
|
133
145
|
accept: 'application/graphql-response+json',
|
|
134
146
|
},
|
|
135
147
|
});
|
|
136
|
-
(0, utils_1.assert)('Status code', res.status).
|
|
148
|
+
(0, utils_1.assert)('Status code', res.status).toBeBetween(400, 499);
|
|
137
149
|
}),
|
|
138
150
|
// Request POST
|
|
139
151
|
(0, utils_1.audit)('SHOULD respond with 4xx status code if content-type is not supplied on POST requests', async () => {
|
|
140
152
|
const res = await fetchFn(opts.url, {
|
|
141
153
|
method: 'POST',
|
|
142
154
|
});
|
|
143
|
-
(0, utils_1.assert)('Status code', res.status).
|
|
144
|
-
(0, utils_1.assert)('Status code', res.status).toBeLessThanOrEqual(499);
|
|
155
|
+
(0, utils_1.assert)('Status code', res.status).toBeBetween(400, 499);
|
|
145
156
|
}),
|
|
146
157
|
(0, utils_1.audit)('MUST accept application/json POST requests', async () => {
|
|
147
158
|
const res = await fetchFn(opts.url, {
|
|
@@ -366,9 +377,7 @@ function serverAudits(opts) {
|
|
|
366
377
|
(0, utils_1.assert)('Status code', res.status).toBe(200);
|
|
367
378
|
(0, utils_1.assert)('Execution result', await (0, utils_1.assertBodyAsExecutionResult)(res)).notToHaveProperty('errors');
|
|
368
379
|
}),
|
|
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 () => {
|
|
380
|
+
(0, utils_1.audit)('MAY allow URL-encoded JSON string {variables} parameter in GETs when accepting application/graphql-response+json', async () => {
|
|
372
381
|
const url = new URL(opts.url);
|
|
373
382
|
url.searchParams.set('query', 'query Type($name: String!) { __type(name: $name) { name } }');
|
|
374
383
|
url.searchParams.set('variables', JSON.stringify({ name: 'sometype' }));
|
|
@@ -380,7 +389,7 @@ function serverAudits(opts) {
|
|
|
380
389
|
});
|
|
381
390
|
(0, utils_1.assert)('Status code', res.status).toBe(200);
|
|
382
391
|
}),
|
|
383
|
-
(0, utils_1.audit)('
|
|
392
|
+
(0, utils_1.audit)('MAY allow URL-encoded JSON string {variables} parameter in GETs when accepting application/json', async () => {
|
|
384
393
|
const url = new URL(opts.url);
|
|
385
394
|
url.searchParams.set('query', 'query Type($name: String!) { __type(name: $name) { name } }');
|
|
386
395
|
url.searchParams.set('variables', JSON.stringify({ name: 'sometype' }));
|
|
@@ -469,29 +478,39 @@ function serverAudits(opts) {
|
|
|
469
478
|
(0, utils_1.assert)('Status code', res.status).toBe(200);
|
|
470
479
|
}),
|
|
471
480
|
(0, utils_1.audit)('SHOULD use 200 status code if parameters are invalid when accepting application/json', async () => {
|
|
472
|
-
const
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
481
|
+
const res = await fetchFn(opts.url, {
|
|
482
|
+
method: 'POST',
|
|
483
|
+
headers: {
|
|
484
|
+
'content-type': 'application/json',
|
|
485
|
+
accept: 'application/json',
|
|
486
|
+
},
|
|
487
|
+
body: JSON.stringify({
|
|
488
|
+
qeury: /* typo */ '{ __typename }',
|
|
489
|
+
}),
|
|
477
490
|
});
|
|
478
491
|
(0, utils_1.assert)('Status code', res.status).toBe(200);
|
|
479
492
|
}),
|
|
480
493
|
(0, utils_1.audit)('SHOULD use 200 status code on document parsing failure when accepting application/json', async () => {
|
|
481
|
-
const
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
494
|
+
const res = await fetchFn(opts.url, {
|
|
495
|
+
method: 'POST',
|
|
496
|
+
headers: {
|
|
497
|
+
'content-type': 'application/json',
|
|
498
|
+
accept: 'application/json',
|
|
499
|
+
},
|
|
500
|
+
body: JSON.stringify({ query: '{' }),
|
|
486
501
|
});
|
|
487
502
|
(0, utils_1.assert)('Status code', res.status).toBe(200);
|
|
488
503
|
}),
|
|
489
504
|
(0, utils_1.audit)('SHOULD use 200 status code on document validation failure when accepting application/json', async () => {
|
|
490
|
-
const
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
505
|
+
const res = await fetchFn(opts.url, {
|
|
506
|
+
method: 'POST',
|
|
507
|
+
headers: {
|
|
508
|
+
'content-type': 'application/json',
|
|
509
|
+
accept: 'application/json',
|
|
510
|
+
},
|
|
511
|
+
body: JSON.stringify({
|
|
512
|
+
query: '{ 8f31403dfe404bccbb0e835f2629c6a7 }', // making sure the field doesnt exist
|
|
513
|
+
}),
|
|
495
514
|
});
|
|
496
515
|
(0, utils_1.assert)('Status code', res.status).toBe(200);
|
|
497
516
|
}),
|
|
@@ -507,8 +526,7 @@ function serverAudits(opts) {
|
|
|
507
526
|
},
|
|
508
527
|
body: '{ "not a JSON',
|
|
509
528
|
});
|
|
510
|
-
(0, utils_1.assert)('Status code', res.status).
|
|
511
|
-
(0, utils_1.assert)('Status code', res.status).toBeLessThanOrEqual(599);
|
|
529
|
+
(0, utils_1.assert)('Status code', res.status).toBeBetween(400, 499);
|
|
512
530
|
}),
|
|
513
531
|
(0, utils_1.audit)('SHOULD use 400 status code on JSON parsing failure when accepting application/graphql-response+json', async () => {
|
|
514
532
|
const res = await fetchFn(opts.url, {
|
|
@@ -535,90 +553,123 @@ function serverAudits(opts) {
|
|
|
535
553
|
(0, utils_1.audit)(
|
|
536
554
|
// TODO: convert to MUST after watershed
|
|
537
555
|
'SHOULD use 4xx or 5xx status codes if parameters are invalid when accepting application/graphql-response+json', async () => {
|
|
538
|
-
const
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
556
|
+
const res = await fetchFn(opts.url, {
|
|
557
|
+
method: 'POST',
|
|
558
|
+
headers: {
|
|
559
|
+
'content-type': 'application/json',
|
|
560
|
+
accept: 'application/graphql-response+json',
|
|
561
|
+
},
|
|
562
|
+
body: JSON.stringify({
|
|
563
|
+
qeury /* typo */: '{ __typename }',
|
|
564
|
+
}),
|
|
543
565
|
});
|
|
544
|
-
(0, utils_1.assert)('Status code', res.status).
|
|
545
|
-
(0, utils_1.assert)('Status code', res.status).toBeLessThanOrEqual(599);
|
|
566
|
+
(0, utils_1.assert)('Status code', res.status).toBeBetween(400, 599);
|
|
546
567
|
}),
|
|
547
568
|
(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
|
-
|
|
569
|
+
const res = await fetchFn(opts.url, {
|
|
570
|
+
method: 'POST',
|
|
571
|
+
headers: {
|
|
572
|
+
'content-type': 'application/json',
|
|
573
|
+
accept: 'application/graphql-response+json',
|
|
574
|
+
},
|
|
575
|
+
body: JSON.stringify({
|
|
576
|
+
qeury: /* typo */ '{ __typename }',
|
|
577
|
+
}),
|
|
553
578
|
});
|
|
554
579
|
(0, utils_1.assert)('Status code', res.status).toBe(400);
|
|
555
580
|
}),
|
|
556
581
|
(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
|
-
|
|
582
|
+
const res = await fetchFn(opts.url, {
|
|
583
|
+
method: 'POST',
|
|
584
|
+
headers: {
|
|
585
|
+
'content-type': 'application/json',
|
|
586
|
+
accept: 'application/graphql-response+json',
|
|
587
|
+
},
|
|
588
|
+
body: JSON.stringify({
|
|
589
|
+
qeury: /* typo */ '{ __typename }',
|
|
590
|
+
}),
|
|
562
591
|
});
|
|
563
592
|
(0, utils_1.assert)('Data entry', (await (0, utils_1.assertBodyAsExecutionResult)(res)).data).toBe(undefined);
|
|
564
593
|
}),
|
|
565
594
|
(0, utils_1.audit)(
|
|
566
595
|
// TODO: convert to MUST after watershed
|
|
567
596
|
'SHOULD use 4xx or 5xx status codes on document parsing failure when accepting application/graphql-response+json', async () => {
|
|
568
|
-
const
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
597
|
+
const res = await fetchFn(opts.url, {
|
|
598
|
+
method: 'POST',
|
|
599
|
+
headers: {
|
|
600
|
+
'content-type': 'application/json',
|
|
601
|
+
accept: 'application/graphql-response+json',
|
|
602
|
+
},
|
|
603
|
+
body: JSON.stringify({
|
|
604
|
+
query: '{',
|
|
605
|
+
}),
|
|
573
606
|
});
|
|
574
|
-
(0, utils_1.assert)('Status code', res.status).
|
|
575
|
-
(0, utils_1.assert)('Status code', res.status).toBeLessThanOrEqual(599);
|
|
607
|
+
(0, utils_1.assert)('Status code', res.status).toBeBetween(400, 599);
|
|
576
608
|
}),
|
|
577
609
|
(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
|
-
|
|
610
|
+
const res = await fetchFn(opts.url, {
|
|
611
|
+
method: 'POST',
|
|
612
|
+
headers: {
|
|
613
|
+
'content-type': 'application/json',
|
|
614
|
+
accept: 'application/graphql-response+json',
|
|
615
|
+
},
|
|
616
|
+
body: JSON.stringify({
|
|
617
|
+
query: '{',
|
|
618
|
+
}),
|
|
583
619
|
});
|
|
584
620
|
(0, utils_1.assert)('Status code', res.status).toBe(400);
|
|
585
621
|
}),
|
|
586
622
|
(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
|
-
|
|
623
|
+
const res = await fetchFn(opts.url, {
|
|
624
|
+
method: 'POST',
|
|
625
|
+
headers: {
|
|
626
|
+
'content-type': 'application/json',
|
|
627
|
+
accept: 'application/graphql-response+json',
|
|
628
|
+
},
|
|
629
|
+
body: JSON.stringify({
|
|
630
|
+
query: '{',
|
|
631
|
+
}),
|
|
592
632
|
});
|
|
593
633
|
(0, utils_1.assert)('Data entry', (await (0, utils_1.assertBodyAsExecutionResult)(res)).data).toBe(undefined);
|
|
594
634
|
}),
|
|
595
635
|
(0, utils_1.audit)(
|
|
596
636
|
// TODO: convert to MUST after watershed
|
|
597
637
|
'SHOULD use 4xx or 5xx status codes on document validation failure when accepting application/graphql-response+json', async () => {
|
|
598
|
-
const
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
638
|
+
const res = await fetchFn(opts.url, {
|
|
639
|
+
method: 'POST',
|
|
640
|
+
headers: {
|
|
641
|
+
'content-type': 'application/json',
|
|
642
|
+
accept: 'application/graphql-response+json',
|
|
643
|
+
},
|
|
644
|
+
body: JSON.stringify({
|
|
645
|
+
query: '{ 8f31403dfe404bccbb0e835f2629c6a7 }', // making sure the field doesnt exist
|
|
646
|
+
}),
|
|
603
647
|
});
|
|
604
|
-
(0, utils_1.assert)('Status code', res.status).
|
|
605
|
-
(0, utils_1.assert)('Status code', res.status).toBeLessThanOrEqual(599);
|
|
648
|
+
(0, utils_1.assert)('Status code', res.status).toBeBetween(400, 599);
|
|
606
649
|
}),
|
|
607
650
|
(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
|
-
|
|
651
|
+
const res = await fetchFn(opts.url, {
|
|
652
|
+
method: 'POST',
|
|
653
|
+
headers: {
|
|
654
|
+
'content-type': 'application/json',
|
|
655
|
+
accept: 'application/graphql-response+json',
|
|
656
|
+
},
|
|
657
|
+
body: JSON.stringify({
|
|
658
|
+
query: '{ 8f31403dfe404bccbb0e835f2629c6a7 }', // making sure the field doesnt exist
|
|
659
|
+
}),
|
|
613
660
|
});
|
|
614
661
|
(0, utils_1.assert)('Status code', res.status).toBe(400);
|
|
615
662
|
}),
|
|
616
663
|
(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
|
-
|
|
664
|
+
const res = await fetchFn(opts.url, {
|
|
665
|
+
method: 'POST',
|
|
666
|
+
headers: {
|
|
667
|
+
'content-type': 'application/json',
|
|
668
|
+
accept: 'application/graphql-response+json',
|
|
669
|
+
},
|
|
670
|
+
body: JSON.stringify({
|
|
671
|
+
query: '{ 8f31403dfe404bccbb0e835f2629c6a7 }', // making sure the field doesnt exist
|
|
672
|
+
}),
|
|
622
673
|
});
|
|
623
674
|
(0, utils_1.assert)('Data entry', (await (0, utils_1.assertBodyAsExecutionResult)(res)).data).toBe(undefined);
|
|
624
675
|
}),
|
package/lib/audits/server.mjs
CHANGED
|
@@ -16,23 +16,25 @@ 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');
|
|
@@ -40,12 +42,13 @@ export function serverAudits(opts) {
|
|
|
40
42
|
audit(
|
|
41
43
|
// TODO: convert to MUST after watershed
|
|
42
44
|
'SHOULD accept */* and use application/graphql-response+json for the content-type', async () => {
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
const res = await fetchFn(url.toString(), {
|
|
45
|
+
const res = await fetchFn(opts.url, {
|
|
46
|
+
method: 'POST',
|
|
46
47
|
headers: {
|
|
48
|
+
'content-type': 'application/json',
|
|
47
49
|
accept: '*/*',
|
|
48
50
|
},
|
|
51
|
+
body: JSON.stringify({ query: '{ __typename }' }),
|
|
49
52
|
});
|
|
50
53
|
assert('Status code', res.status).toBe(200);
|
|
51
54
|
assert('Content-Type header', res.headers.get('content-type')).toContain('application/graphql-response+json');
|
|
@@ -53,16 +56,24 @@ export function serverAudits(opts) {
|
|
|
53
56
|
audit(
|
|
54
57
|
// TODO: convert to MUST after watershed
|
|
55
58
|
'SHOULD assume application/graphql-response+json content-type when accept is missing', async () => {
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
+
const res = await fetchFn(opts.url, {
|
|
60
|
+
method: 'POST',
|
|
61
|
+
headers: {
|
|
62
|
+
'content-type': 'application/json',
|
|
63
|
+
},
|
|
64
|
+
body: JSON.stringify({ query: '{ __typename }' }),
|
|
65
|
+
});
|
|
59
66
|
assert('Status code', res.status).toBe(200);
|
|
60
67
|
assert('Content-Type header', res.headers.get('content-type')).toContain('application/graphql-response+json');
|
|
61
68
|
}),
|
|
62
69
|
audit('MUST use utf-8 encoding when responding', async () => {
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
70
|
+
const res = await fetchFn(opts.url, {
|
|
71
|
+
method: 'POST',
|
|
72
|
+
headers: {
|
|
73
|
+
'content-type': 'application/json',
|
|
74
|
+
},
|
|
75
|
+
body: JSON.stringify({ query: '{ __typename }' }),
|
|
76
|
+
});
|
|
66
77
|
assert('Status code', res.status).toBe(200);
|
|
67
78
|
// has charset set to utf-8
|
|
68
79
|
try {
|
|
@@ -85,23 +96,23 @@ export function serverAudits(opts) {
|
|
|
85
96
|
}
|
|
86
97
|
}),
|
|
87
98
|
audit('MUST accept utf-8 encoding', async () => {
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
const res = await fetchFn(url.toString(), {
|
|
99
|
+
const res = await fetchFn(opts.url, {
|
|
100
|
+
method: 'POST',
|
|
91
101
|
headers: {
|
|
92
102
|
'content-type': 'application/json; charset=utf-8',
|
|
93
103
|
},
|
|
104
|
+
body: JSON.stringify({ query: '{ __typename }' }),
|
|
94
105
|
});
|
|
95
106
|
assert('Status code', res.status).toBe(200);
|
|
96
107
|
assert('Content-Type header', res.headers.get('content-type')).toContain('utf-8');
|
|
97
108
|
}),
|
|
98
109
|
audit('MUST assume utf-8 if encoding is unspecified', async () => {
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
const res = await fetchFn(url.toString(), {
|
|
110
|
+
const res = await fetchFn(opts.url, {
|
|
111
|
+
method: 'POST',
|
|
102
112
|
headers: {
|
|
103
113
|
'content-type': 'application/json',
|
|
104
114
|
},
|
|
115
|
+
body: JSON.stringify({ query: '{ __typename }' }),
|
|
105
116
|
});
|
|
106
117
|
assert('Status code', res.status).toBe(200);
|
|
107
118
|
assert('Content-Type header', res.headers.get('content-type')).toContain('utf-8');
|
|
@@ -122,7 +133,8 @@ export function serverAudits(opts) {
|
|
|
122
133
|
assert('Status code', res.status).toBe(200);
|
|
123
134
|
}),
|
|
124
135
|
// Request GET
|
|
125
|
-
|
|
136
|
+
// TODO: this is a MUST if the server supports GET requests
|
|
137
|
+
audit('MAY NOT allow executing mutations on GET requests', async () => {
|
|
126
138
|
const url = new URL(opts.url);
|
|
127
139
|
url.searchParams.set('query', 'mutation { __typename }');
|
|
128
140
|
const res = await fetchFn(url.toString(), {
|
|
@@ -130,15 +142,14 @@ export function serverAudits(opts) {
|
|
|
130
142
|
accept: 'application/graphql-response+json',
|
|
131
143
|
},
|
|
132
144
|
});
|
|
133
|
-
assert('Status code', res.status).
|
|
145
|
+
assert('Status code', res.status).toBeBetween(400, 499);
|
|
134
146
|
}),
|
|
135
147
|
// Request POST
|
|
136
148
|
audit('SHOULD respond with 4xx status code if content-type is not supplied on POST requests', async () => {
|
|
137
149
|
const res = await fetchFn(opts.url, {
|
|
138
150
|
method: 'POST',
|
|
139
151
|
});
|
|
140
|
-
assert('Status code', res.status).
|
|
141
|
-
assert('Status code', res.status).toBeLessThanOrEqual(499);
|
|
152
|
+
assert('Status code', res.status).toBeBetween(400, 499);
|
|
142
153
|
}),
|
|
143
154
|
audit('MUST accept application/json POST requests', async () => {
|
|
144
155
|
const res = await fetchFn(opts.url, {
|
|
@@ -363,9 +374,7 @@ export function serverAudits(opts) {
|
|
|
363
374
|
assert('Status code', res.status).toBe(200);
|
|
364
375
|
assert('Execution result', await assertBodyAsExecutionResult(res)).notToHaveProperty('errors');
|
|
365
376
|
}),
|
|
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 () => {
|
|
377
|
+
audit('MAY allow URL-encoded JSON string {variables} parameter in GETs when accepting application/graphql-response+json', async () => {
|
|
369
378
|
const url = new URL(opts.url);
|
|
370
379
|
url.searchParams.set('query', 'query Type($name: String!) { __type(name: $name) { name } }');
|
|
371
380
|
url.searchParams.set('variables', JSON.stringify({ name: 'sometype' }));
|
|
@@ -377,7 +386,7 @@ export function serverAudits(opts) {
|
|
|
377
386
|
});
|
|
378
387
|
assert('Status code', res.status).toBe(200);
|
|
379
388
|
}),
|
|
380
|
-
audit('
|
|
389
|
+
audit('MAY allow URL-encoded JSON string {variables} parameter in GETs when accepting application/json', async () => {
|
|
381
390
|
const url = new URL(opts.url);
|
|
382
391
|
url.searchParams.set('query', 'query Type($name: String!) { __type(name: $name) { name } }');
|
|
383
392
|
url.searchParams.set('variables', JSON.stringify({ name: 'sometype' }));
|
|
@@ -466,29 +475,39 @@ export function serverAudits(opts) {
|
|
|
466
475
|
assert('Status code', res.status).toBe(200);
|
|
467
476
|
}),
|
|
468
477
|
audit('SHOULD use 200 status code if parameters are invalid when accepting application/json', async () => {
|
|
469
|
-
const
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
478
|
+
const res = await fetchFn(opts.url, {
|
|
479
|
+
method: 'POST',
|
|
480
|
+
headers: {
|
|
481
|
+
'content-type': 'application/json',
|
|
482
|
+
accept: 'application/json',
|
|
483
|
+
},
|
|
484
|
+
body: JSON.stringify({
|
|
485
|
+
qeury: /* typo */ '{ __typename }',
|
|
486
|
+
}),
|
|
474
487
|
});
|
|
475
488
|
assert('Status code', res.status).toBe(200);
|
|
476
489
|
}),
|
|
477
490
|
audit('SHOULD use 200 status code on document parsing failure when accepting application/json', async () => {
|
|
478
|
-
const
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
491
|
+
const res = await fetchFn(opts.url, {
|
|
492
|
+
method: 'POST',
|
|
493
|
+
headers: {
|
|
494
|
+
'content-type': 'application/json',
|
|
495
|
+
accept: 'application/json',
|
|
496
|
+
},
|
|
497
|
+
body: JSON.stringify({ query: '{' }),
|
|
483
498
|
});
|
|
484
499
|
assert('Status code', res.status).toBe(200);
|
|
485
500
|
}),
|
|
486
501
|
audit('SHOULD use 200 status code on document validation failure when accepting application/json', async () => {
|
|
487
|
-
const
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
502
|
+
const res = await fetchFn(opts.url, {
|
|
503
|
+
method: 'POST',
|
|
504
|
+
headers: {
|
|
505
|
+
'content-type': 'application/json',
|
|
506
|
+
accept: 'application/json',
|
|
507
|
+
},
|
|
508
|
+
body: JSON.stringify({
|
|
509
|
+
query: '{ 8f31403dfe404bccbb0e835f2629c6a7 }', // making sure the field doesnt exist
|
|
510
|
+
}),
|
|
492
511
|
});
|
|
493
512
|
assert('Status code', res.status).toBe(200);
|
|
494
513
|
}),
|
|
@@ -504,8 +523,7 @@ export function serverAudits(opts) {
|
|
|
504
523
|
},
|
|
505
524
|
body: '{ "not a JSON',
|
|
506
525
|
});
|
|
507
|
-
assert('Status code', res.status).
|
|
508
|
-
assert('Status code', res.status).toBeLessThanOrEqual(599);
|
|
526
|
+
assert('Status code', res.status).toBeBetween(400, 499);
|
|
509
527
|
}),
|
|
510
528
|
audit('SHOULD use 400 status code on JSON parsing failure when accepting application/graphql-response+json', async () => {
|
|
511
529
|
const res = await fetchFn(opts.url, {
|
|
@@ -532,90 +550,123 @@ export function serverAudits(opts) {
|
|
|
532
550
|
audit(
|
|
533
551
|
// TODO: convert to MUST after watershed
|
|
534
552
|
'SHOULD use 4xx or 5xx status codes if parameters are invalid when accepting application/graphql-response+json', async () => {
|
|
535
|
-
const
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
553
|
+
const res = await fetchFn(opts.url, {
|
|
554
|
+
method: 'POST',
|
|
555
|
+
headers: {
|
|
556
|
+
'content-type': 'application/json',
|
|
557
|
+
accept: 'application/graphql-response+json',
|
|
558
|
+
},
|
|
559
|
+
body: JSON.stringify({
|
|
560
|
+
qeury /* typo */: '{ __typename }',
|
|
561
|
+
}),
|
|
540
562
|
});
|
|
541
|
-
assert('Status code', res.status).
|
|
542
|
-
assert('Status code', res.status).toBeLessThanOrEqual(599);
|
|
563
|
+
assert('Status code', res.status).toBeBetween(400, 599);
|
|
543
564
|
}),
|
|
544
565
|
audit('SHOULD use 400 status code if parameters are invalid when accepting application/graphql-response+json', async () => {
|
|
545
|
-
const
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
566
|
+
const res = await fetchFn(opts.url, {
|
|
567
|
+
method: 'POST',
|
|
568
|
+
headers: {
|
|
569
|
+
'content-type': 'application/json',
|
|
570
|
+
accept: 'application/graphql-response+json',
|
|
571
|
+
},
|
|
572
|
+
body: JSON.stringify({
|
|
573
|
+
qeury: /* typo */ '{ __typename }',
|
|
574
|
+
}),
|
|
550
575
|
});
|
|
551
576
|
assert('Status code', res.status).toBe(400);
|
|
552
577
|
}),
|
|
553
578
|
audit('SHOULD not contain the data entry if parameters are invalid when accepting application/graphql-response+json', async () => {
|
|
554
|
-
const
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
579
|
+
const res = await fetchFn(opts.url, {
|
|
580
|
+
method: 'POST',
|
|
581
|
+
headers: {
|
|
582
|
+
'content-type': 'application/json',
|
|
583
|
+
accept: 'application/graphql-response+json',
|
|
584
|
+
},
|
|
585
|
+
body: JSON.stringify({
|
|
586
|
+
qeury: /* typo */ '{ __typename }',
|
|
587
|
+
}),
|
|
559
588
|
});
|
|
560
589
|
assert('Data entry', (await assertBodyAsExecutionResult(res)).data).toBe(undefined);
|
|
561
590
|
}),
|
|
562
591
|
audit(
|
|
563
592
|
// TODO: convert to MUST after watershed
|
|
564
593
|
'SHOULD use 4xx or 5xx status codes on document parsing failure when accepting application/graphql-response+json', async () => {
|
|
565
|
-
const
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
594
|
+
const res = await fetchFn(opts.url, {
|
|
595
|
+
method: 'POST',
|
|
596
|
+
headers: {
|
|
597
|
+
'content-type': 'application/json',
|
|
598
|
+
accept: 'application/graphql-response+json',
|
|
599
|
+
},
|
|
600
|
+
body: JSON.stringify({
|
|
601
|
+
query: '{',
|
|
602
|
+
}),
|
|
570
603
|
});
|
|
571
|
-
assert('Status code', res.status).
|
|
572
|
-
assert('Status code', res.status).toBeLessThanOrEqual(599);
|
|
604
|
+
assert('Status code', res.status).toBeBetween(400, 599);
|
|
573
605
|
}),
|
|
574
606
|
audit('SHOULD use 400 status code on document parsing failure when accepting application/graphql-response+json', async () => {
|
|
575
|
-
const
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
607
|
+
const res = await fetchFn(opts.url, {
|
|
608
|
+
method: 'POST',
|
|
609
|
+
headers: {
|
|
610
|
+
'content-type': 'application/json',
|
|
611
|
+
accept: 'application/graphql-response+json',
|
|
612
|
+
},
|
|
613
|
+
body: JSON.stringify({
|
|
614
|
+
query: '{',
|
|
615
|
+
}),
|
|
580
616
|
});
|
|
581
617
|
assert('Status code', res.status).toBe(400);
|
|
582
618
|
}),
|
|
583
619
|
audit('SHOULD not contain the data entry on document parsing failure when accepting application/graphql-response+json', async () => {
|
|
584
|
-
const
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
620
|
+
const res = await fetchFn(opts.url, {
|
|
621
|
+
method: 'POST',
|
|
622
|
+
headers: {
|
|
623
|
+
'content-type': 'application/json',
|
|
624
|
+
accept: 'application/graphql-response+json',
|
|
625
|
+
},
|
|
626
|
+
body: JSON.stringify({
|
|
627
|
+
query: '{',
|
|
628
|
+
}),
|
|
589
629
|
});
|
|
590
630
|
assert('Data entry', (await assertBodyAsExecutionResult(res)).data).toBe(undefined);
|
|
591
631
|
}),
|
|
592
632
|
audit(
|
|
593
633
|
// TODO: convert to MUST after watershed
|
|
594
634
|
'SHOULD use 4xx or 5xx status codes on document validation failure when accepting application/graphql-response+json', async () => {
|
|
595
|
-
const
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
635
|
+
const res = await fetchFn(opts.url, {
|
|
636
|
+
method: 'POST',
|
|
637
|
+
headers: {
|
|
638
|
+
'content-type': 'application/json',
|
|
639
|
+
accept: 'application/graphql-response+json',
|
|
640
|
+
},
|
|
641
|
+
body: JSON.stringify({
|
|
642
|
+
query: '{ 8f31403dfe404bccbb0e835f2629c6a7 }', // making sure the field doesnt exist
|
|
643
|
+
}),
|
|
600
644
|
});
|
|
601
|
-
assert('Status code', res.status).
|
|
602
|
-
assert('Status code', res.status).toBeLessThanOrEqual(599);
|
|
645
|
+
assert('Status code', res.status).toBeBetween(400, 599);
|
|
603
646
|
}),
|
|
604
647
|
audit('SHOULD use 400 status code on document validation failure when accepting application/graphql-response+json', async () => {
|
|
605
|
-
const
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
648
|
+
const res = await fetchFn(opts.url, {
|
|
649
|
+
method: 'POST',
|
|
650
|
+
headers: {
|
|
651
|
+
'content-type': 'application/json',
|
|
652
|
+
accept: 'application/graphql-response+json',
|
|
653
|
+
},
|
|
654
|
+
body: JSON.stringify({
|
|
655
|
+
query: '{ 8f31403dfe404bccbb0e835f2629c6a7 }', // making sure the field doesnt exist
|
|
656
|
+
}),
|
|
610
657
|
});
|
|
611
658
|
assert('Status code', res.status).toBe(400);
|
|
612
659
|
}),
|
|
613
660
|
audit('SHOULD not contain the data entry on document validation failure when accepting application/graphql-response+json', async () => {
|
|
614
|
-
const
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
661
|
+
const res = await fetchFn(opts.url, {
|
|
662
|
+
method: 'POST',
|
|
663
|
+
headers: {
|
|
664
|
+
'content-type': 'application/json',
|
|
665
|
+
accept: 'application/graphql-response+json',
|
|
666
|
+
},
|
|
667
|
+
body: JSON.stringify({
|
|
668
|
+
query: '{ 8f31403dfe404bccbb0e835f2629c6a7 }', // making sure the field doesnt exist
|
|
669
|
+
}),
|
|
619
670
|
});
|
|
620
671
|
assert('Data entry', (await assertBodyAsExecutionResult(res)).data).toBe(undefined);
|
|
621
672
|
}),
|
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "graphql-http",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.1",
|
|
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
|