Skip to content

Commit

Permalink
feat: Add routing support + add unit test + travis config
Browse files Browse the repository at this point in the history
  • Loading branch information
nicolasdao committed Aug 10, 2017
1 parent 15c25d2 commit deddeee
Show file tree
Hide file tree
Showing 5 changed files with 237 additions and 20 deletions.
9 changes: 9 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
language: node_js
node_js:
- "7"
deploy:
provider: npm
email: "nicolas.dao@gmail.com"
api_key: $NPM_TOKEN
on:
tags: true
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
<a href="https://neap.co" target="_blank"><img src="https://neap.co/img/neap_black_small_logo.png" alt="Neap Pty Ltd logo" title="Neap" align="right" height="50" width="120"/></a>

# GraphQL For Google Cloud Functions
[![NPM][1]][2] [![Tests][3]][4]

[1]: https://img.shields.io/npm/v/google-graphql-functions.svg?style=flat
[2]: https://www.npmjs.com/package/google-graphql-functions
[3]: https://travis-ci.org/nicolasdao/google-graphql-functions.svg?branch=master
[4]: https://travis-ci.org/nicolasdao/google-graphql-functions

## Install
Using npm in an existing Google Cloud Functions project:
Expand Down
10 changes: 7 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
],
"main": "src/index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"test": "mocha",
"eslint": "eslint src/",
"release": "standard-version --prerelease alpha"
},
Expand Down Expand Up @@ -37,9 +37,13 @@
"raw-body": "^2.2.0",
"standard-version": "^4.2.0",
"url": "^0.11.0",
"webfunc": "^0.2.1-alpha.0"
"webfunc": "^0.4.0-alpha.0"
},
"devDependencies": {
"eslint": "^4.1.1"
"chai": "^4.1.0",
"eslint": "^4.1.1",
"mocha": "^3.4.2",
"node-mocks-http": "^1.6.4",
"standard-version": "^4.2.0"
}
}
79 changes: 62 additions & 17 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,25 +26,70 @@ const url = require('url')
const parseBody = require('./parseBody')
const renderGraphiQL = require('./renderGraphiQL')

exports.serveHTTP = getOptions => serveHttp(
(req, res) => {
if (!res.headersSent) {
if (!getOptions)
throw httpError(500, 'GraphQL middleware requires getOptions.')
else {
const optionType = typeof(getOptions)
const getHttpHandler =
optionType == 'object' ? Promise.resolve(graphqlHTTP(getOptions)) :
optionType == 'function' ? getOptions(req, res).then(options => !res.headersSent ? graphqlHTTP(options) : null) :
null

if (!getHttpHandler)
throw httpError(500, `GraphQL middleware requires a valid 'getOptions' argument(allowed types: 'object', 'function'). Type '${typeof(getOptions)}' is invalid.`)

return getHttpHandler.then(httpHandler => httpHandler(req, res))
exports.serveHTTP = (arg1, arg2, arg3) => {
let route = null
let getOptions = null
let appConfig = null
const typeOfArg1 = typeof(arg1 || undefined)
const typeOfArg2 = typeof(arg2 || undefined)
const typeOfArg3 = typeof(arg3 || undefined)

if (arg1) {
if (typeOfArg1 != 'string' && typeOfArg1 != 'object' && typeOfArg1 != 'function')
throw new Error('The first argument of the \'serveHTTP\' method can only be a route, a graphQL options object, or a function similar to (req, res, params) => ... that returns a promise containing a graphQL option object.')

if (typeOfArg1 == 'string') {
route = arg1
if (!arg2)
throw new Error('If the first argument of the \'serveHTTP\' method is a route, then the second argument is required and must either be a graphQL options object, or a function similar to (req, res, params) => ... that returns a promise containing a graphQL option object.')
if (typeOfArg2 != 'object' && typeOfArg2 != 'function')
throw new Error('If the first argument of the \'serveHTTP\' method is a route, then the second argument must either be a graphQL options object, or a function similar to (req, res, params) => ... that returns a promise containing a graphQL option object.')
if (arg2.length != undefined)
throw new Error('If the first argument of the \'serveHTTP\' method is a route, then the second argument of the \'serveHTTP\' method cannot be an array. It must either be a route, a graphQL options object, or a function similar to (req, res, params) => ... that returns a promise containing a graphQL option object.')
if (typeOfArg2 == 'object' && !arg2.schema)
throw new Error('If the first argument of the \'serveHTTP\' method is a route and the second a graphQL object, then the second argument must contain a valid property called \'schema\'.')

getOptions = arg2
if (typeOfArg3 != 'undefined' && typeOfArg3 != 'object')
throw new Error('If the first 2 arguments of the \'serveHTTP\' method are properly defined, then the third one must represent an optional appConfig object.')
appConfig = arg3
}
else {
if (arg1.length != undefined)
throw new Error('The first argument of the \'serveHTTP\' method cannot be an array. It must either be a route, a graphQL options object, or a function similar to (req, res, params) => ... that returns a promise containing a graphQL option object.')
if (typeOfArg1 == 'object' && !arg1.schema)
throw new Error('If the first argument of the \'serveHTTP\' method is a graphQL object, then it must contain a valid property called \'schema\'.')

getOptions = arg1
if (typeOfArg2 != 'undefined' && typeOfArg2 != 'object')
throw new Error('If the first argument of the \'serveHTTP\' method is either a graphQL options object, or a function similar to (req, res, params) => ... that returns a promise containing a graphQL option object, then the second one must represent an optional appConfig object.')
appConfig = arg2
}

const func = (req, res, params) => {
if (!res.headersSent) {
if (!getOptions)
throw httpError(500, 'GraphQL middleware requires getOptions.')
else {
const optionType = typeof(getOptions)
const getHttpHandler =
optionType == 'object' ? Promise.resolve(graphqlHTTP(getOptions)) :
optionType == 'function' ? getOptions(req, res, params).then(options => !res.headersSent ? graphqlHTTP(options) : null) :
null

if (!getHttpHandler)
throw httpError(500, `GraphQL middleware requires a valid 'getOptions' argument(allowed types: 'object', 'function'). Type '${typeof(getOptions)}' is invalid.`)

return getHttpHandler.then(httpHandler => httpHandler(req, res))
}
}
}
})

return route ? serveHttp(route, func, appConfig) : serveHttp(func, appConfig)
}
else
throw new Error('The first argument of the \'serveHTTP\' method is required. It must either be a route, a graphQL options object, or a function similar to (req, res, params) => ... that returns a promise containing a graphQL option object.')
}

exports.graphqlHTTP = graphqlHTTP

Expand Down
153 changes: 153 additions & 0 deletions test/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
/**
* Copyright (c) 2017, Neap Pty Ltd.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
const { assert } = require('chai')
const httpMocks = require('node-mocks-http')
const { serveHTTP } = require('../src/index')

/*eslint-disable */
describe('index', () =>
describe('#serveHTTP: 01', () =>
it(`Should fail at build time if bad arguments are passed to the 'serveHTTP' method.`, () => {
/*eslint-enable */
const appconfig = {
headers: {
'Access-Control-Allow-Methods': 'GET, HEAD, OPTIONS, POST',
'Access-Control-Allow-Headers': 'Authorization, Content-Type, Origin',
'Access-Control-Allow-Origin': 'http://boris.com, http://localhost:8080',
'Access-Control-Max-Age': '1296000'
}
}

const endpoints = [1,2]

assert.throws(() => serveHTTP('/users/{username}', endpoints, appconfig), Error, `If the first argument of the 'serveHTTP' method is a route, then the second argument of the 'serveHTTP' method cannot be an array. It must either be a route, a graphQL options object, or a function similar to (req, res, params) => ... that returns a promise containing a graphQL option object.`)
assert.throws(() => serveHTTP('/users/{username}', appconfig), Error, `If the first argument of the 'serveHTTP' method is a route and the second a graphQL object, then the second argument must contain a valid property called 'schema'.`)
assert.throws(() => serveHTTP(), Error, `The first argument of the 'serveHTTP' method is required. It must either be a route, a graphQL options object, or a function similar to (req, res, params) => ... that returns a promise containing a graphQL option object.`)
assert.throws(() => serveHTTP(appconfig), Error, `If the first argument of the 'serveHTTP' method is a graphQL object, then it must contain a valid property called 'schema'.`)
})))

/*eslint-disable */
describe('index', () =>
describe('#serveHTTP: 02', () =>
it(`Should fail if the query does not match the specified routing.`, () => {
/*eslint-enable */
const req_01 = httpMocks.createRequest({
method: 'GET',
headers: {
origin: 'http://localhost:8080',
referer: 'http://localhost:8080'
},
_parsedUrl: {
pathname: '/'
}
})
const res_01 = httpMocks.createResponse()

const appconfig = {
headers: {
'Access-Control-Allow-Methods': 'GET, HEAD, OPTIONS, POST',
'Access-Control-Allow-Headers': 'Authorization, Content-Type, Origin',
'Access-Control-Allow-Origin': 'http://boris.com, http://localhost:8080',
'Access-Control-Max-Age': '1296000'
}
}

const fn = serveHTTP('/users/{graphiqlpath}', { schema: {} }, appconfig)

const result_01 = fn(req_01, res_01).then(() => {
assert.equal(1,2, 'Requests with the wrong route should failed with a \'not found\' error.')
})
.catch(err => assert.equal(err.message, 'Endpoint \'/\' not found.'))

return Promise.all([result_01])
})))

/*eslint-disable */
describe('index', () =>
describe('#serveHTTP: 03', () =>
it(`Should succeed if the query matches the specified routing.`, () => {
/*eslint-enable */
const req_01 = httpMocks.createRequest({
method: 'GET',
headers: {
origin: 'http://localhost:8080',
referer: 'http://localhost:8080'
},
_parsedUrl: {
pathname: '/users/graphiql'
}
})
const res_01 = httpMocks.createResponse()

const appconfig = {
headers: {
'Access-Control-Allow-Methods': 'GET, HEAD, OPTIONS, POST',
'Access-Control-Allow-Headers': 'Authorization, Content-Type, Origin',
'Access-Control-Allow-Origin': 'http://boris.com, http://localhost:8080',
'Access-Control-Max-Age': '1296000'
}
}

const fn = serveHTTP('/users', { schema: {} }, appconfig)

const result_01 = fn(req_01, res_01).then(() => {
assert.equal(1,1)
})
.catch(() => assert.equal(1,2, `This request should have succeeded.`))

return Promise.all([result_01])
})))

/*eslint-disable */
describe('index', () =>
describe('#serveHTTP: 04', () =>
it(`Should succeed regardless of the resource required if no routing is defined.`, () => {
/*eslint-enable */
const req_01 = httpMocks.createRequest({
method: 'GET',
headers: {
origin: 'http://localhost:8080',
referer: 'http://localhost:8080'
},
_parsedUrl: {
pathname: '/users/graphiql'
}
})
const res_01 = httpMocks.createResponse()
const req_02 = httpMocks.createRequest({
method: 'GET',
headers: {
origin: 'http://localhost:8080',
referer: 'http://localhost:8080'
}
})
const res_02 = httpMocks.createResponse()

const appconfig = {
headers: {
'Access-Control-Allow-Methods': 'GET, HEAD, OPTIONS, POST',
'Access-Control-Allow-Headers': 'Authorization, Content-Type, Origin',
'Access-Control-Allow-Origin': 'http://boris.com, http://localhost:8080',
'Access-Control-Max-Age': '1296000'
}
}

const fn = serveHTTP({ schema: {} }, appconfig)

const result_01 = fn(req_01, res_01).then(() => {
assert.equal(1,1)
})
.catch(() => assert.equal(1,2, `This request should have succeeded.`))

const result_02 = fn(req_02, res_02).then(() => {
assert.equal(1,1)
})
.catch(() => assert.equal(1,2, `This request should have succeeded.`))

return Promise.all([result_01, result_02])
})))

0 comments on commit deddeee

Please sign in to comment.