Compare commits
19 Commits
Author | SHA1 | Date | |
---|---|---|---|
548a6ecd98 | |||
be993eda40 | |||
bcb7861061 | |||
07176018b7 | |||
83eadf0929 | |||
e318948fe4 | |||
d35b616a13 | |||
8966d7b287 | |||
5923f0dbcb | |||
adb1d71984 | |||
3c516aa604 | |||
ddc6fcceb8 | |||
f403e214a9 | |||
71e6390282 | |||
a720e30a85 | |||
518c4a4226 | |||
469aaec66f | |||
8ee5661252 | |||
c59943a717 |
2
.babelrc
2
.babelrc
@ -10,5 +10,5 @@
|
||||
"transform-decorators-legacy",
|
||||
"transform-es2015-modules-commonjs"
|
||||
],
|
||||
"sourceMaps": true
|
||||
"sourceMaps": "inline"
|
||||
}
|
||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -33,3 +33,6 @@ node_modules
|
||||
|
||||
# Debug log from npm
|
||||
npm-debug.log
|
||||
|
||||
# System
|
||||
.DS_Store
|
||||
|
92
README.md
92
README.md
@ -9,7 +9,7 @@ This plugin depends on [`hapi-sequelize`](https://github.com/danecando/hapi-sequ
|
||||
npm install -S hapi-sequelize-crud
|
||||
```
|
||||
|
||||
##Configure
|
||||
## Configure
|
||||
|
||||
Please note that you should register `hapi-sequelize-crud` after defining your
|
||||
associations.
|
||||
@ -52,6 +52,20 @@ await register({
|
||||
// `config` if provided, overrides the default config
|
||||
{model: 'bat', methods: ['list'], config: { ... }},
|
||||
{model: 'bat', methods: ['create']}
|
||||
{model: 'fly', config: {
|
||||
// interact with the request before hapi-sequelize-crud does
|
||||
, ext: {
|
||||
onPreHandler: (request, reply) => {
|
||||
if (request.auth.hasAccessToFly) reply.continue()
|
||||
else reply(Boom.unauthorized())
|
||||
}
|
||||
}
|
||||
// change the response data
|
||||
response: {
|
||||
schema: {id: joi.string()},
|
||||
modify: true
|
||||
}
|
||||
}}
|
||||
]
|
||||
}
|
||||
});
|
||||
@ -95,7 +109,7 @@ Getting related models is easy, just use a query parameter `include`.
|
||||
|
||||
```js
|
||||
// returns all teams with their related City model
|
||||
// GET /teams?include=City
|
||||
// GET /teams?include=city
|
||||
|
||||
// results in a Sequelize query:
|
||||
Team.findAll({include: City})
|
||||
@ -104,12 +118,84 @@ 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
|
||||
// GET /teams?include[]=city&include[]=uniform
|
||||
|
||||
// results in a Sequelize query:
|
||||
Team.findAll({include: [City, Uniform]})
|
||||
```
|
||||
|
||||
For models that have a many-to-many relationship, you can also pass the plural version of the association.
|
||||
```js
|
||||
// returns all teams with their related City and Uniform models
|
||||
// GET /teams?include=players
|
||||
|
||||
// results in a Sequelize query:
|
||||
Team.findAll({include: [Player]})
|
||||
```
|
||||
|
||||
## `limit` and `offset` queries
|
||||
Restricting list (`GET`) and scope queries to a restricted count can be done by passing `limit=<number>` and/or `offset=<number>`.
|
||||
|
||||
```js
|
||||
// returns 10 teams starting from the 10th
|
||||
// GET /teams?limit=10&offset=10
|
||||
|
||||
// results in a Sequelize query:
|
||||
Team.findAll({limit: 10, offset: 10})
|
||||
```
|
||||
|
||||
## `order` queries
|
||||
You can change the order of the resulting query by passing `order` to the query.
|
||||
|
||||
```js
|
||||
// returns the teams ordered by the name column
|
||||
// GET /teams?order[]=name
|
||||
|
||||
// results in a Sequelize query:
|
||||
Team.findAll({order: ['name']})
|
||||
```
|
||||
|
||||
```js
|
||||
// returns the teams ordered by the name column, descending
|
||||
// GET /teams?order[0]=name&order[0]=DESC
|
||||
|
||||
// results in a Sequelize query:
|
||||
Team.findAll({order: [['name', 'DESC']]})
|
||||
```
|
||||
|
||||
```js
|
||||
// returns the teams ordered by the name, then the city columns, descending
|
||||
// GET /teams?order[0]=name&order[1]=city
|
||||
|
||||
// results in a Sequelize query:
|
||||
Team.findAll({order: [['name'], ['city']]})
|
||||
```
|
||||
|
||||
|
||||
## Authorization and other hooks
|
||||
You can use Hapi's [`ext` option](http://hapijs.com/api#route-options) to interact with the request both before and after this module does. This is useful if you want to enforce authorization, or modify the request before or after this module does. Hapi [has a full list of hooks](http://hapijs.com/api#request-lifecycle) you can use.
|
||||
|
||||
## Modify the response format
|
||||
By default, `hapi-sequelize-crud` routes will respond with the full model. You can modify this using the built-in [hapi settings](http://hapijs.com/tutorials/validation#output).
|
||||
|
||||
```js
|
||||
await register({
|
||||
register: require('hapi-sequelize-crud'),
|
||||
options: {
|
||||
…
|
||||
{model: 'fly', config: {
|
||||
response: {
|
||||
// setting this schema will restrict the response to only the id
|
||||
schema: { id: joi.string() },
|
||||
// This tells Hapi to restrict the response to the keys specified in `schema`
|
||||
modify: true
|
||||
}
|
||||
}}
|
||||
}
|
||||
|
||||
})
|
||||
```
|
||||
|
||||
## Full list of methods
|
||||
|
||||
Let's say you have a `many-to-many` association like this:
|
||||
|
27
package.json
27
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "hapi-sequelize-crud",
|
||||
"version": "2.6.0",
|
||||
"version": "2.7.1",
|
||||
"description": "Hapi plugin that automatically generates RESTful API for CRUD",
|
||||
"main": "build/index.js",
|
||||
"config": {
|
||||
@ -10,8 +10,8 @@
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "eslint src",
|
||||
"test": "ava --require babel-register --source='*.test.js' --tap=${CI-false} | $(if [ -z ${CI:-} ]; then echo 'tail'; else tap-xunit > $CIRCLE_TEST_REPORTS/ava/ava.xml; fi;)",
|
||||
"tdd": "ava --require babel-register --source='*.test.js' --watch",
|
||||
"test": "ava --require babel-register --source='src/**/*.js' --source='!build/**/*' --tap=${CI-false} src/**/*.test.js | $(if [ -z ${CI:-} ]; then echo 'tail'; else tap-xunit > $CIRCLE_TEST_REPORTS/ava/ava.xml; fi;)",
|
||||
"tdd": "ava --require babel-register --source='src/**/*.js' --source='!build/**/*' --watch src/**/*.test.js",
|
||||
"build": "scripty",
|
||||
"watch": "scripty"
|
||||
},
|
||||
@ -25,25 +25,26 @@
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"ava": "^0.16.0",
|
||||
"babel-cli": "^6.14.0",
|
||||
"babel-cli": "^6.16.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.14.0",
|
||||
"babel-preset-stage-1": "^6.13.0",
|
||||
"eslint": "^3.4.0",
|
||||
"babel-plugin-transform-es2015-modules-commonjs": "^6.16.0",
|
||||
"babel-preset-stage-1": "^6.16.0",
|
||||
"babel-register": "^6.16.3",
|
||||
"eslint": "^3.8.1",
|
||||
"eslint-config-pichak": "^1.1.2",
|
||||
"eslint-plugin-ava": "^3.0.0",
|
||||
"eslint-plugin-ava": "^3.1.1",
|
||||
"ghooks": "^1.3.2",
|
||||
"scripty": "^1.6.0",
|
||||
"sinon": "^1.17.5",
|
||||
"sinon-bluebird": "^3.0.2",
|
||||
"sinon": "^1.17.6",
|
||||
"sinon-bluebird": "^3.1.0",
|
||||
"tap-xunit": "^1.4.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"boom": "^4.0.0",
|
||||
"joi": "^9.0.4",
|
||||
"lodash": "^4.15.0"
|
||||
"boom": "^4.2.0",
|
||||
"joi": "^9.2.0",
|
||||
"lodash": "^4.16.4"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"babel-polyfill": "^6.13.0"
|
||||
|
70
src/crud.js
70
src/crud.js
@ -2,7 +2,7 @@ import joi from 'joi';
|
||||
import path from 'path';
|
||||
import error from './error';
|
||||
import _ from 'lodash';
|
||||
import { parseInclude, parseWhere } from './utils';
|
||||
import { parseInclude, parseWhere, parseLimitAndOffset, parseOrder } from './utils';
|
||||
import { notFound } from 'boom';
|
||||
import * as associations from './associations/index';
|
||||
import getConfigForMethod from './get-config-for-method.js';
|
||||
@ -14,6 +14,7 @@ const createAll = ({
|
||||
config,
|
||||
attributeValidation,
|
||||
associationValidation,
|
||||
scopes,
|
||||
}) => {
|
||||
Object.keys(methods).forEach((method) => {
|
||||
methods[method]({
|
||||
@ -25,6 +26,7 @@ const createAll = ({
|
||||
attributeValidation,
|
||||
associationValidation,
|
||||
config,
|
||||
scopes,
|
||||
}),
|
||||
});
|
||||
});
|
||||
@ -53,17 +55,28 @@ 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 associatedModelNames = Object.keys(model.associations);
|
||||
const modelAssociations = [
|
||||
...associatedModelNames,
|
||||
..._.flatMap(associatedModelNames, (associationName) => {
|
||||
const { target } = model.associations[associationName];
|
||||
const { _singular, _plural, _Singular, _Plural } = target;
|
||||
return [_singular, _plural, _Singular, _Plural];
|
||||
}),
|
||||
];
|
||||
|
||||
const attributeValidation = modelAttributes.reduce((params, attribute) => {
|
||||
params[attribute] = joi.any();
|
||||
return params;
|
||||
}, {});
|
||||
|
||||
const validAssociations = joi.string().valid(...modelAssociations);
|
||||
const associationValidation = {
|
||||
include: joi.array().items(joi.string().valid(...modelAssociations)),
|
||||
include: [joi.array().items(validAssociations), validAssociations],
|
||||
};
|
||||
|
||||
const scopes = Object.keys(model.options.scopes);
|
||||
|
||||
// if we don't have any permissions set, just create all the methods
|
||||
if (!permissions) {
|
||||
createAll({
|
||||
@ -73,6 +86,7 @@ export default (server, model, { prefix, defaultConfig: config, models: permissi
|
||||
config,
|
||||
attributeValidation,
|
||||
associationValidation,
|
||||
scopes,
|
||||
});
|
||||
// if permissions are set, but we can't parse them, throw an error
|
||||
} else if (!Array.isArray(permissions)) {
|
||||
@ -87,6 +101,7 @@ export default (server, model, { prefix, defaultConfig: config, models: permissi
|
||||
config,
|
||||
attributeValidation,
|
||||
associationValidation,
|
||||
scopes,
|
||||
});
|
||||
// if we've gotten here, we have complex permissions and need to set them
|
||||
} else {
|
||||
@ -108,6 +123,7 @@ export default (server, model, { prefix, defaultConfig: config, models: permissi
|
||||
method,
|
||||
attributeValidation,
|
||||
associationValidation,
|
||||
scopes,
|
||||
config: permissionConfig,
|
||||
}),
|
||||
});
|
||||
@ -119,6 +135,7 @@ export default (server, model, { prefix, defaultConfig: config, models: permissi
|
||||
prefix,
|
||||
attributeValidation,
|
||||
associationValidation,
|
||||
scopes,
|
||||
config: permissionConfig,
|
||||
});
|
||||
}
|
||||
@ -136,11 +153,13 @@ export const list = ({ server, model, prefix = '/', config }) => {
|
||||
async handler(request, reply) {
|
||||
const include = parseInclude(request);
|
||||
const where = parseWhere(request);
|
||||
const { limit, offset } = parseLimitAndOffset(request);
|
||||
const order = parseOrder(request);
|
||||
|
||||
if (include instanceof Error) return void reply(include);
|
||||
|
||||
const list = await model.findAll({
|
||||
where, include,
|
||||
where, include, limit, offset, order,
|
||||
});
|
||||
|
||||
reply(list.map((item) => item.toJSON()));
|
||||
@ -170,19 +189,11 @@ export const get = ({ server, model, prefix = '/', config }) => {
|
||||
|
||||
reply(instance.toJSON());
|
||||
},
|
||||
config: _.defaultsDeep(config, {
|
||||
validate: {
|
||||
params: {
|
||||
id: joi.any(),
|
||||
},
|
||||
},
|
||||
}),
|
||||
config,
|
||||
});
|
||||
};
|
||||
|
||||
export const scope = ({ server, model, prefix = '/', config }) => {
|
||||
const scopes = Object.keys(model.options.scopes);
|
||||
|
||||
server.route({
|
||||
method: 'GET',
|
||||
path: path.join(prefix, model._plural, '{scope}'),
|
||||
@ -191,20 +202,18 @@ export const scope = ({ server, model, prefix = '/', config }) => {
|
||||
async handler(request, reply) {
|
||||
const include = parseInclude(request);
|
||||
const where = parseWhere(request);
|
||||
const { limit, offset } = parseLimitAndOffset(request);
|
||||
const order = parseOrder(request);
|
||||
|
||||
if (include instanceof Error) return void reply(include);
|
||||
|
||||
const list = await model.scope(request.params.scope).findAll({ include, where });
|
||||
const list = await model.scope(request.params.scope).findAll({
|
||||
include, where, limit, offset, order,
|
||||
});
|
||||
|
||||
reply(list.map((item) => item.toJSON()));
|
||||
},
|
||||
config: _.defaultsDeep(config, {
|
||||
validate: {
|
||||
params: {
|
||||
scope: joi.string().valid(...scopes),
|
||||
},
|
||||
},
|
||||
}),
|
||||
config,
|
||||
});
|
||||
};
|
||||
|
||||
@ -268,8 +277,6 @@ export const destroyAll = ({ server, model, prefix = '/', config }) => {
|
||||
};
|
||||
|
||||
export const destroyScope = ({ server, model, prefix = '/', config }) => {
|
||||
const scopes = Object.keys(model.options.scopes);
|
||||
|
||||
server.route({
|
||||
method: 'DELETE',
|
||||
path: path.join(prefix, model._plural, '{scope}'),
|
||||
@ -288,13 +295,7 @@ export const destroyScope = ({ server, model, prefix = '/', config }) => {
|
||||
const listAsJSON = list.map((item) => item.toJSON());
|
||||
reply(listAsJSON.length === 1 ? listAsJSON[0] : listAsJSON);
|
||||
},
|
||||
config: _.defaultsDeep(config, {
|
||||
validate: {
|
||||
params: {
|
||||
scope: joi.string().valid(...scopes),
|
||||
},
|
||||
},
|
||||
}),
|
||||
config,
|
||||
});
|
||||
};
|
||||
|
||||
@ -315,14 +316,7 @@ export const update = ({ server, model, prefix = '/', config }) => {
|
||||
reply(instance.toJSON());
|
||||
},
|
||||
|
||||
config: _.defaultsDeep(config, {
|
||||
validate: {
|
||||
payload: joi.object().required(),
|
||||
params: {
|
||||
id: joi.any(),
|
||||
},
|
||||
},
|
||||
}),
|
||||
config,
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -37,7 +37,7 @@ test.beforeEach('setup request stub', (t) => {
|
||||
t.context.request = {
|
||||
query: {},
|
||||
payload: {},
|
||||
models: [t.context.model],
|
||||
models: t.context.models,
|
||||
};
|
||||
});
|
||||
|
||||
@ -126,6 +126,8 @@ test('crud#list handler', async (t) => {
|
||||
|
||||
const response = reply.args[0][0];
|
||||
|
||||
t.falsy(response instanceof Error, response);
|
||||
|
||||
t.deepEqual(
|
||||
response,
|
||||
models.map(({ id }) => ({ id })),
|
||||
@ -155,3 +157,75 @@ test('crud#list handler if parseInclude errors', async (t) => {
|
||||
'responds with a Boom error'
|
||||
);
|
||||
});
|
||||
|
||||
test('crud#list handler with limit', async (t) => {
|
||||
const { server, model, request, reply, models } = t.context;
|
||||
const { findAll } = model;
|
||||
|
||||
// set the limit
|
||||
request.query.limit = 1;
|
||||
|
||||
list({ server, model });
|
||||
const { handler } = server.route.args[0][0];
|
||||
model.findAll.resolves(models);
|
||||
|
||||
try {
|
||||
await handler(request, reply);
|
||||
} catch (e) {
|
||||
t.ifError(e, 'does not error while handling');
|
||||
} finally {
|
||||
t.pass('does not error while handling');
|
||||
}
|
||||
|
||||
t.truthy(
|
||||
reply.calledOnce
|
||||
, 'calls reply only once'
|
||||
);
|
||||
|
||||
const response = reply.args[0][0];
|
||||
const findAllArgs = findAll.args[0][0];
|
||||
|
||||
t.falsy(response instanceof Error, response);
|
||||
|
||||
t.is(
|
||||
findAllArgs.limit,
|
||||
request.query.limit,
|
||||
'queries with the limit'
|
||||
);
|
||||
});
|
||||
|
||||
test('crud#list handler with order', async (t) => {
|
||||
const { server, model, request, reply, models } = t.context;
|
||||
const { findAll } = model;
|
||||
|
||||
// set the limit
|
||||
request.query.order = 'key';
|
||||
|
||||
list({ server, model });
|
||||
const { handler } = server.route.args[0][0];
|
||||
model.findAll.resolves(models);
|
||||
|
||||
try {
|
||||
await handler(request, reply);
|
||||
} catch (e) {
|
||||
t.ifError(e, 'does not error while handling');
|
||||
} finally {
|
||||
t.pass('does not error while handling');
|
||||
}
|
||||
|
||||
t.truthy(
|
||||
reply.calledOnce
|
||||
, 'calls reply only once'
|
||||
);
|
||||
|
||||
const response = reply.args[0][0];
|
||||
const findAllArgs = findAll.args[0][0];
|
||||
|
||||
t.falsy(response instanceof Error, response);
|
||||
|
||||
t.deepEqual(
|
||||
findAllArgs.order,
|
||||
[request.query.order],
|
||||
'queries with the order as an array b/c that\'s what sequelize wants'
|
||||
);
|
||||
});
|
||||
|
@ -1,6 +1,15 @@
|
||||
import { defaultsDeep } from 'lodash';
|
||||
import { set, get } from 'lodash';
|
||||
import joi from 'joi';
|
||||
|
||||
// if the custom validation is a joi object we need to concat
|
||||
// else, assume it's an plain object and we can just add it in with .keys
|
||||
const concatToJoiObject = (joi, candidate) => {
|
||||
if (!candidate) return joi;
|
||||
else if (candidate.isJoi) return joi.concat(candidate);
|
||||
else return joi.keys(candidate);
|
||||
};
|
||||
|
||||
|
||||
export const sequelizeOperators = {
|
||||
$and: joi.any(),
|
||||
$or: joi.any(),
|
||||
@ -47,41 +56,100 @@ export const payloadMethods = [
|
||||
'update',
|
||||
];
|
||||
|
||||
export default ({ method, attributeValidation, associationValidation, config = {} }) => {
|
||||
export const scopeParamsMethods = [
|
||||
'destroyScope',
|
||||
'scope',
|
||||
];
|
||||
|
||||
export const idParamsMethods = [
|
||||
'get',
|
||||
'update',
|
||||
];
|
||||
|
||||
export const restrictMethods = [
|
||||
'list',
|
||||
'scope',
|
||||
];
|
||||
|
||||
export default ({
|
||||
method, attributeValidation, associationValidation, scopes = [], config = {},
|
||||
}) => {
|
||||
const hasWhere = whereMethods.includes(method);
|
||||
const hasInclude = includeMethods.includes(method);
|
||||
const hasPayload = payloadMethods.includes(method);
|
||||
const methodConfig = { ...config };
|
||||
const hasScopeParams = scopeParamsMethods.includes(method);
|
||||
const hasIdParams = idParamsMethods.includes(method);
|
||||
const hasRestrictMethods = restrictMethods.includes(method);
|
||||
// clone the config so we don't modify it on multiple passes.
|
||||
let methodConfig = { ...config, validate: { ...config.validate } };
|
||||
|
||||
if (hasWhere) {
|
||||
defaultsDeep(methodConfig, {
|
||||
validate: {
|
||||
query: {
|
||||
...attributeValidation,
|
||||
...sequelizeOperators,
|
||||
},
|
||||
},
|
||||
});
|
||||
const query = concatToJoiObject(joi.object()
|
||||
.keys({
|
||||
...attributeValidation,
|
||||
...sequelizeOperators,
|
||||
}),
|
||||
get(methodConfig, 'validate.query')
|
||||
);
|
||||
|
||||
methodConfig = set(methodConfig, 'validate.query', query);
|
||||
}
|
||||
|
||||
if (hasInclude) {
|
||||
defaultsDeep(methodConfig, {
|
||||
validate: {
|
||||
query: {
|
||||
...associationValidation,
|
||||
},
|
||||
},
|
||||
});
|
||||
const query = concatToJoiObject(joi.object()
|
||||
.keys({
|
||||
...associationValidation,
|
||||
}),
|
||||
get(methodConfig, 'validate.query')
|
||||
);
|
||||
|
||||
methodConfig = set(methodConfig, 'validate.query', query);
|
||||
}
|
||||
|
||||
if (hasPayload) {
|
||||
defaultsDeep(methodConfig, {
|
||||
validate: {
|
||||
payload: {
|
||||
...attributeValidation,
|
||||
},
|
||||
},
|
||||
});
|
||||
const payload = concatToJoiObject(joi.object()
|
||||
.keys({
|
||||
...attributeValidation,
|
||||
}),
|
||||
get(methodConfig, 'validate.payload')
|
||||
);
|
||||
|
||||
methodConfig = set(methodConfig, 'validate.payload', payload);
|
||||
}
|
||||
|
||||
if (hasScopeParams) {
|
||||
const params = concatToJoiObject(joi.object()
|
||||
.keys({
|
||||
scope: joi.string().valid(...scopes),
|
||||
}),
|
||||
get(methodConfig, 'validate.params')
|
||||
);
|
||||
|
||||
methodConfig = set(methodConfig, 'validate.params', params);
|
||||
}
|
||||
|
||||
if (hasIdParams) {
|
||||
const params = concatToJoiObject(joi.object()
|
||||
.keys({
|
||||
id: joi.any(),
|
||||
}),
|
||||
get(methodConfig, 'validate.params')
|
||||
);
|
||||
|
||||
methodConfig = set(methodConfig, 'validate.params', params);
|
||||
}
|
||||
|
||||
if (hasRestrictMethods) {
|
||||
const query = concatToJoiObject(joi.object()
|
||||
.keys({
|
||||
limit: joi.number().min(0).integer(),
|
||||
offset: joi.number().min(0).integer(),
|
||||
order: joi.array(),
|
||||
}),
|
||||
get(methodConfig, 'validate.query')
|
||||
);
|
||||
|
||||
methodConfig = set(methodConfig, 'validate.query', query);
|
||||
}
|
||||
|
||||
return methodConfig;
|
||||
|
@ -5,16 +5,23 @@ import
|
||||
whereMethods,
|
||||
includeMethods,
|
||||
payloadMethods,
|
||||
scopeParamsMethods,
|
||||
idParamsMethods,
|
||||
restrictMethods,
|
||||
sequelizeOperators,
|
||||
} from './get-config-for-method.js';
|
||||
|
||||
test.beforeEach((t) => {
|
||||
t.context.models = ['MyModel'];
|
||||
|
||||
t.context.scopes = ['aScope'];
|
||||
|
||||
t.context.attributeValidation = {
|
||||
myKey: joi.any(),
|
||||
};
|
||||
|
||||
t.context.associationValidation = {
|
||||
include: ['MyModel'],
|
||||
include: joi.array().items(joi.string().valid(t.context.models)),
|
||||
};
|
||||
|
||||
t.context.config = {
|
||||
@ -22,11 +29,10 @@ test.beforeEach((t) => {
|
||||
};
|
||||
});
|
||||
|
||||
test('get-config-for-method validate.query seqeulizeOperators', (t) => {
|
||||
test('validate.query seqeulizeOperators', (t) => {
|
||||
whereMethods.forEach((method) => {
|
||||
const configForMethod = getConfigForMethod({ method });
|
||||
const { query } = configForMethod.validate;
|
||||
const configForMethodValidateQueryKeys = Object.keys(query);
|
||||
|
||||
t.truthy(
|
||||
query,
|
||||
@ -34,15 +40,20 @@ test('get-config-for-method validate.query seqeulizeOperators', (t) => {
|
||||
);
|
||||
|
||||
Object.keys(sequelizeOperators).forEach((operator) => {
|
||||
t.truthy(
|
||||
configForMethodValidateQueryKeys.includes(operator),
|
||||
`applies sequelize operator "${operator}" in validate.where for ${method}`
|
||||
t.ifError(
|
||||
query.validate({ [operator]: true }).error
|
||||
, `applies sequelize operator "${operator}" in validate.where for ${method}`
|
||||
);
|
||||
});
|
||||
|
||||
t.truthy(
|
||||
query.validate({ notAThing: true }).error
|
||||
, 'errors on a non-valid key'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test('get-config-for-method validate.query attributeValidation', (t) => {
|
||||
test('validate.query attributeValidation', (t) => {
|
||||
const { attributeValidation } = t.context;
|
||||
|
||||
whereMethods.forEach((method) => {
|
||||
@ -50,16 +61,96 @@ test('get-config-for-method validate.query attributeValidation', (t) => {
|
||||
const { query } = configForMethod.validate;
|
||||
|
||||
Object.keys(attributeValidation).forEach((key) => {
|
||||
t.truthy(
|
||||
query[key]
|
||||
t.ifError(
|
||||
query.validate({ [key]: true }).error
|
||||
, `applies attributeValidation (${key}) to validate.query`
|
||||
);
|
||||
});
|
||||
|
||||
t.truthy(
|
||||
query.validate({ notAThing: true }).error
|
||||
, 'errors on a non-valid key'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test('get-config-for-method validate.query associationValidation', (t) => {
|
||||
const { attributeValidation, associationValidation } = t.context;
|
||||
test('query attributeValidation w/ config as plain object', (t) => {
|
||||
const { attributeValidation } = t.context;
|
||||
const config = {
|
||||
validate: {
|
||||
query: {
|
||||
aKey: joi.boolean(),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
whereMethods.forEach((method) => {
|
||||
const configForMethod = getConfigForMethod({
|
||||
method,
|
||||
attributeValidation,
|
||||
config,
|
||||
});
|
||||
const { query } = configForMethod.validate;
|
||||
|
||||
const keys = [
|
||||
...Object.keys(attributeValidation),
|
||||
...Object.keys(config.validate.query),
|
||||
];
|
||||
|
||||
keys.forEach((key) => {
|
||||
t.ifError(
|
||||
query.validate({ [key]: true }).error
|
||||
, `applies ${key} to validate.query`
|
||||
);
|
||||
});
|
||||
|
||||
t.truthy(
|
||||
query.validate({ notAThing: true }).error
|
||||
, 'errors on a non-valid key'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test('query attributeValidation w/ config as joi object', (t) => {
|
||||
const { attributeValidation } = t.context;
|
||||
const queryKeys = {
|
||||
aKey: joi.boolean(),
|
||||
};
|
||||
const config = {
|
||||
validate: {
|
||||
query: joi.object().keys(queryKeys),
|
||||
},
|
||||
};
|
||||
|
||||
whereMethods.forEach((method) => {
|
||||
const configForMethod = getConfigForMethod({
|
||||
method,
|
||||
attributeValidation,
|
||||
config,
|
||||
});
|
||||
const { query } = configForMethod.validate;
|
||||
|
||||
const keys = [
|
||||
...Object.keys(attributeValidation),
|
||||
...Object.keys(queryKeys),
|
||||
];
|
||||
|
||||
keys.forEach((key) => {
|
||||
t.ifError(
|
||||
query.validate({ [key]: true }).error
|
||||
, `applies ${key} to validate.query`
|
||||
);
|
||||
});
|
||||
|
||||
t.truthy(
|
||||
query.validate({ notAThing: true }).error
|
||||
, 'errors on a non-valid key'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test('validate.query associationValidation', (t) => {
|
||||
const { attributeValidation, associationValidation, models } = t.context;
|
||||
|
||||
includeMethods.forEach((method) => {
|
||||
const configForMethod = getConfigForMethod({
|
||||
@ -70,22 +161,106 @@ test('get-config-for-method validate.query associationValidation', (t) => {
|
||||
const { query } = configForMethod.validate;
|
||||
|
||||
Object.keys(attributeValidation).forEach((key) => {
|
||||
t.truthy(
|
||||
query[key]
|
||||
t.ifError(
|
||||
query.validate({ [key]: true }).error
|
||||
, `applies attributeValidation (${key}) to validate.query when include should be applied`
|
||||
);
|
||||
});
|
||||
|
||||
Object.keys(associationValidation).forEach((key) => {
|
||||
t.truthy(
|
||||
query[key]
|
||||
t.ifError(
|
||||
query.validate({ [key]: models }).error
|
||||
, `applies associationValidation (${key}) to validate.query when include should be applied`
|
||||
);
|
||||
});
|
||||
|
||||
t.truthy(
|
||||
query.validate({ notAThing: true }).error
|
||||
, 'errors on a non-valid key'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test('get-config-for-method validate.payload associationValidation', (t) => {
|
||||
test('query associationValidation w/ config as plain object', (t) => {
|
||||
const { associationValidation, models } = t.context;
|
||||
const config = {
|
||||
validate: {
|
||||
query: {
|
||||
aKey: joi.boolean(),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
includeMethods.forEach((method) => {
|
||||
const configForMethod = getConfigForMethod({
|
||||
method,
|
||||
associationValidation,
|
||||
config,
|
||||
});
|
||||
const { query } = configForMethod.validate;
|
||||
|
||||
Object.keys(associationValidation).forEach((key) => {
|
||||
t.ifError(
|
||||
query.validate({ [key]: models }).error
|
||||
, `applies ${key} to validate.query`
|
||||
);
|
||||
});
|
||||
|
||||
Object.keys(config.validate.query).forEach((key) => {
|
||||
t.ifError(
|
||||
query.validate({ [key]: true }).error
|
||||
, `applies ${key} to validate.query`
|
||||
);
|
||||
});
|
||||
|
||||
t.truthy(
|
||||
query.validate({ notAThing: true }).error
|
||||
, 'errors on a non-valid key'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test('query associationValidation w/ config as joi object', (t) => {
|
||||
const { associationValidation, models } = t.context;
|
||||
const queryKeys = {
|
||||
aKey: joi.boolean(),
|
||||
};
|
||||
const config = {
|
||||
validate: {
|
||||
query: joi.object().keys(queryKeys),
|
||||
},
|
||||
};
|
||||
|
||||
includeMethods.forEach((method) => {
|
||||
const configForMethod = getConfigForMethod({
|
||||
method,
|
||||
associationValidation,
|
||||
config,
|
||||
});
|
||||
const { query } = configForMethod.validate;
|
||||
|
||||
Object.keys(associationValidation).forEach((key) => {
|
||||
t.ifError(
|
||||
query.validate({ [key]: models }).error
|
||||
, `applies ${key} to validate.query`
|
||||
);
|
||||
});
|
||||
|
||||
Object.keys(queryKeys).forEach((key) => {
|
||||
t.ifError(
|
||||
query.validate({ [key]: true }).error
|
||||
, `applies ${key} to validate.query`
|
||||
);
|
||||
});
|
||||
|
||||
t.truthy(
|
||||
query.validate({ notAThing: true }).error
|
||||
, 'errors on a non-valid key'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test('validate.payload associationValidation', (t) => {
|
||||
const { attributeValidation } = t.context;
|
||||
|
||||
payloadMethods.forEach((method) => {
|
||||
@ -93,20 +268,308 @@ test('get-config-for-method validate.payload associationValidation', (t) => {
|
||||
const { payload } = configForMethod.validate;
|
||||
|
||||
Object.keys(attributeValidation).forEach((key) => {
|
||||
t.truthy(
|
||||
payload[key]
|
||||
t.ifError(
|
||||
payload.validate({ [key]: true }).error
|
||||
, `applies attributeValidation (${key}) to validate.payload`
|
||||
);
|
||||
});
|
||||
|
||||
t.truthy(
|
||||
payload.validate({ notAThing: true }).error
|
||||
, 'errors on a non-valid key'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test('get-config-for-method does not modify initial config on multiple passes', (t) => {
|
||||
test('payload attributeValidation w/ config as plain object', (t) => {
|
||||
const { attributeValidation } = t.context;
|
||||
const config = {
|
||||
validate: {
|
||||
payload: {
|
||||
aKey: joi.boolean(),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
payloadMethods.forEach((method) => {
|
||||
const configForMethod = getConfigForMethod({
|
||||
method,
|
||||
attributeValidation,
|
||||
config,
|
||||
});
|
||||
const { payload } = configForMethod.validate;
|
||||
|
||||
const keys = [
|
||||
...Object.keys(attributeValidation),
|
||||
...Object.keys(config.validate.payload),
|
||||
];
|
||||
|
||||
keys.forEach((key) => {
|
||||
t.ifError(
|
||||
payload.validate({ [key]: true }).error
|
||||
, `applies ${key} to validate.payload`
|
||||
);
|
||||
});
|
||||
|
||||
t.truthy(
|
||||
payload.validate({ notAThing: true }).error
|
||||
, 'errors on a non-valid key'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test('payload attributeValidation w/ config as joi object', (t) => {
|
||||
const { attributeValidation } = t.context;
|
||||
const payloadKeys = {
|
||||
aKey: joi.boolean(),
|
||||
};
|
||||
const config = {
|
||||
validate: {
|
||||
payload: joi.object().keys(payloadKeys),
|
||||
},
|
||||
};
|
||||
|
||||
payloadMethods.forEach((method) => {
|
||||
const configForMethod = getConfigForMethod({
|
||||
method,
|
||||
attributeValidation,
|
||||
config,
|
||||
});
|
||||
const { payload } = configForMethod.validate;
|
||||
|
||||
const keys = [
|
||||
...Object.keys(attributeValidation),
|
||||
...Object.keys(payloadKeys),
|
||||
];
|
||||
|
||||
keys.forEach((key) => {
|
||||
t.ifError(
|
||||
payload.validate({ [key]: true }).error
|
||||
, `applies ${key} to validate.payload`
|
||||
);
|
||||
});
|
||||
|
||||
t.truthy(
|
||||
payload.validate({ notAThing: true }).error
|
||||
, 'errors on a non-valid key'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test('validate.params scopeParamsMethods', (t) => {
|
||||
const { scopes } = t.context;
|
||||
|
||||
scopeParamsMethods.forEach((method) => {
|
||||
const configForMethod = getConfigForMethod({ method, scopes });
|
||||
const { params } = configForMethod.validate;
|
||||
|
||||
scopes.forEach((key) => {
|
||||
t.ifError(
|
||||
params.validate({ scope: key }).error
|
||||
, `applies "scope: ${key}" to validate.params`
|
||||
);
|
||||
});
|
||||
|
||||
t.truthy(
|
||||
params.validate({ scope: 'notAthing' }).error
|
||||
, 'errors on a non-valid key'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test('params scopeParamsMethods w/ config as plain object', (t) => {
|
||||
const { scopes } = t.context;
|
||||
const config = {
|
||||
validate: {
|
||||
params: {
|
||||
aKey: joi.boolean(),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
scopeParamsMethods.forEach((method) => {
|
||||
const configForMethod = getConfigForMethod({
|
||||
method,
|
||||
scopes,
|
||||
config,
|
||||
});
|
||||
const { params } = configForMethod.validate;
|
||||
|
||||
scopes.forEach((key) => {
|
||||
t.ifError(
|
||||
params.validate({ scope: key }).error
|
||||
, `applies "scope: ${key}" to validate.params`
|
||||
);
|
||||
});
|
||||
|
||||
Object.keys(config.validate.params).forEach((key) => {
|
||||
t.ifError(
|
||||
params.validate({ [key]: true }).error
|
||||
, `applies ${key} to validate.params`
|
||||
);
|
||||
});
|
||||
|
||||
t.truthy(
|
||||
params.validate({ notAThing: true }).error
|
||||
, 'errors on a non-valid key'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test('params scopeParamsMethods w/ config as joi object', (t) => {
|
||||
const { scopes } = t.context;
|
||||
const paramsKeys = {
|
||||
aKey: joi.boolean(),
|
||||
};
|
||||
const config = {
|
||||
validate: {
|
||||
params: joi.object().keys(paramsKeys),
|
||||
},
|
||||
};
|
||||
|
||||
scopeParamsMethods.forEach((method) => {
|
||||
const configForMethod = getConfigForMethod({
|
||||
method,
|
||||
scopes,
|
||||
config,
|
||||
});
|
||||
const { params } = configForMethod.validate;
|
||||
|
||||
scopes.forEach((key) => {
|
||||
t.ifError(
|
||||
params.validate({ scope: key }).error
|
||||
, `applies "scope: ${key}" to validate.params`
|
||||
);
|
||||
});
|
||||
|
||||
Object.keys(paramsKeys).forEach((key) => {
|
||||
t.ifError(
|
||||
params.validate({ [key]: true }).error
|
||||
, `applies ${key} to validate.params`
|
||||
);
|
||||
});
|
||||
|
||||
t.truthy(
|
||||
params.validate({ notAThing: true }).error
|
||||
, 'errors on a non-valid key'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
test('validate.payload idParamsMethods', (t) => {
|
||||
idParamsMethods.forEach((method) => {
|
||||
const configForMethod = getConfigForMethod({ method });
|
||||
const { params } = configForMethod.validate;
|
||||
|
||||
t.ifError(
|
||||
params.validate({ id: 'aThing' }).error
|
||||
, 'applies id to validate.params'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test('validate.query restrictMethods', (t) => {
|
||||
restrictMethods.forEach((method) => {
|
||||
const configForMethod = getConfigForMethod({ method });
|
||||
const { query } = configForMethod.validate;
|
||||
const restrictKeys = ['limit', 'offset'];
|
||||
|
||||
restrictKeys.forEach((key) => {
|
||||
t.ifError(
|
||||
query.validate({ [key]: 0 }).error
|
||||
, `applies restriction (${key}) to validate.query`
|
||||
);
|
||||
});
|
||||
|
||||
t.ifError(
|
||||
query.validate({ order: ['thing', 'DESC'] }).error
|
||||
, 'applies `order` to validate.query'
|
||||
);
|
||||
|
||||
t.truthy(
|
||||
query.validate({ notAThing: true }).error
|
||||
, 'errors on a non-valid key'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test('validate.query restrictMethods w/ config as plain object', (t) => {
|
||||
const config = {
|
||||
validate: {
|
||||
query: {
|
||||
aKey: joi.boolean(),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
restrictMethods.forEach((method) => {
|
||||
const configForMethod = getConfigForMethod({
|
||||
method,
|
||||
config,
|
||||
});
|
||||
const { query } = configForMethod.validate;
|
||||
|
||||
const keys = [
|
||||
...Object.keys(config.validate.query),
|
||||
];
|
||||
|
||||
keys.forEach((key) => {
|
||||
t.ifError(
|
||||
query.validate({ [key]: true }).error
|
||||
, `applies ${key} to validate.query`
|
||||
);
|
||||
});
|
||||
|
||||
t.truthy(
|
||||
query.validate({ notAThing: true }).error
|
||||
, 'errors on a non-valid key'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test('validate.query restrictMethods w/ config as joi object', (t) => {
|
||||
const queryKeys = {
|
||||
aKey: joi.boolean(),
|
||||
};
|
||||
const config = {
|
||||
validate: {
|
||||
query: joi.object().keys(queryKeys),
|
||||
},
|
||||
};
|
||||
|
||||
whereMethods.forEach((method) => {
|
||||
const configForMethod = getConfigForMethod({
|
||||
method,
|
||||
config,
|
||||
});
|
||||
const { query } = configForMethod.validate;
|
||||
|
||||
const keys = [
|
||||
...Object.keys(queryKeys),
|
||||
];
|
||||
|
||||
keys.forEach((key) => {
|
||||
t.ifError(
|
||||
query.validate({ [key]: true }).error
|
||||
, `applies ${key} to validate.query`
|
||||
);
|
||||
});
|
||||
|
||||
t.truthy(
|
||||
query.validate({ notAThing: true }).error
|
||||
, 'errors on a non-valid key'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
test('does not modify initial config on multiple passes', (t) => {
|
||||
const { config } = t.context;
|
||||
const originalConfig = { ...config };
|
||||
|
||||
whereMethods.forEach((method) => {
|
||||
getConfigForMethod({ method, config });
|
||||
getConfigForMethod({ method, ...t.context });
|
||||
});
|
||||
|
||||
t.deepEqual(
|
||||
|
10
src/index.js
10
src/index.js
@ -32,11 +32,12 @@ const register = (server, options = {}, next) => {
|
||||
const { plural, singular } = model.options.name;
|
||||
model._plural = plural.toLowerCase();
|
||||
model._singular = singular.toLowerCase();
|
||||
model._Plural = plural;
|
||||
model._Singular = singular;
|
||||
|
||||
// Join tables
|
||||
if (model.options.name.singular !== model.name) continue;
|
||||
|
||||
crud(server, model, options);
|
||||
|
||||
for (const key of Object.keys(model.associations)) {
|
||||
const association = model.associations[key];
|
||||
@ -92,6 +93,13 @@ const register = (server, options = {}, next) => {
|
||||
}
|
||||
}
|
||||
|
||||
// build the methods for each model now that we've defined all the
|
||||
// associations
|
||||
Object.keys(models).forEach((modelName) => {
|
||||
const model = models[modelName];
|
||||
crud(server, model, options);
|
||||
});
|
||||
|
||||
next();
|
||||
};
|
||||
|
||||
|
51
src/utils.js
51
src/utils.js
@ -1,9 +1,13 @@
|
||||
import { omit, identity } from 'lodash';
|
||||
import { omit, identity, toNumber, isString, isUndefined } from 'lodash';
|
||||
import { notImplemented } from 'boom';
|
||||
|
||||
const sequelizeKeys = ['include', 'order', 'limit', 'offset'];
|
||||
|
||||
export const parseInclude = request => {
|
||||
const include = Array.isArray(request.query.include) ? request.query.include
|
||||
: [request.query.include];
|
||||
const include = Array.isArray(request.query.include)
|
||||
? request.query.include
|
||||
: [request.query.include]
|
||||
;
|
||||
|
||||
const noGetDb = typeof request.getDb !== 'function';
|
||||
const noRequestModels = !request.models;
|
||||
@ -16,6 +20,13 @@ export const parseInclude = request => {
|
||||
const { models } = noGetDb ? request : request.getDb();
|
||||
|
||||
return include.map(a => {
|
||||
const singluarOrPluralMatch = Object.keys(models).find((modelName) => {
|
||||
const { _singular, _plural } = models[modelName];
|
||||
return _singular === a || _plural === a;
|
||||
});
|
||||
|
||||
if (singluarOrPluralMatch) return models[singluarOrPluralMatch];
|
||||
|
||||
if (typeof a === 'string') return models[a];
|
||||
|
||||
if (a && typeof a.model === 'string' && a.model.length) {
|
||||
@ -27,7 +38,7 @@ export const parseInclude = request => {
|
||||
};
|
||||
|
||||
export const parseWhere = request => {
|
||||
const where = omit(request.query, 'include');
|
||||
const where = omit(request.query, sequelizeKeys);
|
||||
|
||||
for (const key of Object.keys(where)) {
|
||||
try {
|
||||
@ -40,6 +51,38 @@ export const parseWhere = request => {
|
||||
return where;
|
||||
};
|
||||
|
||||
export const parseLimitAndOffset = (request) => {
|
||||
const { limit, offset } = request.query;
|
||||
const out = {};
|
||||
if (!isUndefined(limit)) {
|
||||
out.limit = toNumber(limit);
|
||||
}
|
||||
if (!isUndefined(offset)) {
|
||||
out.offset = toNumber(offset);
|
||||
}
|
||||
return out;
|
||||
};
|
||||
|
||||
export const parseOrder = (request) => {
|
||||
const { order } = request.query;
|
||||
|
||||
if (!order) return null;
|
||||
|
||||
// transform to an array so sequelize will escape the input for us and
|
||||
// maintain security. See http://docs.sequelizejs.com/en/latest/docs/querying/#ordering
|
||||
if (isString(order)) return order.split(' ');
|
||||
|
||||
for (const key of Object.keys(order)) {
|
||||
try {
|
||||
order[key] = JSON.parse(order[key]);
|
||||
} catch (e) {
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
return order;
|
||||
};
|
||||
|
||||
export const getMethod = (model, association, plural = true, method = 'get') => {
|
||||
const a = plural ? association.original.plural : association.original.singular;
|
||||
const b = plural ? association.original.singular : association.original.plural; // alternative
|
||||
|
113
src/utils.test.js
Normal file
113
src/utils.test.js
Normal file
@ -0,0 +1,113 @@
|
||||
import test from 'ava';
|
||||
import { parseLimitAndOffset, parseOrder, parseWhere } from './utils.js';
|
||||
|
||||
test.beforeEach((t) => {
|
||||
t.context.request = { query: {} };
|
||||
});
|
||||
|
||||
test('parseLimitAndOffset is a function', (t) => {
|
||||
t.is(typeof parseLimitAndOffset, 'function');
|
||||
});
|
||||
|
||||
test('parseLimitAndOffset returns limit and offset', (t) => {
|
||||
const { request } = t.context;
|
||||
request.query.limit = 1;
|
||||
request.query.offset = 2;
|
||||
request.query.thing = 'hi';
|
||||
|
||||
t.is(
|
||||
parseLimitAndOffset(request).limit
|
||||
, request.query.limit
|
||||
);
|
||||
|
||||
t.is(
|
||||
parseLimitAndOffset(request).offset
|
||||
, request.query.offset
|
||||
);
|
||||
});
|
||||
|
||||
test('parseLimitAndOffset returns limit and offset as numbers', (t) => {
|
||||
const { request } = t.context;
|
||||
const limit = 1;
|
||||
const offset = 2;
|
||||
request.query.limit = `${limit}`;
|
||||
request.query.offset = `${offset}`;
|
||||
request.query.thing = 'hi';
|
||||
|
||||
t.is(
|
||||
parseLimitAndOffset(request).limit
|
||||
, limit
|
||||
);
|
||||
|
||||
t.is(
|
||||
parseLimitAndOffset(request).offset
|
||||
, offset
|
||||
);
|
||||
});
|
||||
|
||||
test('parseOrder is a function', (t) => {
|
||||
t.is(typeof parseOrder, 'function');
|
||||
});
|
||||
|
||||
test('parseOrder returns order when a string', (t) => {
|
||||
const { request } = t.context;
|
||||
const order = 'thing';
|
||||
request.query.order = order;
|
||||
request.query.thing = 'hi';
|
||||
|
||||
t.deepEqual(
|
||||
parseOrder(request)
|
||||
, [order]
|
||||
);
|
||||
});
|
||||
|
||||
test('parseOrder returns order when json', (t) => {
|
||||
const { request } = t.context;
|
||||
const order = [{ model: 'User' }, 'DESC'];
|
||||
request.query.order = [JSON.stringify({ model: 'User' }), 'DESC'];
|
||||
request.query.thing = 'hi';
|
||||
|
||||
t.deepEqual(
|
||||
parseOrder(request)
|
||||
, order
|
||||
);
|
||||
});
|
||||
|
||||
test('parseOrder returns null when not defined', (t) => {
|
||||
const { request } = t.context;
|
||||
request.query.thing = 'hi';
|
||||
|
||||
t.is(
|
||||
parseOrder(request)
|
||||
, null
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
test('parseWhere is a function', (t) => {
|
||||
t.is(typeof parseWhere, 'function');
|
||||
});
|
||||
|
||||
test('parseWhere returns the non-sequelize keys', (t) => {
|
||||
const { request } = t.context;
|
||||
request.query.order = 'thing';
|
||||
request.query.include = 'User';
|
||||
request.query.limit = 2;
|
||||
request.query.thing = 'hi';
|
||||
|
||||
t.deepEqual(
|
||||
parseWhere(request)
|
||||
, { thing: 'hi' }
|
||||
);
|
||||
});
|
||||
|
||||
test('parseWhere returns json converted keys', (t) => {
|
||||
const { request } = t.context;
|
||||
request.query.order = 'hi';
|
||||
request.query.thing = '{"id": {"$in": [2, 3]}}';
|
||||
|
||||
t.deepEqual(
|
||||
parseWhere(request)
|
||||
, { thing: { id: { $in: [2, 3] } } }
|
||||
);
|
||||
});
|
Reference in New Issue
Block a user