mirror of
https://github.com/serverless/serverless.git
synced 2025-12-08 19:46:03 +00:00
339 lines
10 KiB
JavaScript
339 lines
10 KiB
JavaScript
'use strict';
|
|
|
|
const { expect } = require('chai');
|
|
|
|
const Ajv = require('ajv');
|
|
const memoize = require('memoizee');
|
|
const normalizeAjvErrors = require('./normalizeAjvErrors');
|
|
|
|
describe('#normalizeAjvErrors', () => {
|
|
const resolveAjv = memoize(() => new Ajv({ allErrors: true }));
|
|
const resolveValidate = memoize(schema => resolveAjv().compile(schema));
|
|
|
|
const schema = {
|
|
type: 'object',
|
|
properties: {
|
|
provider: {
|
|
anyOf: [
|
|
{
|
|
type: 'object',
|
|
properties: {
|
|
name: { const: 'aws' },
|
|
deploymentBucket: {
|
|
type: 'object',
|
|
properties: { maxPreviousDeploymentArtifacts: { type: 'number' } },
|
|
},
|
|
},
|
|
},
|
|
{
|
|
type: 'object',
|
|
properties: {
|
|
name: { const: 'other' },
|
|
otherProp: {
|
|
type: 'object',
|
|
properties: { foo: { type: 'number' } },
|
|
},
|
|
},
|
|
},
|
|
],
|
|
},
|
|
custom: {
|
|
type: 'object',
|
|
properties: {
|
|
someCustom: {
|
|
anyOf: [
|
|
{
|
|
type: 'object',
|
|
properties: {
|
|
name: { const: 'first' },
|
|
},
|
|
},
|
|
{
|
|
type: 'object',
|
|
properties: {
|
|
name: { const: 'second' },
|
|
},
|
|
},
|
|
],
|
|
},
|
|
},
|
|
},
|
|
package: {
|
|
type: 'object',
|
|
properties: {
|
|
include: { type: 'array', items: { type: 'string' } },
|
|
},
|
|
additionalProperties: false,
|
|
},
|
|
functions: {
|
|
type: 'object',
|
|
patternProperties: {
|
|
'^[a-zA-Z0-9-_]+$': {
|
|
type: 'object',
|
|
properties: {
|
|
handler: {
|
|
type: 'string',
|
|
},
|
|
events: {
|
|
type: 'array',
|
|
items: {
|
|
anyOf: [
|
|
{
|
|
type: 'object',
|
|
properties: { __schemaWorkaround__: { const: null } },
|
|
required: ['__schemaWorkaround__'],
|
|
additionalProperties: false,
|
|
},
|
|
{
|
|
type: 'object',
|
|
properties: {
|
|
http: {
|
|
anyOf: [
|
|
{ type: 'string', pattern: '^(get|post|put) [a-zA-Z0-9]+$' },
|
|
{
|
|
type: 'object',
|
|
properties: {
|
|
path: { type: 'string' },
|
|
method: { type: 'string' },
|
|
},
|
|
required: ['path', 'method'],
|
|
additionalProperties: false,
|
|
},
|
|
],
|
|
},
|
|
},
|
|
required: ['http'],
|
|
additionalProperties: false,
|
|
},
|
|
],
|
|
},
|
|
},
|
|
},
|
|
required: ['handler'],
|
|
additionalProperties: false,
|
|
},
|
|
'additionalProperties': false,
|
|
},
|
|
additionalProperties: false,
|
|
},
|
|
},
|
|
additionalProperties: false,
|
|
};
|
|
|
|
const resolveNormalizedErrors = config => {
|
|
const validate = resolveValidate(schema);
|
|
validate(config);
|
|
if (!validate.errors) return [];
|
|
return normalizeAjvErrors(validate.errors);
|
|
};
|
|
|
|
let errors;
|
|
before(() => {
|
|
errors = resolveNormalizedErrors({
|
|
foo: 'bar',
|
|
provider: {
|
|
name: 'aws',
|
|
deploymentBucket: { maxPreviousDeploymentArtifacts: '20' },
|
|
},
|
|
custom: {
|
|
someCustom: { name: 'third' },
|
|
},
|
|
package: { incclude: ['./folder'] },
|
|
functions: {
|
|
'invalid name': {},
|
|
'foo': {
|
|
handler: 'foo',
|
|
events: [
|
|
{
|
|
bar: {},
|
|
},
|
|
{
|
|
http: { path: '/foo', method: 'GET' },
|
|
method: 'GET',
|
|
},
|
|
{
|
|
http: null,
|
|
method: 'GET',
|
|
},
|
|
{
|
|
http: { path: '/foo', method: 'GET', other: 'foo' },
|
|
},
|
|
{
|
|
http: 'gets foo',
|
|
},
|
|
{
|
|
http: { method: 'GET' },
|
|
},
|
|
],
|
|
},
|
|
},
|
|
});
|
|
});
|
|
|
|
describe('Reporting', () => {
|
|
it('should report error for unrecognized root property', () =>
|
|
expect(
|
|
errors.some(error => {
|
|
if (error.dataPath !== '') return false;
|
|
if (error.keyword !== 'additionalProperties') return false;
|
|
error.isExpected = true;
|
|
return true;
|
|
})
|
|
).to.be.true);
|
|
it('should report error for unrecognized deep level property', () =>
|
|
expect(
|
|
errors.some(error => {
|
|
if (error.dataPath !== '.package') return false;
|
|
if (error.keyword !== 'additionalProperties') return false;
|
|
error.isExpected = true;
|
|
return true;
|
|
})
|
|
).to.be.true);
|
|
it('should report error for invalid function name', () =>
|
|
expect(
|
|
errors.some(error => {
|
|
if (error.dataPath !== '.functions') return false;
|
|
if (error.keyword !== 'additionalProperties') return false;
|
|
error.isExpected = true;
|
|
return true;
|
|
})
|
|
).to.be.true);
|
|
it('should report error for unrecognized event', () =>
|
|
expect(
|
|
errors.some(error => {
|
|
if (error.dataPath !== ".functions['foo'].events[0]") return false;
|
|
if (error.keyword !== 'anyOf') return false;
|
|
error.isExpected = true;
|
|
return true;
|
|
})
|
|
).to.be.true);
|
|
it('should report error for unrecognized property at event type configuration level', () =>
|
|
expect(
|
|
errors.some(error => {
|
|
if (error.dataPath !== ".functions['foo'].events[1]") return false;
|
|
if (error.keyword !== 'additionalProperties') return false;
|
|
error.isExpected = true;
|
|
return true;
|
|
})
|
|
).to.be.true);
|
|
it(
|
|
'should report error for unrecognized property at event type configuration level, ' +
|
|
'as result of improper indentation in YAML config',
|
|
() =>
|
|
// Catches following yaml issue:
|
|
//
|
|
// functions:
|
|
// foo:
|
|
// events:
|
|
// - http:
|
|
// method: GET # Should be additionally indented
|
|
expect(
|
|
errors.some(error => {
|
|
if (error.dataPath !== ".functions['foo'].events[2]") return false;
|
|
if (error.keyword !== 'additionalProperties') return false;
|
|
error.isExpected = true;
|
|
return true;
|
|
})
|
|
).to.be.true
|
|
);
|
|
it(
|
|
'should report error in anyOf case, where two types are possible (string and object), ' +
|
|
'and object with unrecognized property was used',
|
|
() =>
|
|
expect(
|
|
errors.some(error => {
|
|
if (error.dataPath !== ".functions['foo'].events[3].http") return false;
|
|
if (error.keyword !== 'additionalProperties') return false;
|
|
error.isExpected = true;
|
|
return true;
|
|
})
|
|
).to.be.true
|
|
);
|
|
it(
|
|
'should report error in anyOf case, where two types are possible (string and object), ' +
|
|
'and invalid string was used',
|
|
() =>
|
|
expect(
|
|
errors.some(error => {
|
|
if (error.dataPath !== ".functions['foo'].events[4].http") return false;
|
|
if (error.keyword !== 'pattern') return false;
|
|
error.isExpected = true;
|
|
return true;
|
|
})
|
|
).to.be.true
|
|
);
|
|
it(
|
|
'should report in anyOf case, where two types are possible (string and object), ' +
|
|
'and object with missing required property was used',
|
|
() =>
|
|
expect(
|
|
errors.some(error => {
|
|
if (error.dataPath !== ".functions['foo'].events[5].http") return false;
|
|
if (error.keyword !== 'required') return false;
|
|
error.isExpected = true;
|
|
return true;
|
|
})
|
|
).to.be.true
|
|
);
|
|
it(
|
|
'should report in anyOf case, where two values of same (object) type are possible ' +
|
|
'and for one variant error for deeper path was reported',
|
|
() =>
|
|
expect(
|
|
errors.some(error => {
|
|
if (error.dataPath !== '.provider.deploymentBucket.maxPreviousDeploymentArtifacts') {
|
|
return false;
|
|
}
|
|
if (error.keyword !== 'type') return false;
|
|
error.isExpected = true;
|
|
return true;
|
|
})
|
|
).to.be.true
|
|
);
|
|
it(
|
|
'should report in anyOf case, where two values of same (object) type are possible ' +
|
|
'and for all variants errors relate to paths of same depth',
|
|
() =>
|
|
expect(
|
|
errors.some(error => {
|
|
if (error.dataPath !== '.custom.someCustom') {
|
|
return false;
|
|
}
|
|
if (error.keyword !== 'anyOf') return false;
|
|
error.isExpected = true;
|
|
return true;
|
|
})
|
|
).to.be.true
|
|
);
|
|
it('should not report side errors', () =>
|
|
expect(errors.filter(error => !error.isExpected)).to.deep.equal([]));
|
|
});
|
|
|
|
describe('Message customization', () => {
|
|
it('should report "additionalProperties" error with meaningful message', () =>
|
|
expect(
|
|
errors.find(error => {
|
|
if (error.dataPath !== '.package') return false;
|
|
if (error.keyword !== 'additionalProperties') return false;
|
|
return true;
|
|
}).message
|
|
).to.include('unrecognized property '));
|
|
it('should report invalid function name error with meaningful message', () =>
|
|
expect(
|
|
errors.find(error => {
|
|
if (error.dataPath !== '.functions') return false;
|
|
if (error.keyword !== 'additionalProperties') return false;
|
|
return true;
|
|
}).message
|
|
).to.include('must be alphanumeric'));
|
|
it('should report unrecognized event error with a meaningful message', () =>
|
|
expect(
|
|
errors.find(error => {
|
|
if (error.dataPath !== ".functions['foo'].events[0]") return false;
|
|
if (error.keyword !== 'anyOf') return false;
|
|
return true;
|
|
}).message
|
|
).to.include('unsupported function event'));
|
|
});
|
|
});
|