It turns out defaultsDeep doesn't ever correctly combine Joi objects. So, the only option is to use Joi's concat method to combine Joi schemas. This complicates `getConfigForMethod`, but simplifies actual route creation. I ran into this because I'm setting up [lout](https://github.com/hapijs/lout) on a server, and it requires properly formatted Joi schemas. This leads me to believe there was something already wrong and Lout just exposed the problem.
485 lines
11 KiB
JavaScript
485 lines
11 KiB
JavaScript
import test from 'ava';
|
|
import joi from 'joi';
|
|
import
|
|
getConfigForMethod, {
|
|
whereMethods,
|
|
includeMethods,
|
|
payloadMethods,
|
|
scopeParamsMethods,
|
|
idParamsMethods,
|
|
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: joi.array().items(joi.string().valid(t.context.models)),
|
|
};
|
|
|
|
t.context.config = {
|
|
cors: {},
|
|
};
|
|
});
|
|
|
|
test('validate.query seqeulizeOperators', (t) => {
|
|
whereMethods.forEach((method) => {
|
|
const configForMethod = getConfigForMethod({ method });
|
|
const { query } = configForMethod.validate;
|
|
|
|
t.truthy(
|
|
query,
|
|
`applies query validation for ${method}`
|
|
);
|
|
|
|
Object.keys(sequelizeOperators).forEach((operator) => {
|
|
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('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.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('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({
|
|
method,
|
|
attributeValidation,
|
|
associationValidation,
|
|
});
|
|
const { query } = configForMethod.validate;
|
|
|
|
Object.keys(attributeValidation).forEach((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.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('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) => {
|
|
const configForMethod = getConfigForMethod({ method, attributeValidation });
|
|
const { payload } = configForMethod.validate;
|
|
|
|
Object.keys(attributeValidation).forEach((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('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('does not modify initial config on multiple passes', (t) => {
|
|
const { config } = t.context;
|
|
const originalConfig = { ...config };
|
|
|
|
whereMethods.forEach((method) => {
|
|
getConfigForMethod({ method, ...t.context });
|
|
});
|
|
|
|
t.deepEqual(
|
|
config
|
|
, originalConfig
|
|
, 'does not modify the original config object'
|
|
);
|
|
});
|