-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
12 changed files
with
599 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{ | ||
"extends": "@12core/eslint-config-12core" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
name: tests | ||
|
||
on: [push] | ||
|
||
jobs: | ||
test: | ||
runs-on: ${{ matrix.os }} | ||
|
||
strategy: | ||
matrix: | ||
os: [ubuntu-latest] | ||
node: [12] | ||
|
||
steps: | ||
- uses: actions/checkout@v1 | ||
- name: Use Node.js ${{ matrix.node }} | ||
uses: actions/setup-node@v1 | ||
with: | ||
node-version: ${{ matrix.node }} | ||
- run: npm i | ||
- run: npm test |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
node_modules | ||
.nyc_output |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
'use strict'; | ||
const fetch = (m => m.__esModule ? /* istanbul ignore next */ m.default : /* istanbul ignore next */ m)(require('node-fetch')) | ||
const { ClientError } = require('./types.js') | ||
|
||
class GraphQLClient { | ||
constructor (url, options = {}) { | ||
this.url = url | ||
this.options = options | ||
} | ||
|
||
async rawRequest (query, variables) { | ||
const { headers, ...others } = this.options | ||
|
||
const body = JSON.stringify({ | ||
query, | ||
variables: variables | ||
}) | ||
|
||
const response = await fetch(this.url, { | ||
method: 'POST', | ||
headers: { 'Content-Type': 'application/json', ...headers }, | ||
body, | ||
...others | ||
}) | ||
|
||
const result = await getResult(response) | ||
|
||
if (response.ok && !result.errors && result.data) { | ||
const { headers, status } = response | ||
return { ...result, headers, status } | ||
} else { | ||
const errorResult = typeof result === 'string' ? { error: result } : result | ||
throw new ClientError( | ||
{ ...errorResult, status: response.status, headers: response.headers }, | ||
{ query, variables } | ||
) | ||
} | ||
} | ||
|
||
async request (query, variables) { | ||
const { data } = await this.rawRequest(query, variables) | ||
return data | ||
} | ||
|
||
setHeaders (headers) { | ||
this.options.headers = headers | ||
|
||
return this | ||
} | ||
|
||
setHeader (key, value) { | ||
const { headers } = this.options | ||
|
||
if (headers) { | ||
// todo what if headers is in nested array form... ? | ||
headers[key] = value | ||
} else { | ||
this.options.headers = { [key]: value } | ||
} | ||
return this | ||
} | ||
} | ||
exports.GraphQLClient = GraphQLClient | ||
|
||
function rawRequest (url, query, variables) { | ||
const client = new GraphQLClient(url) | ||
return client.rawRequest(query, variables) | ||
} | ||
exports.rawRequest = rawRequest | ||
|
||
function request (url, query, variables) { | ||
const client = new GraphQLClient(url) | ||
return client.request(query, variables) | ||
} | ||
exports.request = request | ||
|
||
function getResult (response) { | ||
const contentType = response.headers.get('Content-Type') | ||
if (contentType && contentType.startsWith('application/json')) { | ||
return response.json() | ||
} else { | ||
return response.text() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
'use strict'; | ||
const tap = (m => m.__esModule ? /* istanbul ignore next */ m.default : /* istanbul ignore next */ m)(require('tap')) | ||
const body = (m => m.__esModule ? /* istanbul ignore next */ m.default : /* istanbul ignore next */ m)(require('body-parser')) | ||
const express = (m => m.__esModule ? /* istanbul ignore next */ m.default : /* istanbul ignore next */ m)(require('express')) | ||
const { createServer } = require('http') | ||
|
||
const { GraphQLClient, request, rawRequest } = require('./index.js') | ||
|
||
tap.afterEach((done, t) => { | ||
// https://stackoverflow.com/questions/10378690/remove-route-mappings-in-nodejs-express/28369539#28369539 | ||
ctx.server._router.stack.forEach((item, i) => { | ||
if (item.name === 'mock') ctx.server._router.stack.splice(i, 1) | ||
}) | ||
|
||
done() | ||
}) | ||
|
||
const ctx = {} | ||
tap.test('set up test server', t => { | ||
ctx.server = express() | ||
ctx.server.use(body.json()) | ||
ctx.nodeServer = createServer() | ||
ctx.nodeServer.listen({ port: 3210 }) | ||
ctx.url = 'http://localhost:3210' | ||
ctx.nodeServer.on('request', ctx.server) | ||
ctx.nodeServer.once('listening', t.end) | ||
ctx.mock = (spec) => { | ||
const requests = [] | ||
ctx.server.use('*', function mock (req, res) { | ||
requests.push({ | ||
method: req.method, | ||
headers: req.headers, | ||
body: req.body | ||
}) | ||
if (spec.headers) { | ||
Object.entries(spec.headers).forEach(([name, value]) => { | ||
res.setHeader(name, value) | ||
}) | ||
} | ||
res.send(spec.body ?? {}) | ||
}) | ||
return { ...spec, requests } | ||
} | ||
}) | ||
|
||
tap.test('export contract', async t => { | ||
t.ok(GraphQLClient) | ||
t.ok(request) | ||
t.ok(rawRequest) | ||
|
||
const client = new GraphQLClient('https://example.com/graphql') | ||
|
||
t.ok(client) | ||
}) | ||
|
||
tap.test('minimal query', async t => { | ||
console.log() | ||
const data = ctx.mock({ | ||
body: { | ||
data: { | ||
viewer: { | ||
id: 'some-id' | ||
} | ||
} | ||
} | ||
}).body.data | ||
|
||
const results = await request(ctx.url, '{ viewer { id } }') | ||
t.deepEqual(results, data) | ||
}) | ||
|
||
tap.test('minimal raw query', async t => { | ||
const { extensions, data } = ctx.mock({ | ||
body: { | ||
data: { | ||
viewer: { | ||
id: 'some-id' | ||
} | ||
}, | ||
extensions: { | ||
version: '1' | ||
} | ||
} | ||
}).body | ||
const { headers, ...result } = await rawRequest(ctx.url, '{ viewer { id } }') | ||
|
||
t.deepEqual(result, { data, extensions, status: 200 }) | ||
}) | ||
|
||
tap.test('minimal raw query with response headers', async t => { | ||
const { | ||
headers: reqHeaders, | ||
body: { data, extensions } | ||
} = ctx.mock({ | ||
headers: { | ||
'Content-Type': 'application/json', | ||
'X-Custom-Header': 'test-custom-header' | ||
}, | ||
body: { | ||
data: { | ||
viewer: { | ||
id: 'some-id' | ||
} | ||
}, | ||
extensions: { | ||
version: '1' | ||
} | ||
} | ||
}) | ||
const { headers, ...result } = await rawRequest(ctx.url, '{ viewer { id } }') | ||
|
||
t.deepEqual(result, { data, extensions, status: 200 }) | ||
t.deepEqual(headers.get('X-Custom-Header'), reqHeaders['X-Custom-Header']) | ||
}) | ||
|
||
tap.test('content-type with charset', async t => { | ||
const { data } = ctx.mock({ | ||
// headers: { 'Content-Type': 'application/json; charset=utf-8' }, | ||
body: { | ||
data: { | ||
viewer: { | ||
id: 'some-id' | ||
} | ||
} | ||
} | ||
}).body | ||
const results = await request(ctx.url, '{ viewer { id } }') | ||
t.deepEqual(results, data) | ||
}) | ||
|
||
tap.test('basic error', async t => { | ||
ctx.mock({ | ||
body: { | ||
errors: { | ||
message: 'Syntax Error GraphQL request (1:1) Unexpected Name "x"\n\n1: x\n ^\n', | ||
locations: [ | ||
{ | ||
line: 1, | ||
column: 1 | ||
} | ||
] | ||
} | ||
} | ||
}) | ||
|
||
const res = await request(ctx.url, 'x').catch((x) => x) | ||
|
||
t.deepEqual(res.message, 'GraphQL Error (Code: 200): {"response":{"errors":{"message":"Syntax Error GraphQL request (1:1) Unexpected Name \\"x\\"\\n\\n1: x\\n ^\\n","locations":[{"line":1,"column":1}]},"status":200,"headers":{}},"request":{"query":"x"}}') | ||
}) | ||
|
||
tap.test('basic error with raw request', async t => { | ||
ctx.mock({ | ||
body: { | ||
errors: { | ||
message: 'Syntax Error GraphQL request (1:1) Unexpected Name "x"\n\n1: x\n ^\n', | ||
locations: [ | ||
{ | ||
line: 1, | ||
column: 1 | ||
} | ||
] | ||
} | ||
} | ||
}) | ||
const res = await rawRequest(ctx.url, 'x').catch((x) => x) | ||
t.deepEqual(res.message, 'GraphQL Error (Code: 200): {"response":{"errors":{"message":"Syntax Error GraphQL request (1:1) Unexpected Name \\"x\\"\\n\\n1: x\\n ^\\n","locations":[{"line":1,"column":1}]},"status":200,"headers":{}},"request":{"query":"x"}}') | ||
}) | ||
|
||
tap.test('shut down test server', t => { | ||
ctx.nodeServer.close(t.end) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
{"type":"commonjs"} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
'use strict'; | ||
class ClientError extends Error { | ||
constructor (response, request) { | ||
const message = `${extractMessage(response)}: ${JSON.stringify({ | ||
response, | ||
request | ||
})}` | ||
|
||
super(message) | ||
|
||
Object.setPrototypeOf(this, ClientError.prototype) | ||
|
||
this.response = response | ||
this.request = request | ||
|
||
// this is needed as Safari doesn't support .captureStackTrace | ||
if (typeof Error.captureStackTrace === 'function') { | ||
Error.captureStackTrace(this, ClientError) | ||
} | ||
} | ||
} | ||
exports.ClientError = ClientError | ||
|
||
function extractMessage (response) { | ||
try { | ||
return response.errors[0].message | ||
} catch (e) { | ||
return `GraphQL Error (Code: ${response.status})` | ||
} | ||
} |
Oops, something went wrong.