graphql-http
Advanced tools
Comparing version 1.7.0 to 1.7.1
@@ -22,8 +22,9 @@ "use strict"; | ||
'SHOULD accept application/graphql-response+json and match the content-type', async () => { | ||
const url = new URL(opts.url); | ||
url.searchParams.set('query', '{ __typename }'); | ||
const res = await fetchFn(url.toString(), { | ||
const res = await fetchFn(opts.url, { | ||
method: 'POST', | ||
headers: { | ||
'content-type': 'application/json', | ||
accept: 'application/graphql-response+json', | ||
}, | ||
body: JSON.stringify({ query: '{ __typename }' }), | ||
}); | ||
@@ -34,8 +35,9 @@ (0, utils_1.assert)('Status code', res.status).toBe(200); | ||
(0, utils_1.audit)('MUST accept application/json and match the content-type', async () => { | ||
const url = new URL(opts.url); | ||
url.searchParams.set('query', '{ __typename }'); | ||
const res = await fetchFn(url.toString(), { | ||
const res = await fetchFn(opts.url, { | ||
method: 'POST', | ||
headers: { | ||
'content-type': 'application/json', | ||
accept: 'application/json', | ||
}, | ||
body: JSON.stringify({ query: '{ __typename }' }), | ||
}); | ||
@@ -48,8 +50,9 @@ (0, utils_1.assert)('Status code', res.status).toBe(200); | ||
'SHOULD accept */* and use application/graphql-response+json for the content-type', async () => { | ||
const url = new URL(opts.url); | ||
url.searchParams.set('query', '{ __typename }'); | ||
const res = await fetchFn(url.toString(), { | ||
const res = await fetchFn(opts.url, { | ||
method: 'POST', | ||
headers: { | ||
'content-type': 'application/json', | ||
accept: '*/*', | ||
}, | ||
body: JSON.stringify({ query: '{ __typename }' }), | ||
}); | ||
@@ -62,5 +65,9 @@ (0, utils_1.assert)('Status code', res.status).toBe(200); | ||
'SHOULD assume application/graphql-response+json content-type when accept is missing', async () => { | ||
const url = new URL(opts.url); | ||
url.searchParams.set('query', '{ __typename }'); | ||
const res = await fetchFn(url.toString()); | ||
const res = await fetchFn(opts.url, { | ||
method: 'POST', | ||
headers: { | ||
'content-type': 'application/json', | ||
}, | ||
body: JSON.stringify({ query: '{ __typename }' }), | ||
}); | ||
(0, utils_1.assert)('Status code', res.status).toBe(200); | ||
@@ -70,5 +77,9 @@ (0, utils_1.assert)('Content-Type header', res.headers.get('content-type')).toContain('application/graphql-response+json'); | ||
(0, utils_1.audit)('MUST use utf-8 encoding when responding', async () => { | ||
const url = new URL(opts.url); | ||
url.searchParams.set('query', '{ __typename }'); | ||
const res = await fetchFn(url.toString()); | ||
const res = await fetchFn(opts.url, { | ||
method: 'POST', | ||
headers: { | ||
'content-type': 'application/json', | ||
}, | ||
body: JSON.stringify({ query: '{ __typename }' }), | ||
}); | ||
(0, utils_1.assert)('Status code', res.status).toBe(200); | ||
@@ -96,8 +107,8 @@ // has charset set to utf-8 | ||
(0, utils_1.audit)('MUST accept utf-8 encoding', async () => { | ||
const url = new URL(opts.url); | ||
url.searchParams.set('query', '{ __typename }'); | ||
const res = await fetchFn(url.toString(), { | ||
const res = await fetchFn(opts.url, { | ||
method: 'POST', | ||
headers: { | ||
'content-type': 'application/json; charset=utf-8', | ||
}, | ||
body: JSON.stringify({ query: '{ __typename }' }), | ||
}); | ||
@@ -108,8 +119,8 @@ (0, utils_1.assert)('Status code', res.status).toBe(200); | ||
(0, utils_1.audit)('MUST assume utf-8 if encoding is unspecified', async () => { | ||
const url = new URL(opts.url); | ||
url.searchParams.set('query', '{ __typename }'); | ||
const res = await fetchFn(url.toString(), { | ||
const res = await fetchFn(opts.url, { | ||
method: 'POST', | ||
headers: { | ||
'content-type': 'application/json', | ||
}, | ||
body: JSON.stringify({ query: '{ __typename }' }), | ||
}); | ||
@@ -135,3 +146,4 @@ (0, utils_1.assert)('Status code', res.status).toBe(200); | ||
// Request GET | ||
(0, utils_1.audit)('MUST NOT allow executing mutations on GET requests', async () => { | ||
// TODO: this is a MUST if the server supports GET requests | ||
(0, utils_1.audit)('MAY NOT allow executing mutations on GET requests', async () => { | ||
const url = new URL(opts.url); | ||
@@ -144,3 +156,3 @@ url.searchParams.set('query', 'mutation { __typename }'); | ||
}); | ||
(0, utils_1.assert)('Status code', res.status).toBe(405); | ||
(0, utils_1.assert)('Status code', res.status).toBeBetween(400, 499); | ||
}), | ||
@@ -152,4 +164,3 @@ // Request POST | ||
}); | ||
(0, utils_1.assert)('Status code', res.status).toBeGreaterThanOrEqual(400); | ||
(0, utils_1.assert)('Status code', res.status).toBeLessThanOrEqual(499); | ||
(0, utils_1.assert)('Status code', res.status).toBeBetween(400, 499); | ||
}), | ||
@@ -379,5 +390,3 @@ (0, utils_1.audit)('MUST accept application/json POST requests', async () => { | ||
}), | ||
(0, utils_1.audit)( | ||
// TODO: convert to MUST after watershed | ||
'SHOULD allow URL-encoded JSON string {variables} parameter in GETs when accepting application/graphql-response+json', async () => { | ||
(0, utils_1.audit)('MAY allow URL-encoded JSON string {variables} parameter in GETs when accepting application/graphql-response+json', async () => { | ||
const url = new URL(opts.url); | ||
@@ -394,3 +403,3 @@ url.searchParams.set('query', 'query Type($name: String!) { __type(name: $name) { name } }'); | ||
}), | ||
(0, utils_1.audit)('MUST allow URL-encoded JSON string {variables} parameter in GETs when accepting application/json', async () => { | ||
(0, utils_1.audit)('MAY allow URL-encoded JSON string {variables} parameter in GETs when accepting application/json', async () => { | ||
const url = new URL(opts.url); | ||
@@ -484,7 +493,11 @@ url.searchParams.set('query', 'query Type($name: String!) { __type(name: $name) { name } }'); | ||
(0, utils_1.audit)('SHOULD use 200 status code if parameters are invalid when accepting application/json', async () => { | ||
const url = new URL(opts.url); | ||
url.searchParams.set('qeury' /* typo */, '{ __typename }'); | ||
const res = await fetchFn(url.toString(), { | ||
method: 'GET', | ||
headers: { accept: 'application/json' }, | ||
const res = await fetchFn(opts.url, { | ||
method: 'POST', | ||
headers: { | ||
'content-type': 'application/json', | ||
accept: 'application/json', | ||
}, | ||
body: JSON.stringify({ | ||
qeury: /* typo */ '{ __typename }', | ||
}), | ||
}); | ||
@@ -494,7 +507,9 @@ (0, utils_1.assert)('Status code', res.status).toBe(200); | ||
(0, utils_1.audit)('SHOULD use 200 status code on document parsing failure when accepting application/json', async () => { | ||
const url = new URL(opts.url); | ||
url.searchParams.set('query', '{'); | ||
const res = await fetchFn(url.toString(), { | ||
method: 'GET', | ||
headers: { accept: 'application/json' }, | ||
const res = await fetchFn(opts.url, { | ||
method: 'POST', | ||
headers: { | ||
'content-type': 'application/json', | ||
accept: 'application/json', | ||
}, | ||
body: JSON.stringify({ query: '{' }), | ||
}); | ||
@@ -504,7 +519,11 @@ (0, utils_1.assert)('Status code', res.status).toBe(200); | ||
(0, utils_1.audit)('SHOULD use 200 status code on document validation failure when accepting application/json', async () => { | ||
const url = new URL(opts.url); | ||
url.searchParams.set('query', '{ 8f31403dfe404bccbb0e835f2629c6a7 }'); // making sure the field doesnt exist | ||
const res = await fetchFn(url.toString(), { | ||
method: 'GET', | ||
headers: { accept: 'application/json' }, | ||
const res = await fetchFn(opts.url, { | ||
method: 'POST', | ||
headers: { | ||
'content-type': 'application/json', | ||
accept: 'application/json', | ||
}, | ||
body: JSON.stringify({ | ||
query: '{ 8f31403dfe404bccbb0e835f2629c6a7 }', // making sure the field doesnt exist | ||
}), | ||
}); | ||
@@ -525,4 +544,3 @@ (0, utils_1.assert)('Status code', res.status).toBe(200); | ||
}); | ||
(0, utils_1.assert)('Status code', res.status).toBeGreaterThanOrEqual(400); | ||
(0, utils_1.assert)('Status code', res.status).toBeLessThanOrEqual(599); | ||
(0, utils_1.assert)('Status code', res.status).toBeBetween(400, 499); | ||
}), | ||
@@ -554,17 +572,24 @@ (0, utils_1.audit)('SHOULD use 400 status code on JSON parsing failure when accepting application/graphql-response+json', async () => { | ||
'SHOULD use 4xx or 5xx status codes if parameters are invalid when accepting application/graphql-response+json', async () => { | ||
const url = new URL(opts.url); | ||
url.searchParams.set('qeury' /* typo */, '{ __typename }'); | ||
const res = await fetchFn(url.toString(), { | ||
method: 'GET', | ||
headers: { accept: 'application/graphql-response+json' }, | ||
const res = await fetchFn(opts.url, { | ||
method: 'POST', | ||
headers: { | ||
'content-type': 'application/json', | ||
accept: 'application/graphql-response+json', | ||
}, | ||
body: JSON.stringify({ | ||
qeury /* typo */: '{ __typename }', | ||
}), | ||
}); | ||
(0, utils_1.assert)('Status code', res.status).toBeGreaterThanOrEqual(400); | ||
(0, utils_1.assert)('Status code', res.status).toBeLessThanOrEqual(599); | ||
(0, utils_1.assert)('Status code', res.status).toBeBetween(400, 599); | ||
}), | ||
(0, utils_1.audit)('SHOULD use 400 status code if parameters are invalid when accepting application/graphql-response+json', async () => { | ||
const url = new URL(opts.url); | ||
url.searchParams.set('qeury' /* typo */, '{ __typename }'); | ||
const res = await fetchFn(url.toString(), { | ||
method: 'GET', | ||
headers: { accept: 'application/graphql-response+json' }, | ||
const res = await fetchFn(opts.url, { | ||
method: 'POST', | ||
headers: { | ||
'content-type': 'application/json', | ||
accept: 'application/graphql-response+json', | ||
}, | ||
body: JSON.stringify({ | ||
qeury: /* typo */ '{ __typename }', | ||
}), | ||
}); | ||
@@ -574,7 +599,11 @@ (0, utils_1.assert)('Status code', res.status).toBe(400); | ||
(0, utils_1.audit)('SHOULD not contain the data entry if parameters are invalid when accepting application/graphql-response+json', async () => { | ||
const url = new URL(opts.url); | ||
url.searchParams.set('qeury' /* typo */, '{ __typename }'); | ||
const res = await fetchFn(url.toString(), { | ||
method: 'GET', | ||
headers: { accept: 'application/graphql-response+json' }, | ||
const res = await fetchFn(opts.url, { | ||
method: 'POST', | ||
headers: { | ||
'content-type': 'application/json', | ||
accept: 'application/graphql-response+json', | ||
}, | ||
body: JSON.stringify({ | ||
qeury: /* typo */ '{ __typename }', | ||
}), | ||
}); | ||
@@ -586,17 +615,24 @@ (0, utils_1.assert)('Data entry', (await (0, utils_1.assertBodyAsExecutionResult)(res)).data).toBe(undefined); | ||
'SHOULD use 4xx or 5xx status codes on document parsing failure when accepting application/graphql-response+json', async () => { | ||
const url = new URL(opts.url); | ||
url.searchParams.set('query', '{'); | ||
const res = await fetchFn(url.toString(), { | ||
method: 'GET', | ||
headers: { accept: 'application/graphql-response+json' }, | ||
const res = await fetchFn(opts.url, { | ||
method: 'POST', | ||
headers: { | ||
'content-type': 'application/json', | ||
accept: 'application/graphql-response+json', | ||
}, | ||
body: JSON.stringify({ | ||
query: '{', | ||
}), | ||
}); | ||
(0, utils_1.assert)('Status code', res.status).toBeGreaterThanOrEqual(400); | ||
(0, utils_1.assert)('Status code', res.status).toBeLessThanOrEqual(599); | ||
(0, utils_1.assert)('Status code', res.status).toBeBetween(400, 599); | ||
}), | ||
(0, utils_1.audit)('SHOULD use 400 status code on document parsing failure when accepting application/graphql-response+json', async () => { | ||
const url = new URL(opts.url); | ||
url.searchParams.set('query', '{'); | ||
const res = await fetchFn(url.toString(), { | ||
method: 'GET', | ||
headers: { accept: 'application/graphql-response+json' }, | ||
const res = await fetchFn(opts.url, { | ||
method: 'POST', | ||
headers: { | ||
'content-type': 'application/json', | ||
accept: 'application/graphql-response+json', | ||
}, | ||
body: JSON.stringify({ | ||
query: '{', | ||
}), | ||
}); | ||
@@ -606,7 +642,11 @@ (0, utils_1.assert)('Status code', res.status).toBe(400); | ||
(0, utils_1.audit)('SHOULD not contain the data entry on document parsing failure when accepting application/graphql-response+json', async () => { | ||
const url = new URL(opts.url); | ||
url.searchParams.set('query', '{'); | ||
const res = await fetchFn(url.toString(), { | ||
method: 'GET', | ||
headers: { accept: 'application/graphql-response+json' }, | ||
const res = await fetchFn(opts.url, { | ||
method: 'POST', | ||
headers: { | ||
'content-type': 'application/json', | ||
accept: 'application/graphql-response+json', | ||
}, | ||
body: JSON.stringify({ | ||
query: '{', | ||
}), | ||
}); | ||
@@ -618,17 +658,24 @@ (0, utils_1.assert)('Data entry', (await (0, utils_1.assertBodyAsExecutionResult)(res)).data).toBe(undefined); | ||
'SHOULD use 4xx or 5xx status codes on document validation failure when accepting application/graphql-response+json', async () => { | ||
const url = new URL(opts.url); | ||
url.searchParams.set('query', '{ 8f31403dfe404bccbb0e835f2629c6a7 }'); // making sure the field doesnt exist | ||
const res = await fetchFn(url.toString(), { | ||
method: 'GET', | ||
headers: { accept: 'application/graphql-response+json' }, | ||
const res = await fetchFn(opts.url, { | ||
method: 'POST', | ||
headers: { | ||
'content-type': 'application/json', | ||
accept: 'application/graphql-response+json', | ||
}, | ||
body: JSON.stringify({ | ||
query: '{ 8f31403dfe404bccbb0e835f2629c6a7 }', // making sure the field doesnt exist | ||
}), | ||
}); | ||
(0, utils_1.assert)('Status code', res.status).toBeGreaterThanOrEqual(400); | ||
(0, utils_1.assert)('Status code', res.status).toBeLessThanOrEqual(599); | ||
(0, utils_1.assert)('Status code', res.status).toBeBetween(400, 599); | ||
}), | ||
(0, utils_1.audit)('SHOULD use 400 status code on document validation failure when accepting application/graphql-response+json', async () => { | ||
const url = new URL(opts.url); | ||
url.searchParams.set('query', '{ 8f31403dfe404bccbb0e835f2629c6a7 }'); // making sure the field doesnt exist | ||
const res = await fetchFn(url.toString(), { | ||
method: 'GET', | ||
headers: { accept: 'application/graphql-response+json' }, | ||
const res = await fetchFn(opts.url, { | ||
method: 'POST', | ||
headers: { | ||
'content-type': 'application/json', | ||
accept: 'application/graphql-response+json', | ||
}, | ||
body: JSON.stringify({ | ||
query: '{ 8f31403dfe404bccbb0e835f2629c6a7 }', // making sure the field doesnt exist | ||
}), | ||
}); | ||
@@ -638,7 +685,11 @@ (0, utils_1.assert)('Status code', res.status).toBe(400); | ||
(0, utils_1.audit)('SHOULD not contain the data entry on document validation failure when accepting application/graphql-response+json', async () => { | ||
const url = new URL(opts.url); | ||
url.searchParams.set('query', '{ 8f31403dfe404bccbb0e835f2629c6a7 }'); // making sure the field doesnt exist | ||
const res = await fetchFn(url.toString(), { | ||
method: 'GET', | ||
headers: { accept: 'application/graphql-response+json' }, | ||
const res = await fetchFn(opts.url, { | ||
method: 'POST', | ||
headers: { | ||
'content-type': 'application/json', | ||
accept: 'application/graphql-response+json', | ||
}, | ||
body: JSON.stringify({ | ||
query: '{ 8f31403dfe404bccbb0e835f2629c6a7 }', // making sure the field doesnt exist | ||
}), | ||
}); | ||
@@ -645,0 +696,0 @@ (0, utils_1.assert)('Data entry', (await (0, utils_1.assertBodyAsExecutionResult)(res)).data).toBe(undefined); |
@@ -24,4 +24,3 @@ /** | ||
toBe: (expected: T) => void; | ||
toBeLessThanOrEqual: (expected: T extends number ? T : never) => void; | ||
toBeGreaterThanOrEqual: (expected: T extends number ? T : never) => void; | ||
toBeBetween: (min: T extends number ? T : never, max: T extends number ? T : never) => void; | ||
toContain: (expected: T extends (infer U)[] ? U : T extends string ? T : never) => void; | ||
@@ -28,0 +27,0 @@ notToContain: (expected: T extends (infer U_1)[] ? U_1 : T extends string ? T : never) => void; |
@@ -73,12 +73,7 @@ "use strict"; | ||
}, | ||
toBeLessThanOrEqual: (expected) => { | ||
if (!(actual <= expected)) { | ||
throw `${name} ${actual} is not less than or equal to ${expected}`; | ||
toBeBetween: (min, max) => { | ||
if (!(min <= actual && actual <= max)) { | ||
throw `${name} ${actual} is not between ${min} and ${max}`; | ||
} | ||
}, | ||
toBeGreaterThanOrEqual: (expected) => { | ||
if (!(actual >= expected)) { | ||
throw `${name} ${actual} is not greater than or equal to ${expected}`; | ||
} | ||
}, | ||
toContain: (expected) => { | ||
@@ -85,0 +80,0 @@ // @ts-expect-error types will match, otherwise never |
{ | ||
"name": "graphql-http", | ||
"version": "1.7.0", | ||
"version": "1.7.1", | ||
"description": "Simple, pluggable, zero-dependency, GraphQL over HTTP Protocol compliant server and client", | ||
@@ -85,3 +85,3 @@ "keywords": [ | ||
"workspaces": [ | ||
"implementations/*" | ||
"implementations/**/*" | ||
], | ||
@@ -88,0 +88,0 @@ "peerDependencies": { |
@@ -10,6 +10,6 @@ <div align="center"> | ||
<i>Want a full-featured server? See the <b>[servers section](#servers)</b>!</i> | ||
<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> | ||
<i>Want a full-featured server? See the <b>[servers section](#servers)</b></i>! | ||
<br /> | ||
@@ -733,8 +733,9 @@ </div> | ||
| Name | Audit | | ||
| -------------------------------------------------------------- | ------------------------------------------------------------------ | | ||
| [graphql-yoga](https://www.the-guild.dev/graphql/yoga-server) | [✅ Fully compliant](/implementations/graphql-yoga/README.md) | | ||
| [apollo-server](https://www.the-guild.dev/graphql/yoga-server) | [⚠️ Partially compliant](/implementations/apollo-server/README.md) | | ||
| [mercurius](https://mercurius.dev) | [⚠️ Partially compliant](/implementations/mercurius/README.md) | | ||
| [graphql-helix](https://www.graphql-helix.com/) | [⚠️ Partially compliant](/implementations/graphql-helix/README.md) | | ||
| Name | Audit | | ||
| ------------------------------------------------------------------ | -------------------------------------------------------------------- | | ||
| [graphql-yoga](https://www.the-guild.dev/graphql/yoga-server) | [✅ Compliant (0 warnings)](/implementations/graphql-yoga/README.md) | | ||
| [hotchocolate](https://chillicream.com/docs/hotchocolate) | [✅ Compliant (0 warnings)](/implementations/hotchocolate/README.md) | | ||
| [postgraphile](https://www.graphile.org/postgraphile/) | [✅ Compliant](/implementations/postgraphile/README.md) | | ||
| [apollo-server](https://www.apollographql.com/docs/apollo-server/) | [✅ Compliant](/implementations/apollo-server/README.md) | | ||
| [mercurius](https://mercurius.dev) | [✅ Compliant](/implementations/mercurius/README.md) | | ||
@@ -741,0 +742,0 @@ ## [Documentation](docs/) |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
235311
4359
759