From e0132c2cae773ce33ff5abadfc698a0a138aa411 Mon Sep 17 00:00:00 2001 From: Joey Baker Date: Sat, 3 Sep 2016 18:48:25 -0700 Subject: [PATCH 01/14] Fix: handle all `parseInclude` errors --- src/crud.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/crud.js b/src/crud.js index 2476dc1..bf06dc3 100644 --- a/src/crud.js +++ b/src/crud.js @@ -102,6 +102,8 @@ export const get = ({ server, model, prefix = '/', config }) => { const { id } = request.params; if (id) where[model.primaryKeyField] = id; + if (include instanceof Error) return void reply(include); + const instance = await model.findOne({ where, include }); if (!instance) return void reply(notFound(`${id} not found.`)); @@ -130,6 +132,8 @@ export const scope = ({ server, model, prefix = '/', config }) => { const include = parseInclude(request); const where = parseWhere(request); + if (include instanceof Error) return void reply(include); + const list = await model.scope(request.params.scope).findAll({ include, where }); reply(list); @@ -213,6 +217,8 @@ export const destroyScope = ({ server, model, prefix = '/', config }) => { const include = parseInclude(request); const where = parseWhere(request); + if (include instanceof Error) return void reply(include); + const list = await model.scope(request.params.scope).findAll({ include, where }); await Promise.all(list.map(instance => instance.destroy())); -- 2.34.1 From 85cd2823daf1de5ead7f45c680371045e7de7ad2 Mon Sep 17 00:00:00 2001 From: Joey Baker Date: Sat, 3 Sep 2016 18:50:09 -0700 Subject: [PATCH 02/14] Docs: #cleanup and style fixes --- README.md | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 2eba0be..fa30b76 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,9 @@ npm install -S hapi-sequelize-crud ##Configure +Please note that you should register `hapi-sequelize-crud` after defining your +associations. + ```javascript // First, register hapi-sequelize await register({ @@ -35,6 +38,7 @@ await register({ // `models` property. If you omit this property, all models will have // models defined for them. e.g. models: ['cat', 'dog'] // only the cat and dog models will have routes created + // or models: [ // possible methods: list, get, scope, create, destroy, destroyAll, destroyScope, update @@ -54,18 +58,17 @@ await register({ ``` ### Methods -* list: get all rows in a table -* get: get a single row -* scope: reference a [sequelize scope](http://docs.sequelizejs.com/en/latest/api/model/#scopeoptions-model) -* create: create a new row -* destroy: delete a row -* destroyAll: delete all models in the table -* destroyScope: use a [sequelize scope](http://docs.sequelizejs.com/en/latest/api/model/#scopeoptions-model) to find rows, then delete them -* update: update a row +* **list**: get all rows in a table +* **get**: get a single row +* **scope**: reference a [sequelize scope](http://docs.sequelizejs.com/en/latest/api/model/#scopeoptions-model) +* **create**: create a new row +* **destroy**: delete a row +* **destroyAll**: delete all models in the table +* **destroyScope**: use a [sequelize scope](http://docs.sequelizejs.com/en/latest/api/model/#scopeoptions-model) to find rows, then delete them +* **update**: update a row + -Please note that you should register `hapi-sequelize-crud` after defining your -associations. ##What do I get -- 2.34.1 From b4ea8c5b8ea33e38e509db0eb44a621f5d133dac Mon Sep 17 00:00:00 2001 From: Joey Baker Date: Sat, 3 Sep 2016 18:50:26 -0700 Subject: [PATCH 03/14] Docs: add more details for `include` and `where` --- README.md | 45 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index fa30b76..c24879a 100644 --- a/README.md +++ b/README.md @@ -67,10 +67,50 @@ await register({ * **destroyScope**: use a [sequelize scope](http://docs.sequelizejs.com/en/latest/api/model/#scopeoptions-model) to find rows, then delete them * **update**: update a row +## `where` queries +It's easy to restrict your requests using Sequelize's `where` query option. Just pass a query parameter. +```js +// returns only teams that have a `city` property of "windsor" +// GET /team?city=windsor +// results in the Sequelize query: +Team.findOne({ where: { city: 'windsor' }}) +``` -##What do I get +You can also do more complex queries by setting the value of a key to JSON. + +```js +// returns only teams that have a `address.city` property of "windsor" +// GET /team?city={"address": "windsor"} +// or +// GET /team?city[address]=windsor + +// results in the Sequelize query: +Team.findOne({ where: { address: { city: 'windsor' }}}) +``` + +## `include` queries +Getting related models is easy, just use a query parameter `include`. + +```js +// returns all teams with their related City model +// GET /teams?include=City + +// results in a Sequelize query: +Team.findAll({include: City}) +``` + +If you want to get multiple related models, just pass multiple `include` parameters. +```js +// returns all teams with their related City and Uniform models +// GET /teams?include=City&include=Uniform + +// results in a Sequelize query: +Team.findAll({include: [City, Uniform]}) +``` + +## Full list of methods Let's say you have a `many-to-many` association like this: @@ -85,8 +125,9 @@ You get these: # get an array of records GET /team/{id}/roles GET /role/{id}/teams -# might also append query parameters to search for +# might also append `where` query parameters to search for GET /role/{id}/teams?members=5 +GET /role/{id}/teams?city=healdsburg # you might also use scopes GET /teams/{scope}/roles/{scope} -- 2.34.1 From b35bd23c912fef1e87f29b5be4d5bfee6b336d36 Mon Sep 17 00:00:00 2001 From: Joey Baker Date: Sun, 4 Sep 2016 15:33:58 -0700 Subject: [PATCH 04/14] Fix: prefer user's config before our own --- src/crud.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/crud.js b/src/crud.js index bf06dc3..66d0867 100644 --- a/src/crud.js +++ b/src/crud.js @@ -110,13 +110,13 @@ export const get = ({ server, model, prefix = '/', config }) => { reply(instance); }, - config: _.defaultsDeep({ + config: _.defaultsDeep(config, { validate: { params: joi.object().keys({ id: joi.any(), }), }, - }, config), + }), }); }; @@ -138,13 +138,13 @@ export const scope = ({ server, model, prefix = '/', config }) => { reply(list); }, - config: _.defaultsDeep({ + config: _.defaultsDeep(config, { validate: { params: joi.object().keys({ scope: joi.string().valid(...scopes), }), }, - }, config), + }), }); }; @@ -225,13 +225,13 @@ export const destroyScope = ({ server, model, prefix = '/', config }) => { reply(list); }, - config: _.defaultsDeep({ + config: _.defaultsDeep(config, { validate: { params: joi.object().keys({ scope: joi.string().valid(...scopes), }), }, - }, config), + }), }); }; @@ -256,11 +256,11 @@ export const update = ({ server, model, prefix = '/', config }) => { reply(instance); }, - config: _.defaultsDeep({ + config: _.defaultsDeep(config, { validate: { payload: joi.object().required(), }, - }, config), + }), }); }; -- 2.34.1 From 32a539c3d987272c3f2a2e334524269e646d21ac Mon Sep 17 00:00:00 2001 From: Joey Baker Date: Sun, 4 Sep 2016 15:35:19 -0700 Subject: [PATCH 05/14] =?UTF-8?q?Fix=20(crud)=20`update`:=20`findOne`=20?= =?UTF-8?q?=E2=86=92=20`findById`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit b/c `findById` uses an index to lookup, and should be fast. --- src/crud.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/crud.js b/src/crud.js index 66d0867..98fae67 100644 --- a/src/crud.js +++ b/src/crud.js @@ -243,11 +243,7 @@ export const update = ({ server, model, prefix = '/', config }) => { @error async handler(request, reply) { const { id } = request.params; - const instance = await model.findOne({ - where: { - id, - }, - }); + const instance = await model.findById(id); if (!instance) return void reply(notFound(`${id} not found.`)); -- 2.34.1 From 833df491731662dcb865f8ba4fc607f0000c7f6c Mon Sep 17 00:00:00 2001 From: Joey Baker Date: Sun, 4 Sep 2016 16:36:50 -0700 Subject: [PATCH 06/14] Chore add comments for config creation --- src/crud.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/crud.js b/src/crud.js index 98fae67..a092995 100644 --- a/src/crud.js +++ b/src/crud.js @@ -35,12 +35,17 @@ models: { export default (server, model, { prefix, defaultConfig: config, models: permissions }) => { const modelName = model._singular; + // if we don't have any permissions set, just create all the methods if (!permissions) { createAll({ server, model, prefix, config }); + // if permissions are set, but we can't parse them, throw an error } else if (!Array.isArray(permissions)) { throw new Error('hapi-sequelize-crud: `models` property must be an array'); + // if permissions are set, but the only thing we've got is a model name, there + // are no permissions to be set, so just create all methods and move on } else if (permissions.includes(modelName)) { createAll({ server, model, prefix, config }); + // if we've gotten here, we have complex permissions and need to set them } else { const permissionOptions = permissions.filter((permission) => { return permission.model === modelName; -- 2.34.1 From f33c8da55d9c5787172fb3cc8c6b35788dbbde1c Mon Sep 17 00:00:00 2001 From: Joey Baker Date: Sun, 4 Sep 2016 16:37:43 -0700 Subject: [PATCH 07/14] Fix (CRUD update) validate `id` --- src/crud.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/crud.js b/src/crud.js index a092995..059deca 100644 --- a/src/crud.js +++ b/src/crud.js @@ -260,6 +260,9 @@ export const update = ({ server, model, prefix = '/', config }) => { config: _.defaultsDeep(config, { validate: { payload: joi.object().required(), + params: joi.object().keys({ + id: joi.any(), + }), }, }), }); -- 2.34.1 From 69221ea331c3705a7b99d67d79dc8a140ef1609a Mon Sep 17 00:00:00 2001 From: Joey Baker Date: Sun, 4 Sep 2016 16:39:05 -0700 Subject: [PATCH 08/14] Feat query & payload now validated --- src/crud.js | 152 +++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 146 insertions(+), 6 deletions(-) diff --git a/src/crud.js b/src/crud.js index 059deca..5050dd0 100644 --- a/src/crud.js +++ b/src/crud.js @@ -6,9 +6,112 @@ import { parseInclude, parseWhere } from './utils'; import { notFound } from 'boom'; import * as associations from './associations/index'; -const createAll = ({ server, model, prefix, config }) => { +const sequelizeOperators = { + $and: joi.any(), + $or: joi.any(), + $gt: joi.any(), + $gte: joi.any(), + $lt: joi.any(), + $lte: joi.any(), + $ne: joi.any(), + $eq: joi.any(), + $not: joi.any(), + $between: joi.any(), + $notBetween: joi.any(), + $in: joi.any(), + $notIn: joi.any(), + $like: joi.any(), + $notLike: joi.any(), + $iLike: joi.any(), + $notILike: joi.any(), + $overlap: joi.any(), + $contains: joi.any(), + $contained: joi.any(), + $any: joi.any(), + $col: joi.any(), +}; + +const whereMethods = [ + 'list', + 'get', + 'scope', + 'destroy', + 'destoryScope', + 'destroyAll', +]; + +const includeMethods = [ + 'list', + 'get', + 'scope', + 'destoryScope', +]; + +const payloadMethods = [ + 'create', + 'update', +]; + +const getConfigForMethod = ({ method, attributeValidation, associationValidation, config }) => { + const hasWhere = whereMethods.includes(method); + const hasInclude = includeMethods.includes(method); + const hasPayload = payloadMethods.includes(method); + const methodConfig = { ...config }; + + if (hasWhere) { + _.defaultsDeep(methodConfig, { + validate: { + query: { + ...attributeValidation, + ...sequelizeOperators, + }, + }, + }); + } + + if (hasInclude) { + _.defaultsDeep(methodConfig, { + validate: { + query: { + ...associationValidation, + }, + }, + }); + } + + if (hasPayload) { + _.defaultsDeep(methodConfig, { + validate: { + payload: { + ...attributeValidation, + }, + }, + }); + } + + return methodConfig; +}; + +const createAll = ({ + server, + model, + prefix, + config, + attributeValidation, + associationValidation, +}) => { Object.keys(methods).forEach((method) => { - methods[method]({ server, model, prefix, config }); + methods[method]({ + server, + model, + prefix, + config: getConfigForMethod({ + method, + attributeValidation, + associationValidation, + config, + }), + }); }); }; @@ -34,17 +137,42 @@ models: { export default (server, model, { prefix, defaultConfig: config, models: permissions }) => { const modelName = model._singular; + const modelAttributes = Object.keys(model.attributes); + const modelAssociations = Object.keys(model.associations); + + const attributeValidation = modelAttributes.reduce((params, attribute) => { + params[attribute] = joi.any(); + return params; + }, {}); + + const associationValidation = { + include: joi.array().items(joi.string().valid(...modelAssociations)), + }; // if we don't have any permissions set, just create all the methods if (!permissions) { - createAll({ server, model, prefix, config }); + createAll({ + server, + model, + prefix, + config, + attributeValidation, + associationValidation, + }); // if permissions are set, but we can't parse them, throw an error } else if (!Array.isArray(permissions)) { throw new Error('hapi-sequelize-crud: `models` property must be an array'); // if permissions are set, but the only thing we've got is a model name, there // are no permissions to be set, so just create all methods and move on } else if (permissions.includes(modelName)) { - createAll({ server, model, prefix, config }); + createAll({ + server, + model, + prefix, + config, + attributeValidation, + associationValidation, + }); // if we've gotten here, we have complex permissions and need to set them } else { const permissionOptions = permissions.filter((permission) => { @@ -61,11 +189,23 @@ export default (server, model, { prefix, defaultConfig: config, models: permissi server, model, prefix, - config: permissionConfig, + config: getConfigForMethod({ + method, + attributeValidation, + associationValidation, + config: permissionConfig, + }), }); }); } else { - createAll({ server, model, prefix, config: permissionConfig }); + createAll({ + server, + model, + prefix, + attributeValidation, + associationValidation, + config: permissionConfig, + }); } } }); -- 2.34.1 From f062e2b37fcc0c633037030addf9ec4c7c7746c8 Mon Sep 17 00:00:00 2001 From: Joey Baker Date: Sun, 4 Sep 2016 16:49:07 -0700 Subject: [PATCH 09/14] Fix (validation) params is a plain object If we use a Joi object here, we can't use `defaultsDeep` to extend b/c the joi prototype won't extend cleanly. We'd need to use joi's `contact` method, but that gets really complicated and error prone. So, just use a plain object which is more correct anyway. http://hapijs.com/tutorials/validation --- src/crud.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/crud.js b/src/crud.js index 5050dd0..0006b3b 100644 --- a/src/crud.js +++ b/src/crud.js @@ -257,9 +257,9 @@ export const get = ({ server, model, prefix = '/', config }) => { }, config: _.defaultsDeep(config, { validate: { - params: joi.object().keys({ + params: { id: joi.any(), - }), + }, }, }), }); @@ -285,9 +285,9 @@ export const scope = ({ server, model, prefix = '/', config }) => { }, config: _.defaultsDeep(config, { validate: { - params: joi.object().keys({ + params: { scope: joi.string().valid(...scopes), - }), + }, }, }), }); @@ -372,9 +372,9 @@ export const destroyScope = ({ server, model, prefix = '/', config }) => { }, config: _.defaultsDeep(config, { validate: { - params: joi.object().keys({ + params: { scope: joi.string().valid(...scopes), - }), + }, }, }), }); @@ -400,9 +400,9 @@ export const update = ({ server, model, prefix = '/', config }) => { config: _.defaultsDeep(config, { validate: { payload: joi.object().required(), - params: joi.object().keys({ + params: { id: joi.any(), - }), + }, }, }), }); -- 2.34.1 From 0d8ab9f02e7796d262a705ab66c724cf815b82d3 Mon Sep 17 00:00:00 2001 From: Joey Baker Date: Sun, 4 Sep 2016 17:22:21 -0700 Subject: [PATCH 10/14] Chore (deps) update boom (major) They just removed a method we don't use. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1e5c0a1..a3ce659 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "tap-xunit": "^1.4.0" }, "dependencies": { - "boom": "^3.2.2", + "boom": "^4.0.0", "joi": "7.2.1", "lodash": "4.0.0" }, -- 2.34.1 From edccfb2316731bff21027e8b51807e0634b4b453 Mon Sep 17 00:00:00 2001 From: Joey Baker Date: Sun, 4 Sep 2016 17:22:47 -0700 Subject: [PATCH 11/14] =?UTF-8?q?Chore=20(deps)=20update=20Joi=207=20?= =?UTF-8?q?=E2=86=92=209?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Shouldn't impact us --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a3ce659..bb0c5a1 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ }, "dependencies": { "boom": "^4.0.0", - "joi": "7.2.1", + "joi": "^9.0.4", "lodash": "4.0.0" }, "optionalDependencies": { -- 2.34.1 From 4558ad132796fd84e9ac38eba0adbb12064e4356 Mon Sep 17 00:00:00 2001 From: Joey Baker Date: Sun, 4 Sep 2016 17:23:30 -0700 Subject: [PATCH 12/14] Chore (deps) update minors and patches Not strictly necessary, but kinda nice to prove we're up-to-date --- package.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index bb0c5a1..f70de76 100644 --- a/package.json +++ b/package.json @@ -25,16 +25,16 @@ "license": "MIT", "devDependencies": { "ava": "^0.16.0", - "babel-cli": "^6.10.1", + "babel-cli": "^6.14.0", "babel-plugin-add-module-exports": "^0.2.1", "babel-plugin-closure-elimination": "^1.0.6", "babel-plugin-transform-decorators-legacy": "^1.3.4", - "babel-plugin-transform-es2015-modules-commonjs": "^6.10.3", - "babel-preset-stage-1": "^6.5.0", + "babel-plugin-transform-es2015-modules-commonjs": "^6.14.0", + "babel-preset-stage-1": "^6.13.0", "eslint": "^3.4.0", - "eslint-config-pichak": "1.1.0", + "eslint-config-pichak": "^1.1.2", "eslint-plugin-ava": "^3.0.0", - "ghooks": "1.0.3", + "ghooks": "^1.3.2", "scripty": "^1.6.0", "sinon": "^1.17.5", "sinon-bluebird": "^3.0.2", @@ -43,7 +43,7 @@ "dependencies": { "boom": "^4.0.0", "joi": "^9.0.4", - "lodash": "4.0.0" + "lodash": "^4.15.0" }, "optionalDependencies": { "babel-polyfill": "^6.13.0" -- 2.34.1 From 4c9ae36c5c1bf7a792cef3850fed6413c1e5edee Mon Sep 17 00:00:00 2001 From: Joey Baker Date: Tue, 6 Sep 2016 11:24:41 -0700 Subject: [PATCH 13/14] Refactor: move get-config-for-method to a file --- src/crud.js | 87 +---------------------------------- src/get-config-for-method.js | 88 ++++++++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+), 86 deletions(-) create mode 100644 src/get-config-for-method.js diff --git a/src/crud.js b/src/crud.js index 0006b3b..64e44b7 100644 --- a/src/crud.js +++ b/src/crud.js @@ -5,92 +5,7 @@ import _ from 'lodash'; import { parseInclude, parseWhere } from './utils'; import { notFound } from 'boom'; import * as associations from './associations/index'; - -const sequelizeOperators = { - $and: joi.any(), - $or: joi.any(), - $gt: joi.any(), - $gte: joi.any(), - $lt: joi.any(), - $lte: joi.any(), - $ne: joi.any(), - $eq: joi.any(), - $not: joi.any(), - $between: joi.any(), - $notBetween: joi.any(), - $in: joi.any(), - $notIn: joi.any(), - $like: joi.any(), - $notLike: joi.any(), - $iLike: joi.any(), - $notILike: joi.any(), - $overlap: joi.any(), - $contains: joi.any(), - $contained: joi.any(), - $any: joi.any(), - $col: joi.any(), -}; - -const whereMethods = [ - 'list', - 'get', - 'scope', - 'destroy', - 'destoryScope', - 'destroyAll', -]; - -const includeMethods = [ - 'list', - 'get', - 'scope', - 'destoryScope', -]; - -const payloadMethods = [ - 'create', - 'update', -]; - -const getConfigForMethod = ({ method, attributeValidation, associationValidation, config }) => { - const hasWhere = whereMethods.includes(method); - const hasInclude = includeMethods.includes(method); - const hasPayload = payloadMethods.includes(method); - const methodConfig = { ...config }; - - if (hasWhere) { - _.defaultsDeep(methodConfig, { - validate: { - query: { - ...attributeValidation, - ...sequelizeOperators, - }, - }, - }); - } - - if (hasInclude) { - _.defaultsDeep(methodConfig, { - validate: { - query: { - ...associationValidation, - }, - }, - }); - } - - if (hasPayload) { - _.defaultsDeep(methodConfig, { - validate: { - payload: { - ...attributeValidation, - }, - }, - }); - } - - return methodConfig; -}; +import getConfigForMethod from './get-config-for-method.js'; const createAll = ({ server, diff --git a/src/get-config-for-method.js b/src/get-config-for-method.js new file mode 100644 index 0000000..de8c828 --- /dev/null +++ b/src/get-config-for-method.js @@ -0,0 +1,88 @@ +import { defaultsDeep } from 'lodash'; +import joi from 'joi'; + +export const sequelizeOperators = { + $and: joi.any(), + $or: joi.any(), + $gt: joi.any(), + $gte: joi.any(), + $lt: joi.any(), + $lte: joi.any(), + $ne: joi.any(), + $eq: joi.any(), + $not: joi.any(), + $between: joi.any(), + $notBetween: joi.any(), + $in: joi.any(), + $notIn: joi.any(), + $like: joi.any(), + $notLike: joi.any(), + $iLike: joi.any(), + $notILike: joi.any(), + $overlap: joi.any(), + $contains: joi.any(), + $contained: joi.any(), + $any: joi.any(), + $col: joi.any(), +}; + +export const whereMethods = [ + 'list', + 'get', + 'scope', + 'destroy', + 'destoryScope', + 'destroyAll', +]; + +export const includeMethods = [ + 'list', + 'get', + 'scope', + 'destoryScope', +]; + +export const payloadMethods = [ + 'create', + 'update', +]; + +export default ({ method, attributeValidation, associationValidation, config = {} }) => { + const hasWhere = whereMethods.includes(method); + const hasInclude = includeMethods.includes(method); + const hasPayload = payloadMethods.includes(method); + const methodConfig = { ...config }; + + if (hasWhere) { + defaultsDeep(methodConfig, { + validate: { + query: { + ...attributeValidation, + ...sequelizeOperators, + }, + }, + }); + } + + if (hasInclude) { + defaultsDeep(methodConfig, { + validate: { + query: { + ...associationValidation, + }, + }, + }); + } + + if (hasPayload) { + defaultsDeep(methodConfig, { + validate: { + payload: { + ...attributeValidation, + }, + }, + }); + } + + return methodConfig; +}; -- 2.34.1 From 3e9f024dcf754eb2148488540ce143ebfffaf2da Mon Sep 17 00:00:00 2001 From: Joey Baker Date: Tue, 6 Sep 2016 11:25:03 -0700 Subject: [PATCH 14/14] Test: now testing get-config-for-method --- src/get-config-for-method.test.js | 117 ++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 src/get-config-for-method.test.js diff --git a/src/get-config-for-method.test.js b/src/get-config-for-method.test.js new file mode 100644 index 0000000..8a9512f --- /dev/null +++ b/src/get-config-for-method.test.js @@ -0,0 +1,117 @@ +import test from 'ava'; +import joi from 'joi'; +import + getConfigForMethod, { + whereMethods, + includeMethods, + payloadMethods, + sequelizeOperators, +} from './get-config-for-method.js'; + +test.beforeEach((t) => { + t.context.attributeValidation = { + myKey: joi.any(), + }; + + t.context.associationValidation = { + include: ['MyModel'], + }; + + t.context.config = { + cors: {}, + }; +}); + +test('get-config-for-method validate.query seqeulizeOperators', (t) => { + whereMethods.forEach((method) => { + const configForMethod = getConfigForMethod({ method }); + const { query } = configForMethod.validate; + const configForMethodValidateQueryKeys = Object.keys(query); + + t.truthy( + query, + `applies query validation for ${method}` + ); + + Object.keys(sequelizeOperators).forEach((operator) => { + t.truthy( + configForMethodValidateQueryKeys.includes(operator), + `applies sequelize operator "${operator}" in validate.where for ${method}` + ); + }); + }); +}); + +test('get-config-for-method validate.query attributeValidation', (t) => { + const { attributeValidation } = t.context; + + whereMethods.forEach((method) => { + const configForMethod = getConfigForMethod({ method, attributeValidation }); + const { query } = configForMethod.validate; + + Object.keys(attributeValidation).forEach((key) => { + t.truthy( + query[key] + , `applies attributeValidation (${key}) to validate.query` + ); + }); + }); +}); + +test('get-config-for-method validate.query associationValidation', (t) => { + const { attributeValidation, associationValidation } = t.context; + + includeMethods.forEach((method) => { + const configForMethod = getConfigForMethod({ + method, + attributeValidation, + associationValidation, + }); + const { query } = configForMethod.validate; + + Object.keys(attributeValidation).forEach((key) => { + t.truthy( + query[key] + , `applies attributeValidation (${key}) to validate.query when include should be applied` + ); + }); + + Object.keys(associationValidation).forEach((key) => { + t.truthy( + query[key] + , `applies associationValidation (${key}) to validate.query when include should be applied` + ); + }); + }); +}); + +test('get-config-for-method validate.payload associationValidation', (t) => { + const { attributeValidation } = t.context; + + payloadMethods.forEach((method) => { + const configForMethod = getConfigForMethod({ method, attributeValidation }); + const { payload } = configForMethod.validate; + + Object.keys(attributeValidation).forEach((key) => { + t.truthy( + payload[key] + , `applies attributeValidation (${key}) to validate.payload` + ); + }); + }); +}); + +test('get-config-for-method does not modify initial config on multiple passes', (t) => { + const { config } = t.context; + const originalConfig = { ...config }; + + whereMethods.forEach((method) => { + getConfigForMethod({ method, config }); + }); + + t.deepEqual( + config + , originalConfig + , 'does not modify the original config object' + ); +}); -- 2.34.1