Skip to content

Commit

Permalink
fix: end-to-end validation
Browse files Browse the repository at this point in the history
  • Loading branch information
patrickhulce committed Aug 2, 2016
1 parent f6729df commit 65a55d4
Show file tree
Hide file tree
Showing 9 changed files with 163 additions and 17 deletions.
1 change: 0 additions & 1 deletion lib/Model.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,6 @@ Model.prototype.nullable = function (value) {
};

Model.prototype.strict = function (value) {
assert.oneOf(this.spec.type, ['object', 'conditional'], 'type');
return this._with({strict: boolOrTrue(value)});
};

Expand Down
29 changes: 18 additions & 11 deletions lib/ValidationResult.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,26 @@ function ValidationResult(value, conforms, errors) {
}

ValidationResult.prototype.merge = function (other, name) {
if (other instanceof ValidationResult) {
var value = typeof name === 'undefined' ?
this.value || other.value :
_.set(_.clone(this.value), name, other.value);

return new ValidationResult(
value,
this.conforms && other.conforms,
this.errors.concat(other.errors)
);
if (!(other instanceof ValidationResult)) {
other = new ValidationResult(other);
}

var value = _.clone(this.value);
if (typeof name === 'string' || typeof name === 'number') {
value = _.set(value, name, other.value);
} else if (_.isArray(value) && _.isArray(other.value)) {
value = this.value.concat(other.value);
} else if (typeof value === 'object' && typeof other.value === 'object') {
value = _.assign({}, value, other.value);
} else {
throw new Error('cannot merge ValidationResult with non-ValidationResult');
value = value || other.value;
}

return new ValidationResult(
value,
this.conforms && other.conforms,
this.errors.concat(other.errors)
);
};

ValidationResult.coalesce = function (validationResults, base) {
Expand Down
2 changes: 1 addition & 1 deletion lib/assert.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ AssertionError.prototype._getErrorMessage = function (operator) {

AssertionError.prototype.toErrorObject = function () {
if (operators.indexOf(this.operator) === -1) {
return {path: this.path, message: this.message};
return {path: this.path || this.valuePath, message: this.message};
}

var path = this.path || this.valuePath;
Expand Down
2 changes: 1 addition & 1 deletion lib/klay.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ var Model = require('./Model');
var _initializedWith;
var klay = function (options) {
options = Object.assign({
extensions: ['builders']
extensions: ['builders', 'date', 'minmax', 'strings']
}, options);

if (_.isEqual(_initializedWith, options)) {
Expand Down
8 changes: 6 additions & 2 deletions lib/transformations.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,11 @@ module.exports = {

assert.typeof(value, 'object');

var transformedRoot = {};
var priorityByType = {object: 1, conditional: 2};
var leftovers = _.omit(value, _.map(children, 'name'));
var orderedChildren = _.sortBy(children, item => priorityByType[item.model.spec.type] || 0);

var transformedRoot = _.assign({}, leftovers);
var validationResults = _.map(orderedChildren, function (item) {
var validation = item.model._validate(
value[item.name],
Expand All @@ -46,7 +48,9 @@ module.exports = {
return {name: item.name, validation: validation};
});

return ValidationResult.coalesce(validationResults, {});
return ValidationResult.
coalesce(validationResults, {}).
merge(leftovers);
}
},
array: {
Expand Down
81 changes: 81 additions & 0 deletions test/e2e.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
var _ = require('lodash');
var klay = relativeRequire('klay.js');

describe('klay', function () {
context('fixtures/document.js', function () {
var fixture = require('./fixtures/document.js');
var model;

beforeEach(function () {
model = fixture(klay());
});

afterEach(function () {
klay.reset();
});

it('should pass valid html document', function () {
var obj = {
id: '12345678-1234-1234-1234-123412341234',
parentId: '12345678-1234-1234-1234-123412341234',
type: 'html',
metadata: {title: 'my document', version: 'html5'},
source: {raw: '<html></html>', text: ''},
createdAt: '2016-07-27T04:51:22.820Z',
updatedAt: '2016-07-27T04:51:22.820Z',
};

var validated = model.validate(obj);
validated.should.have.property('conforms', true);
validated.should.have.property('value').eql(_.defaults({
createdAt: new Date('2016-07-27T04:51:22.820Z'),
updatedAt: new Date('2016-07-27T04:51:22.820Z'),
}, obj));
});

it('should pass valid json document', function () {
var obj = {
id: '12345678-1234-1234-1234-123412341234',
parentId: '12345678-1234-1234-1234-123412341234',
type: 'json',
metadata: {type: 'array', size: '120'},
source: {prop1: 'foobar', prop2: 'something'},
createdAt: '2016-07-27T04:51:22.820Z',
updatedAt: '2016-07-27T04:51:22.820Z',
};

var validated = model.validate(obj);
validated.should.have.property('conforms', true);
validated.should.have.property('value').eql(_.defaults({
metadata: {type: 'array', size: 120},
createdAt: new Date('2016-07-27T04:51:22.820Z'),
updatedAt: new Date('2016-07-27T04:51:22.820Z'),
}, obj));
});

it('should fail invalid html document', function () {
var obj = {
id: '12345678-1234-1234-1234-123412341234',
parentId: '12345678-1234-1234-1234-123412341234',
type: 'html',
metadata: {title: 'foo', version: 'v1', html5: true},
source: {raw: 'something'},
createdAt: '2016-07-27T04:51:22.820Z',
updatedAt: '2016-07-27T04:51:22.820Z',
};

var validated = model.validate(obj);
validated.should.have.property('conforms', false);
validated.should.have.property('errors').eql([
{message: 'unexpected properties: html5', path: 'metadata'},
{message: 'expected value to be defined', path: 'source.text'},
]);

validated.should.have.property('value').eql(_.defaults({
source: {raw: 'something', text: undefined},
createdAt: new Date('2016-07-27T04:51:22.820Z'),
updatedAt: new Date('2016-07-27T04:51:22.820Z'),
}, obj));
});
});
});
37 changes: 37 additions & 0 deletions test/fixtures/document.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
module.exports = function (klay) {
klay.use({defaults: {required: true, strict: true}});
var types = klay.builders;

var HtmlMetadata = types.object({
title: types.string(),
version: types.string(),
});

var JsonMetadata = types.object({
type: types.enum(['object', 'array']),
size: types.integer(),
});

var HtmlSource = types.object({
raw: types.string(),
text: types.string(),
});

var JsonSource = types.object().strict(false);

var Document = {
id: types.uuid(),
parentId: types.uuid(),
type: types.enum(['html', 'json']),
metadata: types.conditional().
option(HtmlMetadata, 'type', 'html').
option(JsonMetadata, 'type', 'json'),
source: types.conditional().
option(HtmlSource, 'type', 'html').
option(JsonSource, 'type', 'json'),
createdAt: types.date(),
updatedAt: types.date(),
};

return types.object(Document);
};
2 changes: 1 addition & 1 deletion test/klay.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ defineTest('klay.js', function (klay) {
var inst;

beforeEach(function () {
inst = klay();
inst = klay({extensions: ['builders']});
});

it('should merge types', function () {
Expand Down
18 changes: 18 additions & 0 deletions test/transformations.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,24 @@ defineTest('transformations.js', function (transformations) {
});
});

it('should pass along unknown children', function () {
transform.call(model, {
id: '1234',
name: 'ABC4ME',
isAdmin: 'false',
unknown: 'value',
}).asObject().should.eql({
conforms: true,
errors: [],
value: {
id: 1234,
name: 'ABC4ME',
isAdmin: false,
unknown: 'value',
},
});
});

it('should fail when a child fails', function () {
transform.call(model, {
id: '1234',
Expand Down

0 comments on commit 65a55d4

Please sign in to comment.