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 CHANGED
@@ -7,9 +7,9 @@
7
7
 
8
8
  [![Continuous integration](https://github.com/graphql/graphql-http/workflows/Continuous%20integration/badge.svg)](https://github.com/graphql/graphql-http/actions?query=workflow%3A%22Continuous+integration%22) [![graphql-http](https://img.shields.io/npm/v/graphql-http.svg?label=graphql-http&logo=npm)](https://www.npmjs.com/package/graphql-http)
9
9
 
10
- <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>
10
+ <i>Want a full-featured server? See the <b>[servers section](#servers)</b>!</i>
11
11
 
12
- <i>Want a full-featured server? See the <b>[servers section](#servers)</b></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 | Audit |
733
- | -------------------------------------------------------------- | ------------------------------------------------------------------ |
734
- | [graphql-yoga](https://www.the-guild.dev/graphql/yoga-server) | [✅ Fully compliant](/implementations/graphql-yoga/README.md) |
735
- | [apollo-server](https://www.the-guild.dev/graphql/yoga-server) | [⚠️ Partially compliant](/implementations/apollo-server/README.md) |
736
- | [mercurius](https://mercurius.dev) | [⚠️ Partially compliant](/implementations/mercurius/README.md) |
737
- | [graphql-helix](https://www.graphql-helix.com/) | [⚠️ Partially compliant](/implementations/graphql-helix/README.md) |
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
 
@@ -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 url = new URL(opts.url);
23
- url.searchParams.set('query', '{ __typename }');
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 url = new URL(opts.url);
34
- url.searchParams.set('query', '{ __typename }');
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 url = new URL(opts.url);
47
- url.searchParams.set('query', '{ __typename }');
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 url = new URL(opts.url);
60
- url.searchParams.set('query', '{ __typename }');
61
- const res = await fetchFn(url.toString());
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 url = new URL(opts.url);
67
- url.searchParams.set('query', '{ __typename }');
68
- const res = await fetchFn(url.toString());
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 url = new URL(opts.url);
92
- url.searchParams.set('query', '{ __typename }');
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 url = new URL(opts.url);
103
- url.searchParams.set('query', '{ __typename }');
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
- (0, utils_1.audit)('MUST NOT allow executing mutations on GET requests', async () => {
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).toBe(405);
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).toBeGreaterThanOrEqual(400);
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)('MUST allow URL-encoded JSON string {variables} parameter in GETs when accepting application/json', async () => {
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 url = new URL(opts.url);
473
- url.searchParams.set('qeury' /* typo */, '{ __typename }');
474
- const res = await fetchFn(url.toString(), {
475
- method: 'GET',
476
- headers: { accept: 'application/json' },
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 url = new URL(opts.url);
482
- url.searchParams.set('query', '{');
483
- const res = await fetchFn(url.toString(), {
484
- method: 'GET',
485
- headers: { accept: 'application/json' },
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 url = new URL(opts.url);
491
- url.searchParams.set('query', '{ 8f31403dfe404bccbb0e835f2629c6a7 }'); // making sure the field doesnt exist
492
- const res = await fetchFn(url.toString(), {
493
- method: 'GET',
494
- headers: { accept: 'application/json' },
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).toBeGreaterThanOrEqual(400);
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 url = new URL(opts.url);
539
- url.searchParams.set('qeury' /* typo */, '{ __typename }');
540
- const res = await fetchFn(url.toString(), {
541
- method: 'GET',
542
- headers: { accept: 'application/graphql-response+json' },
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).toBeGreaterThanOrEqual(400);
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 url = new URL(opts.url);
549
- url.searchParams.set('qeury' /* typo */, '{ __typename }');
550
- const res = await fetchFn(url.toString(), {
551
- method: 'GET',
552
- headers: { accept: 'application/graphql-response+json' },
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 url = new URL(opts.url);
558
- url.searchParams.set('qeury' /* typo */, '{ __typename }');
559
- const res = await fetchFn(url.toString(), {
560
- method: 'GET',
561
- headers: { accept: 'application/graphql-response+json' },
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 url = new URL(opts.url);
569
- url.searchParams.set('query', '{');
570
- const res = await fetchFn(url.toString(), {
571
- method: 'GET',
572
- headers: { accept: 'application/graphql-response+json' },
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).toBeGreaterThanOrEqual(400);
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 url = new URL(opts.url);
579
- url.searchParams.set('query', '{');
580
- const res = await fetchFn(url.toString(), {
581
- method: 'GET',
582
- headers: { accept: 'application/graphql-response+json' },
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 url = new URL(opts.url);
588
- url.searchParams.set('query', '{');
589
- const res = await fetchFn(url.toString(), {
590
- method: 'GET',
591
- headers: { accept: 'application/graphql-response+json' },
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 url = new URL(opts.url);
599
- url.searchParams.set('query', '{ 8f31403dfe404bccbb0e835f2629c6a7 }'); // making sure the field doesnt exist
600
- const res = await fetchFn(url.toString(), {
601
- method: 'GET',
602
- headers: { accept: 'application/graphql-response+json' },
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).toBeGreaterThanOrEqual(400);
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 url = new URL(opts.url);
609
- url.searchParams.set('query', '{ 8f31403dfe404bccbb0e835f2629c6a7 }'); // making sure the field doesnt exist
610
- const res = await fetchFn(url.toString(), {
611
- method: 'GET',
612
- headers: { accept: 'application/graphql-response+json' },
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 url = new URL(opts.url);
618
- url.searchParams.set('query', '{ 8f31403dfe404bccbb0e835f2629c6a7 }'); // making sure the field doesnt exist
619
- const res = await fetchFn(url.toString(), {
620
- method: 'GET',
621
- headers: { accept: 'application/graphql-response+json' },
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
  }),
@@ -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 url = new URL(opts.url);
20
- url.searchParams.set('query', '{ __typename }');
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 url = new URL(opts.url);
31
- url.searchParams.set('query', '{ __typename }');
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 url = new URL(opts.url);
44
- url.searchParams.set('query', '{ __typename }');
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 url = new URL(opts.url);
57
- url.searchParams.set('query', '{ __typename }');
58
- const res = await fetchFn(url.toString());
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 url = new URL(opts.url);
64
- url.searchParams.set('query', '{ __typename }');
65
- const res = await fetchFn(url.toString());
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 url = new URL(opts.url);
89
- url.searchParams.set('query', '{ __typename }');
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 url = new URL(opts.url);
100
- url.searchParams.set('query', '{ __typename }');
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
- audit('MUST NOT allow executing mutations on GET requests', async () => {
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).toBe(405);
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).toBeGreaterThanOrEqual(400);
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('MUST allow URL-encoded JSON string {variables} parameter in GETs when accepting application/json', async () => {
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 url = new URL(opts.url);
470
- url.searchParams.set('qeury' /* typo */, '{ __typename }');
471
- const res = await fetchFn(url.toString(), {
472
- method: 'GET',
473
- headers: { accept: 'application/json' },
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 url = new URL(opts.url);
479
- url.searchParams.set('query', '{');
480
- const res = await fetchFn(url.toString(), {
481
- method: 'GET',
482
- headers: { accept: 'application/json' },
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 url = new URL(opts.url);
488
- url.searchParams.set('query', '{ 8f31403dfe404bccbb0e835f2629c6a7 }'); // making sure the field doesnt exist
489
- const res = await fetchFn(url.toString(), {
490
- method: 'GET',
491
- headers: { accept: 'application/json' },
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).toBeGreaterThanOrEqual(400);
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 url = new URL(opts.url);
536
- url.searchParams.set('qeury' /* typo */, '{ __typename }');
537
- const res = await fetchFn(url.toString(), {
538
- method: 'GET',
539
- headers: { accept: 'application/graphql-response+json' },
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).toBeGreaterThanOrEqual(400);
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 url = new URL(opts.url);
546
- url.searchParams.set('qeury' /* typo */, '{ __typename }');
547
- const res = await fetchFn(url.toString(), {
548
- method: 'GET',
549
- headers: { accept: 'application/graphql-response+json' },
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 url = new URL(opts.url);
555
- url.searchParams.set('qeury' /* typo */, '{ __typename }');
556
- const res = await fetchFn(url.toString(), {
557
- method: 'GET',
558
- headers: { accept: 'application/graphql-response+json' },
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 url = new URL(opts.url);
566
- url.searchParams.set('query', '{');
567
- const res = await fetchFn(url.toString(), {
568
- method: 'GET',
569
- headers: { accept: 'application/graphql-response+json' },
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).toBeGreaterThanOrEqual(400);
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 url = new URL(opts.url);
576
- url.searchParams.set('query', '{');
577
- const res = await fetchFn(url.toString(), {
578
- method: 'GET',
579
- headers: { accept: 'application/graphql-response+json' },
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 url = new URL(opts.url);
585
- url.searchParams.set('query', '{');
586
- const res = await fetchFn(url.toString(), {
587
- method: 'GET',
588
- headers: { accept: 'application/graphql-response+json' },
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 url = new URL(opts.url);
596
- url.searchParams.set('query', '{ 8f31403dfe404bccbb0e835f2629c6a7 }'); // making sure the field doesnt exist
597
- const res = await fetchFn(url.toString(), {
598
- method: 'GET',
599
- headers: { accept: 'application/graphql-response+json' },
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).toBeGreaterThanOrEqual(400);
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 url = new URL(opts.url);
606
- url.searchParams.set('query', '{ 8f31403dfe404bccbb0e835f2629c6a7 }'); // making sure the field doesnt exist
607
- const res = await fetchFn(url.toString(), {
608
- method: 'GET',
609
- headers: { accept: 'application/graphql-response+json' },
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 url = new URL(opts.url);
615
- url.searchParams.set('query', '{ 8f31403dfe404bccbb0e835f2629c6a7 }'); // making sure the field doesnt exist
616
- const res = await fetchFn(url.toString(), {
617
- method: 'GET',
618
- headers: { accept: 'application/graphql-response+json' },
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
  }),
@@ -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
- toBeLessThanOrEqual: (expected: T extends number ? T : never) => void;
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;
@@ -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
- toBeLessThanOrEqual: (expected: T extends number ? T : never) => void;
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;
@@ -70,14 +70,9 @@ function assert(name, actual) {
70
70
  throw `${name} ${actual} is not ${expected}`;
71
71
  }
72
72
  },
73
- toBeLessThanOrEqual: (expected) => {
74
- if (!(actual <= expected)) {
75
- throw `${name} ${actual} is not less than or equal to ${expected}`;
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) => {
@@ -52,14 +52,9 @@ export function assert(name, actual) {
52
52
  throw `${name} ${actual} is not ${expected}`;
53
53
  }
54
54
  },
55
- toBeLessThanOrEqual: (expected) => {
56
- if (!(actual <= expected)) {
57
- throw `${name} ${actual} is not less than or equal to ${expected}`;
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.0",
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