From 30ea05fecdfc68ec59ed3d39a8fb5c9eacda95b2 Mon Sep 17 00:00:00 2001 From: Mehdi Avdi Date: Wed, 14 Oct 2015 00:44:25 +0100 Subject: [PATCH 01/42] Project and initial mocha test setup --- .gitignore | 3 +++ .vscode/launch.json | 41 +++++++++++++++++++++++++++++++++++++++++ index.js | 0 jsconfig.json | 6 ++++++ lib/plugin.js | 8 ++++++++ package.json | 37 +++++++++++++++++++++++++++++++++++++ test/global.spec.js | 10 ++++++++++ test/plugin.spec.js | 27 +++++++++++++++++++++++++++ 8 files changed, 132 insertions(+) create mode 100644 .gitignore create mode 100644 .vscode/launch.json create mode 100644 index.js create mode 100644 jsconfig.json create mode 100644 lib/plugin.js create mode 100644 package.json create mode 100644 test/global.spec.js create mode 100644 test/plugin.spec.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9239e9b --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.iml +.idea +node_modules diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..380db53 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,41 @@ +{ + "version": "0.1.0", + // List of configurations. Add new configurations or edit existing ones. + "configurations": [ + { + // Name of configuration; appears in the launch configuration drop down menu. + "name": "Launch Mocha", + // Type of configuration. + "type": "node", + // Workspace relative or absolute path to the program. + "program": "node_modules/mocha/bin/_mocha", + // Automatically stop program after launch. + "stopOnEntry": false, + // Command line arguments passed to the program. + "args": ["--debug-brk"], + // Workspace relative or absolute path to the working directory of the program being debugged. Default is the current workspace. + "cwd": ".", + // Workspace relative or absolute path to the runtime executable to be used. Default is the runtime executable on the PATH. + "runtimeExecutable": null, + // Optional arguments passed to the runtime executable. + "runtimeArgs": ["--nolazy"], + // Environment variables passed to the program. + "env": { + "NODE_ENV": "development" + }, + // Use JavaScript source maps (if they exist). + "sourceMaps": false, + // If JavaScript source maps are enabled, the generated code is expected in this directory. + "outDir": null + }, + { + "name": "Attach", + "type": "node", + // TCP/IP address. Default is "localhost". + "address": "localhost", + // Port to attach to. + "port": 5858, + "sourceMaps": false + } + ] +} diff --git a/index.js b/index.js new file mode 100644 index 0000000..e69de29 diff --git a/jsconfig.json b/jsconfig.json new file mode 100644 index 0000000..56705ea --- /dev/null +++ b/jsconfig.json @@ -0,0 +1,6 @@ +{ + "compilerOptions": { + "target": "ES6", + "module": "commonjs" + } +} \ No newline at end of file diff --git a/lib/plugin.js b/lib/plugin.js new file mode 100644 index 0000000..7e74b8b --- /dev/null +++ b/lib/plugin.js @@ -0,0 +1,8 @@ +module.exports.register = function (server, options, next) { + console.log('PLUGIN STARTED') + next() +} + +exports.register.attributes = { + pkg: require('./package.json') +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..2cb8d76 --- /dev/null +++ b/package.json @@ -0,0 +1,37 @@ +{ + "name": "hapi-harvester", + "version": "0.1.0", + "description": "Harvester Hapi Plugin", + "main": "index.js", + "scripts": { + "test": "mocha test" + }, + "repository": { + "type": "git", + "url": "https://github.com/agco/hapi-harvester.git" + }, + "keywords": [ + "hapi", + "api", + "harvester" + ], + "author": "", + "license": "ISC", + "bugs": { + "url": "https://github.com/agco/hapi-harvester/issues" + }, + "homepage": "https://github.com/agco/hapi-harvester", + "dependencies": { + "boom": "^2.9.0", + "hapi": "^10.4.1", + "joi": "^6.9.0", + "lodash": "^3.10.1", + "mongoose": "^4.1.11" + }, + "devDependencies": { + "chai": "^3.3.0", + "chai-things": "^0.2.0", + "inject-then": "^2.0.2", + "mocha": "^2.3.3" + } +} diff --git a/test/global.spec.js b/test/global.spec.js new file mode 100644 index 0000000..eb78e99 --- /dev/null +++ b/test/global.spec.js @@ -0,0 +1,10 @@ +var chai = require('chai') + +chai.use(require('chai-things')) + +chai.config.includeStack = true + +global.expect = chai.expect +global.AssertionError = chai.AssertionError +global.Assertion = chai.Assertion +global.assert = chai.assert \ No newline at end of file diff --git a/test/plugin.spec.js b/test/plugin.spec.js new file mode 100644 index 0000000..e292dbe --- /dev/null +++ b/test/plugin.spec.js @@ -0,0 +1,27 @@ +'use strict' + +let server; + +describe('Plugin', function() { + beforeEach(function() { + buildServer(); + }) + + afterEach(function(done) { + destroyServer(done); + }) + it('Attaches the plugin to Hapi server configuration', function() { + console.log(server.plugins) + }); +}) + +var buildServer = function() { + const Hapi = require('hapi') + server = new Hapi.Server() + server.connection() + return server +} + +var destroyServer = function(done) { + server.stop(done) +} \ No newline at end of file From f00cf3b71cab369dd4b646e1ac157e98e4c6b1ea Mon Sep 17 00:00:00 2001 From: Mehdi Avdi Date: Wed, 14 Oct 2015 01:12:03 +0100 Subject: [PATCH 02/42] plugin up and our first test is running --- lib/plugin.js | 5 +++-- package.json | 2 +- test/plugin.spec.js | 10 +++++----- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/lib/plugin.js b/lib/plugin.js index 7e74b8b..56e95ec 100644 --- a/lib/plugin.js +++ b/lib/plugin.js @@ -1,8 +1,9 @@ -module.exports.register = function (server, options, next) { +exports.register = function (server, options, next) { console.log('PLUGIN STARTED') + server.expose('version', require('../package.json').version); next() } exports.register.attributes = { - pkg: require('./package.json') + pkg: require('../package.json') } diff --git a/package.json b/package.json index 2cb8d76..7e32f20 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "hapi-harvester", + "name": "harvester", "version": "0.1.0", "description": "Harvester Hapi Plugin", "main": "index.js", diff --git a/test/plugin.spec.js b/test/plugin.spec.js index e292dbe..9069731 100644 --- a/test/plugin.spec.js +++ b/test/plugin.spec.js @@ -3,23 +3,23 @@ let server; describe('Plugin', function() { - beforeEach(function() { - buildServer(); + beforeEach(function(done) { + buildServer(done); }) afterEach(function(done) { destroyServer(done); }) it('Attaches the plugin to Hapi server configuration', function() { - console.log(server.plugins) + expect(server.plugins.harvester.version).to.equal('0.1.0') }); }) -var buildServer = function() { +var buildServer = function(done) { const Hapi = require('hapi') server = new Hapi.Server() server.connection() - return server + server.register({register: require('../lib/plugin')}, done) } var destroyServer = function(done) { From ecab7c0d66acca4ff9f970be8f29c6c90dda06e9 Mon Sep 17 00:00:00 2001 From: Mehdi Avdi Date: Wed, 14 Oct 2015 01:25:14 +0100 Subject: [PATCH 03/42] got inject-then working with mocha tests --- test/plugin.spec.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/test/plugin.spec.js b/test/plugin.spec.js index 9069731..1a0945e 100644 --- a/test/plugin.spec.js +++ b/test/plugin.spec.js @@ -13,13 +13,23 @@ describe('Plugin', function() { it('Attaches the plugin to Hapi server configuration', function() { expect(server.plugins.harvester.version).to.equal('0.1.0') }); + + it('should fail with a descriptive error', function() { + return server.injectThen({method: 'GET', url: '/chuck'}) + .then(function(res) { + expect(res.result).to.deep.equal({ statusCode: 404, error: 'Not Found' }); + }); + }); }) var buildServer = function(done) { const Hapi = require('hapi') server = new Hapi.Server() server.connection() - server.register({register: require('../lib/plugin')}, done) + server.register([ + {register: require('../lib/plugin')}, + {register: require('inject-then')} + ], done) } var destroyServer = function(done) { From d76412264382b01297bdef6cc9db66e872c12111 Mon Sep 17 00:00:00 2001 From: Mehdi Avdi Date: Wed, 14 Oct 2015 02:06:47 +0100 Subject: [PATCH 04/42] added basic REST verb functions to route --- lib/plugin.js | 56 ++++++++++++++++++++++++++++++++++++++++++++- lib/routes.js | 45 ++++++++++++++++++++++++++++++++++++ package.json | 1 + test/plugin.spec.js | 51 +++++++++++++++++++++++++++++++++-------- 4 files changed, 143 insertions(+), 10 deletions(-) create mode 100644 lib/routes.js diff --git a/lib/plugin.js b/lib/plugin.js index 56e95ec..14dc3cf 100644 --- a/lib/plugin.js +++ b/lib/plugin.js @@ -1,7 +1,61 @@ +'use strict' + +const _ = require('lodash') +const routes = require('./routes')() + exports.register = function (server, options, next) { console.log('PLUGIN STARTED') server.expose('version', require('../package.json').version); - next() + + let get = function (schema) { + return _.merge(routes.get(schema), { + handler: (req, reply) => { + reply({foo: 'bar'}) + } + }) + } + + let post = function (schema) { + return _.merge(routes.post(schema), { + handler: (req, reply) => { + reply({foo: 'bar'}) + } + }) + } + + let put = function (schema) { + return _.merge(routes.put(schema), { + handler: (req, reply) => { + reply({foo: 'bar'}) + } + }) + } + + let patch = function (schema) { + return _.merge(routes.patch(schema), { + handler: (req, reply) => { + reply({foo: 'bar'}) + } + }) + } + + let del = function (schema) { + return _.merge(routes.delete(schema), { + handler: (req, reply) => { + reply({foo: 'bar'}) + } + }) + } + + server.expose('routes', { + get: get, + post: post, + put: put, + patch: patch, + delete: del + }) + + next() } exports.register.attributes = { diff --git a/lib/routes.js b/lib/routes.js new file mode 100644 index 0000000..1b7712f --- /dev/null +++ b/lib/routes.js @@ -0,0 +1,45 @@ +module.exports = function () { + + const get = function (schema) { + return { + method: 'GET', + path: `/${schema.type}` + } + } + const post = function (schema) { + return { + method: 'POST', + path: `/${schema.type}` + } + } + + const put = function (schema) { + return { + method: 'PUT', + path: `/${schema.type}` + } + } + + const patch = function (schema) { + return { + method: 'PATCH', + path: `/${schema.type}` + } + } + + const del = function (schema) { + return { + method: 'DELETE', + path: `/${schema.type}` + } + } + + return { + get: get, + post: post, + put: put, + patch: patch, + delete: del + } + +} \ No newline at end of file diff --git a/package.json b/package.json index 7e32f20..4feeb90 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ }, "homepage": "https://github.com/agco/hapi-harvester", "dependencies": { + "bluebird": "^2.10.2", "boom": "^2.9.0", "hapi": "^10.4.1", "joi": "^6.9.0", diff --git a/test/plugin.spec.js b/test/plugin.spec.js index 1a0945e..7949eca 100644 --- a/test/plugin.spec.js +++ b/test/plugin.spec.js @@ -1,6 +1,18 @@ 'use strict' -let server; +const hh = require('../') +const Joi = require('joi') +const Promise = require('bluebird') + +let server, buildServer, destroyServer; + +const schema = { + type: 'brands', + attributes: { + code: Joi.string().min(2).max(10), + description: Joi.string() + } +}; describe('Plugin', function() { beforeEach(function(done) { @@ -10,19 +22,40 @@ describe('Plugin', function() { afterEach(function(done) { destroyServer(done); }) + it('Attaches the plugin to Hapi server configuration', function() { expect(server.plugins.harvester.version).to.equal('0.1.0') - }); + }) - it('should fail with a descriptive error', function() { + it('should have the injectThen method available', function() { return server.injectThen({method: 'GET', url: '/chuck'}) - .then(function(res) { - expect(res.result).to.deep.equal({ statusCode: 404, error: 'Not Found' }); - }); - }); + .then((res) => { + expect(res.result).to.deep.equal({ statusCode: 404, error: 'Not Found' }) + }) + }) + + it('all the REST verbs available', function() { + + const hh = server.plugins.harvester; + + let promises = []; + + ['get', 'put', 'post', 'patch', 'delete'].forEach(function(verb) { + server.route(hh.routes[verb](schema)) + + let promise = server.injectThen({method: verb.toUpperCase(), url: '/brands'}).then((res) => { + expect(res.result).to.deep.equal({ foo: 'bar' }) + }) + + promises.push(promise) + }) + + return server.injectThen({method: 'GET', url: '/chuck'}) + return Promise.all(promises) + }) }) -var buildServer = function(done) { +buildServer = function(done) { const Hapi = require('hapi') server = new Hapi.Server() server.connection() @@ -32,6 +65,6 @@ var buildServer = function(done) { ], done) } -var destroyServer = function(done) { +destroyServer = function(done) { server.stop(done) } \ No newline at end of file From b74d8d1b42814ae55468e390c4cd8b06371e52f7 Mon Sep 17 00:00:00 2001 From: Mehdi Avdi Date: Thu, 15 Oct 2015 11:50:04 +0100 Subject: [PATCH 05/42] OPTIONS returning the right REST opts available --- lib/plugin.js | 26 +++++++++++++++++++++++--- lib/routes.js | 23 +++++++++++++++++------ test/plugin.spec.js | 17 ++++++++++++++++- 3 files changed, 56 insertions(+), 10 deletions(-) diff --git a/lib/plugin.js b/lib/plugin.js index 14dc3cf..4d7e4a7 100644 --- a/lib/plugin.js +++ b/lib/plugin.js @@ -3,11 +3,30 @@ const _ = require('lodash') const routes = require('./routes')() -exports.register = function (server, options, next) { +exports.register = function (server, opts, next) { console.log('PLUGIN STARTED') server.expose('version', require('../package.json').version); - + + let options = function (schema) { + return _.merge(routes.options(schema), { + handler: (req, reply) => { + let tables = _.map(req.server.table()[0].table, (table) => { + return _.pick(table, 'path', 'method') + }) + + let pathVerbs = _.chain(tables) + .filter((table) => { return table.path === req.path }) + .pluck('method') + .map((verb) => { return verb.toUpperCase() }) + .value(); + + reply().header('Allow', pathVerbs.join(',')) + } + }) + } + let get = function (schema) { + server.route(options(schema)) return _.merge(routes.get(schema), { handler: (req, reply) => { reply({foo: 'bar'}) @@ -52,7 +71,8 @@ exports.register = function (server, options, next) { post: post, put: put, patch: patch, - delete: del + delete: del, + options: options }) next() diff --git a/lib/routes.js b/lib/routes.js index 1b7712f..83e48ec 100644 --- a/lib/routes.js +++ b/lib/routes.js @@ -1,45 +1,56 @@ +'use strict' + module.exports = function () { - const get = function (schema) { + let get = function (schema) { return { method: 'GET', path: `/${schema.type}` } } - const post = function (schema) { + + let post = function (schema) { return { method: 'POST', path: `/${schema.type}` } } - const put = function (schema) { + let put = function (schema) { return { method: 'PUT', path: `/${schema.type}` } } - const patch = function (schema) { + let patch = function (schema) { return { method: 'PATCH', path: `/${schema.type}` } } - const del = function (schema) { + let del = function (schema) { return { method: 'DELETE', path: `/${schema.type}` } } + + let options = function (schema) { + return { + method: 'OPTIONS', + path: `/${schema.type}` + } + } return { get: get, post: post, put: put, patch: patch, - delete: del + delete: del, + options: options } } \ No newline at end of file diff --git a/test/plugin.spec.js b/test/plugin.spec.js index 7949eca..90f4bdd 100644 --- a/test/plugin.spec.js +++ b/test/plugin.spec.js @@ -50,9 +50,24 @@ describe('Plugin', function() { promises.push(promise) }) - return server.injectThen({method: 'GET', url: '/chuck'}) return Promise.all(promises) }) + + it('only send the available verbs on OPTIONS call', function() { + + const hh = server.plugins.harvester; + + let promises = []; + + ['get', 'put', 'post', 'patch', 'delete'].forEach(function(verb) { + server.route(hh.routes[verb](schema)) + }) + + return server.injectThen({method: 'OPTIONS', url: '/brands'}) + .then(function(res) { + expect(res.headers.allow).to.equal('OPTIONS,GET,PUT,POST,PATCH,DELETE') + }) + }) }) buildServer = function(done) { From 140790ab002c8b448de4422f8f9015cd268b910c Mon Sep 17 00:00:00 2001 From: Mehdi Avdi Date: Thu, 15 Oct 2015 15:04:45 +0100 Subject: [PATCH 06/42] creating options endpoint for all resources --- .vscode/launch.json | 2 +- lib/plugin.js | 20 ++++++++++++++------ test/plugin.spec.js | 6 ++++-- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 380db53..f467ee1 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -12,7 +12,7 @@ // Automatically stop program after launch. "stopOnEntry": false, // Command line arguments passed to the program. - "args": ["--debug-brk"], + "args": ["--debug-brk", "--timeout", "10000"], // Workspace relative or absolute path to the working directory of the program being debugged. Default is the current workspace. "cwd": ".", // Workspace relative or absolute path to the runtime executable to be used. Default is the runtime executable on the PATH. diff --git a/lib/plugin.js b/lib/plugin.js index 4d7e4a7..ca312fb 100644 --- a/lib/plugin.js +++ b/lib/plugin.js @@ -7,8 +7,13 @@ exports.register = function (server, opts, next) { console.log('PLUGIN STARTED') server.expose('version', require('../package.json').version); - let options = function (schema) { - return _.merge(routes.options(schema), { + let createOptionsRoute = function(schema) { + let tables = _.map(server.table()[0].table) + + //see if the options method already exists, if so, don't duplicate it + if (_.find(tables, {path : '/' + schema.type, method: 'options'})) return; + + server.route(_.merge(routes.options(schema), { handler: (req, reply) => { let tables = _.map(req.server.table()[0].table, (table) => { return _.pick(table, 'path', 'method') @@ -22,11 +27,11 @@ exports.register = function (server, opts, next) { reply().header('Allow', pathVerbs.join(',')) } - }) + })) } let get = function (schema) { - server.route(options(schema)) + createOptionsRoute(schema) return _.merge(routes.get(schema), { handler: (req, reply) => { reply({foo: 'bar'}) @@ -35,6 +40,7 @@ exports.register = function (server, opts, next) { } let post = function (schema) { + createOptionsRoute(schema) return _.merge(routes.post(schema), { handler: (req, reply) => { reply({foo: 'bar'}) @@ -43,6 +49,7 @@ exports.register = function (server, opts, next) { } let put = function (schema) { + createOptionsRoute(schema) return _.merge(routes.put(schema), { handler: (req, reply) => { reply({foo: 'bar'}) @@ -51,6 +58,7 @@ exports.register = function (server, opts, next) { } let patch = function (schema) { + createOptionsRoute(schema) return _.merge(routes.patch(schema), { handler: (req, reply) => { reply({foo: 'bar'}) @@ -59,6 +67,7 @@ exports.register = function (server, opts, next) { } let del = function (schema) { + createOptionsRoute(schema) return _.merge(routes.delete(schema), { handler: (req, reply) => { reply({foo: 'bar'}) @@ -71,8 +80,7 @@ exports.register = function (server, opts, next) { post: post, put: put, patch: patch, - delete: del, - options: options + delete: del }) next() diff --git a/test/plugin.spec.js b/test/plugin.spec.js index 90f4bdd..92ee40f 100644 --- a/test/plugin.spec.js +++ b/test/plugin.spec.js @@ -73,11 +73,13 @@ describe('Plugin', function() { buildServer = function(done) { const Hapi = require('hapi') server = new Hapi.Server() - server.connection() + server.connection({port : 9100}) server.register([ {register: require('../lib/plugin')}, {register: require('inject-then')} - ], done) + ], () => { + server.start(done) + }) } destroyServer = function(done) { From 7a58f3dbddb3fae6dbafb6edc58fa270589ef9a9 Mon Sep 17 00:00:00 2001 From: Mehdi Avdi Date: Thu, 15 Oct 2015 23:50:42 +0100 Subject: [PATCH 07/42] only allowing application/json, tests for req, res allowed headers --- lib/routes.js | 21 ++++++++++++--- test/plugin.spec.js | 63 ++++++++++++++++++++++++++++++++++++++------- 2 files changed, 72 insertions(+), 12 deletions(-) diff --git a/lib/routes.js b/lib/routes.js index 83e48ec..3e620b9 100644 --- a/lib/routes.js +++ b/lib/routes.js @@ -12,21 +12,36 @@ module.exports = function () { let post = function (schema) { return { method: 'POST', - path: `/${schema.type}` + path: `/${schema.type}`, + config: { + payload: { + allow : 'application/json' + } + } } } let put = function (schema) { return { method: 'PUT', - path: `/${schema.type}` + path: `/${schema.type}`, + config: { + payload: { + allow : 'application/json' + } + } } } let patch = function (schema) { return { method: 'PATCH', - path: `/${schema.type}` + path: `/${schema.type}`, + config: { + payload: { + allow : 'application/json' + } + } } } diff --git a/test/plugin.spec.js b/test/plugin.spec.js index 92ee40f..59049bf 100644 --- a/test/plugin.spec.js +++ b/test/plugin.spec.js @@ -1,10 +1,9 @@ 'use strict' -const hh = require('../') const Joi = require('joi') const Promise = require('bluebird') -let server, buildServer, destroyServer; +let server, buildServer, destroyServer, hh; const schema = { type: 'brands', @@ -35,8 +34,6 @@ describe('Plugin', function() { }) it('all the REST verbs available', function() { - - const hh = server.plugins.harvester; let promises = []; @@ -53,11 +50,7 @@ describe('Plugin', function() { return Promise.all(promises) }) - it('only send the available verbs on OPTIONS call', function() { - - const hh = server.plugins.harvester; - - let promises = []; + it('only sends the available verbs on OPTIONS call', function() { ['get', 'put', 'post', 'patch', 'delete'].forEach(function(verb) { server.route(hh.routes[verb](schema)) @@ -68,6 +61,57 @@ describe('Plugin', function() { expect(res.headers.allow).to.equal('OPTIONS,GET,PUT,POST,PATCH,DELETE') }) }) + + it('should set the content-type header to application/json by default', function() { + server.route(hh.routes.get(schema)) + return server.injectThen({method: 'GET', url: '/brands'}) + .then((res) => { + expect(res.headers['content-type']).to.equal('application/json; charset=utf-8') + }) + }) + + it('should reject all request with content-type not set to application/json', function() { + + let promises = []; + + ['put', 'post', 'patch'].forEach(function(verb) { + server.route(hh.routes[verb](schema)) + + let headers = { + 'content-type' : 'text/html' + } + + let promise = server.injectThen({method: verb.toUpperCase(), url: '/brands', headers : headers}).then((res) => { + expect(res.statusCode).to.equal(415) + + }) + + promises.push(promise) + }) + + return Promise.all(promises) + }) + + it('should allow all request with content-type set to application/json', function() { + + let promises = []; + + ['put', 'post', 'patch'].forEach(function(verb) { + server.route(hh.routes[verb](schema)) + + let headers = { + 'content-type' : 'application/json' + } + + let promise = server.injectThen({method: verb.toUpperCase(), url: '/brands', headers : headers}).then((res) => { + expect(res.statusCode).to.equal(200) + }) + + promises.push(promise) + }) + + return Promise.all(promises) + }) }) buildServer = function(done) { @@ -78,6 +122,7 @@ buildServer = function(done) { {register: require('../lib/plugin')}, {register: require('inject-then')} ], () => { + hh = server.plugins.harvester; server.start(done) }) } From cd7ca27a5f11892831cd7645c8d6a144dfbc33e8 Mon Sep 17 00:00:00 2001 From: Mehdi Avdi Date: Fri, 16 Oct 2015 01:14:30 +0100 Subject: [PATCH 08/42] checking adapter validity with Hoek --- lib/adapters/mongodb/converters.js | 56 ++++++++++++++++ lib/adapters/mongodb/index.js | 100 +++++++++++++++++++++++++++++ lib/plugin.js | 6 +- lib/utils/adapter.js | 33 ++++++++++ package.json | 4 +- test/adapter.spec.js | 71 ++++++++++++++++++++ test/plugin.spec.js | 5 +- 7 files changed, 271 insertions(+), 4 deletions(-) create mode 100644 lib/adapters/mongodb/converters.js create mode 100644 lib/adapters/mongodb/index.js create mode 100644 lib/utils/adapter.js create mode 100644 test/adapter.spec.js diff --git a/lib/adapters/mongodb/converters.js b/lib/adapters/mongodb/converters.js new file mode 100644 index 0000000..798a635 --- /dev/null +++ b/lib/adapters/mongodb/converters.js @@ -0,0 +1,56 @@ +const Hapi = require('hapi'), + _ = require('lodash'), + Hoek = require('hoek'), + mongoose = require('mongoose'), + uuid = require('node-uuid') + +module.exports.toJsonApi = function (resources) { + + if (_.isArray(resources)) { + return _.map(resources, (resource) => { + return toJsonApiSingle(resource); + }) + } else { + return toJsonApiSingle(resources); + } + + function toJsonApiSingle(resource) { + var mapped = _.mapKeys(resource, function (val, key) { + if (key === '_id') return 'id' + else return key + }); + return _.omit(mapped, '__v') + } +} + +module.exports.toMongooseModel = function (hhSchema) { + + const mongooseSchema = {} + mongooseSchema._id = { + type: String, + default: () => { + return uuid.v4() + } + } + + var schemaMap = { + 'string': String, + 'number': Number, + 'date': Date, + 'buffer': Buffer, + 'boolean': Boolean, + 'array': Array, + 'any': Object + } + + mongooseSchema.attributes = + _.mapValues(hhSchema.attributes, function (val) { + Hoek.assert(val.isJoi, 'attribute values in the hh schema should be defined with Joi') + return schemaMap[val._type] + }) + + const schema = mongoose.Schema(mongooseSchema) + return mongoose.model(hhSchema.type, schema) + +} + diff --git a/lib/adapters/mongodb/index.js b/lib/adapters/mongodb/index.js new file mode 100644 index 0000000..2538c09 --- /dev/null +++ b/lib/adapters/mongodb/index.js @@ -0,0 +1,100 @@ +'use strict' + +const Hapi = require('hapi') +const _ = require('lodash') +const mongoose = require('mongoose') +const converters = require('./converters') + +mongoose.Promise = require('bluebird') + +module.exports = function (options) { + + const models = {} + + let disconnect = function(cb) { + mongoose.disconnect(cb) + } + + let connect = function(cb) { + mongoose.connect(options.mongodbUrl, cb) + } + + let find = function (type, req) { + + const model = models[type] + const query = req.query + const limit = query.limit || 1000 + const skip = query.offset || 0 + const sort = query.sort || {'_id': -1} + var predicate = toMongoosePredicate(query) + return model.find(predicate).skip(skip).sort(sort).limit(limit).lean().exec() + .then((resources)=> { + return {data: converters.toJsonApi(resources)} + }) + + } + + let findById = function(type, req) { + + const model = models[type] + var predicate = toMongoosePredicate({id: req.params.id}) + return model.find(predicate).lean().exec() + .then((resources) => { + return {data: converters.toJsonApi(resources)} + }) + + } + + let create = function(type, req) { + + const model = models[type] + var data = req.payload.data + return model.create(data).then((created) => { + return {data: converters.toJsonApi(created.toObject())} + }) + + } + + let del = function(type, req) { + + } + + let processSchema = function(hhSchema) { + + if (!models[hhSchema.type]) { + + // clean up existing models and schemas + delete mongoose.models[hhSchema.type] + delete mongoose.modelSchemas[hhSchema.type] + + models[hhSchema.type] = converters.toMongooseModel(hhSchema) + } + return models[hhSchema.type] + } + + var toMongoosePredicate = function(query) { + const mappedToModel = _.mapKeys(query.filter, function (val, key) { + if (key == 'id') return '_id' + else return `attributes.${key}` + }) + + return _.mapValues(mappedToModel, function (val, key) { + if (val.indexOf(',') != -1) return {$in: val.split(',')} + else return val + }) + } + + return { + connect, + disconnect, + find, + findById, + create, + delete: del, + models, + processSchema + } + +} + + diff --git a/lib/plugin.js b/lib/plugin.js index ca312fb..4c5788d 100644 --- a/lib/plugin.js +++ b/lib/plugin.js @@ -2,11 +2,15 @@ const _ = require('lodash') const routes = require('./routes')() +const adapterUtils = require('./utils/adapter')() exports.register = function (server, opts, next) { - console.log('PLUGIN STARTED') server.expose('version', require('../package.json').version); + let adapter = adapterUtils.getStandardAdapter(opts.adapter); + + adapterUtils.checkValidAdapter(adapter); + let createOptionsRoute = function(schema) { let tables = _.map(server.table()[0].table) diff --git a/lib/utils/adapter.js b/lib/utils/adapter.js new file mode 100644 index 0000000..78b4e09 --- /dev/null +++ b/lib/utils/adapter.js @@ -0,0 +1,33 @@ +'use strict' + +const _ = require('lodash') +const Hoek = require('hoek') +const protocolFunctions = ['connect','disconnect','find','findById','create','delete', 'models','processSchema']; + +module.exports = function() { + let checkValidAdapter = function(adapter) { + + Hoek.assert(adapter, new Error('No adapter passed. Please see docs.')) + + protocolFunctions.forEach((func) => { + Hoek.assert(adapter[func], new Error('Adapter validation failed. Adapter missing ' + func)); + }) + } + + let getStandardAdapter = function(adapter) { + if ( _.isString(adapter)) { + try { + return require('../../lib/adapters/' + adapter)(); + } catch (err) { + Hoek.assert(!err, new Error('Wrong adapter name, see docs for built in adapter')) + } + } + + return adapter; + } + + return { + checkValidAdapter, + getStandardAdapter + } +} \ No newline at end of file diff --git a/package.json b/package.json index 4feeb90..365bf52 100644 --- a/package.json +++ b/package.json @@ -25,9 +25,11 @@ "bluebird": "^2.10.2", "boom": "^2.9.0", "hapi": "^10.4.1", + "hoek": "^2.16.3", "joi": "^6.9.0", "lodash": "^3.10.1", - "mongoose": "^4.1.11" + "mongoose": "^4.1.11", + "node-uuid": "^1.4.3" }, "devDependencies": { "chai": "^3.3.0", diff --git a/test/adapter.spec.js b/test/adapter.spec.js new file mode 100644 index 0000000..97484b1 --- /dev/null +++ b/test/adapter.spec.js @@ -0,0 +1,71 @@ +'use strict' + +const _ = require('lodash') +const Joi = require('joi') +const Promise = require('bluebird') +const Hapi = require('hapi') + +let server, buildServer, destroyServer, hh; + +const schema = { + type: 'brands', + attributes: { + code: Joi.string().min(2).max(10), + description: Joi.string() + } +}; + +describe('Adapter Validation', function() { + + afterEach(function(done) { + destroyServer(done); + }) + + it('Will check the given adapter for the required functions', function() { + let adapter = require('../lib/adapters/mongodb')({mongodbUrl: 'mongodb://localhost/test'}) + + adapter = _.remove(adapter, 'delete'); + + //rebuild server with the aling adapter + server = new Hapi.Server() + server.connection({port : 9100}) + + let serverSetup = function() { + server.register([ + {register: require('../lib/plugin'), options: {adapter : adapter}}, + {register: require('inject-then')} + ], () => { + hh = server.plugins.harvester; + server.start(()=> {}) + }) + } + + expect(serverSetup).to.throw('Adapter validation failed. Adapter missing connect') + }) + + it('Will won\'t accept a string adapter if it doesn\'t exist ', function() { + let adapter = require('../lib/adapters/mongodb')({mongodbUrl: 'mongodb://localhost/test'}) + + adapter = _.remove(adapter, 'delete'); + + //rebuild server with the aling adapter + server = new Hapi.Server() + server.connection({port : 9100}) + + let serverSetup = function() { + server.register([ + {register: require('../lib/plugin'), options: {adapter : 'notexistentdb'}}, + {register: require('inject-then')} + ], () => { + hh = server.plugins.harvester; + server.start(()=> {}) + }) + } + + expect(serverSetup).to.throw('Wrong adapter name, see docs for built in adapter') + }) +}) + +destroyServer = function(done) { + server.stop(done) +} \ No newline at end of file diff --git a/test/plugin.spec.js b/test/plugin.spec.js index 59049bf..1007829 100644 --- a/test/plugin.spec.js +++ b/test/plugin.spec.js @@ -13,7 +13,7 @@ const schema = { } }; -describe('Plugin', function() { +describe('Plugin Basics', function() { beforeEach(function(done) { buildServer(done); }) @@ -116,10 +116,11 @@ describe('Plugin', function() { buildServer = function(done) { const Hapi = require('hapi') + let adapter = require('../lib/adapters/mongodb')({mongodbUrl: 'mongodb://localhost/test'}) server = new Hapi.Server() server.connection({port : 9100}) server.register([ - {register: require('../lib/plugin')}, + {register: require('../lib/plugin'), options: {adapter : 'mongodb'}}, {register: require('inject-then')} ], () => { hh = server.plugins.harvester; From 8db5220c9040731063f2b21324fdc51c58d5d59b Mon Sep 17 00:00:00 2001 From: Mehdi Avdi Date: Fri, 16 Oct 2015 01:43:32 +0100 Subject: [PATCH 09/42] converted all tabs to spaces... --- .vscode/launch.json | 78 ++++++++++++++++++++++---------------------- lib/utils/adapter.js | 48 +++++++++++++-------------- 2 files changed, 63 insertions(+), 63 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index f467ee1..11e560b 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,41 +1,41 @@ { - "version": "0.1.0", - // List of configurations. Add new configurations or edit existing ones. - "configurations": [ - { - // Name of configuration; appears in the launch configuration drop down menu. - "name": "Launch Mocha", - // Type of configuration. - "type": "node", - // Workspace relative or absolute path to the program. - "program": "node_modules/mocha/bin/_mocha", - // Automatically stop program after launch. - "stopOnEntry": false, - // Command line arguments passed to the program. - "args": ["--debug-brk", "--timeout", "10000"], - // Workspace relative or absolute path to the working directory of the program being debugged. Default is the current workspace. - "cwd": ".", - // Workspace relative or absolute path to the runtime executable to be used. Default is the runtime executable on the PATH. - "runtimeExecutable": null, - // Optional arguments passed to the runtime executable. - "runtimeArgs": ["--nolazy"], - // Environment variables passed to the program. - "env": { - "NODE_ENV": "development" - }, - // Use JavaScript source maps (if they exist). - "sourceMaps": false, - // If JavaScript source maps are enabled, the generated code is expected in this directory. - "outDir": null - }, - { - "name": "Attach", - "type": "node", - // TCP/IP address. Default is "localhost". - "address": "localhost", - // Port to attach to. - "port": 5858, - "sourceMaps": false - } - ] + "version": "0.1.0", + // List of configurations. Add new configurations or edit existing ones. + "configurations": [ + { + // Name of configuration; appears in the launch configuration drop down menu. + "name": "Launch Mocha", + // Type of configuration. + "type": "node", + // Workspace relative or absolute path to the program. + "program": "node_modules/mocha/bin/_mocha", + // Automatically stop program after launch. + "stopOnEntry": false, + // Command line arguments passed to the program. + "args": ["--debug-brk", "--timeout", "10000"], + // Workspace relative or absolute path to the working directory of the program being debugged. Default is the current workspace. + "cwd": ".", + // Workspace relative or absolute path to the runtime executable to be used. Default is the runtime executable on the PATH. + "runtimeExecutable": null, + // Optional arguments passed to the runtime executable. + "runtimeArgs": ["--nolazy"], + // Environment variables passed to the program. + "env": { + "NODE_ENV": "development" + }, + // Use JavaScript source maps (if they exist). + "sourceMaps": false, + // If JavaScript source maps are enabled, the generated code is expected in this directory. + "outDir": null + }, + { + "name": "Attach", + "type": "node", + // TCP/IP address. Default is "localhost". + "address": "localhost", + // Port to attach to. + "port": 5858, + "sourceMaps": false + } + ] } diff --git a/lib/utils/adapter.js b/lib/utils/adapter.js index 78b4e09..6ef5314 100644 --- a/lib/utils/adapter.js +++ b/lib/utils/adapter.js @@ -5,29 +5,29 @@ const Hoek = require('hoek') const protocolFunctions = ['connect','disconnect','find','findById','create','delete', 'models','processSchema']; module.exports = function() { - let checkValidAdapter = function(adapter) { + let checkValidAdapter = function(adapter) { - Hoek.assert(adapter, new Error('No adapter passed. Please see docs.')) - - protocolFunctions.forEach((func) => { - Hoek.assert(adapter[func], new Error('Adapter validation failed. Adapter missing ' + func)); - }) - } - - let getStandardAdapter = function(adapter) { - if ( _.isString(adapter)) { - try { - return require('../../lib/adapters/' + adapter)(); - } catch (err) { - Hoek.assert(!err, new Error('Wrong adapter name, see docs for built in adapter')) - } - } - - return adapter; - } - - return { - checkValidAdapter, - getStandardAdapter - } + Hoek.assert(adapter, new Error('No adapter passed. Please see docs.')) + + protocolFunctions.forEach((func) => { + Hoek.assert(adapter[func], new Error('Adapter validation failed. Adapter missing ' + func)); + }) + } + + let getStandardAdapter = function(adapter) { + if ( _.isString(adapter)) { + try { + return require('../../lib/adapters/' + adapter)(); + } catch (err) { + Hoek.assert(!err, new Error('Wrong adapter name, see docs for built in adapter')) + } + } + + return adapter; + } + + return { + checkValidAdapter, + getStandardAdapter + } } \ No newline at end of file From 3483b5921173e443779c786cce28ae30005e4d40 Mon Sep 17 00:00:00 2001 From: Mehdi Avdi Date: Fri, 16 Oct 2015 01:50:37 +0100 Subject: [PATCH 10/42] more tabs to spaces --- lib/plugin.js | 40 ++++---- lib/routes.js | 18 ++-- test/adapter.spec.js | 108 +++++++++++----------- test/plugin.spec.js | 216 +++++++++++++++++++++---------------------- 4 files changed, 191 insertions(+), 191 deletions(-) diff --git a/lib/plugin.js b/lib/plugin.js index 4c5788d..7d7c401 100644 --- a/lib/plugin.js +++ b/lib/plugin.js @@ -5,19 +5,19 @@ const routes = require('./routes')() const adapterUtils = require('./utils/adapter')() exports.register = function (server, opts, next) { - server.expose('version', require('../package.json').version); + server.expose('version', require('../package.json').version); let adapter = adapterUtils.getStandardAdapter(opts.adapter); adapterUtils.checkValidAdapter(adapter); - let createOptionsRoute = function(schema) { + let createOptionsRoute = function(schema) { let tables = _.map(server.table()[0].table) //see if the options method already exists, if so, don't duplicate it if (_.find(tables, {path : '/' + schema.type, method: 'options'})) return; - server.route(_.merge(routes.options(schema), { + server.route(_.merge(routes.options(schema), { handler: (req, reply) => { let tables = _.map(req.server.table()[0].table, (table) => { return _.pick(table, 'path', 'method') @@ -34,18 +34,18 @@ exports.register = function (server, opts, next) { })) } - let get = function (schema) { + let get = function (schema) { createOptionsRoute(schema) - return _.merge(routes.get(schema), { + return _.merge(routes.get(schema), { handler: (req, reply) => { reply({foo: 'bar'}) } }) } - - let post = function (schema) { + + let post = function (schema) { createOptionsRoute(schema) - return _.merge(routes.post(schema), { + return _.merge(routes.post(schema), { handler: (req, reply) => { reply({foo: 'bar'}) } @@ -54,42 +54,42 @@ exports.register = function (server, opts, next) { let put = function (schema) { createOptionsRoute(schema) - return _.merge(routes.put(schema), { + return _.merge(routes.put(schema), { handler: (req, reply) => { reply({foo: 'bar'}) } }) } - - let patch = function (schema) { + + let patch = function (schema) { createOptionsRoute(schema) - return _.merge(routes.patch(schema), { + return _.merge(routes.patch(schema), { handler: (req, reply) => { reply({foo: 'bar'}) } }) } - - let del = function (schema) { + + let del = function (schema) { createOptionsRoute(schema) - return _.merge(routes.delete(schema), { + return _.merge(routes.delete(schema), { handler: (req, reply) => { reply({foo: 'bar'}) } }) } - - server.expose('routes', { + + server.expose('routes', { get: get, post: post, - put: put, + put: put, patch: patch, - delete: del + delete: del }) next() } exports.register.attributes = { - pkg: require('../package.json') + pkg: require('../package.json') } diff --git a/lib/routes.js b/lib/routes.js index 3e620b9..105d597 100644 --- a/lib/routes.js +++ b/lib/routes.js @@ -20,8 +20,8 @@ module.exports = function () { } } } - - let put = function (schema) { + + let put = function (schema) { return { method: 'PUT', path: `/${schema.type}`, @@ -32,8 +32,8 @@ module.exports = function () { } } } - - let patch = function (schema) { + + let patch = function (schema) { return { method: 'PATCH', path: `/${schema.type}`, @@ -44,8 +44,8 @@ module.exports = function () { } } } - - let del = function (schema) { + + let del = function (schema) { return { method: 'DELETE', path: `/${schema.type}` @@ -58,13 +58,13 @@ module.exports = function () { path: `/${schema.type}` } } - + return { get: get, post: post, - put: put, + put: put, patch: patch, - delete: del, + delete: del, options: options } diff --git a/test/adapter.spec.js b/test/adapter.spec.js index 97484b1..854af4d 100644 --- a/test/adapter.spec.js +++ b/test/adapter.spec.js @@ -8,64 +8,64 @@ const Hapi = require('hapi') let server, buildServer, destroyServer, hh; const schema = { - type: 'brands', - attributes: { - code: Joi.string().min(2).max(10), - description: Joi.string() - } + type: 'brands', + attributes: { + code: Joi.string().min(2).max(10), + description: Joi.string() + } }; describe('Adapter Validation', function() { - - afterEach(function(done) { - destroyServer(done); - }) - - it('Will check the given adapter for the required functions', function() { - let adapter = require('../lib/adapters/mongodb')({mongodbUrl: 'mongodb://localhost/test'}) - - adapter = _.remove(adapter, 'delete'); - - //rebuild server with the aling adapter - server = new Hapi.Server() - server.connection({port : 9100}) - - let serverSetup = function() { - server.register([ - {register: require('../lib/plugin'), options: {adapter : adapter}}, - {register: require('inject-then')} - ], () => { - hh = server.plugins.harvester; - server.start(()=> {}) - }) - } - - expect(serverSetup).to.throw('Adapter validation failed. Adapter missing connect') - }) - - it('Will won\'t accept a string adapter if it doesn\'t exist ', function() { - let adapter = require('../lib/adapters/mongodb')({mongodbUrl: 'mongodb://localhost/test'}) - - adapter = _.remove(adapter, 'delete'); - - //rebuild server with the aling adapter - server = new Hapi.Server() - server.connection({port : 9100}) - - let serverSetup = function() { - server.register([ - {register: require('../lib/plugin'), options: {adapter : 'notexistentdb'}}, - {register: require('inject-then')} - ], () => { - hh = server.plugins.harvester; - server.start(()=> {}) - }) - } - - expect(serverSetup).to.throw('Wrong adapter name, see docs for built in adapter') - }) + + afterEach(function(done) { + destroyServer(done); + }) + + it('Will check the given adapter for the required functions', function() { + let adapter = require('../lib/adapters/mongodb')({mongodbUrl: 'mongodb://localhost/test'}) + + adapter = _.remove(adapter, 'delete'); + + //rebuild server with the aling adapter + server = new Hapi.Server() + server.connection({port : 9100}) + + let serverSetup = function() { + server.register([ + {register: require('../lib/plugin'), options: {adapter : adapter}}, + {register: require('inject-then')} + ], () => { + hh = server.plugins.harvester; + server.start(()=> {}) + }) + } + + expect(serverSetup).to.throw('Adapter validation failed. Adapter missing connect') + }) + + it('Will won\'t accept a string adapter if it doesn\'t exist ', function() { + let adapter = require('../lib/adapters/mongodb')({mongodbUrl: 'mongodb://localhost/test'}) + + adapter = _.remove(adapter, 'delete'); + + //rebuild server with the aling adapter + server = new Hapi.Server() + server.connection({port : 9100}) + + let serverSetup = function() { + server.register([ + {register: require('../lib/plugin'), options: {adapter : 'notexistentdb'}}, + {register: require('inject-then')} + ], () => { + hh = server.plugins.harvester; + server.start(()=> {}) + }) + } + + expect(serverSetup).to.throw('Wrong adapter name, see docs for built in adapter') + }) }) destroyServer = function(done) { - server.stop(done) + server.stop(done) } \ No newline at end of file diff --git a/test/plugin.spec.js b/test/plugin.spec.js index 1007829..b415a8a 100644 --- a/test/plugin.spec.js +++ b/test/plugin.spec.js @@ -6,128 +6,128 @@ const Promise = require('bluebird') let server, buildServer, destroyServer, hh; const schema = { - type: 'brands', - attributes: { - code: Joi.string().min(2).max(10), - description: Joi.string() - } + type: 'brands', + attributes: { + code: Joi.string().min(2).max(10), + description: Joi.string() + } }; describe('Plugin Basics', function() { - beforeEach(function(done) { - buildServer(done); - }) - - afterEach(function(done) { - destroyServer(done); - }) - - it('Attaches the plugin to Hapi server configuration', function() { - expect(server.plugins.harvester.version).to.equal('0.1.0') - }) - - it('should have the injectThen method available', function() { - return server.injectThen({method: 'GET', url: '/chuck'}) - .then((res) => { - expect(res.result).to.deep.equal({ statusCode: 404, error: 'Not Found' }) - }) - }) - - it('all the REST verbs available', function() { - - let promises = []; + beforeEach(function(done) { + buildServer(done); + }) + + afterEach(function(done) { + destroyServer(done); + }) + + it('Attaches the plugin to Hapi server configuration', function() { + expect(server.plugins.harvester.version).to.equal('0.1.0') + }) + + it('should have the injectThen method available', function() { + return server.injectThen({method: 'GET', url: '/chuck'}) + .then((res) => { + expect(res.result).to.deep.equal({ statusCode: 404, error: 'Not Found' }) + }) + }) + + it('all the REST verbs available', function() { + + let promises = []; - ['get', 'put', 'post', 'patch', 'delete'].forEach(function(verb) { - server.route(hh.routes[verb](schema)) - - let promise = server.injectThen({method: verb.toUpperCase(), url: '/brands'}).then((res) => { - expect(res.result).to.deep.equal({ foo: 'bar' }) - }) - - promises.push(promise) - }) - - return Promise.all(promises) - }) - - it('only sends the available verbs on OPTIONS call', function() { + ['get', 'put', 'post', 'patch', 'delete'].forEach(function(verb) { + server.route(hh.routes[verb](schema)) + + let promise = server.injectThen({method: verb.toUpperCase(), url: '/brands'}).then((res) => { + expect(res.result).to.deep.equal({ foo: 'bar' }) + }) + + promises.push(promise) + }) + + return Promise.all(promises) + }) + + it('only sends the available verbs on OPTIONS call', function() { - ['get', 'put', 'post', 'patch', 'delete'].forEach(function(verb) { - server.route(hh.routes[verb](schema)) - }) - - return server.injectThen({method: 'OPTIONS', url: '/brands'}) - .then(function(res) { - expect(res.headers.allow).to.equal('OPTIONS,GET,PUT,POST,PATCH,DELETE') - }) - }) - - it('should set the content-type header to application/json by default', function() { - server.route(hh.routes.get(schema)) - return server.injectThen({method: 'GET', url: '/brands'}) - .then((res) => { - expect(res.headers['content-type']).to.equal('application/json; charset=utf-8') - }) - }) - - it('should reject all request with content-type not set to application/json', function() { + ['get', 'put', 'post', 'patch', 'delete'].forEach(function(verb) { + server.route(hh.routes[verb](schema)) + }) + + return server.injectThen({method: 'OPTIONS', url: '/brands'}) + .then(function(res) { + expect(res.headers.allow).to.equal('OPTIONS,GET,PUT,POST,PATCH,DELETE') + }) + }) + + it('should set the content-type header to application/json by default', function() { + server.route(hh.routes.get(schema)) + return server.injectThen({method: 'GET', url: '/brands'}) + .then((res) => { + expect(res.headers['content-type']).to.equal('application/json; charset=utf-8') + }) + }) + + it('should reject all request with content-type not set to application/json', function() { - let promises = []; + let promises = []; - ['put', 'post', 'patch'].forEach(function(verb) { - server.route(hh.routes[verb](schema)) - - let headers = { - 'content-type' : 'text/html' - } - - let promise = server.injectThen({method: verb.toUpperCase(), url: '/brands', headers : headers}).then((res) => { - expect(res.statusCode).to.equal(415) - - }) - - promises.push(promise) - }) - - return Promise.all(promises) - }) - - it('should allow all request with content-type set to application/json', function() { + ['put', 'post', 'patch'].forEach(function(verb) { + server.route(hh.routes[verb](schema)) + + let headers = { + 'content-type' : 'text/html' + } + + let promise = server.injectThen({method: verb.toUpperCase(), url: '/brands', headers : headers}).then((res) => { + expect(res.statusCode).to.equal(415) + + }) + + promises.push(promise) + }) + + return Promise.all(promises) + }) + + it('should allow all request with content-type set to application/json', function() { - let promises = []; + let promises = []; - ['put', 'post', 'patch'].forEach(function(verb) { - server.route(hh.routes[verb](schema)) - - let headers = { - 'content-type' : 'application/json' - } + ['put', 'post', 'patch'].forEach(function(verb) { + server.route(hh.routes[verb](schema)) + + let headers = { + 'content-type' : 'application/json' + } - let promise = server.injectThen({method: verb.toUpperCase(), url: '/brands', headers : headers}).then((res) => { - expect(res.statusCode).to.equal(200) - }) - - promises.push(promise) - }) - - return Promise.all(promises) - }) + let promise = server.injectThen({method: verb.toUpperCase(), url: '/brands', headers : headers}).then((res) => { + expect(res.statusCode).to.equal(200) + }) + + promises.push(promise) + }) + + return Promise.all(promises) + }) }) buildServer = function(done) { - const Hapi = require('hapi') - let adapter = require('../lib/adapters/mongodb')({mongodbUrl: 'mongodb://localhost/test'}) - server = new Hapi.Server() - server.connection({port : 9100}) - server.register([ - {register: require('../lib/plugin'), options: {adapter : 'mongodb'}}, - {register: require('inject-then')} - ], () => { - hh = server.plugins.harvester; - server.start(done) - }) + const Hapi = require('hapi') + let adapter = require('../lib/adapters/mongodb')({mongodbUrl: 'mongodb://localhost/test'}) + server = new Hapi.Server() + server.connection({port : 9100}) + server.register([ + {register: require('../lib/plugin'), options: {adapter: 'mongodb'}}, + {register: require('inject-then')} + ], () => { + hh = server.plugins.harvester; + server.start(done) + }) } destroyServer = function(done) { - server.stop(done) + server.stop(done) } \ No newline at end of file From c097b7d2731ff609e248ab458c54095dc1ddad82 Mon Sep 17 00:00:00 2001 From: Mehdi Avdi Date: Mon, 19 Oct 2015 17:58:02 +0100 Subject: [PATCH 11/42] added helper function to get adapter --- index.js | 1 + lib/plugin.js | 2 ++ lib/utils/adapter.js | 2 +- test/plugin.spec.js | 5 +++-- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/index.js b/index.js index e69de29..446753a 100644 --- a/index.js +++ b/index.js @@ -0,0 +1 @@ +module.exports = require ('./lib/plugin') \ No newline at end of file diff --git a/lib/plugin.js b/lib/plugin.js index 7d7c401..b9604a5 100644 --- a/lib/plugin.js +++ b/lib/plugin.js @@ -93,3 +93,5 @@ exports.register = function (server, opts, next) { exports.register.attributes = { pkg: require('../package.json') } + +exports.getAdapter = adapterUtils.getStandardAdapter; diff --git a/lib/utils/adapter.js b/lib/utils/adapter.js index 6ef5314..cc68df2 100644 --- a/lib/utils/adapter.js +++ b/lib/utils/adapter.js @@ -17,7 +17,7 @@ module.exports = function() { let getStandardAdapter = function(adapter) { if ( _.isString(adapter)) { try { - return require('../../lib/adapters/' + adapter)(); + return require('../../lib/adapters/' + adapter); } catch (err) { Hoek.assert(!err, new Error('Wrong adapter name, see docs for built in adapter')) } diff --git a/test/plugin.spec.js b/test/plugin.spec.js index b415a8a..a04f0ae 100644 --- a/test/plugin.spec.js +++ b/test/plugin.spec.js @@ -116,11 +116,12 @@ describe('Plugin Basics', function() { buildServer = function(done) { const Hapi = require('hapi') - let adapter = require('../lib/adapters/mongodb')({mongodbUrl: 'mongodb://localhost/test'}) + const plugin = require('../') + let adapter = plugin.getAdapter('mongodb') server = new Hapi.Server() server.connection({port : 9100}) server.register([ - {register: require('../lib/plugin'), options: {adapter: 'mongodb'}}, + {register: require('../'), options: {adapter: adapter({mongodbUrl: 'mongodb://localhost/test'})}}, {register: require('inject-then')} ], () => { hh = server.plugins.harvester; From 130497fc0f018cb6b129fe35995d95fa65c553a6 Mon Sep 17 00:00:00 2001 From: Mehdi Avdi Date: Mon, 19 Oct 2015 18:02:43 +0100 Subject: [PATCH 12/42] fixed adapter tests --- lib/plugin.js | 2 +- test/adapter.spec.js | 9 +++------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/lib/plugin.js b/lib/plugin.js index b9604a5..bd2b088 100644 --- a/lib/plugin.js +++ b/lib/plugin.js @@ -7,7 +7,7 @@ const adapterUtils = require('./utils/adapter')() exports.register = function (server, opts, next) { server.expose('version', require('../package.json').version); - let adapter = adapterUtils.getStandardAdapter(opts.adapter); + let adapter = opts.adapter; adapterUtils.checkValidAdapter(adapter); diff --git a/test/adapter.spec.js b/test/adapter.spec.js index 854af4d..1d9f954 100644 --- a/test/adapter.spec.js +++ b/test/adapter.spec.js @@ -22,7 +22,7 @@ describe('Adapter Validation', function() { }) it('Will check the given adapter for the required functions', function() { - let adapter = require('../lib/adapters/mongodb')({mongodbUrl: 'mongodb://localhost/test'}) + let adapter = require('../').getAdapter('mongodb') adapter = _.remove(adapter, 'delete'); @@ -44,17 +44,14 @@ describe('Adapter Validation', function() { }) it('Will won\'t accept a string adapter if it doesn\'t exist ', function() { - let adapter = require('../lib/adapters/mongodb')({mongodbUrl: 'mongodb://localhost/test'}) - - adapter = _.remove(adapter, 'delete'); - //rebuild server with the aling adapter server = new Hapi.Server() server.connection({port : 9100}) let serverSetup = function() { + let adapter = require('../').getAdapter('nonexistant') server.register([ - {register: require('../lib/plugin'), options: {adapter : 'notexistentdb'}}, + {register: require('../lib/plugin'), options: {adapter : adapter}}, {register: require('inject-then')} ], () => { hh = server.plugins.harvester; From a3d839d15fdd6b9d79f2a9ae60aead2c0b8c76a0 Mon Sep 17 00:00:00 2001 From: Mehdi Avdi Date: Mon, 19 Oct 2015 18:03:08 +0100 Subject: [PATCH 13/42] fixed adapter tests --- lib/plugin.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/plugin.js b/lib/plugin.js index bd2b088..7791dd6 100644 --- a/lib/plugin.js +++ b/lib/plugin.js @@ -11,6 +11,8 @@ exports.register = function (server, opts, next) { adapterUtils.checkValidAdapter(adapter); + adapter.connect(next); + let createOptionsRoute = function(schema) { let tables = _.map(server.table()[0].table) From 23573105c5c9afb86d5d7e36e7b3c11169a81c8f Mon Sep 17 00:00:00 2001 From: Mehdi Avdi Date: Mon, 19 Oct 2015 18:44:44 +0100 Subject: [PATCH 14/42] now calling into adapter functions --- lib/adapters/mongodb/converters.js | 10 +++---- lib/adapters/mongodb/index.js | 44 ++++++++++++++++++++++++++---- lib/plugin.js | 32 ++++++++++++++++------ test/plugin.spec.js | 6 ++-- 4 files changed, 71 insertions(+), 21 deletions(-) diff --git a/lib/adapters/mongodb/converters.js b/lib/adapters/mongodb/converters.js index 798a635..d2d5681 100644 --- a/lib/adapters/mongodb/converters.js +++ b/lib/adapters/mongodb/converters.js @@ -1,8 +1,8 @@ -const Hapi = require('hapi'), - _ = require('lodash'), - Hoek = require('hoek'), - mongoose = require('mongoose'), - uuid = require('node-uuid') +const Hapi = require('hapi') +const _ = require('lodash') +const Hoek = require('hoek') +const mongoose = require('mongoose') +const uuid = require('node-uuid') module.exports.toJsonApi = function (resources) { diff --git a/lib/adapters/mongodb/index.js b/lib/adapters/mongodb/index.js index 2538c09..2df86f0 100644 --- a/lib/adapters/mongodb/index.js +++ b/lib/adapters/mongodb/index.js @@ -4,6 +4,7 @@ const Hapi = require('hapi') const _ = require('lodash') const mongoose = require('mongoose') const converters = require('./converters') +const utils = require('./utils') mongoose.Promise = require('bluebird') @@ -31,6 +32,9 @@ module.exports = function (options) { .then((resources)=> { return {data: converters.toJsonApi(resources)} }) + .catch((err) => { + console.log(err) + }) } @@ -42,21 +46,50 @@ module.exports = function (options) { .then((resources) => { return {data: converters.toJsonApi(resources)} }) + .catch((err) => { + console.log(err) + }) } let create = function(type, req) { const model = models[type] - var data = req.payload.data - return model.create(data).then((created) => { - return {data: converters.toJsonApi(created.toObject())} - }) + var data = utils.getPayload(req) + return model.create(data) + .then((created) => { + return {data: converters.toJsonApi(created.toObject())} + }) + .catch((err) => { + console.log(err) + }) } - let del = function(type, req) { + let update = function(type, req) { + const model = models[type] + var data = utils.getPayload(req) + return model.findByIdAndUpdate(req.params.id, data) + .then((created) => { + return {data: converters.toJsonApi(created.toObject())} + }) + .catch((err) => { + console.log(err) + }) + + } + + let del = function(type, req) { + const model = models[type] + var predicate = toMongoosePredicate({id: req.params.id}) + return model.remove(predicate) + .then((created) => { + return {data: converters.toJsonApi(created.toObject())} + }) + .catch((err) => { + console.log(err) + }) } let processSchema = function(hhSchema) { @@ -90,6 +123,7 @@ module.exports = function (options) { find, findById, create, + update, delete: del, models, processSchema diff --git a/lib/plugin.js b/lib/plugin.js index 7791dd6..cd4aeba 100644 --- a/lib/plugin.js +++ b/lib/plugin.js @@ -11,7 +11,9 @@ exports.register = function (server, opts, next) { adapterUtils.checkValidAdapter(adapter); - adapter.connect(next); + adapter.connect(() => { + next() + }); let createOptionsRoute = function(schema) { let tables = _.map(server.table()[0].table) @@ -38,58 +40,72 @@ exports.register = function (server, opts, next) { let get = function (schema) { createOptionsRoute(schema) + adapter.processSchema(schema) return _.merge(routes.get(schema), { handler: (req, reply) => { - reply({foo: 'bar'}) + reply(adapter.find(schema.type, req)) + } + }) + } + + let getById = function (schema) { + createOptionsRoute(schema) + adapter.processSchema(schema) + return _.merge(routes.get(schema), { + handler: (req, reply) => { + reply(adapter.findById(schema.type, req)) } }) } let post = function (schema) { createOptionsRoute(schema) + adapter.processSchema(schema) return _.merge(routes.post(schema), { handler: (req, reply) => { - reply({foo: 'bar'}) + reply(adapter.create(schema.type, req)).code(201) } }) } let put = function (schema) { createOptionsRoute(schema) + adapter.processSchema(schema) return _.merge(routes.put(schema), { handler: (req, reply) => { - reply({foo: 'bar'}) + reply(adapter.update(schema.type, req)) } }) } let patch = function (schema) { createOptionsRoute(schema) + adapter.processSchema(schema) return _.merge(routes.patch(schema), { handler: (req, reply) => { - reply({foo: 'bar'}) + reply(adapter.update(schema.type, req)) } }) } let del = function (schema) { createOptionsRoute(schema) + adapter.processSchema(schema) return _.merge(routes.delete(schema), { handler: (req, reply) => { - reply({foo: 'bar'}) + reply(adapter.delete(schema.type, req)) } }) } server.expose('routes', { get: get, + getById: getById, post: post, put: put, patch: patch, delete: del }) - - next() } exports.register.attributes = { diff --git a/test/plugin.spec.js b/test/plugin.spec.js index a04f0ae..46c975c 100644 --- a/test/plugin.spec.js +++ b/test/plugin.spec.js @@ -41,7 +41,7 @@ describe('Plugin Basics', function() { server.route(hh.routes[verb](schema)) let promise = server.injectThen({method: verb.toUpperCase(), url: '/brands'}).then((res) => { - expect(res.result).to.deep.equal({ foo: 'bar' }) + expect(res.statusCode).to.be.within(200, 201) }) promises.push(promise) @@ -104,7 +104,7 @@ describe('Plugin Basics', function() { } let promise = server.injectThen({method: verb.toUpperCase(), url: '/brands', headers : headers}).then((res) => { - expect(res.statusCode).to.equal(200) + expect(res.statusCode).to.be.within(200, 201) }) promises.push(promise) @@ -123,7 +123,7 @@ buildServer = function(done) { server.register([ {register: require('../'), options: {adapter: adapter({mongodbUrl: 'mongodb://localhost/test'})}}, {register: require('inject-then')} - ], () => { + ], function() { hh = server.plugins.harvester; server.start(done) }) From de2410324d6fb0374b685ec889aa34c38d56e23c Mon Sep 17 00:00:00 2001 From: Mehdi Avdi Date: Mon, 19 Oct 2015 19:25:47 +0100 Subject: [PATCH 15/42] post operation and test done --- lib/adapters/mongodb/index.js | 9 ++-- lib/adapters/mongodb/utils.js | 9 ++++ test/global.spec.js | 11 ++++- test/rest.spec.js | 81 +++++++++++++++++++++++++++++++++++ 4 files changed, 103 insertions(+), 7 deletions(-) create mode 100644 lib/adapters/mongodb/utils.js create mode 100644 test/rest.spec.js diff --git a/lib/adapters/mongodb/index.js b/lib/adapters/mongodb/index.js index 2df86f0..6d670e6 100644 --- a/lib/adapters/mongodb/index.js +++ b/lib/adapters/mongodb/index.js @@ -53,7 +53,6 @@ module.exports = function (options) { } let create = function(type, req) { - const model = models[type] var data = utils.getPayload(req) return model.create(data) @@ -71,8 +70,8 @@ module.exports = function (options) { const model = models[type] var data = utils.getPayload(req) return model.findByIdAndUpdate(req.params.id, data) - .then((created) => { - return {data: converters.toJsonApi(created.toObject())} + .then((updated) => { + return {data: converters.toJsonApi(updated.toObject())} }) .catch((err) => { console.log(err) @@ -84,8 +83,8 @@ module.exports = function (options) { const model = models[type] var predicate = toMongoosePredicate({id: req.params.id}) return model.remove(predicate) - .then((created) => { - return {data: converters.toJsonApi(created.toObject())} + .then(() => { + return {} }) .catch((err) => { console.log(err) diff --git a/lib/adapters/mongodb/utils.js b/lib/adapters/mongodb/utils.js new file mode 100644 index 0000000..2d391fc --- /dev/null +++ b/lib/adapters/mongodb/utils.js @@ -0,0 +1,9 @@ +const Hapi = require('hapi') +const _ = require('lodash') +const Hoek = require('hoek') +const mongoose = require('mongoose') +const uuid = require('node-uuid') + +module.exports.getPayload = function (req) { + return (req.payload) ? req.payload.data : {} +} \ No newline at end of file diff --git a/test/global.spec.js b/test/global.spec.js index eb78e99..c3a0739 100644 --- a/test/global.spec.js +++ b/test/global.spec.js @@ -1,4 +1,5 @@ -var chai = require('chai') +const chai = require('chai') +const _ = require('lodash') chai.use(require('chai-things')) @@ -7,4 +8,10 @@ chai.config.includeStack = true global.expect = chai.expect global.AssertionError = chai.AssertionError global.Assertion = chai.Assertion -global.assert = chai.assert \ No newline at end of file +global.assert = chai.assert +global.utils = { + getData : function(res) { + var data = res.result.data; + return _.omit(data, 'id') + } +} \ No newline at end of file diff --git a/test/rest.spec.js b/test/rest.spec.js new file mode 100644 index 0000000..07cbc15 --- /dev/null +++ b/test/rest.spec.js @@ -0,0 +1,81 @@ +'use strict' + +const _ = require('lodash') +const Joi = require('joi') +const Promise = require('bluebird') +const Hapi = require('hapi') + +let server, buildServer, destroyServer, hh; + +const schema = { + type: 'brands', + attributes: { + code: Joi.string().min(2).max(10), + description: Joi.string() + } +}; + +describe('Rest operations', function() { + + beforeEach(function(done) { + buildServer(done) + }) + + afterEach(function(done) { + destroyServer(done) + }) + + it('Will be able to get from /brands', function() { + const data = { + attributes: { + code: 'MF', + description: 'Massey Furgeson' + } + }; + + return server.injectThen({method: 'post', url: '/brands', payload: {data}}).then((res) => { + var result = _.omit(res.result, 'id'); + expect(res.result.data.id).to.match(/[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}/) + expect(utils.getData(res)).to.deep.equal(data) + }) + }) + + it('Will be able to post to /brands', function() { + const data = { + attributes: { + code: 'MF', + description: 'Massey Furgeson' + } + }; + + return server.injectThen({method: 'post', url: '/brands', payload: {data}}).then((res) => { + var result = _.omit(res.result, 'id'); + expect(res.result.data.id).to.match(/[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}/) + expect(utils.getData(res)).to.deep.equal(data) + }) + }) +}) + +buildServer = function(done) { + const Hapi = require('hapi') + const plugin = require('../') + let adapter = plugin.getAdapter('mongodb') + server = new Hapi.Server() + server.connection({port : 9100}) + server.register([ + {register: require('../'), options: {adapter: adapter({mongodbUrl: 'mongodb://localhost/test'})}}, + {register: require('inject-then')} + ], () => { + hh = server.plugins.harvester; + server.start(() => { + ['get', 'put', 'post', 'patch', 'delete'].forEach(function(verb) { + server.route(hh.routes[verb](schema)) + }) + done() + }) + }) +} + +destroyServer = function(done) { + server.stop(done) +} \ No newline at end of file From e224e06b080ad0622870087032d7392d8bd8ab24 Mon Sep 17 00:00:00 2001 From: Mehdi Avdi Date: Mon, 19 Oct 2015 21:56:33 +0100 Subject: [PATCH 16/42] db removal and get all done --- lib/adapters/mongodb/index.js | 5 +-- lib/adapters/mongodb/utils.js | 6 ++-- lib/plugin.js | 3 +- lib/routes.js | 20 +++++++++++- test/global.spec.js | 14 ++++++--- test/rest.spec.js | 57 +++++++++++++++++++++++------------ 6 files changed, 74 insertions(+), 31 deletions(-) diff --git a/lib/adapters/mongodb/index.js b/lib/adapters/mongodb/index.js index 6d670e6..34f8e4a 100644 --- a/lib/adapters/mongodb/index.js +++ b/lib/adapters/mongodb/index.js @@ -41,8 +41,7 @@ module.exports = function (options) { let findById = function(type, req) { const model = models[type] - var predicate = toMongoosePredicate({id: req.params.id}) - return model.find(predicate).lean().exec() + return model.findById(req.params.id).lean().exec() .then((resources) => { return {data: converters.toJsonApi(resources)} }) @@ -129,5 +128,3 @@ module.exports = function (options) { } } - - diff --git a/lib/adapters/mongodb/utils.js b/lib/adapters/mongodb/utils.js index 2d391fc..f3c0aac 100644 --- a/lib/adapters/mongodb/utils.js +++ b/lib/adapters/mongodb/utils.js @@ -4,6 +4,8 @@ const Hoek = require('hoek') const mongoose = require('mongoose') const uuid = require('node-uuid') -module.exports.getPayload = function (req) { - return (req.payload) ? req.payload.data : {} +module.exports = { + getPayload: function (req) { + return (req.payload) ? req.payload.data : {} + } } \ No newline at end of file diff --git a/lib/plugin.js b/lib/plugin.js index cd4aeba..4989ed4 100644 --- a/lib/plugin.js +++ b/lib/plugin.js @@ -12,6 +12,7 @@ exports.register = function (server, opts, next) { adapterUtils.checkValidAdapter(adapter); adapter.connect(() => { + server.expose('adapter', adapter); next() }); @@ -51,7 +52,7 @@ exports.register = function (server, opts, next) { let getById = function (schema) { createOptionsRoute(schema) adapter.processSchema(schema) - return _.merge(routes.get(schema), { + return _.merge(routes.getById(schema), { handler: (req, reply) => { reply(adapter.findById(schema.type, req)) } diff --git a/lib/routes.js b/lib/routes.js index 105d597..efd8f12 100644 --- a/lib/routes.js +++ b/lib/routes.js @@ -5,7 +5,24 @@ module.exports = function () { let get = function (schema) { return { method: 'GET', - path: `/${schema.type}` + path: `/${schema.type}`, + config: { + validate: { + options: {allowUnknown: true} + } + } + } + } + + const getById = function (schema) { + return { + method: 'GET', + path: `/${schema.type}/{id}`, + config: { + validate: { + query: false + } + } } } @@ -61,6 +78,7 @@ module.exports = function () { return { get: get, + getById: getById, post: post, put: put, patch: patch, diff --git a/test/global.spec.js b/test/global.spec.js index c3a0739..904ef20 100644 --- a/test/global.spec.js +++ b/test/global.spec.js @@ -1,3 +1,5 @@ +'use strict' + const chai = require('chai') const _ = require('lodash') @@ -10,8 +12,12 @@ global.AssertionError = chai.AssertionError global.Assertion = chai.Assertion global.assert = chai.assert global.utils = { - getData : function(res) { - var data = res.result.data; - return _.omit(data, 'id') - } + getData: (res) => { + const data = res.result.data; + return _.omit(data, 'id') + }, + removeFromDB: (server, collection) => { + const model = server.plugins.harvester.adapter.models['brands'] + return model.remove({}).lean().exec() + } } \ No newline at end of file diff --git a/test/rest.spec.js b/test/rest.spec.js index 07cbc15..524b246 100644 --- a/test/rest.spec.js +++ b/test/rest.spec.js @@ -1,8 +1,8 @@ 'use strict' const _ = require('lodash') -const Joi = require('joi') const Promise = require('bluebird') +const Joi = require('joi') const Hapi = require('hapi') let server, buildServer, destroyServer, hh; @@ -15,6 +15,13 @@ const schema = { } }; +const data = { + attributes: { + code: 'MF', + description: 'Massey Furgeson' + } +}; + describe('Rest operations', function() { beforeEach(function(done) { @@ -25,28 +32,37 @@ describe('Rest operations', function() { destroyServer(done) }) - it('Will be able to get from /brands', function() { - const data = { - attributes: { - code: 'MF', - description: 'Massey Furgeson' - } - }; - - return server.injectThen({method: 'post', url: '/brands', payload: {data}}).then((res) => { - var result = _.omit(res.result, 'id'); + it('Will be able to get by id from /brands', function() { + return server.injectThen({method: 'post', url: '/brands', payload: {data}}) + .then((res) => { + return server.injectThen({method: 'get', url: '/brands/' + res.result.data.id}) + }) + .then((res) => { expect(res.result.data.id).to.match(/[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}/) expect(utils.getData(res)).to.deep.equal(data) }) }) + it.only('Will be able to get all from /brands', function() { + let promises = []; + + _.times(10, () => { + promises.push(server.injectThen({method: 'post', url: '/brands', payload: {data}})) + }) + + return Promise.all(promises) + .then((res) => { + return server.injectThen({method: 'get', url: '/brands'}) + }) + .then((res) => { + res.result.data.forEach((data) => { + expect(data.id).to.match(/[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}/) + expect(data).to.deep.equal(data) + }) + }) + }) + it('Will be able to post to /brands', function() { - const data = { - attributes: { - code: 'MF', - description: 'Massey Furgeson' - } - }; return server.injectThen({method: 'post', url: '/brands', payload: {data}}).then((res) => { var result = _.omit(res.result, 'id'); @@ -68,7 +84,7 @@ buildServer = function(done) { ], () => { hh = server.plugins.harvester; server.start(() => { - ['get', 'put', 'post', 'patch', 'delete'].forEach(function(verb) { + ['get', 'getById', 'put', 'post', 'patch', 'delete'].forEach(function(verb) { server.route(hh.routes[verb](schema)) }) done() @@ -77,5 +93,8 @@ buildServer = function(done) { } destroyServer = function(done) { - server.stop(done) + utils.removeFromDB(server, 'brands') + .then((res) => { + server.stop(done) + }) } \ No newline at end of file From b1127e791510a064ea15fd4ebf47351b39324d3f Mon Sep 17 00:00:00 2001 From: Mehdi Avdi Date: Mon, 19 Oct 2015 22:03:57 +0100 Subject: [PATCH 17/42] PUT done --- test/rest.spec.js | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/test/rest.spec.js b/test/rest.spec.js index 524b246..d5d5d1d 100644 --- a/test/rest.spec.js +++ b/test/rest.spec.js @@ -32,7 +32,7 @@ describe('Rest operations', function() { destroyServer(done) }) - it('Will be able to get by id from /brands', function() { + it('Will be able to GET by id from /brands', function() { return server.injectThen({method: 'post', url: '/brands', payload: {data}}) .then((res) => { return server.injectThen({method: 'get', url: '/brands/' + res.result.data.id}) @@ -43,7 +43,7 @@ describe('Rest operations', function() { }) }) - it.only('Will be able to get all from /brands', function() { + it('Will be able to GET all from /brands', function() { let promises = []; _.times(10, () => { @@ -62,14 +62,31 @@ describe('Rest operations', function() { }) }) - it('Will be able to post to /brands', function() { + it('Will be able to POST to /brands', function() { return server.injectThen({method: 'post', url: '/brands', payload: {data}}).then((res) => { - var result = _.omit(res.result, 'id'); expect(res.result.data.id).to.match(/[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}/) expect(utils.getData(res)).to.deep.equal(data) }) }) + + it.only('Will be able to update using PUT in /brands', function() { + const payload = { + attributes: { + code: 'VT', + description: 'Valtra' + } + }; + return server.injectThen({method: 'post', url: '/brands', payload: {data : payload}}) + .then((res) => { + + return server.injectThen({method: 'put', url: '/brands', payload: {data}}) + }) + .then((res) => { + expect(res.result.data.id).to.match(/[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}/) + expect(utils.getData(res)).to.deep.equal(payload) + }) + }) }) buildServer = function(done) { From 0ecdd36c87ed1c33b0e357b7312b3618496326e3 Mon Sep 17 00:00:00 2001 From: Mehdi Avdi Date: Tue, 20 Oct 2015 01:17:02 +0100 Subject: [PATCH 18/42] added patch method --- test/rest.spec.js | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/test/rest.spec.js b/test/rest.spec.js index d5d5d1d..009d9c2 100644 --- a/test/rest.spec.js +++ b/test/rest.spec.js @@ -70,7 +70,7 @@ describe('Rest operations', function() { }) }) - it.only('Will be able to update using PUT in /brands', function() { + it('Will be able to update using PUT in /brands', function() { const payload = { attributes: { code: 'VT', @@ -79,7 +79,6 @@ describe('Rest operations', function() { }; return server.injectThen({method: 'post', url: '/brands', payload: {data : payload}}) .then((res) => { - return server.injectThen({method: 'put', url: '/brands', payload: {data}}) }) .then((res) => { @@ -87,6 +86,23 @@ describe('Rest operations', function() { expect(utils.getData(res)).to.deep.equal(payload) }) }) + + it('Will be able to update using PATCH in /brands', function() { + const payload = { + attributes: { + code: 'VT', + description: 'Valtra' + } + }; + return server.injectThen({method: 'post', url: '/brands', payload: {data : payload}}) + .then((res) => { + return server.injectThen({method: 'patch', url: '/brands', payload: {data}}) + }) + .then((res) => { + expect(res.result.data.id).to.match(/[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}/) + expect(utils.getData(res)).to.deep.equal(payload) + }) + }) }) buildServer = function(done) { From 5acf00ea6557af8ea7149770459c764e2c55a1c7 Mon Sep 17 00:00:00 2001 From: Mehdi Avdi Date: Tue, 20 Oct 2015 01:44:38 +0100 Subject: [PATCH 19/42] mongoose error/reconnect working --- lib/adapters/mongodb/converters.js | 11 +++++++++++ lib/adapters/mongodb/index.js | 30 +++++++++++------------------- lib/plugin.js | 4 ++++ lib/utils/adapter.js | 2 +- 4 files changed, 27 insertions(+), 20 deletions(-) diff --git a/lib/adapters/mongodb/converters.js b/lib/adapters/mongodb/converters.js index d2d5681..e2fe494 100644 --- a/lib/adapters/mongodb/converters.js +++ b/lib/adapters/mongodb/converters.js @@ -51,6 +51,17 @@ module.exports.toMongooseModel = function (hhSchema) { const schema = mongoose.Schema(mongooseSchema) return mongoose.model(hhSchema.type, schema) +} + +module.exports.toMongoosePredicate = function(query) { + const mappedToModel = _.mapKeys(query.filter, function (val, key) { + if (key == 'id') return '_id' + else return `attributes.${key}` + }) + return _.mapValues(mappedToModel, function (val, key) { + if (val.indexOf(',') != -1) return {$in: val.split(',')} + else return val + }) } diff --git a/lib/adapters/mongodb/index.js b/lib/adapters/mongodb/index.js index 34f8e4a..dcae7c5 100644 --- a/lib/adapters/mongodb/index.js +++ b/lib/adapters/mongodb/index.js @@ -10,16 +10,20 @@ mongoose.Promise = require('bluebird') module.exports = function (options) { - const models = {} - - let disconnect = function(cb) { - mongoose.disconnect(cb) - } + let models = {} let connect = function(cb) { mongoose.connect(options.mongodbUrl, cb) } + let disconnect = function(cb) { + //clear out events + mongoose.connection._events = {} + mongoose.disconnect(cb) + } + + mongoose.connection.on('error', connect) + let find = function (type, req) { const model = models[type] @@ -27,7 +31,7 @@ module.exports = function (options) { const limit = query.limit || 1000 const skip = query.offset || 0 const sort = query.sort || {'_id': -1} - var predicate = toMongoosePredicate(query) + var predicate = converters.toMongoosePredicate(query) return model.find(predicate).skip(skip).sort(sort).limit(limit).lean().exec() .then((resources)=> { return {data: converters.toJsonApi(resources)} @@ -80,7 +84,7 @@ module.exports = function (options) { let del = function(type, req) { const model = models[type] - var predicate = toMongoosePredicate({id: req.params.id}) + var predicate = converters.toMongoosePredicate({id: req.params.id}) return model.remove(predicate) .then(() => { return {} @@ -102,18 +106,6 @@ module.exports = function (options) { } return models[hhSchema.type] } - - var toMongoosePredicate = function(query) { - const mappedToModel = _.mapKeys(query.filter, function (val, key) { - if (key == 'id') return '_id' - else return `attributes.${key}` - }) - - return _.mapValues(mappedToModel, function (val, key) { - if (val.indexOf(',') != -1) return {$in: val.split(',')} - else return val - }) - } return { connect, diff --git a/lib/plugin.js b/lib/plugin.js index 4989ed4..30efdbb 100644 --- a/lib/plugin.js +++ b/lib/plugin.js @@ -107,6 +107,10 @@ exports.register = function (server, opts, next) { patch: patch, delete: del }) + + server.ext('onPostStop', (server, next) => { + adapter.disconnect(next) + }) } exports.register.attributes = { diff --git a/lib/utils/adapter.js b/lib/utils/adapter.js index cc68df2..30c8f2e 100644 --- a/lib/utils/adapter.js +++ b/lib/utils/adapter.js @@ -2,7 +2,7 @@ const _ = require('lodash') const Hoek = require('hoek') -const protocolFunctions = ['connect','disconnect','find','findById','create','delete', 'models','processSchema']; +const protocolFunctions = ['connect', 'disconnect', 'find', 'findById', 'create', 'delete', 'models', 'processSchema']; module.exports = function() { let checkValidAdapter = function(adapter) { From 514a794ab9fcc91c7818cf97392423dca46057a6 Mon Sep 17 00:00:00 2001 From: Mehdi Avdi Date: Tue, 20 Oct 2015 02:07:05 +0100 Subject: [PATCH 20/42] refactored all utils to use the same style of modules --- lib/adapters/mongodb/converters.js | 115 +++++++++++++++-------------- lib/adapters/mongodb/index.js | 4 +- lib/adapters/mongodb/utils.js | 8 +- lib/plugin.js | 1 + lib/routes.js | 2 + lib/utils/adapter.js | 7 +- test/plugin.spec.js | 17 ----- test/rest.spec.js | 23 +++++- 8 files changed, 96 insertions(+), 81 deletions(-) diff --git a/lib/adapters/mongodb/converters.js b/lib/adapters/mongodb/converters.js index e2fe494..78629f0 100644 --- a/lib/adapters/mongodb/converters.js +++ b/lib/adapters/mongodb/converters.js @@ -1,67 +1,72 @@ +'use strict' + const Hapi = require('hapi') const _ = require('lodash') const Hoek = require('hoek') const mongoose = require('mongoose') const uuid = require('node-uuid') -module.exports.toJsonApi = function (resources) { - - if (_.isArray(resources)) { - return _.map(resources, (resource) => { - return toJsonApiSingle(resource); - }) - } else { - return toJsonApiSingle(resources); - } - - function toJsonApiSingle(resource) { - var mapped = _.mapKeys(resource, function (val, key) { - if (key === '_id') return 'id' - else return key - }); - return _.omit(mapped, '__v') - } -} - -module.exports.toMongooseModel = function (hhSchema) { - - const mongooseSchema = {} - mongooseSchema._id = { - type: String, - default: () => { - return uuid.v4() +module.exports = function() { + let toJsonApi = function (resources) { + if (_.isArray(resources)) { + return _.map(resources, (resource) => { + return toJsonApiSingle(resource); + }) + } else { + return toJsonApiSingle(resources); + } + + function toJsonApiSingle(resource) { + var mapped = _.mapKeys(resource, function (val, key) { + if (key === '_id') return 'id' + else return key + }); + return _.omit(mapped, '__v') } } - - var schemaMap = { - 'string': String, - 'number': Number, - 'date': Date, - 'buffer': Buffer, - 'boolean': Boolean, - 'array': Array, - 'any': Object + + let toMongooseModel = function (hhSchema) { + + const mongooseSchema = {} + mongooseSchema._id = { + type: String, + default: () => { + return uuid.v4() + } + } + + var schemaMap = { + 'string': String, + 'number': Number, + 'date': Date, + 'buffer': Buffer, + 'boolean': Boolean, + 'array': Array, + 'any': Object + } + + mongooseSchema.attributes = + _.mapValues(hhSchema.attributes, function (val) { + Hoek.assert(val.isJoi, 'attribute values in the hh schema should be defined with Joi') + return schemaMap[val._type] + }) + + const schema = mongoose.Schema(mongooseSchema) + return mongoose.model(hhSchema.type, schema) } - - mongooseSchema.attributes = - _.mapValues(hhSchema.attributes, function (val) { - Hoek.assert(val.isJoi, 'attribute values in the hh schema should be defined with Joi') - return schemaMap[val._type] + + let toMongoosePredicate = function(query) { + const mappedToModel = _.mapKeys(query.filter, function (val, key) { + if (key == 'id') return '_id' + else return `attributes.${key}` }) - - const schema = mongoose.Schema(mongooseSchema) - return mongoose.model(hhSchema.type, schema) -} - -module.exports.toMongoosePredicate = function(query) { - const mappedToModel = _.mapKeys(query.filter, function (val, key) { - if (key == 'id') return '_id' - else return `attributes.${key}` - }) - - return _.mapValues(mappedToModel, function (val, key) { - if (val.indexOf(',') != -1) return {$in: val.split(',')} - else return val - }) + + return _.mapValues(mappedToModel, function (val, key) { + if (val.indexOf(',') != -1) return {$in: val.split(',')} + else return val + }) + } + + return { toJsonApi, toMongooseModel, toMongoosePredicate } } diff --git a/lib/adapters/mongodb/index.js b/lib/adapters/mongodb/index.js index dcae7c5..68df9d3 100644 --- a/lib/adapters/mongodb/index.js +++ b/lib/adapters/mongodb/index.js @@ -3,8 +3,8 @@ const Hapi = require('hapi') const _ = require('lodash') const mongoose = require('mongoose') -const converters = require('./converters') -const utils = require('./utils') +const converters = require('./converters')() +const utils = require('./utils')() mongoose.Promise = require('bluebird') diff --git a/lib/adapters/mongodb/utils.js b/lib/adapters/mongodb/utils.js index f3c0aac..326513e 100644 --- a/lib/adapters/mongodb/utils.js +++ b/lib/adapters/mongodb/utils.js @@ -1,11 +1,15 @@ +'use strict' + const Hapi = require('hapi') const _ = require('lodash') const Hoek = require('hoek') const mongoose = require('mongoose') const uuid = require('node-uuid') -module.exports = { - getPayload: function (req) { +module.exports = function() { + let getPayload = function (req) { return (req.payload) ? req.payload.data : {} } + + return { getPayload } } \ No newline at end of file diff --git a/lib/plugin.js b/lib/plugin.js index 30efdbb..2700f72 100644 --- a/lib/plugin.js +++ b/lib/plugin.js @@ -64,6 +64,7 @@ exports.register = function (server, opts, next) { adapter.processSchema(schema) return _.merge(routes.post(schema), { handler: (req, reply) => { + console.log(req.payload) reply(adapter.create(schema.type, req)).code(201) } }) diff --git a/lib/routes.js b/lib/routes.js index efd8f12..9073b73 100644 --- a/lib/routes.js +++ b/lib/routes.js @@ -33,6 +33,8 @@ module.exports = function () { config: { payload: { allow : 'application/json' + }, + validate: { } } } diff --git a/lib/utils/adapter.js b/lib/utils/adapter.js index 30c8f2e..5ee1a74 100644 --- a/lib/utils/adapter.js +++ b/lib/utils/adapter.js @@ -15,10 +15,12 @@ module.exports = function() { } let getStandardAdapter = function(adapter) { + console.log(adapter) if ( _.isString(adapter)) { try { return require('../../lib/adapters/' + adapter); } catch (err) { + console.log(err.stack) Hoek.assert(!err, new Error('Wrong adapter name, see docs for built in adapter')) } } @@ -26,8 +28,5 @@ module.exports = function() { return adapter; } - return { - checkValidAdapter, - getStandardAdapter - } + return { checkValidAdapter, getStandardAdapter } } \ No newline at end of file diff --git a/test/plugin.spec.js b/test/plugin.spec.js index 46c975c..949412d 100644 --- a/test/plugin.spec.js +++ b/test/plugin.spec.js @@ -33,23 +33,6 @@ describe('Plugin Basics', function() { }) }) - it('all the REST verbs available', function() { - - let promises = []; - - ['get', 'put', 'post', 'patch', 'delete'].forEach(function(verb) { - server.route(hh.routes[verb](schema)) - - let promise = server.injectThen({method: verb.toUpperCase(), url: '/brands'}).then((res) => { - expect(res.statusCode).to.be.within(200, 201) - }) - - promises.push(promise) - }) - - return Promise.all(promises) - }) - it('only sends the available verbs on OPTIONS call', function() { ['get', 'put', 'post', 'patch', 'delete'].forEach(function(verb) { diff --git a/test/rest.spec.js b/test/rest.spec.js index 009d9c2..79b0a79 100644 --- a/test/rest.spec.js +++ b/test/rest.spec.js @@ -22,7 +22,7 @@ const data = { } }; -describe('Rest operations', function() { +describe('Rest operations when things go right', function() { beforeEach(function(done) { buildServer(done) @@ -105,6 +105,27 @@ describe('Rest operations', function() { }) }) +describe('Rest operations when things go wrong', function() { + + beforeEach(function(done) { + buildServer(done) + }) + + afterEach(function(done) { + destroyServer(done) + }) + + it('Won\'t be able to POST to /brands with a payload that doesn\'t match the schema', function() { + + let payload = _.cloneDeep(data); + payload.foo = 'bar' + + return server.injectThen({method: 'post', url: '/brands', payload: {data: payload}}).then((res) => { + expect(res.statusCode).to.equal(400) + }) + }) +}) + buildServer = function(done) { const Hapi = require('hapi') const plugin = require('../') From 42630affa8b63e7860e0859f5d98cc273931b488 Mon Sep 17 00:00:00 2001 From: Mehdi Avdi Date: Tue, 20 Oct 2015 02:41:53 +0100 Subject: [PATCH 21/42] POST validation along with the tests --- lib/plugin.js | 1 - lib/routes.js | 5 ++++- lib/utils/adapter.js | 2 -- test/plugin.spec.js | 33 ++++++++++++++++----------------- test/rest.spec.js | 10 ++++++++++ 5 files changed, 30 insertions(+), 21 deletions(-) diff --git a/lib/plugin.js b/lib/plugin.js index 2700f72..30efdbb 100644 --- a/lib/plugin.js +++ b/lib/plugin.js @@ -64,7 +64,6 @@ exports.register = function (server, opts, next) { adapter.processSchema(schema) return _.merge(routes.post(schema), { handler: (req, reply) => { - console.log(req.payload) reply(adapter.create(schema.type, req)).code(201) } }) diff --git a/lib/routes.js b/lib/routes.js index 9073b73..1804c69 100644 --- a/lib/routes.js +++ b/lib/routes.js @@ -1,5 +1,7 @@ 'use strict' +const schemaUtils = require('./utils/schema')() + module.exports = function () { let get = function (schema) { @@ -32,9 +34,10 @@ module.exports = function () { path: `/${schema.type}`, config: { payload: { - allow : 'application/json' + allow: 'application/json' }, validate: { + payload: schemaUtils.toJoiValidate(schema) } } } diff --git a/lib/utils/adapter.js b/lib/utils/adapter.js index 5ee1a74..a62bea8 100644 --- a/lib/utils/adapter.js +++ b/lib/utils/adapter.js @@ -15,12 +15,10 @@ module.exports = function() { } let getStandardAdapter = function(adapter) { - console.log(adapter) if ( _.isString(adapter)) { try { return require('../../lib/adapters/' + adapter); } catch (err) { - console.log(err.stack) Hoek.assert(!err, new Error('Wrong adapter name, see docs for built in adapter')) } } diff --git a/test/plugin.spec.js b/test/plugin.spec.js index 949412d..d21fa0b 100644 --- a/test/plugin.spec.js +++ b/test/plugin.spec.js @@ -13,6 +13,13 @@ const schema = { } }; +const data = { + attributes: { + code: 'MF', + description: 'Massey Furgeson' + } +}; + describe('Plugin Basics', function() { beforeEach(function(done) { buildServer(done); @@ -76,24 +83,16 @@ describe('Plugin Basics', function() { }) it('should allow all request with content-type set to application/json', function() { - - let promises = []; - - ['put', 'post', 'patch'].forEach(function(verb) { - server.route(hh.routes[verb](schema)) - - let headers = { - 'content-type' : 'application/json' - } - - let promise = server.injectThen({method: verb.toUpperCase(), url: '/brands', headers : headers}).then((res) => { - expect(res.statusCode).to.be.within(200, 201) - }) - - promises.push(promise) - }) + let headers = { + 'content-type' : 'application/json' + } - return Promise.all(promises) + server.route(hh.routes.post(schema)) + + server.injectThen({method: 'post', url: '/brands', headers: headers, payload: {data}}) + .then((res) => { + expect(res.statusCode).to.equal(201) + }) }) }) diff --git a/test/rest.spec.js b/test/rest.spec.js index 79b0a79..ceeb12d 100644 --- a/test/rest.spec.js +++ b/test/rest.spec.js @@ -124,6 +124,16 @@ describe('Rest operations when things go wrong', function() { expect(res.statusCode).to.equal(400) }) }) + + it('Won\'t be able to POST to /brands with a payload that has attributes that don\'t match the schema', function() { + + let payload = _.cloneDeep(data); + payload.attributes.foo = 'bar' + + return server.injectThen({method: 'post', url: '/brands', payload: {data: payload}}).then((res) => { + expect(res.statusCode).to.equal(400) + }) + }) }) buildServer = function(done) { From 6438384385598ed04825cdebe3bdc25e155c5427 Mon Sep 17 00:00:00 2001 From: Mehdi Avdi Date: Tue, 20 Oct 2015 23:21:46 +0100 Subject: [PATCH 22/42] inclusion validation is done --- lib/routes.js | 4 +- lib/utils/schema.js | 24 ++++++++++ test/includes.spec.js | 104 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 130 insertions(+), 2 deletions(-) create mode 100644 lib/utils/schema.js create mode 100644 test/includes.spec.js diff --git a/lib/routes.js b/lib/routes.js index 1804c69..dfef774 100644 --- a/lib/routes.js +++ b/lib/routes.js @@ -10,7 +10,7 @@ module.exports = function () { path: `/${schema.type}`, config: { validate: { - options: {allowUnknown: true} + query: schemaUtils.toJoiGetQueryValidation(schema) } } } @@ -37,7 +37,7 @@ module.exports = function () { allow: 'application/json' }, validate: { - payload: schemaUtils.toJoiValidate(schema) + payload: schemaUtils.toJoiPostValidatation(schema) } } } diff --git a/lib/utils/schema.js b/lib/utils/schema.js new file mode 100644 index 0000000..edb2494 --- /dev/null +++ b/lib/utils/schema.js @@ -0,0 +1,24 @@ +'use strict' + +const _ = require('lodash') +const Joi = require('joi') +const Hoek = require('hoek') + +module.exports = function() { + let toJoiPostValidatation = function(schema) { + return Joi.object().keys({ + data: Joi.object().keys({ + attributes : schema.attributes + }) + }) + } + + let toJoiGetQueryValidation = function(schema) { + let join = _.keys(schema.attributes).join('|') + let regex = new RegExp('^(' + join + ')(,(' + join + '))*$', 'i') + let include = Joi.string().regex(regex) + return {include} + } + + return { toJoiPostValidatation, toJoiGetQueryValidation } +} \ No newline at end of file diff --git a/test/includes.spec.js b/test/includes.spec.js new file mode 100644 index 0000000..1c8ae7f --- /dev/null +++ b/test/includes.spec.js @@ -0,0 +1,104 @@ +'use strict' + +const _ = require('lodash') +const Promise = require('bluebird') +const Joi = require('joi') +const Hapi = require('hapi') + +let server, buildServer, destroyServer, hh; + +const schema = { + type: 'brands', + attributes: { + code: Joi.string().min(2).max(10), + description: Joi.string() + } +}; + +const data = { + attributes: { + code: 'MF', + description: 'Massey Furgeson' + } +}; + + +//TODO just done the validation, actual includes is remaining +describe('Inclusion', function() { + + beforeEach(function(done) { + buildServer(() => { + let promises = []; + + _.times(10, () => { + promises.push(server.injectThen({method: 'post', url: '/brands', payload: {data}})) + }) + + return Promise.all(promises) + .then(() => { + done() + }) + }) + }) + + afterEach(function(done) { + destroyServer(done) + }) + + it('Will be able to GET all from /brands with a inclusion', function() { + + return server.injectThen({method: 'get', url: '/brands?include=code'}) + .then((res) => { + res.result.data.forEach((data) => { + expect(data.id).to.match(/[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}/) + expect(data).to.deep.equal(data) + }) + }) + }) + + it('Will be able to GET all from /brands with multiple inclusions', function() { + + return server.injectThen({method: 'get', url: '/brands?include=code,description'}) + .then((res) => { + res.result.data.forEach((data) => { + expect(data.id).to.match(/[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}/) + expect(data).to.deep.equal(data) + }) + }) + }) + + it('Will be able to GET all from /brands with multiple inclusions bad', function() { + + return server.injectThen({method: 'get', url: '/brands?include=code,foo'}) + .then((res) => { + expect(res.statusCode).to.equal(400) + }) + }) +}) + +buildServer = function(done) { + const Hapi = require('hapi') + const plugin = require('../') + let adapter = plugin.getAdapter('mongodb') + server = new Hapi.Server() + server.connection({port : 9100}) + server.register([ + {register: require('../'), options: {adapter: adapter({mongodbUrl: 'mongodb://localhost/test'})}}, + {register: require('inject-then')} + ], () => { + hh = server.plugins.harvester; + server.start(() => { + ['get', 'getById', 'put', 'post', 'patch', 'delete'].forEach(function(verb) { + server.route(hh.routes[verb](schema)) + }) + done() + }) + }) +} + +destroyServer = function(done) { + utils.removeFromDB(server, 'brands') + .then((res) => { + server.stop(done) + }) +} \ No newline at end of file From cad03495933c24a62ef46b357c2497d14e5d72bc Mon Sep 17 00:00:00 2001 From: Mehdi Avdi Date: Tue, 20 Oct 2015 23:55:37 +0100 Subject: [PATCH 23/42] sparse field validations --- lib/utils/schema.js | 12 +++- test/includes.spec.js | 9 +-- test/sparse.fieldsets.spec.js | 104 ++++++++++++++++++++++++++++++++++ 3 files changed, 117 insertions(+), 8 deletions(-) create mode 100644 test/sparse.fieldsets.spec.js diff --git a/lib/utils/schema.js b/lib/utils/schema.js index edb2494..f0dd3d7 100644 --- a/lib/utils/schema.js +++ b/lib/utils/schema.js @@ -14,10 +14,18 @@ module.exports = function() { } let toJoiGetQueryValidation = function(schema) { - let join = _.keys(schema.attributes).join('|') + let keys = _.keys(schema.attributes); + let join = keys.join('|') + let regex = new RegExp('^(' + join + ')(,(' + join + '))*$', 'i') let include = Joi.string().regex(regex) - return {include} + + let fieldMap = {} + keys.forEach((key) => { + fieldMap[key] = Joi.string() + }) + let fields = Joi.object(fieldMap) + return {include, fields} } return { toJoiPostValidatation, toJoiGetQueryValidation } diff --git a/test/includes.spec.js b/test/includes.spec.js index 1c8ae7f..e694df0 100644 --- a/test/includes.spec.js +++ b/test/includes.spec.js @@ -46,29 +46,26 @@ describe('Inclusion', function() { }) it('Will be able to GET all from /brands with a inclusion', function() { - return server.injectThen({method: 'get', url: '/brands?include=code'}) .then((res) => { res.result.data.forEach((data) => { expect(data.id).to.match(/[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}/) - expect(data).to.deep.equal(data) + expect(data).to.deep.equal(data) }) }) }) it('Will be able to GET all from /brands with multiple inclusions', function() { - return server.injectThen({method: 'get', url: '/brands?include=code,description'}) .then((res) => { res.result.data.forEach((data) => { expect(data.id).to.match(/[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}/) - expect(data).to.deep.equal(data) + expect(data).to.deep.equal(data) }) }) }) - it('Will be able to GET all from /brands with multiple inclusions bad', function() { - + it('Won\'t be able to GET all from /brands with an inclusion not available in attributes', function() { return server.injectThen({method: 'get', url: '/brands?include=code,foo'}) .then((res) => { expect(res.statusCode).to.equal(400) diff --git a/test/sparse.fieldsets.spec.js b/test/sparse.fieldsets.spec.js new file mode 100644 index 0000000..05eb60c --- /dev/null +++ b/test/sparse.fieldsets.spec.js @@ -0,0 +1,104 @@ +'use strict' + +const _ = require('lodash') +const Promise = require('bluebird') +const Joi = require('joi') +const Hapi = require('hapi') + +let server, buildServer, destroyServer, hh; + +const schema = { + type: 'brands', + attributes: { + code: Joi.string().min(2).max(10), + description: Joi.string() + } +}; + +const data = { + attributes: { + code: 'MF', + description: 'Massey Furgeson' + } +}; + + +//TODO just done the validation, actual includes is remaining +describe('Sparse Fieldsets', function() { + + beforeEach(function(done) { + buildServer(() => { + let promises = []; + + _.times(10, () => { + promises.push(server.injectThen({method: 'post', url: '/brands', payload: {data}})) + }) + + return Promise.all(promises) + .then(() => { + done() + }) + }) + }) + + afterEach(function(done) { + destroyServer(done) + }) + + it('Will be able to GET all from /brands with a sparse fieldset', function() { + + return server.injectThen({method: 'get', url: '/brands?include=code&fields[description]=Massey Furgeson'}) + .then((res) => { + res.result.data.forEach((data) => { + expect(data.id).to.match(/[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}/) + expect(data).to.deep.equal(data) + }) + }) + }) + + it('Will be able to GET all from /brands with multiple fieldset', function() { + + return server.injectThen({method: 'get', url: '/brands?fields[code]=MS&fields[description]=Massey Furgeson'}) + .then((res) => { + res.result.data.forEach((data) => { + expect(data.id).to.match(/[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}/) + expect(data).to.deep.equal(data) + }) + }) + }) + + it('Won\'t be able to GET all from /brands with multiple fieldset where one is not available in attributes', function() { + + return server.injectThen({method: 'get', url: '/brands?fields[foo]=bar&fields[description]=Massey Furgeson'}) + .then((res) => { + expect(res.statusCode).to.equal(400) + }) + }) +}) + +buildServer = function(done) { + const Hapi = require('hapi') + const plugin = require('../') + let adapter = plugin.getAdapter('mongodb') + server = new Hapi.Server() + server.connection({port : 9100}) + server.register([ + {register: require('../'), options: {adapter: adapter({mongodbUrl: 'mongodb://localhost/test'})}}, + {register: require('inject-then')} + ], () => { + hh = server.plugins.harvester; + server.start(() => { + ['get', 'getById', 'put', 'post', 'patch', 'delete'].forEach(function(verb) { + server.route(hh.routes[verb](schema)) + }) + done() + }) + }) +} + +destroyServer = function(done) { + utils.removeFromDB(server, 'brands') + .then((res) => { + server.stop(done) + }) +} \ No newline at end of file From 38832ed3ec89fdeeebc50ebcb05bce2acd05c2bc Mon Sep 17 00:00:00 2001 From: Mehdi Avdi Date: Wed, 21 Oct 2015 00:18:09 +0100 Subject: [PATCH 24/42] paging and sorting validations added --- lib/utils/schema.js | 10 ++++- test/page.spec.js | 102 ++++++++++++++++++++++++++++++++++++++++++++ test/sort.spec.js | 101 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 212 insertions(+), 1 deletion(-) create mode 100644 test/page.spec.js create mode 100644 test/sort.spec.js diff --git a/lib/utils/schema.js b/lib/utils/schema.js index f0dd3d7..9752956 100644 --- a/lib/utils/schema.js +++ b/lib/utils/schema.js @@ -25,7 +25,15 @@ module.exports = function() { fieldMap[key] = Joi.string() }) let fields = Joi.object(fieldMap) - return {include, fields} + + let sort = Joi.string().regex(regex) + + let page = Joi.object({ + limit: Joi.number(), + offset: Joi.number() + }) + + return {include, fields, sort, page} } return { toJoiPostValidatation, toJoiGetQueryValidation } diff --git a/test/page.spec.js b/test/page.spec.js new file mode 100644 index 0000000..db17e8a --- /dev/null +++ b/test/page.spec.js @@ -0,0 +1,102 @@ +'use strict' + +const _ = require('lodash') +const Promise = require('bluebird') +const Joi = require('joi') +const Hapi = require('hapi') + +let server, buildServer, destroyServer, hh; + +const schema = { + type: 'brands', + attributes: { + code: Joi.string().min(2).max(10), + description: Joi.string() + } +}; + +const data = { + attributes: { + code: 'MF', + description: 'Massey Furgeson' + } +}; + + +//TODO just done the validation, actual includes is remaining +describe('Paging', function() { + + beforeEach(function(done) { + buildServer(() => { + let promises = []; + + _.times(10, () => { + promises.push(server.injectThen({method: 'post', url: '/brands', payload: {data}})) + }) + + return Promise.all(promises) + .then(() => { + done() + }) + }) + }) + + afterEach(function(done) { + destroyServer(done) + }) + + it('Will be able to GET all from /brands with a paging param', function() { + return server.injectThen({method: 'get', url: '/brands?include=code&page[limit]=100'}) + .then((res) => { + res.result.data.forEach((data) => { + expect(data.id).to.match(/[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}/) + expect(data).to.deep.equal(data) + }) + }) + }) + + it('Will be able to GET all from /brands with multiple paging params', function() { + return server.injectThen({method: 'get', url: '/brands?page[offset]=1000&page[limit]=100'}) + .then((res) => { + res.result.data.forEach((data) => { + expect(data.id).to.match(/[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}/) + expect(data).to.deep.equal(data) + }) + }) + }) + + it('Won\'t be able to GET all from /brands with multiple paging params where one is not available in attributes', function() { + + return server.injectThen({method: 'get', url: '/brands?page[foo]=bar&page[limit]=Massey Furgeson'}) + .then((res) => { + expect(res.statusCode).to.equal(400) + }) + }) +}) + +buildServer = function(done) { + const Hapi = require('hapi') + const plugin = require('../') + let adapter = plugin.getAdapter('mongodb') + server = new Hapi.Server() + server.connection({port : 9100}) + server.register([ + {register: require('../'), options: {adapter: adapter({mongodbUrl: 'mongodb://localhost/test'})}}, + {register: require('inject-then')} + ], () => { + hh = server.plugins.harvester; + server.start(() => { + ['get', 'getById', 'put', 'post', 'patch', 'delete'].forEach(function(verb) { + server.route(hh.routes[verb](schema)) + }) + done() + }) + }) +} + +destroyServer = function(done) { + utils.removeFromDB(server, 'brands') + .then((res) => { + server.stop(done) + }) +} \ No newline at end of file diff --git a/test/sort.spec.js b/test/sort.spec.js new file mode 100644 index 0000000..af89fd4 --- /dev/null +++ b/test/sort.spec.js @@ -0,0 +1,101 @@ +'use strict' + +const _ = require('lodash') +const Promise = require('bluebird') +const Joi = require('joi') +const Hapi = require('hapi') + +let server, buildServer, destroyServer, hh; + +const schema = { + type: 'brands', + attributes: { + code: Joi.string().min(2).max(10), + description: Joi.string() + } +}; + +const data = { + attributes: { + code: 'MF', + description: 'Massey Furgeson' + } +}; + + +//TODO just done the validation, actual sorts is remaining +describe('Sorting', function() { + + beforeEach(function(done) { + buildServer(() => { + let promises = []; + + _.times(10, () => { + promises.push(server.injectThen({method: 'post', url: '/brands', payload: {data}})) + }) + + return Promise.all(promises) + .then(() => { + done() + }) + }) + }) + + afterEach(function(done) { + destroyServer(done) + }) + + it('Will be able to GET all from /brands with a sort param', function() { + return server.injectThen({method: 'get', url: '/brands?sort=code'}) + .then((res) => { + res.result.data.forEach((data) => { + expect(data.id).to.match(/[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}/) + expect(data).to.deep.equal(data) + }) + }) + }) + + it('Will be able to GET all from /brands with multiple sort params', function() { + return server.injectThen({method: 'get', url: '/brands?sort=code,description'}) + .then((res) => { + res.result.data.forEach((data) => { + expect(data.id).to.match(/[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}/) + expect(data).to.deep.equal(data) + }) + }) + }) + + it('Won\'t be able to GET all from /brands with an sort param not available in attributes', function() { + return server.injectThen({method: 'get', url: '/brands?sort=code,foo'}) + .then((res) => { + expect(res.statusCode).to.equal(400) + }) + }) +}) + +buildServer = function(done) { + const Hapi = require('hapi') + const plugin = require('../') + let adapter = plugin.getAdapter('mongodb') + server = new Hapi.Server() + server.connection({port : 9100}) + server.register([ + {register: require('../'), options: {adapter: adapter({mongodbUrl: 'mongodb://localhost/test'})}}, + {register: require('inject-then')} + ], () => { + hh = server.plugins.harvester; + server.start(() => { + ['get', 'getById', 'put', 'post', 'patch', 'delete'].forEach(function(verb) { + server.route(hh.routes[verb](schema)) + }) + done() + }) + }) +} + +destroyServer = function(done) { + utils.removeFromDB(server, 'brands') + .then((res) => { + server.stop(done) + }) +} \ No newline at end of file From dce701c73819dac0018fafd6f5a3425ab47a52e4 Mon Sep 17 00:00:00 2001 From: Mehdi Avdi Date: Thu, 22 Oct 2015 01:47:36 +0100 Subject: [PATCH 25/42] implemented patch and delete as specification --- lib/adapters/mongodb/index.js | 2 +- lib/plugin.js | 6 ++++-- lib/routes.js | 16 ++++++++-------- test/plugin.spec.js | 25 ++++++++----------------- test/rest.spec.js | 22 ++++++++-------------- 5 files changed, 29 insertions(+), 42 deletions(-) diff --git a/lib/adapters/mongodb/index.js b/lib/adapters/mongodb/index.js index 68df9d3..2e0e174 100644 --- a/lib/adapters/mongodb/index.js +++ b/lib/adapters/mongodb/index.js @@ -74,7 +74,7 @@ module.exports = function (options) { var data = utils.getPayload(req) return model.findByIdAndUpdate(req.params.id, data) .then((updated) => { - return {data: converters.toJsonApi(updated.toObject())} + return findById(type, req) }) .catch((err) => { console.log(err) diff --git a/lib/plugin.js b/lib/plugin.js index 30efdbb..be2e3cc 100644 --- a/lib/plugin.js +++ b/lib/plugin.js @@ -29,7 +29,9 @@ exports.register = function (server, opts, next) { }) let pathVerbs = _.chain(tables) - .filter((table) => { return table.path === req.path }) + .filter((table) => { + return table.path.replace('/{id}', '') === req.path + }) .pluck('method') .map((verb) => { return verb.toUpperCase() }) .value(); @@ -94,7 +96,7 @@ exports.register = function (server, opts, next) { adapter.processSchema(schema) return _.merge(routes.delete(schema), { handler: (req, reply) => { - reply(adapter.delete(schema.type, req)) + reply(adapter.delete(schema.type, req)).code(204) } }) } diff --git a/lib/routes.js b/lib/routes.js index dfef774..e8e1323 100644 --- a/lib/routes.js +++ b/lib/routes.js @@ -4,7 +4,7 @@ const schemaUtils = require('./utils/schema')() module.exports = function () { - let get = function (schema) { + const get = function (schema) { return { method: 'GET', path: `/${schema.type}`, @@ -28,7 +28,7 @@ module.exports = function () { } } - let post = function (schema) { + const post = function (schema) { return { method: 'POST', path: `/${schema.type}`, @@ -43,7 +43,7 @@ module.exports = function () { } } - let put = function (schema) { + const put = function (schema) { return { method: 'PUT', path: `/${schema.type}`, @@ -55,10 +55,10 @@ module.exports = function () { } } - let patch = function (schema) { + const patch = function (schema) { return { method: 'PATCH', - path: `/${schema.type}`, + path: `/${schema.type}/{id}`, config: { payload: { allow : 'application/json' @@ -67,14 +67,14 @@ module.exports = function () { } } - let del = function (schema) { + const del = function (schema) { return { method: 'DELETE', - path: `/${schema.type}` + path: `/${schema.type}/{id}`, } } - let options = function (schema) { + const options = function (schema) { return { method: 'OPTIONS', path: `/${schema.type}` diff --git a/test/plugin.spec.js b/test/plugin.spec.js index d21fa0b..976dc64 100644 --- a/test/plugin.spec.js +++ b/test/plugin.spec.js @@ -61,25 +61,16 @@ describe('Plugin Basics', function() { }) it('should reject all request with content-type not set to application/json', function() { + + let headers = { + 'content-type' : 'text/html' + } + + server.route(hh.routes.post(schema)) - let promises = []; - - ['put', 'post', 'patch'].forEach(function(verb) { - server.route(hh.routes[verb](schema)) - - let headers = { - 'content-type' : 'text/html' - } - - let promise = server.injectThen({method: verb.toUpperCase(), url: '/brands', headers : headers}).then((res) => { - expect(res.statusCode).to.equal(415) - - }) - - promises.push(promise) + return server.injectThen({method: 'post', url: '/brands', headers : headers}).then((res) => { + expect(res.statusCode).to.equal(415) }) - - return Promise.all(promises) }) it('should allow all request with content-type set to application/json', function() { diff --git a/test/rest.spec.js b/test/rest.spec.js index ceeb12d..83ca9e7 100644 --- a/test/rest.spec.js +++ b/test/rest.spec.js @@ -70,16 +70,16 @@ describe('Rest operations when things go right', function() { }) }) - it('Will be able to update using PUT in /brands', function() { + it('Will be able to PATCH in /brands', function() { const payload = { attributes: { code: 'VT', description: 'Valtra' } }; - return server.injectThen({method: 'post', url: '/brands', payload: {data : payload}}) + return server.injectThen({method: 'post', url: '/brands', payload: {data}}) .then((res) => { - return server.injectThen({method: 'put', url: '/brands', payload: {data}}) + return server.injectThen({method: 'patch', url: '/brands/' + res.result.data.id, payload: {data : payload}}) }) .then((res) => { expect(res.result.data.id).to.match(/[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}/) @@ -87,20 +87,13 @@ describe('Rest operations when things go right', function() { }) }) - it('Will be able to update using PATCH in /brands', function() { - const payload = { - attributes: { - code: 'VT', - description: 'Valtra' - } - }; - return server.injectThen({method: 'post', url: '/brands', payload: {data : payload}}) + it('Will be able to DELETE in /brands', function() { + return server.injectThen({method: 'post', url: '/brands', payload: {data}}) .then((res) => { - return server.injectThen({method: 'patch', url: '/brands', payload: {data}}) + return server.injectThen({method: 'delete', url: '/brands/' + res.result.data.id}) }) .then((res) => { - expect(res.result.data.id).to.match(/[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}/) - expect(utils.getData(res)).to.deep.equal(payload) + expect(res.statusCode).to.equal(204) }) }) }) @@ -157,6 +150,7 @@ buildServer = function(done) { } destroyServer = function(done) { + return server.stop(done) utils.removeFromDB(server, 'brands') .then((res) => { server.stop(done) From d2c0a247f788c3303a2273af5d4bef71d43ab948 Mon Sep 17 00:00:00 2001 From: Mehdi Avdi Date: Thu, 22 Oct 2015 01:50:14 +0100 Subject: [PATCH 26/42] removed PUT as specification doesn't allow it --- lib/plugin.js | 11 ----------- lib/routes.js | 13 ------------- test/includes.spec.js | 2 +- test/page.spec.js | 2 +- test/plugin.spec.js | 4 ++-- test/rest.spec.js | 2 +- test/sort.spec.js | 2 +- test/sparse.fieldsets.spec.js | 2 +- 8 files changed, 7 insertions(+), 31 deletions(-) diff --git a/lib/plugin.js b/lib/plugin.js index be2e3cc..fdb8ead 100644 --- a/lib/plugin.js +++ b/lib/plugin.js @@ -71,16 +71,6 @@ exports.register = function (server, opts, next) { }) } - let put = function (schema) { - createOptionsRoute(schema) - adapter.processSchema(schema) - return _.merge(routes.put(schema), { - handler: (req, reply) => { - reply(adapter.update(schema.type, req)) - } - }) - } - let patch = function (schema) { createOptionsRoute(schema) adapter.processSchema(schema) @@ -105,7 +95,6 @@ exports.register = function (server, opts, next) { get: get, getById: getById, post: post, - put: put, patch: patch, delete: del }) diff --git a/lib/routes.js b/lib/routes.js index e8e1323..5760c2e 100644 --- a/lib/routes.js +++ b/lib/routes.js @@ -43,18 +43,6 @@ module.exports = function () { } } - const put = function (schema) { - return { - method: 'PUT', - path: `/${schema.type}`, - config: { - payload: { - allow : 'application/json' - } - } - } - } - const patch = function (schema) { return { method: 'PATCH', @@ -85,7 +73,6 @@ module.exports = function () { get: get, getById: getById, post: post, - put: put, patch: patch, delete: del, options: options diff --git a/test/includes.spec.js b/test/includes.spec.js index e694df0..09b68c4 100644 --- a/test/includes.spec.js +++ b/test/includes.spec.js @@ -85,7 +85,7 @@ buildServer = function(done) { ], () => { hh = server.plugins.harvester; server.start(() => { - ['get', 'getById', 'put', 'post', 'patch', 'delete'].forEach(function(verb) { + ['get', 'getById', 'post', 'patch', 'delete'].forEach(function(verb) { server.route(hh.routes[verb](schema)) }) done() diff --git a/test/page.spec.js b/test/page.spec.js index db17e8a..e964b5d 100644 --- a/test/page.spec.js +++ b/test/page.spec.js @@ -86,7 +86,7 @@ buildServer = function(done) { ], () => { hh = server.plugins.harvester; server.start(() => { - ['get', 'getById', 'put', 'post', 'patch', 'delete'].forEach(function(verb) { + ['get', 'getById', 'post', 'patch', 'delete'].forEach(function(verb) { server.route(hh.routes[verb](schema)) }) done() diff --git a/test/plugin.spec.js b/test/plugin.spec.js index 976dc64..b056c31 100644 --- a/test/plugin.spec.js +++ b/test/plugin.spec.js @@ -42,13 +42,13 @@ describe('Plugin Basics', function() { it('only sends the available verbs on OPTIONS call', function() { - ['get', 'put', 'post', 'patch', 'delete'].forEach(function(verb) { + ['get', 'post', 'patch', 'delete'].forEach(function(verb) { server.route(hh.routes[verb](schema)) }) return server.injectThen({method: 'OPTIONS', url: '/brands'}) .then(function(res) { - expect(res.headers.allow).to.equal('OPTIONS,GET,PUT,POST,PATCH,DELETE') + expect(res.headers.allow).to.equal('OPTIONS,GET,POST,PATCH,DELETE') }) }) diff --git a/test/rest.spec.js b/test/rest.spec.js index 83ca9e7..b35bf45 100644 --- a/test/rest.spec.js +++ b/test/rest.spec.js @@ -141,7 +141,7 @@ buildServer = function(done) { ], () => { hh = server.plugins.harvester; server.start(() => { - ['get', 'getById', 'put', 'post', 'patch', 'delete'].forEach(function(verb) { + ['get', 'getById', 'post', 'patch', 'delete'].forEach(function(verb) { server.route(hh.routes[verb](schema)) }) done() diff --git a/test/sort.spec.js b/test/sort.spec.js index af89fd4..77c9bf4 100644 --- a/test/sort.spec.js +++ b/test/sort.spec.js @@ -85,7 +85,7 @@ buildServer = function(done) { ], () => { hh = server.plugins.harvester; server.start(() => { - ['get', 'getById', 'put', 'post', 'patch', 'delete'].forEach(function(verb) { + ['get', 'getById', 'post', 'patch', 'delete'].forEach(function(verb) { server.route(hh.routes[verb](schema)) }) done() diff --git a/test/sparse.fieldsets.spec.js b/test/sparse.fieldsets.spec.js index 05eb60c..eb566d9 100644 --- a/test/sparse.fieldsets.spec.js +++ b/test/sparse.fieldsets.spec.js @@ -88,7 +88,7 @@ buildServer = function(done) { ], () => { hh = server.plugins.harvester; server.start(() => { - ['get', 'getById', 'put', 'post', 'patch', 'delete'].forEach(function(verb) { + ['get', 'getById', 'post', 'patch', 'delete'].forEach(function(verb) { server.route(hh.routes[verb](schema)) }) done() From b502c4a3dfafd60d437a965d7879324185527e08 Mon Sep 17 00:00:00 2001 From: Mehdi Avdi Date: Thu, 22 Oct 2015 01:55:57 +0100 Subject: [PATCH 27/42] using const instead of let where possible --- lib/adapters/mongodb/converters.js | 6 +++--- lib/adapters/mongodb/index.js | 18 +++++++++--------- lib/adapters/mongodb/utils.js | 2 +- lib/plugin.js | 20 ++++++++++---------- lib/routes.js | 8 ++++---- lib/utils/adapter.js | 4 ++-- lib/utils/schema.js | 21 +++++++++++---------- test/adapter.spec.js | 6 +++--- test/includes.spec.js | 2 +- test/page.spec.js | 2 +- test/plugin.spec.js | 6 +++--- test/rest.spec.js | 2 +- test/sort.spec.js | 2 +- test/sparse.fieldsets.spec.js | 2 +- 14 files changed, 51 insertions(+), 50 deletions(-) diff --git a/lib/adapters/mongodb/converters.js b/lib/adapters/mongodb/converters.js index 78629f0..3ee5478 100644 --- a/lib/adapters/mongodb/converters.js +++ b/lib/adapters/mongodb/converters.js @@ -7,7 +7,7 @@ const mongoose = require('mongoose') const uuid = require('node-uuid') module.exports = function() { - let toJsonApi = function (resources) { + const toJsonApi = function (resources) { if (_.isArray(resources)) { return _.map(resources, (resource) => { return toJsonApiSingle(resource); @@ -25,7 +25,7 @@ module.exports = function() { } } - let toMongooseModel = function (hhSchema) { + const toMongooseModel = function (hhSchema) { const mongooseSchema = {} mongooseSchema._id = { @@ -55,7 +55,7 @@ module.exports = function() { return mongoose.model(hhSchema.type, schema) } - let toMongoosePredicate = function(query) { + const toMongoosePredicate = function(query) { const mappedToModel = _.mapKeys(query.filter, function (val, key) { if (key == 'id') return '_id' else return `attributes.${key}` diff --git a/lib/adapters/mongodb/index.js b/lib/adapters/mongodb/index.js index 2e0e174..6e364e9 100644 --- a/lib/adapters/mongodb/index.js +++ b/lib/adapters/mongodb/index.js @@ -10,13 +10,13 @@ mongoose.Promise = require('bluebird') module.exports = function (options) { - let models = {} + const models = {} - let connect = function(cb) { + const connect = function(cb) { mongoose.connect(options.mongodbUrl, cb) } - let disconnect = function(cb) { + const disconnect = function(cb) { //clear out events mongoose.connection._events = {} mongoose.disconnect(cb) @@ -24,7 +24,7 @@ module.exports = function (options) { mongoose.connection.on('error', connect) - let find = function (type, req) { + const find = function (type, req) { const model = models[type] const query = req.query @@ -42,7 +42,7 @@ module.exports = function (options) { } - let findById = function(type, req) { + const findById = function(type, req) { const model = models[type] return model.findById(req.params.id).lean().exec() @@ -55,7 +55,7 @@ module.exports = function (options) { } - let create = function(type, req) { + const create = function(type, req) { const model = models[type] var data = utils.getPayload(req) return model.create(data) @@ -68,7 +68,7 @@ module.exports = function (options) { } - let update = function(type, req) { + const update = function(type, req) { const model = models[type] var data = utils.getPayload(req) @@ -82,7 +82,7 @@ module.exports = function (options) { } - let del = function(type, req) { + const del = function(type, req) { const model = models[type] var predicate = converters.toMongoosePredicate({id: req.params.id}) return model.remove(predicate) @@ -94,7 +94,7 @@ module.exports = function (options) { }) } - let processSchema = function(hhSchema) { + const processSchema = function(hhSchema) { if (!models[hhSchema.type]) { diff --git a/lib/adapters/mongodb/utils.js b/lib/adapters/mongodb/utils.js index 326513e..8d71a4b 100644 --- a/lib/adapters/mongodb/utils.js +++ b/lib/adapters/mongodb/utils.js @@ -7,7 +7,7 @@ const mongoose = require('mongoose') const uuid = require('node-uuid') module.exports = function() { - let getPayload = function (req) { + const getPayload = function (req) { return (req.payload) ? req.payload.data : {} } diff --git a/lib/plugin.js b/lib/plugin.js index fdb8ead..13e1346 100644 --- a/lib/plugin.js +++ b/lib/plugin.js @@ -7,7 +7,7 @@ const adapterUtils = require('./utils/adapter')() exports.register = function (server, opts, next) { server.expose('version', require('../package.json').version); - let adapter = opts.adapter; + const adapter = opts.adapter; adapterUtils.checkValidAdapter(adapter); @@ -16,19 +16,19 @@ exports.register = function (server, opts, next) { next() }); - let createOptionsRoute = function(schema) { - let tables = _.map(server.table()[0].table) + const createOptionsRoute = function(schema) { + const tables = _.map(server.table()[0].table) //see if the options method already exists, if so, don't duplicate it if (_.find(tables, {path : '/' + schema.type, method: 'options'})) return; server.route(_.merge(routes.options(schema), { handler: (req, reply) => { - let tables = _.map(req.server.table()[0].table, (table) => { + const tables = _.map(req.server.table()[0].table, (table) => { return _.pick(table, 'path', 'method') }) - let pathVerbs = _.chain(tables) + const pathVerbs = _.chain(tables) .filter((table) => { return table.path.replace('/{id}', '') === req.path }) @@ -41,7 +41,7 @@ exports.register = function (server, opts, next) { })) } - let get = function (schema) { + const get = function (schema) { createOptionsRoute(schema) adapter.processSchema(schema) return _.merge(routes.get(schema), { @@ -51,7 +51,7 @@ exports.register = function (server, opts, next) { }) } - let getById = function (schema) { + const getById = function (schema) { createOptionsRoute(schema) adapter.processSchema(schema) return _.merge(routes.getById(schema), { @@ -61,7 +61,7 @@ exports.register = function (server, opts, next) { }) } - let post = function (schema) { + const post = function (schema) { createOptionsRoute(schema) adapter.processSchema(schema) return _.merge(routes.post(schema), { @@ -71,7 +71,7 @@ exports.register = function (server, opts, next) { }) } - let patch = function (schema) { + const patch = function (schema) { createOptionsRoute(schema) adapter.processSchema(schema) return _.merge(routes.patch(schema), { @@ -81,7 +81,7 @@ exports.register = function (server, opts, next) { }) } - let del = function (schema) { + const del = function (schema) { createOptionsRoute(schema) adapter.processSchema(schema) return _.merge(routes.delete(schema), { diff --git a/lib/routes.js b/lib/routes.js index 5760c2e..abd3ea3 100644 --- a/lib/routes.js +++ b/lib/routes.js @@ -71,11 +71,11 @@ module.exports = function () { return { get: get, - getById: getById, - post: post, - patch: patch, + getById, + post, + patch, delete: del, - options: options + options } } \ No newline at end of file diff --git a/lib/utils/adapter.js b/lib/utils/adapter.js index a62bea8..cad268d 100644 --- a/lib/utils/adapter.js +++ b/lib/utils/adapter.js @@ -5,7 +5,7 @@ const Hoek = require('hoek') const protocolFunctions = ['connect', 'disconnect', 'find', 'findById', 'create', 'delete', 'models', 'processSchema']; module.exports = function() { - let checkValidAdapter = function(adapter) { + const checkValidAdapter = function(adapter) { Hoek.assert(adapter, new Error('No adapter passed. Please see docs.')) @@ -14,7 +14,7 @@ module.exports = function() { }) } - let getStandardAdapter = function(adapter) { + const getStandardAdapter = function(adapter) { if ( _.isString(adapter)) { try { return require('../../lib/adapters/' + adapter); diff --git a/lib/utils/schema.js b/lib/utils/schema.js index 9752956..c40c234 100644 --- a/lib/utils/schema.js +++ b/lib/utils/schema.js @@ -5,7 +5,7 @@ const Joi = require('joi') const Hoek = require('hoek') module.exports = function() { - let toJoiPostValidatation = function(schema) { + const toJoiPostValidatation = function(schema) { return Joi.object().keys({ data: Joi.object().keys({ attributes : schema.attributes @@ -13,22 +13,23 @@ module.exports = function() { }) } - let toJoiGetQueryValidation = function(schema) { - let keys = _.keys(schema.attributes); - let join = keys.join('|') + const toJoiGetQueryValidation = function(schema) { + const keys = _.keys(schema.attributes); + const join = keys.join('|') - let regex = new RegExp('^(' + join + ')(,(' + join + '))*$', 'i') - let include = Joi.string().regex(regex) + const regex = new RegExp('^(' + join + ')(,(' + join + '))*$', 'i') + const include = Joi.string().regex(regex) - let fieldMap = {} + const fieldMap = {} keys.forEach((key) => { fieldMap[key] = Joi.string() }) - let fields = Joi.object(fieldMap) - let sort = Joi.string().regex(regex) + const fields = Joi.object(fieldMap) - let page = Joi.object({ + const sort = Joi.string().regex(regex) + + const page = Joi.object({ limit: Joi.number(), offset: Joi.number() }) diff --git a/test/adapter.spec.js b/test/adapter.spec.js index 1d9f954..5d36464 100644 --- a/test/adapter.spec.js +++ b/test/adapter.spec.js @@ -30,7 +30,7 @@ describe('Adapter Validation', function() { server = new Hapi.Server() server.connection({port : 9100}) - let serverSetup = function() { + const serverSetup = function() { server.register([ {register: require('../lib/plugin'), options: {adapter : adapter}}, {register: require('inject-then')} @@ -48,8 +48,8 @@ describe('Adapter Validation', function() { server = new Hapi.Server() server.connection({port : 9100}) - let serverSetup = function() { - let adapter = require('../').getAdapter('nonexistant') + const serverSetup = function() { + const adapter = require('../').getAdapter('nonexistant') server.register([ {register: require('../lib/plugin'), options: {adapter : adapter}}, {register: require('inject-then')} diff --git a/test/includes.spec.js b/test/includes.spec.js index 09b68c4..8f1c58b 100644 --- a/test/includes.spec.js +++ b/test/includes.spec.js @@ -76,7 +76,7 @@ describe('Inclusion', function() { buildServer = function(done) { const Hapi = require('hapi') const plugin = require('../') - let adapter = plugin.getAdapter('mongodb') + const adapter = plugin.getAdapter('mongodb') server = new Hapi.Server() server.connection({port : 9100}) server.register([ diff --git a/test/page.spec.js b/test/page.spec.js index e964b5d..d019c31 100644 --- a/test/page.spec.js +++ b/test/page.spec.js @@ -77,7 +77,7 @@ describe('Paging', function() { buildServer = function(done) { const Hapi = require('hapi') const plugin = require('../') - let adapter = plugin.getAdapter('mongodb') + const adapter = plugin.getAdapter('mongodb') server = new Hapi.Server() server.connection({port : 9100}) server.register([ diff --git a/test/plugin.spec.js b/test/plugin.spec.js index b056c31..0661e30 100644 --- a/test/plugin.spec.js +++ b/test/plugin.spec.js @@ -62,7 +62,7 @@ describe('Plugin Basics', function() { it('should reject all request with content-type not set to application/json', function() { - let headers = { + const headers = { 'content-type' : 'text/html' } @@ -74,7 +74,7 @@ describe('Plugin Basics', function() { }) it('should allow all request with content-type set to application/json', function() { - let headers = { + const headers = { 'content-type' : 'application/json' } @@ -90,7 +90,7 @@ describe('Plugin Basics', function() { buildServer = function(done) { const Hapi = require('hapi') const plugin = require('../') - let adapter = plugin.getAdapter('mongodb') + const adapter = plugin.getAdapter('mongodb') server = new Hapi.Server() server.connection({port : 9100}) server.register([ diff --git a/test/rest.spec.js b/test/rest.spec.js index b35bf45..1607831 100644 --- a/test/rest.spec.js +++ b/test/rest.spec.js @@ -132,7 +132,7 @@ describe('Rest operations when things go wrong', function() { buildServer = function(done) { const Hapi = require('hapi') const plugin = require('../') - let adapter = plugin.getAdapter('mongodb') + const adapter = plugin.getAdapter('mongodb') server = new Hapi.Server() server.connection({port : 9100}) server.register([ diff --git a/test/sort.spec.js b/test/sort.spec.js index 77c9bf4..3349c2d 100644 --- a/test/sort.spec.js +++ b/test/sort.spec.js @@ -76,7 +76,7 @@ describe('Sorting', function() { buildServer = function(done) { const Hapi = require('hapi') const plugin = require('../') - let adapter = plugin.getAdapter('mongodb') + const adapter = plugin.getAdapter('mongodb') server = new Hapi.Server() server.connection({port : 9100}) server.register([ diff --git a/test/sparse.fieldsets.spec.js b/test/sparse.fieldsets.spec.js index eb566d9..6f82390 100644 --- a/test/sparse.fieldsets.spec.js +++ b/test/sparse.fieldsets.spec.js @@ -79,7 +79,7 @@ describe('Sparse Fieldsets', function() { buildServer = function(done) { const Hapi = require('hapi') const plugin = require('../') - let adapter = plugin.getAdapter('mongodb') + const adapter = plugin.getAdapter('mongodb') server = new Hapi.Server() server.connection({port : 9100}) server.register([ From aa09deb60ef44ad27c80580a709e737737fade12 Mon Sep 17 00:00:00 2001 From: Mehdi Avdi Date: Thu, 22 Oct 2015 02:46:01 +0100 Subject: [PATCH 28/42] added filter validation --- lib/adapters/mongodb/index.js | 1 + lib/utils/schema.js | 2 +- test/filter.spec.js | 106 ++++++++++++++++++++++++++++++++++ test/page.spec.js | 2 +- 4 files changed, 109 insertions(+), 2 deletions(-) create mode 100644 test/filter.spec.js diff --git a/lib/adapters/mongodb/index.js b/lib/adapters/mongodb/index.js index 6e364e9..762b6c2 100644 --- a/lib/adapters/mongodb/index.js +++ b/lib/adapters/mongodb/index.js @@ -31,6 +31,7 @@ module.exports = function (options) { const limit = query.limit || 1000 const skip = query.offset || 0 const sort = query.sort || {'_id': -1} + delete query.filter; var predicate = converters.toMongoosePredicate(query) return model.find(predicate).skip(skip).sort(sort).limit(limit).lean().exec() .then((resources)=> { diff --git a/lib/utils/schema.js b/lib/utils/schema.js index c40c234..bdc8700 100644 --- a/lib/utils/schema.js +++ b/lib/utils/schema.js @@ -34,7 +34,7 @@ module.exports = function() { offset: Joi.number() }) - return {include, fields, sort, page} + return {include, fields, sort, page, filter: fields} } return { toJoiPostValidatation, toJoiGetQueryValidation } diff --git a/test/filter.spec.js b/test/filter.spec.js new file mode 100644 index 0000000..1419989 --- /dev/null +++ b/test/filter.spec.js @@ -0,0 +1,106 @@ +'use strict' + +const _ = require('lodash') +const Promise = require('bluebird') +const Joi = require('joi') +const Hapi = require('hapi') + +let server, buildServer, destroyServer, hh; + +const schema = { + type: 'brands', + attributes: { + code: Joi.string().min(2).max(10), + year: Joi.number(), + series: Joi.number(), + description: Joi.string() + } +}; + +const data = { + attributes: { + code: 'MF', + year: 2007, + series: 5, + description: 'Massey Furgeson' + } +}; + + +//TODO just done the validation, actual includes is remaining +describe('Filtering', function() { + + beforeEach(function(done) { + buildServer(() => { + let promises = []; + + _.times(10, () => { + promises.push(server.injectThen({method: 'post', url: '/brands', payload: {data}})) + }) + + return Promise.all(promises) + .then(() => { + done() + }) + }) + }) + + afterEach(function(done) { + destroyServer(done) + }) + + it('Will be able to GET all from /brands with a filtering param', function() { + return server.injectThen({method: 'get', url: '/brands?include=code&filter[year]=gt=2006'}) + .then((res) => { + res.result.data.forEach((data) => { + expect(data.id).to.match(/[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}/) + expect(data).to.deep.equal(data) + }) + }) + }) + + it('Will be able to GET all from /brands with multiple filtering params', function() { + return server.injectThen({method: 'get', url: '/brands?filter[year]=lt=2010&filter[series]=gt=2'}) + .then((res) => { + res.result.data.forEach((data) => { + expect(data.id).to.match(/[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}/) + expect(data).to.deep.equal(data) + }) + }) + }) + + it('Won\'t be able to GET all from /brands with multiple filtering params where one is not available in attributes', function() { + + return server.injectThen({method: 'get', url: '/brands?filter[foo]=ge=2007&filter[year]=gt=2000'}) + .then((res) => { + expect(res.statusCode).to.equal(400) + }) + }) +}) + +buildServer = function(done) { + const Hapi = require('hapi') + const plugin = require('../') + const adapter = plugin.getAdapter('mongodb') + server = new Hapi.Server() + server.connection({port : 9100}) + server.register([ + {register: require('../'), options: {adapter: adapter({mongodbUrl: 'mongodb://localhost/test'})}}, + {register: require('inject-then')} + ], () => { + hh = server.plugins.harvester; + server.start(() => { + ['get', 'getById', 'post', 'patch', 'delete'].forEach(function(verb) { + server.route(hh.routes[verb](schema)) + }) + done() + }) + }) +} + +destroyServer = function(done) { + utils.removeFromDB(server, 'brands') + .then((res) => { + server.stop(done) + }) +} \ No newline at end of file diff --git a/test/page.spec.js b/test/page.spec.js index d019c31..c317022 100644 --- a/test/page.spec.js +++ b/test/page.spec.js @@ -67,7 +67,7 @@ describe('Paging', function() { it('Won\'t be able to GET all from /brands with multiple paging params where one is not available in attributes', function() { - return server.injectThen({method: 'get', url: '/brands?page[foo]=bar&page[limit]=Massey Furgeson'}) + return server.injectThen({method: 'get', url: '/brands?page[foo]=bar&page[limit]=100'}) .then((res) => { expect(res.statusCode).to.equal(400) }) From a364e6b04cc79d5483954be51ea595fbbd748d13 Mon Sep 17 00:00:00 2001 From: Mehdi Avdi Date: Thu, 22 Oct 2015 03:03:39 +0100 Subject: [PATCH 29/42] added route utils --- lib/plugin.js | 36 ++++++------------------------------ lib/utils/route.js | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 30 deletions(-) create mode 100644 lib/utils/route.js diff --git a/lib/plugin.js b/lib/plugin.js index 13e1346..c23c1db 100644 --- a/lib/plugin.js +++ b/lib/plugin.js @@ -3,6 +3,7 @@ const _ = require('lodash') const routes = require('./routes')() const adapterUtils = require('./utils/adapter')() +const routeUtils = require('./utils/route')() exports.register = function (server, opts, next) { server.expose('version', require('../package.json').version); @@ -16,33 +17,8 @@ exports.register = function (server, opts, next) { next() }); - const createOptionsRoute = function(schema) { - const tables = _.map(server.table()[0].table) - - //see if the options method already exists, if so, don't duplicate it - if (_.find(tables, {path : '/' + schema.type, method: 'options'})) return; - - server.route(_.merge(routes.options(schema), { - handler: (req, reply) => { - const tables = _.map(req.server.table()[0].table, (table) => { - return _.pick(table, 'path', 'method') - }) - - const pathVerbs = _.chain(tables) - .filter((table) => { - return table.path.replace('/{id}', '') === req.path - }) - .pluck('method') - .map((verb) => { return verb.toUpperCase() }) - .value(); - - reply().header('Allow', pathVerbs.join(',')) - } - })) - } - const get = function (schema) { - createOptionsRoute(schema) + routeUtils.createOptionsRoute(server, schema) adapter.processSchema(schema) return _.merge(routes.get(schema), { handler: (req, reply) => { @@ -52,7 +28,7 @@ exports.register = function (server, opts, next) { } const getById = function (schema) { - createOptionsRoute(schema) + routeUtils.createOptionsRoute(server, schema) adapter.processSchema(schema) return _.merge(routes.getById(schema), { handler: (req, reply) => { @@ -62,7 +38,7 @@ exports.register = function (server, opts, next) { } const post = function (schema) { - createOptionsRoute(schema) + routeUtils.createOptionsRoute(server, schema) adapter.processSchema(schema) return _.merge(routes.post(schema), { handler: (req, reply) => { @@ -72,7 +48,7 @@ exports.register = function (server, opts, next) { } const patch = function (schema) { - createOptionsRoute(schema) + routeUtils.createOptionsRoute(server, schema) adapter.processSchema(schema) return _.merge(routes.patch(schema), { handler: (req, reply) => { @@ -82,7 +58,7 @@ exports.register = function (server, opts, next) { } const del = function (schema) { - createOptionsRoute(schema) + routeUtils.createOptionsRoute(server, schema) adapter.processSchema(schema) return _.merge(routes.delete(schema), { handler: (req, reply) => { diff --git a/lib/utils/route.js b/lib/utils/route.js new file mode 100644 index 0000000..cef2284 --- /dev/null +++ b/lib/utils/route.js @@ -0,0 +1,33 @@ +'use strict' + +const _ = require('lodash') +const routes = require('../routes')() + +module.exports = function() { + const createOptionsRoute = function(server, schema) { + const tables = _.map(server.table()[0].table) + + //see if the options method already exists, if so, don't duplicate it + if (_.find(tables, {path : '/' + schema.type, method: 'options'})) return; + + server.route(_.merge(routes.options(schema), { + handler: (req, reply) => { + const tables = _.map(req.server.table()[0].table, (table) => { + return _.pick(table, 'path', 'method') + }) + + const pathVerbs = _.chain(tables) + .filter((table) => { + return table.path.replace('/{id}', '') === req.path + }) + .pluck('method') + .map((verb) => { return verb.toUpperCase() }) + .value(); + + reply().header('Allow', pathVerbs.join(',')) + } + })) + } + + return { createOptionsRoute } +} \ No newline at end of file From 2383fadb14755efd396cc7a6031b6052e1b448ce Mon Sep 17 00:00:00 2001 From: Mehdi Avdi Date: Thu, 22 Oct 2015 03:17:13 +0100 Subject: [PATCH 30/42] parsing the comparators --- lib/plugin.js | 1 + lib/utils/route.js | 16 +++++++++++++++- test/filter.spec.js | 2 +- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/lib/plugin.js b/lib/plugin.js index c23c1db..9f1077a 100644 --- a/lib/plugin.js +++ b/lib/plugin.js @@ -22,6 +22,7 @@ exports.register = function (server, opts, next) { adapter.processSchema(schema) return _.merge(routes.get(schema), { handler: (req, reply) => { + routeUtils.parseComparators(req) reply(adapter.find(schema.type, req)) } }) diff --git a/lib/utils/route.js b/lib/utils/route.js index cef2284..943af42 100644 --- a/lib/utils/route.js +++ b/lib/utils/route.js @@ -29,5 +29,19 @@ module.exports = function() { })) } - return { createOptionsRoute } + const parseComparators = function(req) { + const supportedComparators = ['lt', 'lte', 'gt', 'gte'] + + req.query.filter && _.each(req.query.filter, (filter, key) => { + const split = filter.split('=') + + if (split.length > 1 && _.contains(supportedComparators, split[0])) { + req.query.filter[key] = {[split[0]] : split[1]} + } + }) + + return req + } + + return { createOptionsRoute, parseComparators } } \ No newline at end of file diff --git a/test/filter.spec.js b/test/filter.spec.js index 1419989..32d4028 100644 --- a/test/filter.spec.js +++ b/test/filter.spec.js @@ -28,7 +28,7 @@ const data = { //TODO just done the validation, actual includes is remaining -describe('Filtering', function() { +describe.only('Filtering', function() { beforeEach(function(done) { buildServer(() => { From 6c1a2d336b87345b452fda9ec3cced40f52b6086 Mon Sep 17 00:00:00 2001 From: Mehdi Avdi Date: Thu, 22 Oct 2015 04:09:11 +0100 Subject: [PATCH 31/42] equal and gt filters implemented --- lib/adapters/mongodb/converters.js | 16 ++++++++++-- lib/adapters/mongodb/index.js | 1 - test/filter.spec.js | 41 +++++++++++++++++++++++++----- 3 files changed, 48 insertions(+), 10 deletions(-) diff --git a/lib/adapters/mongodb/converters.js b/lib/adapters/mongodb/converters.js index 3ee5478..e04a79a 100644 --- a/lib/adapters/mongodb/converters.js +++ b/lib/adapters/mongodb/converters.js @@ -57,12 +57,24 @@ module.exports = function() { const toMongoosePredicate = function(query) { const mappedToModel = _.mapKeys(query.filter, function (val, key) { - if (key == 'id') return '_id' + if (key === 'id') return '_id' else return `attributes.${key}` }) return _.mapValues(mappedToModel, function (val, key) { - if (val.indexOf(',') != -1) return {$in: val.split(',')} + const supportedComparators = ['lt', 'lte', 'gt', 'gte'] + + //if it's a normal value strig, do a $in query + if (_.isString(val) && val.indexOf(',') !== -1) { + return {$in: val.split(',')} + } + + //if it's a comparator, translate to $gt, $lt etc + const valueKey = _.keys(val)[0] + if (_.contains(supportedComparators, valueKey)) { + return {[`$${valueKey}`] : val[valueKey]} + } + else return val }) } diff --git a/lib/adapters/mongodb/index.js b/lib/adapters/mongodb/index.js index 762b6c2..6e364e9 100644 --- a/lib/adapters/mongodb/index.js +++ b/lib/adapters/mongodb/index.js @@ -31,7 +31,6 @@ module.exports = function (options) { const limit = query.limit || 1000 const skip = query.offset || 0 const sort = query.sort || {'_id': -1} - delete query.filter; var predicate = converters.toMongoosePredicate(query) return model.find(predicate).skip(skip).sort(sort).limit(limit).lean().exec() .then((resources)=> { diff --git a/test/filter.spec.js b/test/filter.spec.js index 32d4028..71b963a 100644 --- a/test/filter.spec.js +++ b/test/filter.spec.js @@ -28,14 +28,17 @@ const data = { //TODO just done the validation, actual includes is remaining -describe.only('Filtering', function() { +describe('Filtering', function() { beforeEach(function(done) { buildServer(() => { let promises = []; - _.times(10, () => { - promises.push(server.injectThen({method: 'post', url: '/brands', payload: {data}})) + _.times(10, (index) => { + let payload = Object.assign({}, data) + payload.attributes.year = 2000 + index; + payload.attributes.series = 0 + index; + promises.push(server.injectThen({method: 'post', url: '/brands', payload: {data : payload}})) }) return Promise.all(promises) @@ -49,12 +52,36 @@ describe.only('Filtering', function() { destroyServer(done) }) - it('Will be able to GET all from /brands with a filtering param', function() { - return server.injectThen({method: 'get', url: '/brands?include=code&filter[year]=gt=2006'}) + it('Will be able to GET all from /brands with a equal filtering param', function() { + return server.injectThen({method: 'get', url: '/brands?filter[year]=2007'}) .then((res) => { - res.result.data.forEach((data) => { + expect(res.result.data).to.have.length(1) + expect(res.result.data[0].attributes).to.deep.equal({ + code: 'MF', + year: 2007, + series: 7, + description: 'Massey Furgeson' + }) + }) + }) + + it('Will be able to GET all from /brands with a comparator filtering param', function() { + return server.injectThen({method: 'get', url: '/brands?filter[year]=gt=2005'}) + .then((res) => { + expect(res.result.data).to.have.length(4) + + var expectedResponses = _.times(4, (index) => { + return { + code: 'MF', + year: 2006 + index, + series: 6 + index, + description: 'Massey Furgeson' + } + }) + + res.result.data.forEach((data, index) => { expect(data.id).to.match(/[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}/) - expect(data).to.deep.equal(data) + expect(expectedResponses).to.include.something.that.deep.equals(data.attributes) }) }) }) From 2e0c2713c0a7a71d6f56804ce7178fb706cb00ad Mon Sep 17 00:00:00 2001 From: Mehdi Avdi Date: Thu, 22 Oct 2015 11:04:34 +0100 Subject: [PATCH 32/42] error handling for when the resouce is not found --- lib/adapters/mongodb/index.js | 9 ++++++++- test/rest.spec.js | 26 ++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/lib/adapters/mongodb/index.js b/lib/adapters/mongodb/index.js index 6e364e9..40d6ee1 100644 --- a/lib/adapters/mongodb/index.js +++ b/lib/adapters/mongodb/index.js @@ -1,6 +1,7 @@ 'use strict' const Hapi = require('hapi') +const Boom = require('boom') const _ = require('lodash') const mongoose = require('mongoose') const converters = require('./converters')() @@ -47,6 +48,9 @@ module.exports = function (options) { const model = models[type] return model.findById(req.params.id).lean().exec() .then((resources) => { + if (!resources) { + return Boom.notFound() + } return {data: converters.toJsonApi(resources)} }) .catch((err) => { @@ -73,7 +77,10 @@ module.exports = function (options) { const model = models[type] var data = utils.getPayload(req) return model.findByIdAndUpdate(req.params.id, data) - .then((updated) => { + .then((resource) => { + if (!resource) { + return Boom.notFound() + } return findById(type, req) }) .catch((err) => { diff --git a/test/rest.spec.js b/test/rest.spec.js index 1607831..4cd0384 100644 --- a/test/rest.spec.js +++ b/test/rest.spec.js @@ -127,6 +127,32 @@ describe('Rest operations when things go wrong', function() { expect(res.statusCode).to.equal(400) }) }) + + it('Won\'t be able to GET by id from /brands if id is wrong', function() { + return server.injectThen({method: 'post', url: '/brands', payload: {data}}) + .then((res) => { + return server.injectThen({method: 'get', url: '/brands/foo'}) + }) + .then((res) => { + expect(res.statusCode).to.equal(404) + }) + }) + + it('Will be able to PATCH in /brands with wrong id', function() { + const payload = { + attributes: { + code: 'VT', + description: 'Valtra' + } + }; + return server.injectThen({method: 'post', url: '/brands', payload: {data}}) + .then((res) => { + return server.injectThen({method: 'patch', url: '/brands/foo', payload: {data : payload}}) + }) + .then((res) => { + expect(res.statusCode).to.equal(404) + }) + }) }) buildServer = function(done) { From 9276d9048b4c39f4d0af22ec4798d6cdd4339906 Mon Sep 17 00:00:00 2001 From: Mehdi Avdi Date: Thu, 22 Oct 2015 11:15:03 +0100 Subject: [PATCH 33/42] covering all filtering params with tests --- lib/adapters/mongodb/index.js | 10 ++-- test/filter.spec.js | 100 +++++++++++++++++++++++++++++++++- 2 files changed, 104 insertions(+), 6 deletions(-) diff --git a/lib/adapters/mongodb/index.js b/lib/adapters/mongodb/index.js index 40d6ee1..f84b13b 100644 --- a/lib/adapters/mongodb/index.js +++ b/lib/adapters/mongodb/index.js @@ -38,7 +38,7 @@ module.exports = function (options) { return {data: converters.toJsonApi(resources)} }) .catch((err) => { - console.log(err) + return Boom.badImplementation() }) } @@ -54,7 +54,7 @@ module.exports = function (options) { return {data: converters.toJsonApi(resources)} }) .catch((err) => { - console.log(err) + return Boom.badImplementation() }) } @@ -67,7 +67,7 @@ module.exports = function (options) { return {data: converters.toJsonApi(created.toObject())} }) .catch((err) => { - console.log(err) + return Boom.badImplementation() }) } @@ -84,7 +84,7 @@ module.exports = function (options) { return findById(type, req) }) .catch((err) => { - console.log(err) + return Boom.badImplementation() }) } @@ -97,7 +97,7 @@ module.exports = function (options) { return {} }) .catch((err) => { - console.log(err) + return Boom.badImplementation() }) } diff --git a/test/filter.spec.js b/test/filter.spec.js index 71b963a..c634de4 100644 --- a/test/filter.spec.js +++ b/test/filter.spec.js @@ -65,7 +65,7 @@ describe('Filtering', function() { }) }) - it('Will be able to GET all from /brands with a comparator filtering param', function() { + it('Will be able to GET all from /brands with a "greater than" comparator filtering param', function() { return server.injectThen({method: 'get', url: '/brands?filter[year]=gt=2005'}) .then((res) => { expect(res.result.data).to.have.length(4) @@ -86,6 +86,104 @@ describe('Filtering', function() { }) }) + it('Will be able to GET all from /brands with a "greater than equal" comparator filtering param', function() { + return server.injectThen({method: 'get', url: '/brands?filter[year]=gte=2005'}) + .then((res) => { + expect(res.result.data).to.have.length(5) + + var expectedResponses = _.times(5, (index) => { + return { + code: 'MF', + year: 2005 + index, + series: 5 + index, + description: 'Massey Furgeson' + } + }) + + res.result.data.forEach((data, index) => { + expect(data.id).to.match(/[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}/) + expect(expectedResponses).to.include.something.that.deep.equals(data.attributes) + }) + }) + }) + + it('Will be able to GET all from /brands with a "less than" comparator filtering param', function() { + return server.injectThen({method: 'get', url: '/brands?filter[year]=lt=2005'}) + .then((res) => { + expect(res.result.data).to.have.length(5) + + var expectedResponses = _.times(5, (index) => { + return { + code: 'MF', + year: 2004 - index, + series: 4 - index, + description: 'Massey Furgeson' + } + }) + + res.result.data.forEach((data, index) => { + expect(data.id).to.match(/[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}/) + expect(expectedResponses).to.include.something.that.deep.equals(data.attributes) + }) + }) + }) + + it('Will be able to GET all from /brands with a "less than equal" comparator filtering param', function() { + return server.injectThen({method: 'get', url: '/brands?filter[year]=lte=2005'}) + .then((res) => { + expect(res.result.data).to.have.length(6) + + var expectedResponses = _.times(6, (index) => { + return { + code: 'MF', + year: 2005 - index, + series: 5 - index, + description: 'Massey Furgeson' + } + }) + + res.result.data.forEach((data, index) => { + expect(data.id).to.match(/[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}/) + expect(expectedResponses).to.include.something.that.deep.equals(data.attributes) + }) + }) + }) + + it('Will be able to GET all from /brands with a combination of comparator filtering params', function() { + return server.injectThen({method: 'get', url: '/brands?filter[year]=lte=2005&filter[series]=gte=3'}) + .then((res) => { + expect(res.result.data).to.have.length(3) + + var expectedResponses = _.times(3, (index) => { + return { + code: 'MF', + year: 2005 - index, + series: 5 - index, + description: 'Massey Furgeson' + } + }) + + res.result.data.forEach((data, index) => { + expect(data.id).to.match(/[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}/) + expect(expectedResponses).to.include.something.that.deep.equals(data.attributes) + }) + }) + }) + + it('Will be able to GET all from /brands with a combination of comparator and equal filtering params', function() { + return server.injectThen({method: 'get', url: '/brands?filter[year]=lte=2005&filter[series]=3'}) + .then((res) => { + expect(res.result.data).to.have.length(1) + + expect(res.result.data[0].attributes).to.deep.equal({ + code: 'MF', + year: 2003, + series: 3, + description: 'Massey Furgeson' + }) + }) + }) + it('Will be able to GET all from /brands with multiple filtering params', function() { return server.injectThen({method: 'get', url: '/brands?filter[year]=lt=2010&filter[series]=gt=2'}) .then((res) => { From a98d717df6fd152a4ad3ac39a06a7749eada858a Mon Sep 17 00:00:00 2001 From: Mehdi Avdi Date: Thu, 22 Oct 2015 22:36:30 +0100 Subject: [PATCH 34/42] Implemented includes --- lib/adapters/mongodb/index.js | 11 ++++++++++- test/includes.spec.js | 20 ++++++++++++-------- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/lib/adapters/mongodb/index.js b/lib/adapters/mongodb/index.js index f84b13b..4a77c09 100644 --- a/lib/adapters/mongodb/index.js +++ b/lib/adapters/mongodb/index.js @@ -32,10 +32,19 @@ module.exports = function (options) { const limit = query.limit || 1000 const skip = query.offset || 0 const sort = query.sort || {'_id': -1} + const include = query.include && query.include.split(',') var predicate = converters.toMongoosePredicate(query) return model.find(predicate).skip(skip).sort(sort).limit(limit).lean().exec() .then((resources)=> { - return {data: converters.toJsonApi(resources)} + let data = converters.toJsonApi(resources); + if (include) { + data = _.map(data, (datum) => { + datum.attributes = _.pick(datum.attributes, include) + return datum + }) + } + + return {data} }) .catch((err) => { return Boom.badImplementation() diff --git a/test/includes.spec.js b/test/includes.spec.js index 8f1c58b..f81085a 100644 --- a/test/includes.spec.js +++ b/test/includes.spec.js @@ -11,14 +11,16 @@ const schema = { type: 'brands', attributes: { code: Joi.string().min(2).max(10), - description: Joi.string() + description: Joi.string(), + year: Joi.number() } }; const data = { attributes: { code: 'MF', - description: 'Massey Furgeson' + description: 'Massey Furgeson', + year: 2007 } }; @@ -48,9 +50,10 @@ describe('Inclusion', function() { it('Will be able to GET all from /brands with a inclusion', function() { return server.injectThen({method: 'get', url: '/brands?include=code'}) .then((res) => { - res.result.data.forEach((data) => { - expect(data.id).to.match(/[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}/) - expect(data).to.deep.equal(data) + res.result.data.forEach((result) => { + let dataToCompare = _.pick(data.attributes, 'code') + expect(result.id).to.match(/[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}/) + expect(result.attributes).to.deep.equal(dataToCompare) }) }) }) @@ -58,9 +61,10 @@ describe('Inclusion', function() { it('Will be able to GET all from /brands with multiple inclusions', function() { return server.injectThen({method: 'get', url: '/brands?include=code,description'}) .then((res) => { - res.result.data.forEach((data) => { - expect(data.id).to.match(/[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}/) - expect(data).to.deep.equal(data) + res.result.data.forEach((result) => { + let dataToCompare = _.pick(data.attributes, ['code', 'description']) + expect(result.id).to.match(/[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}/) + expect(result.attributes).to.deep.equal(dataToCompare) }) }) }) From 848ed5725f8c3154e8c4b001ba16f2a97e25198f Mon Sep 17 00:00:00 2001 From: Mehdi Avdi Date: Thu, 22 Oct 2015 22:58:04 +0100 Subject: [PATCH 35/42] sort implemented --- lib/adapters/mongodb/converters.js | 9 +++++++-- lib/adapters/mongodb/index.js | 2 +- test/rest.spec.js | 1 - test/sort.spec.js | 30 +++++++++++------------------- 4 files changed, 19 insertions(+), 23 deletions(-) diff --git a/lib/adapters/mongodb/converters.js b/lib/adapters/mongodb/converters.js index e04a79a..31f7088 100644 --- a/lib/adapters/mongodb/converters.js +++ b/lib/adapters/mongodb/converters.js @@ -60,7 +60,7 @@ module.exports = function() { if (key === 'id') return '_id' else return `attributes.${key}` }) - + return _.mapValues(mappedToModel, function (val, key) { const supportedComparators = ['lt', 'lte', 'gt', 'gte'] @@ -79,6 +79,11 @@ module.exports = function() { }) } - return { toJsonApi, toMongooseModel, toMongoosePredicate } + const toMongooseSort = function(sort) { + if (!sort) return {'_id' : -1} + return {[`attributes.${sort}`] : 1} + } + + return { toJsonApi, toMongooseModel, toMongoosePredicate, toMongooseSort } } diff --git a/lib/adapters/mongodb/index.js b/lib/adapters/mongodb/index.js index 4a77c09..3e33de2 100644 --- a/lib/adapters/mongodb/index.js +++ b/lib/adapters/mongodb/index.js @@ -31,7 +31,7 @@ module.exports = function (options) { const query = req.query const limit = query.limit || 1000 const skip = query.offset || 0 - const sort = query.sort || {'_id': -1} + const sort = converters.toMongooseSort(query.sort) const include = query.include && query.include.split(',') var predicate = converters.toMongoosePredicate(query) return model.find(predicate).skip(skip).sort(sort).limit(limit).lean().exec() diff --git a/test/rest.spec.js b/test/rest.spec.js index 4cd0384..8da9890 100644 --- a/test/rest.spec.js +++ b/test/rest.spec.js @@ -176,7 +176,6 @@ buildServer = function(done) { } destroyServer = function(done) { - return server.stop(done) utils.removeFromDB(server, 'brands') .then((res) => { server.stop(done) diff --git a/test/sort.spec.js b/test/sort.spec.js index 3349c2d..41214bf 100644 --- a/test/sort.spec.js +++ b/test/sort.spec.js @@ -11,14 +11,16 @@ const schema = { type: 'brands', attributes: { code: Joi.string().min(2).max(10), - description: Joi.string() + description: Joi.string(), + year: Joi.number() } }; const data = { attributes: { code: 'MF', - description: 'Massey Furgeson' + description: 'Massey Furgeson', + year: 2000 } }; @@ -30,8 +32,10 @@ describe('Sorting', function() { buildServer(() => { let promises = []; - _.times(10, () => { - promises.push(server.injectThen({method: 'post', url: '/brands', payload: {data}})) + _.times(10, (index) => { + let payload = Object.assign({}, data) + payload.attributes.year = 2000 + index; + promises.push(server.injectThen({method: 'post', url: '/brands', payload: {data: payload}})) }) return Promise.all(promises) @@ -46,22 +50,10 @@ describe('Sorting', function() { }) it('Will be able to GET all from /brands with a sort param', function() { - return server.injectThen({method: 'get', url: '/brands?sort=code'}) + return server.injectThen({method: 'get', url: '/brands?sort=year'}) .then((res) => { - res.result.data.forEach((data) => { - expect(data.id).to.match(/[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}/) - expect(data).to.deep.equal(data) - }) - }) - }) - - it('Will be able to GET all from /brands with multiple sort params', function() { - return server.injectThen({method: 'get', url: '/brands?sort=code,description'}) - .then((res) => { - res.result.data.forEach((data) => { - expect(data.id).to.match(/[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}/) - expect(data).to.deep.equal(data) - }) + var sortedResults = _.sortBy(res.result.data, 'attributes.year') + expect(sortedResults).to.deep.equal(res.result.data) }) }) From e938f9bd198bd5079ac421b8a0dbbbeaa1bd6f5e Mon Sep 17 00:00:00 2001 From: Mehdi Avdi Date: Thu, 22 Oct 2015 23:06:11 +0100 Subject: [PATCH 36/42] paging done --- lib/adapters/mongodb/index.js | 4 ++-- test/page.spec.js | 26 +++++++++++++++----------- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/lib/adapters/mongodb/index.js b/lib/adapters/mongodb/index.js index 3e33de2..c2dbad0 100644 --- a/lib/adapters/mongodb/index.js +++ b/lib/adapters/mongodb/index.js @@ -29,8 +29,8 @@ module.exports = function (options) { const model = models[type] const query = req.query - const limit = query.limit || 1000 - const skip = query.offset || 0 + const limit = (query.page && query.page.limit) || 1000 + const skip = (query.page && query.page.offset) || 0 const sort = converters.toMongooseSort(query.sort) const include = query.include && query.include.split(',') var predicate = converters.toMongoosePredicate(query) diff --git a/test/page.spec.js b/test/page.spec.js index c317022..0594c94 100644 --- a/test/page.spec.js +++ b/test/page.spec.js @@ -11,18 +11,19 @@ const schema = { type: 'brands', attributes: { code: Joi.string().min(2).max(10), - description: Joi.string() + description: Joi.string(), + year: Joi.number() } }; const data = { attributes: { code: 'MF', - description: 'Massey Furgeson' + description: 'Massey Furgeson', + year: 2000 } }; - //TODO just done the validation, actual includes is remaining describe('Paging', function() { @@ -30,8 +31,10 @@ describe('Paging', function() { buildServer(() => { let promises = []; - _.times(10, () => { - promises.push(server.injectThen({method: 'post', url: '/brands', payload: {data}})) + _.times(50, (index) => { + let payload = Object.assign({}, data) + payload.attributes.year = 2000 + index; + promises.push(server.injectThen({method: 'post', url: '/brands', payload: {data: payload}})) }) return Promise.all(promises) @@ -46,21 +49,22 @@ describe('Paging', function() { }) it('Will be able to GET all from /brands with a paging param', function() { - return server.injectThen({method: 'get', url: '/brands?include=code&page[limit]=100'}) + return server.injectThen({method: 'get', url: '/brands?sort=year&page[limit]=10'}) .then((res) => { - res.result.data.forEach((data) => { + expect(res.result.data).to.have.length(10) + res.result.data.forEach((data, index) => { expect(data.id).to.match(/[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}/) - expect(data).to.deep.equal(data) + expect(data.attributes.year).to.equal(2000 + index) }) }) }) it('Will be able to GET all from /brands with multiple paging params', function() { - return server.injectThen({method: 'get', url: '/brands?page[offset]=1000&page[limit]=100'}) + return server.injectThen({method: 'get', url: '/brands?sort=year&page[offset]=20&page[limit]=10'}) .then((res) => { - res.result.data.forEach((data) => { + res.result.data.forEach((data, index) => { expect(data.id).to.match(/[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}/) - expect(data).to.deep.equal(data) + expect(data.attributes.year).to.equal(2020 + index) }) }) }) From 27da43fdd89f8a3de48ab7d0dfc7d6adafa7fc3c Mon Sep 17 00:00:00 2001 From: Mehdi Avdi Date: Thu, 22 Oct 2015 23:14:44 +0100 Subject: [PATCH 37/42] bit of code clean up --- test/filter.spec.js | 23 ++++++----------------- test/global.spec.js | 23 +++++++++++++++++++++++ test/includes.spec.js | 23 ++++++----------------- test/page.spec.js | 23 ++++++----------------- test/rest.spec.js | 23 ++++++----------------- test/sort.spec.js | 23 ++++++----------------- test/sparse.fieldsets.spec.js | 23 ++++++----------------- 7 files changed, 59 insertions(+), 102 deletions(-) diff --git a/test/filter.spec.js b/test/filter.spec.js index c634de4..2b16741 100644 --- a/test/filter.spec.js +++ b/test/filter.spec.js @@ -204,23 +204,12 @@ describe('Filtering', function() { }) buildServer = function(done) { - const Hapi = require('hapi') - const plugin = require('../') - const adapter = plugin.getAdapter('mongodb') - server = new Hapi.Server() - server.connection({port : 9100}) - server.register([ - {register: require('../'), options: {adapter: adapter({mongodbUrl: 'mongodb://localhost/test'})}}, - {register: require('inject-then')} - ], () => { - hh = server.plugins.harvester; - server.start(() => { - ['get', 'getById', 'post', 'patch', 'delete'].forEach(function(verb) { - server.route(hh.routes[verb](schema)) - }) - done() - }) - }) + return utils.buildServer(schema) + .then((res) => { + server = res.server; + hh = res.hh; + done() + }) } destroyServer = function(done) { diff --git a/test/global.spec.js b/test/global.spec.js index 904ef20..ae9dbe9 100644 --- a/test/global.spec.js +++ b/test/global.spec.js @@ -2,6 +2,7 @@ const chai = require('chai') const _ = require('lodash') +const Promise = require('bluebird') chai.use(require('chai-things')) @@ -19,5 +20,27 @@ global.utils = { removeFromDB: (server, collection) => { const model = server.plugins.harvester.adapter.models['brands'] return model.remove({}).lean().exec() + }, + buildServer: (schema) => { + let server, hh; + const Hapi = require('hapi') + const plugin = require('../') + const adapter = plugin.getAdapter('mongodb') + server = new Hapi.Server() + server.connection({port : 9100}) + return new Promise((resolve) => { + server.register([ + {register: require('../'), options: {adapter: adapter({mongodbUrl: 'mongodb://localhost/test'})}}, + {register: require('inject-then')} + ], () => { + hh = server.plugins.harvester; + server.start(() => { + ['get', 'getById', 'post', 'patch', 'delete'].forEach(function(verb) { + server.route(hh.routes[verb](schema)) + }) + resolve({server, hh}) + }) + }) + }) } } \ No newline at end of file diff --git a/test/includes.spec.js b/test/includes.spec.js index f81085a..55d71a7 100644 --- a/test/includes.spec.js +++ b/test/includes.spec.js @@ -78,23 +78,12 @@ describe('Inclusion', function() { }) buildServer = function(done) { - const Hapi = require('hapi') - const plugin = require('../') - const adapter = plugin.getAdapter('mongodb') - server = new Hapi.Server() - server.connection({port : 9100}) - server.register([ - {register: require('../'), options: {adapter: adapter({mongodbUrl: 'mongodb://localhost/test'})}}, - {register: require('inject-then')} - ], () => { - hh = server.plugins.harvester; - server.start(() => { - ['get', 'getById', 'post', 'patch', 'delete'].forEach(function(verb) { - server.route(hh.routes[verb](schema)) - }) - done() - }) - }) + return utils.buildServer(schema) + .then((res) => { + server = res.server; + hh = res.hh; + done() + }) } destroyServer = function(done) { diff --git a/test/page.spec.js b/test/page.spec.js index 0594c94..d47ba03 100644 --- a/test/page.spec.js +++ b/test/page.spec.js @@ -79,23 +79,12 @@ describe('Paging', function() { }) buildServer = function(done) { - const Hapi = require('hapi') - const plugin = require('../') - const adapter = plugin.getAdapter('mongodb') - server = new Hapi.Server() - server.connection({port : 9100}) - server.register([ - {register: require('../'), options: {adapter: adapter({mongodbUrl: 'mongodb://localhost/test'})}}, - {register: require('inject-then')} - ], () => { - hh = server.plugins.harvester; - server.start(() => { - ['get', 'getById', 'post', 'patch', 'delete'].forEach(function(verb) { - server.route(hh.routes[verb](schema)) - }) - done() - }) - }) + return utils.buildServer(schema) + .then((res) => { + server = res.server; + hh = res.hh; + done() + }) } destroyServer = function(done) { diff --git a/test/rest.spec.js b/test/rest.spec.js index 8da9890..7cbba8d 100644 --- a/test/rest.spec.js +++ b/test/rest.spec.js @@ -156,23 +156,12 @@ describe('Rest operations when things go wrong', function() { }) buildServer = function(done) { - const Hapi = require('hapi') - const plugin = require('../') - const adapter = plugin.getAdapter('mongodb') - server = new Hapi.Server() - server.connection({port : 9100}) - server.register([ - {register: require('../'), options: {adapter: adapter({mongodbUrl: 'mongodb://localhost/test'})}}, - {register: require('inject-then')} - ], () => { - hh = server.plugins.harvester; - server.start(() => { - ['get', 'getById', 'post', 'patch', 'delete'].forEach(function(verb) { - server.route(hh.routes[verb](schema)) - }) - done() - }) - }) + return utils.buildServer(schema) + .then((res) => { + server = res.server; + hh = res.hh; + done() + }) } destroyServer = function(done) { diff --git a/test/sort.spec.js b/test/sort.spec.js index 41214bf..0849d1c 100644 --- a/test/sort.spec.js +++ b/test/sort.spec.js @@ -66,23 +66,12 @@ describe('Sorting', function() { }) buildServer = function(done) { - const Hapi = require('hapi') - const plugin = require('../') - const adapter = plugin.getAdapter('mongodb') - server = new Hapi.Server() - server.connection({port : 9100}) - server.register([ - {register: require('../'), options: {adapter: adapter({mongodbUrl: 'mongodb://localhost/test'})}}, - {register: require('inject-then')} - ], () => { - hh = server.plugins.harvester; - server.start(() => { - ['get', 'getById', 'post', 'patch', 'delete'].forEach(function(verb) { - server.route(hh.routes[verb](schema)) - }) - done() - }) - }) + return utils.buildServer(schema) + .then((res) => { + server = res.server; + hh = res.hh; + done() + }) } destroyServer = function(done) { diff --git a/test/sparse.fieldsets.spec.js b/test/sparse.fieldsets.spec.js index 6f82390..4127ad0 100644 --- a/test/sparse.fieldsets.spec.js +++ b/test/sparse.fieldsets.spec.js @@ -77,23 +77,12 @@ describe('Sparse Fieldsets', function() { }) buildServer = function(done) { - const Hapi = require('hapi') - const plugin = require('../') - const adapter = plugin.getAdapter('mongodb') - server = new Hapi.Server() - server.connection({port : 9100}) - server.register([ - {register: require('../'), options: {adapter: adapter({mongodbUrl: 'mongodb://localhost/test'})}}, - {register: require('inject-then')} - ], () => { - hh = server.plugins.harvester; - server.start(() => { - ['get', 'getById', 'post', 'patch', 'delete'].forEach(function(verb) { - server.route(hh.routes[verb](schema)) - }) - done() - }) - }) + return utils.buildServer(schema) + .then((res) => { + server = res.server; + hh = res.hh; + done() + }) } destroyServer = function(done) { From b8dc6c2868feb70fd7b98d53008e36d1e60b9e2d Mon Sep 17 00:00:00 2001 From: Mehdi Avdi Date: Sun, 25 Oct 2015 13:04:39 +0900 Subject: [PATCH 38/42] Implemented descending sort --- lib/adapters/mongodb/converters.js | 4 ++++ lib/utils/schema.js | 3 ++- test/sort.spec.js | 11 +++++++++-- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/lib/adapters/mongodb/converters.js b/lib/adapters/mongodb/converters.js index 31f7088..5886a85 100644 --- a/lib/adapters/mongodb/converters.js +++ b/lib/adapters/mongodb/converters.js @@ -81,6 +81,10 @@ module.exports = function() { const toMongooseSort = function(sort) { if (!sort) return {'_id' : -1} + if(sort.indexOf('-') === 0) { + return {[`attributes.${sort.substr(1)}`] : -1} + } + return {[`attributes.${sort}`] : 1} } diff --git a/lib/utils/schema.js b/lib/utils/schema.js index bdc8700..5921b92 100644 --- a/lib/utils/schema.js +++ b/lib/utils/schema.js @@ -27,7 +27,8 @@ module.exports = function() { const fields = Joi.object(fieldMap) - const sort = Joi.string().regex(regex) + const sortRegex = new RegExp('^-?(' + join + ')(,-?(' + join + '))*$', 'i') + const sort = Joi.string().regex(sortRegex) const page = Joi.object({ limit: Joi.number(), diff --git a/test/sort.spec.js b/test/sort.spec.js index 0849d1c..8d1403c 100644 --- a/test/sort.spec.js +++ b/test/sort.spec.js @@ -24,8 +24,6 @@ const data = { } }; - -//TODO just done the validation, actual sorts is remaining describe('Sorting', function() { beforeEach(function(done) { @@ -57,6 +55,15 @@ describe('Sorting', function() { }) }) + it('Will be able to GET all from /brands with a sort param and descending', function() { + return server.injectThen({method: 'get', url: '/brands?sort=-year'}) + .then((res) => { + + var sortedResults = _.sortBy(res.result.data, 'attributes.year').reverse() + expect(sortedResults).to.deep.equal(res.result.data) + }) + }) + it('Won\'t be able to GET all from /brands with an sort param not available in attributes', function() { return server.injectThen({method: 'get', url: '/brands?sort=code,foo'}) .then((res) => { From b0c352f3d988f096b21c3fa79a73595f4e29a713 Mon Sep 17 00:00:00 2001 From: Mehdi Avdi Date: Sun, 25 Oct 2015 14:06:14 +0900 Subject: [PATCH 39/42] skipping inclusions, implemented parse fields properly --- lib/adapters/mongodb/index.js | 7 +++---- lib/utils/schema.js | 9 +++++---- test/includes.spec.js | 2 +- test/sparse.fieldsets.spec.js | 14 +++++++++----- 4 files changed, 18 insertions(+), 14 deletions(-) diff --git a/lib/adapters/mongodb/index.js b/lib/adapters/mongodb/index.js index c2dbad0..8c0144f 100644 --- a/lib/adapters/mongodb/index.js +++ b/lib/adapters/mongodb/index.js @@ -26,20 +26,19 @@ module.exports = function (options) { mongoose.connection.on('error', connect) const find = function (type, req) { - const model = models[type] const query = req.query const limit = (query.page && query.page.limit) || 1000 const skip = (query.page && query.page.offset) || 0 const sort = converters.toMongooseSort(query.sort) - const include = query.include && query.include.split(',') + const sparse = query.fields && query.fields[type].split(',') var predicate = converters.toMongoosePredicate(query) return model.find(predicate).skip(skip).sort(sort).limit(limit).lean().exec() .then((resources)=> { let data = converters.toJsonApi(resources); - if (include) { + if (sparse) { data = _.map(data, (datum) => { - datum.attributes = _.pick(datum.attributes, include) + datum.attributes = _.pick(datum.attributes, sparse) return datum }) } diff --git a/lib/utils/schema.js b/lib/utils/schema.js index 5921b92..e69edfb 100644 --- a/lib/utils/schema.js +++ b/lib/utils/schema.js @@ -20,12 +20,13 @@ module.exports = function() { const regex = new RegExp('^(' + join + ')(,(' + join + '))*$', 'i') const include = Joi.string().regex(regex) - const fieldMap = {} + let filterMap = {} keys.forEach((key) => { - fieldMap[key] = Joi.string() + filterMap[key] = Joi.string() }) - const fields = Joi.object(fieldMap) + const filter = Joi.object(filterMap) + const fields = Joi.object({[schema.type] : Joi.string()}) const sortRegex = new RegExp('^-?(' + join + ')(,-?(' + join + '))*$', 'i') const sort = Joi.string().regex(sortRegex) @@ -35,7 +36,7 @@ module.exports = function() { offset: Joi.number() }) - return {include, fields, sort, page, filter: fields} + return {include, fields, sort, page, filter} } return { toJoiPostValidatation, toJoiGetQueryValidation } diff --git a/test/includes.spec.js b/test/includes.spec.js index 55d71a7..1bba6fd 100644 --- a/test/includes.spec.js +++ b/test/includes.spec.js @@ -26,7 +26,7 @@ const data = { //TODO just done the validation, actual includes is remaining -describe('Inclusion', function() { +describe.skip('Inclusion', function() { beforeEach(function(done) { buildServer(() => { diff --git a/test/sparse.fieldsets.spec.js b/test/sparse.fieldsets.spec.js index 4127ad0..ac51889 100644 --- a/test/sparse.fieldsets.spec.js +++ b/test/sparse.fieldsets.spec.js @@ -11,14 +11,16 @@ const schema = { type: 'brands', attributes: { code: Joi.string().min(2).max(10), - description: Joi.string() + description: Joi.string(), + year: Joi.number() } }; const data = { attributes: { code: 'MF', - description: 'Massey Furgeson' + description: 'Massey Furgeson', + year: 2000 } }; @@ -47,18 +49,20 @@ describe('Sparse Fieldsets', function() { it('Will be able to GET all from /brands with a sparse fieldset', function() { - return server.injectThen({method: 'get', url: '/brands?include=code&fields[description]=Massey Furgeson'}) + return server.injectThen({method: 'get', url: '/brands?fields[brands]=description'}) .then((res) => { res.result.data.forEach((data) => { expect(data.id).to.match(/[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}/) - expect(data).to.deep.equal(data) + expect(data.attributes.description).to.exist; + expect(data.attributes.code).to.not.exist; + expect(data.attributes.year).to.not.exist; }) }) }) it('Will be able to GET all from /brands with multiple fieldset', function() { - return server.injectThen({method: 'get', url: '/brands?fields[code]=MS&fields[description]=Massey Furgeson'}) + return server.injectThen({method: 'get', url: '/brands?fields[brands]=code,description'}) .then((res) => { res.result.data.forEach((data) => { expect(data.id).to.match(/[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}/) From 380c470839e7e2ae5db33be981bb00d48ece70ff Mon Sep 17 00:00:00 2001 From: Mehdi Avdi Date: Mon, 26 Oct 2015 05:21:14 +0900 Subject: [PATCH 40/42] added type, id and other validation --- lib/adapters/mongodb/converters.js | 1 + lib/utils/schema.js | 8 ++++++- test/filter.spec.js | 1 + test/includes.spec.js | 1 + test/page.spec.js | 1 + test/plugin.spec.js | 1 + test/rest.spec.js | 34 ++++++++++++++++++++++++++++++ test/sort.spec.js | 1 + test/sparse.fieldsets.spec.js | 1 + 9 files changed, 48 insertions(+), 1 deletion(-) diff --git a/lib/adapters/mongodb/converters.js b/lib/adapters/mongodb/converters.js index 5886a85..535eda2 100644 --- a/lib/adapters/mongodb/converters.js +++ b/lib/adapters/mongodb/converters.js @@ -45,6 +45,7 @@ module.exports = function() { 'any': Object } + mongooseSchema.type = 'string' mongooseSchema.attributes = _.mapValues(hhSchema.attributes, function (val) { Hoek.assert(val.isJoi, 'attribute values in the hh schema should be defined with Joi') diff --git a/lib/utils/schema.js b/lib/utils/schema.js index e69edfb..022ba68 100644 --- a/lib/utils/schema.js +++ b/lib/utils/schema.js @@ -8,7 +8,13 @@ module.exports = function() { const toJoiPostValidatation = function(schema) { return Joi.object().keys({ data: Joi.object().keys({ - attributes : schema.attributes + id: Joi.string().regex(/[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89aAbB][a-f0-9]{3}-[a-f0-9]{12}/), + type: Joi.string().regex(new RegExp(schema.type)).required(), + attributes: schema.attributes, + //TODO needs more granular validation once these are implemented + relationships: Joi.object(), + links: Joi.object(), + meta: Joi.object(), }) }) } diff --git a/test/filter.spec.js b/test/filter.spec.js index 2b16741..eb768df 100644 --- a/test/filter.spec.js +++ b/test/filter.spec.js @@ -18,6 +18,7 @@ const schema = { }; const data = { + type: 'brands', attributes: { code: 'MF', year: 2007, diff --git a/test/includes.spec.js b/test/includes.spec.js index 1bba6fd..a8e3bd4 100644 --- a/test/includes.spec.js +++ b/test/includes.spec.js @@ -17,6 +17,7 @@ const schema = { }; const data = { + type: 'brands', attributes: { code: 'MF', description: 'Massey Furgeson', diff --git a/test/page.spec.js b/test/page.spec.js index d47ba03..a6127a8 100644 --- a/test/page.spec.js +++ b/test/page.spec.js @@ -17,6 +17,7 @@ const schema = { }; const data = { + type: 'brands', attributes: { code: 'MF', description: 'Massey Furgeson', diff --git a/test/plugin.spec.js b/test/plugin.spec.js index 0661e30..fabb8d4 100644 --- a/test/plugin.spec.js +++ b/test/plugin.spec.js @@ -14,6 +14,7 @@ const schema = { }; const data = { + type: 'brands', attributes: { code: 'MF', description: 'Massey Furgeson' diff --git a/test/rest.spec.js b/test/rest.spec.js index 7cbba8d..ac0f9b4 100644 --- a/test/rest.spec.js +++ b/test/rest.spec.js @@ -4,6 +4,7 @@ const _ = require('lodash') const Promise = require('bluebird') const Joi = require('joi') const Hapi = require('hapi') +const uuid = require('node-uuid') let server, buildServer, destroyServer, hh; @@ -16,6 +17,7 @@ const schema = { }; const data = { + type: 'brands', attributes: { code: 'MF', description: 'Massey Furgeson' @@ -63,6 +65,8 @@ describe('Rest operations when things go right', function() { }) it('Will be able to POST to /brands', function() { + let payload = _.cloneDeep(data) + payload.id = uuid.v4() return server.injectThen({method: 'post', url: '/brands', payload: {data}}).then((res) => { expect(res.result.data.id).to.match(/[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}/) @@ -70,8 +74,16 @@ describe('Rest operations when things go right', function() { }) }) + it('Will be able to POST to /brands with uuid', function() { + return server.injectThen({method: 'post', url: '/brands', payload: {data}}).then((res) => { + expect(res.result.data.id).to.match(/[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}/) + expect(utils.getData(res)).to.deep.equal(data) + }) + }) + it('Will be able to PATCH in /brands', function() { const payload = { + type: 'brands', attributes: { code: 'VT', description: 'Valtra' @@ -118,6 +130,28 @@ describe('Rest operations when things go wrong', function() { }) }) + it('Won\'t be able to POST to /brands with a payload that doesn\'t have a type property', function() { + + let payload = _.cloneDeep(data); + delete payload.type + + return server.injectThen({method: 'post', url: '/brands', payload: {data: payload}}).then((res) => { + expect(res.statusCode).to.equal(400) + }) + }) + + + it('Won\'t be able to POST to /brands with an invalid uuid', function() { + + let payload = _.cloneDeep(data); + // has to match this /[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89aAbB][a-f0-9]{3}-[a-f0-9]{12} + payload.id = '54ce70cd-9d0e-98e8-89c2-1423affcb0ca' + + return server.injectThen({method: 'post', url: '/brands', payload: {data: payload}}).then((res) => { + expect(res.statusCode).to.equal(400) + }) + }) + it('Won\'t be able to POST to /brands with a payload that has attributes that don\'t match the schema', function() { let payload = _.cloneDeep(data); diff --git a/test/sort.spec.js b/test/sort.spec.js index 8d1403c..8866c56 100644 --- a/test/sort.spec.js +++ b/test/sort.spec.js @@ -17,6 +17,7 @@ const schema = { }; const data = { + type: 'brands', attributes: { code: 'MF', description: 'Massey Furgeson', diff --git a/test/sparse.fieldsets.spec.js b/test/sparse.fieldsets.spec.js index ac51889..bce0427 100644 --- a/test/sparse.fieldsets.spec.js +++ b/test/sparse.fieldsets.spec.js @@ -17,6 +17,7 @@ const schema = { }; const data = { + type: 'brands', attributes: { code: 'MF', description: 'Massey Furgeson', From d83ec35ddaa3ac6aa9afa766c6c833cb38a7dcc3 Mon Sep 17 00:00:00 2001 From: Mehdi Avdi Date: Mon, 26 Oct 2015 05:34:40 +0900 Subject: [PATCH 41/42] now allowing application/vnd.api+json --- lib/routes.js | 2 +- test/plugin.spec.js | 34 ---------------------------------- test/rest.spec.js | 42 +++++++++++++++++++++++++++++++++++++++++- 3 files changed, 42 insertions(+), 36 deletions(-) diff --git a/lib/routes.js b/lib/routes.js index abd3ea3..3ea85f2 100644 --- a/lib/routes.js +++ b/lib/routes.js @@ -34,7 +34,7 @@ module.exports = function () { path: `/${schema.type}`, config: { payload: { - allow: 'application/json' + allow: ['application/json', 'application/vnd.api+json'] }, validate: { payload: schemaUtils.toJoiPostValidatation(schema) diff --git a/test/plugin.spec.js b/test/plugin.spec.js index fabb8d4..f6f5670 100644 --- a/test/plugin.spec.js +++ b/test/plugin.spec.js @@ -52,40 +52,6 @@ describe('Plugin Basics', function() { expect(res.headers.allow).to.equal('OPTIONS,GET,POST,PATCH,DELETE') }) }) - - it('should set the content-type header to application/json by default', function() { - server.route(hh.routes.get(schema)) - return server.injectThen({method: 'GET', url: '/brands'}) - .then((res) => { - expect(res.headers['content-type']).to.equal('application/json; charset=utf-8') - }) - }) - - it('should reject all request with content-type not set to application/json', function() { - - const headers = { - 'content-type' : 'text/html' - } - - server.route(hh.routes.post(schema)) - - return server.injectThen({method: 'post', url: '/brands', headers : headers}).then((res) => { - expect(res.statusCode).to.equal(415) - }) - }) - - it('should allow all request with content-type set to application/json', function() { - const headers = { - 'content-type' : 'application/json' - } - - server.route(hh.routes.post(schema)) - - server.injectThen({method: 'post', url: '/brands', headers: headers, payload: {data}}) - .then((res) => { - expect(res.statusCode).to.equal(201) - }) - }) }) buildServer = function(done) { diff --git a/test/rest.spec.js b/test/rest.spec.js index ac0f9b4..633e1dd 100644 --- a/test/rest.spec.js +++ b/test/rest.spec.js @@ -34,6 +34,36 @@ describe('Rest operations when things go right', function() { destroyServer(done) }) + it('should set the content-type header to application/json by default', function() { + return server.injectThen({method: 'GET', url: '/brands'}) + .then((res) => { + expect(res.headers['content-type']).to.equal('application/json; charset=utf-8') + }) + }) + + it('should allow all request with content-type set to application/json', function() { + const headers = { + 'content-type' : 'application/json' + } + + server.injectThen({method: 'post', url: '/brands', headers: headers, payload: {data}}) + .then((res) => { + expect(res.statusCode).to.equal(201) + }) + }) + + it('should allow all request with content-type set to application/vnd.api+json', function() { + const headers = { + 'content-type' : 'application/vnd.api+json' + } + + server.injectThen({method: 'post', url: '/brands', headers: headers, payload: {data}}) + + .then((res) => { + expect(res.statusCode).to.equal(201) + }) + }) + it('Will be able to GET by id from /brands', function() { return server.injectThen({method: 'post', url: '/brands', payload: {data}}) .then((res) => { @@ -120,6 +150,17 @@ describe('Rest operations when things go wrong', function() { destroyServer(done) }) + it('should reject all request with content-type not set to application/json or application/vnd.api+json', function() { + + const headers = { + 'content-type' : 'text/html' + } + + return server.injectThen({method: 'post', url: '/brands', headers : headers}).then((res) => { + expect(res.statusCode).to.equal(415) + }) + }) + it('Won\'t be able to POST to /brands with a payload that doesn\'t match the schema', function() { let payload = _.cloneDeep(data); @@ -140,7 +181,6 @@ describe('Rest operations when things go wrong', function() { }) }) - it('Won\'t be able to POST to /brands with an invalid uuid', function() { let payload = _.cloneDeep(data); From 359edace5eb6a15a128c11622146e0a7d1600bec Mon Sep 17 00:00:00 2001 From: Mehdi Avdi Date: Mon, 26 Oct 2015 05:39:15 +0900 Subject: [PATCH 42/42] no need to badImplementation handling as Hapi already does it --- lib/adapters/mongodb/index.js | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/lib/adapters/mongodb/index.js b/lib/adapters/mongodb/index.js index 8c0144f..b2c74ba 100644 --- a/lib/adapters/mongodb/index.js +++ b/lib/adapters/mongodb/index.js @@ -45,10 +45,6 @@ module.exports = function (options) { return {data} }) - .catch((err) => { - return Boom.badImplementation() - }) - } const findById = function(type, req) { @@ -61,10 +57,6 @@ module.exports = function (options) { } return {data: converters.toJsonApi(resources)} }) - .catch((err) => { - return Boom.badImplementation() - }) - } const create = function(type, req) { @@ -74,10 +66,6 @@ module.exports = function (options) { .then((created) => { return {data: converters.toJsonApi(created.toObject())} }) - .catch((err) => { - return Boom.badImplementation() - }) - } const update = function(type, req) { @@ -91,10 +79,6 @@ module.exports = function (options) { } return findById(type, req) }) - .catch((err) => { - return Boom.badImplementation() - }) - } const del = function(type, req) { @@ -104,9 +88,6 @@ module.exports = function (options) { .then(() => { return {} }) - .catch((err) => { - return Boom.badImplementation() - }) } const processSchema = function(hhSchema) {