From 73148e0954eaed6f4cc566ed8f813a643e4794b5 Mon Sep 17 00:00:00 2001 From: artem-zakharchenko Date: Fri, 21 Jun 2019 14:58:03 +0200 Subject: [PATCH 1/3] test: introduces custom chai assertions --- .eslintrc.js | 3 +- test/chai.js | 107 ++++++++ test/integration/validate.test.js | 290 ++++++++++----------- test/unit/units/validateBody.test.js | 132 ++++++---- test/unit/units/validateHeaders.test.js | 69 ++--- test/unit/units/validateMethod.test.js | 44 ++-- test/unit/units/validateStatusCode.test.js | 29 ++- test/unit/units/validateURI.test.js | 94 ++++--- 8 files changed, 457 insertions(+), 311 deletions(-) create mode 100644 test/chai.js diff --git a/.eslintrc.js b/.eslintrc.js index 84b5bf14..9d41475f 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -14,6 +14,7 @@ module.exports = { // Temporary overrides. Logic to be rewritten. // TODO https://github.com/apiaryio/gavel.js/issues/150 - 'no-param-reassign': 'off' + 'no-param-reassign': 'off', + 'no-unused-expressions': 'off' } }; diff --git a/test/chai.js b/test/chai.js new file mode 100644 index 00000000..ff299192 --- /dev/null +++ b/test/chai.js @@ -0,0 +1,107 @@ +/* eslint-disable no-underscore-dangle */ +const chai = require('chai'); + +const stringify = (obj) => { + return JSON.stringify(obj, null, 2); +}; + +chai.use(({ Assertion }, utils) => { + utils.addProperty(Assertion.prototype, 'valid', function() { + const { isValid } = this._obj; + const stringifiedObj = stringify(this._obj); + + this.assert( + isValid === true, + ` +Expected the following HTTP message field: + +${stringifiedObj} + +to have "isValid" equal #{exp}, but got #{act}'. +`, + ` +Expected the following HTTP message field: + +${stringifiedObj} + +to be invalid, but it is actually valid.` + ); + }); + + Assertion.addMethod('validator', function(expectedValue) { + const { validator: actualValue } = this._obj; + const stringifiedObj = stringify(this._obj); + + this.assert( + actualValue === expectedValue, + ` +Expected the following HTTP message field: + +${stringifiedObj} + +to have "${expectedValue}" validator, but got "${actualValue}". + `, + ` +Expected the following HTTP message field: + +${stringifiedObj} + +to not have validator equal to "${expectedValue}". +`, + expectedValue, + actualValue + ); + }); + + Assertion.addMethod('expectedType', function(expectedValue) { + const { expectedType: actualValue } = this._obj; + const stringifiedObj = stringify(this._obj); + + this.assert( + actualValue === expectedValue, + ` +Expected the following HTTP message field: + +${stringifiedObj} + +to have an "expectedType" equal to "${expectedValue}", but got "${actualValue}". + `, + ` +Expected the following HTTP message field: + +${stringifiedObj} + +to not have an "expectedType" of "${expectedValue}". + `, + expectedValue, + actualValue + ); + }); + + Assertion.addMethod('realType', function(expectedValue) { + const { realType: actualValue } = this._obj; + const stringifiedObj = stringify(this._obj); + + this.assert( + actualValue === expectedValue, + ` +Expected the following HTTP message field: + +${stringifiedObj} + +to have an "realType" equal to "${expectedValue}", but got "${actualValue}". +`, + ` +Expected the following HTTP message field: + +${stringifiedObj} + +to not have an "realType" of "${expectedValue}". + `, + expectedValue, + actualValue + ); + }); +}); + +module.exports = chai; diff --git a/test/integration/validate.test.js b/test/integration/validate.test.js index ea907288..2c6712ea 100644 --- a/test/integration/validate.test.js +++ b/test/integration/validate.test.js @@ -1,29 +1,9 @@ -const { assert } = require('chai'); +const { expect } = require('../chai'); const { validate } = require('../../lib/validate'); -const isValid = (result, expectedValid = true) => { - it(`sets "isValid" to ${JSON.stringify(expectedValid)}`, () => { - assert.propertyVal(result, 'isValid', expectedValid); - }); -}; - -const validator = (obj, expected) => { - it(`has "${expected}" validator`, () => { - assert.propertyVal(obj, 'validator', expected); - }); -}; - -const createTypeAssertion = (typeName, propName) => (obj, expected) => { - it(`has "${expected}" ${typeName} type`, () => { - assert.propertyVal(obj, propName, expected); - }); -}; - -const realType = createTypeAssertion('real', 'realType'); -const expectedType = createTypeAssertion('expected', 'expectedType'); const noErrors = (obj) => { it('has no errors', () => { - assert.lengthOf(obj.errors, 0); + expect(obj.errors).to.have.length(0); }); }; @@ -38,39 +18,44 @@ describe('validate', () => { }; const result = validate(request, request); - isValid(result); + it('marks pair as valid', () => { + expect(result).to.be.valid; + }); it('contains all validatable keys', () => { - assert.hasAllKeys(result.fields, ['method', 'headers', 'body']); + expect(result.fields).to.have.all.keys(['method', 'headers', 'body']); }); describe('method', () => { - isValid(result.fields.method); - validator(result.fields.method, null); - expectedType(result.fields.method, 'text/vnd.apiary.method'); - realType(result.fields.method, 'text/vnd.apiary.method'); + expect(result.fields.method).to.be.valid; + expect(result.fields.method).to.have.validator(null); + expect(result.fields.method).to.have.expectedType( + 'text/vnd.apiary.method' + ); + expect(result.fields.method).to.have.realType('text/vnd.apiary.method'); + noErrors(result.fields.method); }); describe('headers', () => { - isValid(result.fields.headers); - validator(result.fields.headers, 'HeadersJsonExample'); - expectedType( - result.fields.headers, + expect(result.fields.headers).to.be.valid; + expect(result.fields.headers).to.have.validator('HeadersJsonExample'); + expect(result.fields.headers).to.have.expectedType( 'application/vnd.apiary.http-headers+json' ); - realType( - result.fields.headers, + expect(result.fields.headers).to.have.realType( 'application/vnd.apiary.http-headers+json' ); + noErrors(result.fields.headers); }); describe('body', () => { - isValid(result.fields.body); - validator(result.fields.body, 'JsonExample'); - expectedType(result.fields.body, 'application/json'); - realType(result.fields.body, 'application/json'); + expect(result.fields.body).to.be.valid; + expect(result.fields.body).to.have.validator('JsonExample'); + expect(result.fields.body).to.have.expectedType('application/json'); + expect(result.fields.body).to.have.realType('application/json'); + noErrors(result.fields.body); }); }); @@ -91,26 +76,29 @@ describe('validate', () => { } ); - isValid(result, false); + it('marks pairs as invalid', () => { + expect(result).to.not.be.valid; + }); it('contains all validatable keys', () => { - assert.hasAllKeys(result.fields, ['method', 'headers', 'body']); + expect(result.fields).to.have.all.keys(['method', 'headers', 'body']); }); describe('method', () => { - isValid(result.fields.method, false); - validator(result.fields.method, null); - expectedType(result.fields.method, 'text/vnd.apiary.method'); - realType(result.fields.method, 'text/vnd.apiary.method'); + expect(result.fields.method).to.not.be.valid; + expect(result.fields.method).to.have.validator(null); + expect(result.fields.method).to.have.expectedType( + 'text/vnd.apiary.method' + ); + expect(result.fields.method).to.have.realType('text/vnd.apiary.method'); describe('produces one error', () => { it('exactly one error', () => { - assert.lengthOf(result.fields.method.errors, 1); + expect(result.fields.method.errors).to.have.length(1); }); it('has explanatory message', () => { - assert.propertyVal( - result.fields.method.errors[0], + expect(result.fields.method.errors[0]).to.have.property( 'message', 'Expected "method" field to equal "PUT", but got "POST".' ); @@ -119,33 +107,31 @@ describe('validate', () => { }); describe('headers', () => { - isValid(result.fields.headers); - validator(result.fields.headers, 'HeadersJsonExample'); - expectedType( - result.fields.headers, + expect(result.fields.headers).to.be.valid; + expect(result.fields.headers).to.have.validator('HeadersJsonExample'); + expect(result.fields.headers).to.have.expectedType( 'application/vnd.apiary.http-headers+json' ); - realType( - result.fields.headers, + expect(result.fields.headers).to.have.realType( 'application/vnd.apiary.http-headers+json' ); + noErrors(result.fields.headers); }); describe('body', () => { - isValid(result.fields.body, false); - validator(result.fields.body, 'JsonExample'); - expectedType(result.fields.body, 'application/json'); - realType(result.fields.body, 'application/json'); + expect(result.fields.body).to.not.be.valid; + expect(result.fields.body).to.have.validator('JsonExample'); + expect(result.fields.body).to.have.expectedType('application/json'); + expect(result.fields.body).to.have.realType('application/json'); describe('produces an error', () => { it('exactly one error', () => { - assert.lengthOf(result.fields.body.errors, 1); + expect(result.fields.body.errors).to.have.length(1); }); it('has explanatory message', () => { - assert.propertyVal( - result.fields.body.errors[0], + expect(result.fields.body.errors[0]).to.have.property( 'message', `At '' Invalid type: object (expected integer)` ); @@ -164,45 +150,46 @@ describe('validate', () => { }; const result = validate(response, response); - it('returns validation result object', () => { - assert.isObject(result); - }); - - it('sets "isValid" to true', () => { - assert.propertyVal(result, 'isValid', true); + it('marks pairs as valid', () => { + expect(result).to.be.valid; }); it('contains all validatable keys', () => { - assert.hasAllKeys(result.fields, ['statusCode', 'headers', 'body']); + expect(result.fields).to.have.all.keys(['statusCode', 'headers', 'body']); }); describe('statusCode', () => { - isValid(result.fields.statusCode); - validator(result.fields.statusCode, 'TextDiff'); - expectedType(result.fields.statusCode, 'text/vnd.apiary.status-code'); - realType(result.fields.statusCode, 'text/vnd.apiary.status-code'); + expect(result.fields.statusCode).to.be.valid; + expect(result.fields.statusCode).to.have.validator('TextDiff'); + expect(result.fields.statusCode).to.have.expectedType( + 'text/vnd.apiary.status-code' + ); + expect(result.fields.statusCode).to.have.realType( + 'text/vnd.apiary.status-code' + ); + noErrors(result.fields.statusCode); }); describe('headers', () => { - isValid(result.fields.headers); - validator(result.fields.headers, 'HeadersJsonExample'); - expectedType( - result.fields.headers, + expect(result.fields.headers).to.be.valid; + expect(result.fields.headers).to.have.validator('HeadersJsonExample'); + expect(result.fields.headers).to.have.expectedType( 'application/vnd.apiary.http-headers+json' ); - realType( - result.fields.headers, + expect(result.fields.headers).to.have.realType( 'application/vnd.apiary.http-headers+json' ); + noErrors(result.fields.headers); }); describe('body', () => { - isValid(result.fields.body); - validator(result.fields.body, 'JsonExample'); - expectedType(result.fields.body, 'application/json'); - realType(result.fields.body, 'application/json'); + expect(result.fields.body).to.be.valid; + expect(result.fields.body).to.have.validator('JsonExample'); + expect(result.fields.body).to.have.expectedType('application/json'); + expect(result.fields.body).to.have.realType('application/json'); + noErrors(result.fields.body); }); }); @@ -222,26 +209,31 @@ describe('validate', () => { }; const result = validate(expectedResponse, realResponse); - isValid(result, false); + it('marks pairs as invalid', () => { + expect(result).to.not.be.valid; + }); it('contains all validatable keys', () => { - assert.hasAllKeys(result.fields, ['statusCode', 'headers']); + expect(result.fields).to.have.all.keys(['statusCode', 'headers']); }); describe('statusCode', () => { - isValid(result.fields.statusCode, false); - validator(result.fields.statusCode, 'TextDiff'); - expectedType(result.fields.statusCode, 'text/vnd.apiary.status-code'); - realType(result.fields.statusCode, 'text/vnd.apiary.status-code'); + expect(result.fields.statusCode).to.not.be.valid; + expect(result.fields.statusCode).to.have.validator('TextDiff'); + expect(result.fields.statusCode).to.have.expectedType( + 'text/vnd.apiary.status-code' + ); + expect(result.fields.statusCode).to.have.realType( + 'text/vnd.apiary.status-code' + ); describe('produces an error', () => { it('exactly one error', () => { - assert.lengthOf(result.fields.statusCode.errors, 1); + expect(result.fields.statusCode.errors).to.have.length(1); }); it('has explanatory message', () => { - assert.propertyVal( - result.fields.statusCode.errors[0], + expect(result.fields.statusCode.errors[0]).to.have.property( 'message', `Status code is '400' instead of '200'` ); @@ -250,25 +242,22 @@ describe('validate', () => { }); describe('headers', () => { - isValid(result.fields.headers, false); - validator(result.fields.headers, 'HeadersJsonExample'); - expectedType( - result.fields.headers, + expect(result.fields.headers).to.not.be.valid; + expect(result.fields.headers).to.have.validator('HeadersJsonExample'); + expect(result.fields.headers).to.have.expectedType( 'application/vnd.apiary.http-headers+json' ); - realType( - result.fields.headers, + expect(result.fields.headers).to.have.realType( 'application/vnd.apiary.http-headers+json' ); describe('produces an error', () => { it('exactly one error', () => { - assert.lengthOf(result.fields.headers.errors, 1); + expect(result.fields.headers.errors).to.have.length(1); }); it('includes missing header in the message', () => { - assert.propertyVal( - result.fields.headers.errors[0], + expect(result.fields.headers.errors[0]).to.have.property( 'message', `At '/accept-language' Missing required property: accept-language` ); @@ -290,48 +279,51 @@ describe('validate', () => { } ); - isValid(result, false); + it('marks paris as invalid', () => { + expect(result).to.not.be.valid; + }); it('contains all validatable keys', () => { - assert.hasAllKeys(result.fields, ['statusCode', 'headers']); + expect(result.fields).to.have.all.keys(['statusCode', 'headers']); }); describe('statusCode', () => { - isValid(result.fields.statusCode); - validator(result.fields.statusCode, 'TextDiff'); - expectedType(result.fields.statusCode, 'text/vnd.apiary.status-code'); - realType(result.fields.statusCode, 'text/vnd.apiary.status-code'); + expect(result.fields.statusCode).to.be.valid; + expect(result.fields.statusCode).to.have.validator('TextDiff'); + expect(result.fields.statusCode).to.have.expectedType( + 'text/vnd.apiary.status-code' + ); + expect(result.fields.statusCode).to.have.realType( + 'text/vnd.apiary.status-code' + ); + noErrors(result.fields.statusCode); }); describe('headers', () => { - isValid(result.fields.headers, false); - validator(result.fields.headers, 'HeadersJsonExample'); - expectedType( - result.fields.headers, + expect(result.fields.headers).to.not.be.valid; + expect(result.fields.headers).to.have.validator('HeadersJsonExample'); + expect(result.fields.headers).to.have.expectedType( 'application/vnd.apiary.http-headers+json' ); - realType( - result.fields.headers, + expect(result.fields.headers).to.have.realType( 'application/vnd.apiary.http-headers+json' ); describe('produces an error', () => { it('exactly one error', () => { - assert.lengthOf(result.fields.headers.errors, 1); + expect(result.fields.headers.errors).to.have.length(1); }); it('has pointer to missing "Content-Type"', () => { - assert.propertyVal( - result.fields.headers.errors[0], + expect(result.fields.headers.errors[0]).to.have.property( 'pointer', '/content-type' ); }); it('has explanatory message', () => { - assert.propertyVal( - result.fields.headers.errors[0], + expect(result.fields.headers.errors[0]).to.have.property( 'message', `At '/content-type' Missing required property: content-type` ); @@ -355,10 +347,12 @@ describe('validate', () => { } ); - isValid(result, false); + it('marks pairs as invalid', () => { + expect(result).to.not.be.valid; + }); it('contains all validatable keys', () => { - assert.hasAllKeys(result.fields, [ + expect(result.fields).to.have.all.keys([ 'method', 'statusCode', 'headers', @@ -368,19 +362,20 @@ describe('validate', () => { describe('for properties present in both expected and real', () => { describe('method', () => { - isValid(result.fields.method, false); - validator(result.fields.method, null); - expectedType(result.fields.method, 'text/vnd.apiary.method'); - realType(result.fields.method, 'text/vnd.apiary.method'); + expect(result.fields.method).to.not.be.valid; + expect(result.fields.method).to.have.validator(null); + expect(result.fields.method).to.have.expectedType( + 'text/vnd.apiary.method' + ); + expect(result.fields.method).to.have.realType('text/vnd.apiary.method'); describe('produces an error', () => { it('exactly one error', () => { - assert.lengthOf(result.fields.method.errors, 1); + expect(result.fields.method.errors).to.have.length(1); }); it('has explanatory message', () => { - assert.propertyVal( - result.fields.method.errors[0], + expect(result.fields.method.errors[0]).to.have.property( 'message', 'Expected "method" field to equal "POST", but got "PUT".' ); @@ -391,19 +386,22 @@ describe('validate', () => { describe('for properties present in expected, but not in real', () => { describe('statusCode', () => { - isValid(result.fields.statusCode, false); - validator(result.fields.statusCode, 'TextDiff'); - expectedType(result.fields.statusCode, 'text/vnd.apiary.status-code'); - realType(result.fields.statusCode, 'text/vnd.apiary.status-code'); + expect(result.fields.statusCode).to.not.be.valid; + expect(result.fields.statusCode).to.have.validator('TextDiff'); + expect(result.fields.statusCode).to.have.expectedType( + 'text/vnd.apiary.status-code' + ); + expect(result.fields.statusCode).to.have.realType( + 'text/vnd.apiary.status-code' + ); describe('produces an error', () => { it('exactly one error', () => { - assert.lengthOf(result.fields.statusCode.errors, 1); + expect(result.fields.statusCode.errors).to.have.length(1); }); it('has explanatory message', () => { - assert.propertyVal( - result.fields.statusCode.errors[0], + expect(result.fields.statusCode.errors[0]).to.have.property( 'message', `Status code is 'undefined' instead of '200'` ); @@ -412,25 +410,22 @@ describe('validate', () => { }); describe('headers', () => { - isValid(result.fields.headers, false); - validator(result.fields.headers, 'HeadersJsonExample'); - expectedType( - result.fields.headers, + expect(result.fields.headers).to.not.be.valid; + expect(result.fields.headers).to.have.validator('HeadersJsonExample'); + expect(result.fields.headers).to.have.expectedType( 'application/vnd.apiary.http-headers+json' ); - realType( - result.fields.headers, + expect(result.fields.headers).to.have.realType( 'application/vnd.apiary.http-headers+json' ); describe('produces one error', () => { it('exactly one error', () => { - assert.lengthOf(result.fields.headers.errors, 1); + expect(result.fields.headers.errors).to.have.length(1); }); it('has explanatory message', () => { - assert.propertyVal( - result.fields.headers.errors[0], + expect(result.fields.headers.errors[0]).to.have.property( 'message', `At '/content-type' Missing required property: content-type` ); @@ -439,19 +434,18 @@ describe('validate', () => { }); describe('body', () => { - isValid(result.fields.body, false); - validator(result.fields.body, null); - expectedType(result.fields.body, 'application/json'); - realType(result.fields.body, 'text/plain'); + expect(result.fields.body).to.not.be.valid; + expect(result.fields.body).to.have.validator(null); + expect(result.fields.body).to.have.expectedType('application/json'); + expect(result.fields.body).to.have.realType('text/plain'); describe('produces an error', () => { it('exactly one error', () => { - assert.lengthOf(result.fields.body.errors, 1); + expect(result.fields.body.errors).to.have.length(1); }); it('has explanatory message', () => { - assert.propertyVal( - result.fields.body.errors[0], + expect(result.fields.body.errors[0]).to.have.property( 'message', 'Expected "body" of "application/json" media type, but actual "body" is missing.' ); diff --git a/test/unit/units/validateBody.test.js b/test/unit/units/validateBody.test.js index 3163898c..30f72281 100644 --- a/test/unit/units/validateBody.test.js +++ b/test/unit/units/validateBody.test.js @@ -1,4 +1,4 @@ -const { assert } = require('chai'); +const { assert, expect } = require('../../chai'); const { validateBody } = require('../../../lib/units/validateBody'); describe('validateBody', () => { @@ -31,16 +31,20 @@ describe('validateBody', () => { } ); + it('marks field as invalid', () => { + expect(result).to.not.be.valid; + }); + it('has no validator', () => { - assert.isNull(result.validator); + expect(result).to.have.validator(null); }); it('has "application/json" real type', () => { - assert.propertyVal(result, 'realType', 'application/json'); + expect(result).to.have.realType('application/json'); }); it('has "text/plain" expected type', () => { - assert.propertyVal(result, 'expectedType', 'text/plain'); + expect(result).to.have.expectedType('text/plain'); }); describe('produces validation error', () => { @@ -71,16 +75,20 @@ describe('validateBody', () => { } ); + it('marks field as valid', () => { + expect(result).to.be.valid; + }); + it('has "JsonExample" validator', () => { - assert.propertyVal(result, 'validator', 'JsonExample'); + expect(result).to.have.validator('JsonExample'); }); it('has "application/json" real type', () => { - assert.propertyVal(result, 'realType', 'application/json'); + expect(result).to.have.realType('application/json'); }); it('has "application/json" expected type', () => { - assert.propertyVal(result, 'expectedType', 'application/json'); + expect(result).to.have.expectedType('application/json'); }); it('has no errors', () => { @@ -99,16 +107,20 @@ describe('validateBody', () => { } ); + it('marks field as invalid', () => { + expect(result).to.not.be.valid; + }); + it('has no validator', () => { - assert.propertyVal(result, 'validator', null); + expect(result).to.have.validator(null); }); it('fallbacks to "text/plain" real type', () => { - assert.propertyVal(result, 'realType', 'text/plain'); + expect(result).to.have.realType('text/plain'); }); it('has "application/json" expected type', () => { - assert.propertyVal(result, 'expectedType', 'application/json'); + expect(result).to.have.expectedType('application/json'); }); describe('produces content-type error', () => { @@ -136,16 +148,20 @@ describe('validateBody', () => { } ); + it('marks field as valid', () => { + expect(result).to.be.valid; + }); + it('has "JsonExample" validator', () => { - assert.propertyVal(result, 'validator', 'JsonExample'); + expect(result).to.have.validator('JsonExample'); }); it('has "application/hal+json" real type', () => { - assert.propertyVal(result, 'realType', 'application/hal+json'); + expect(result).to.have.realType('application/hal+json'); }); it('has "application/json" expected type', () => { - assert.propertyVal(result, 'expectedType', 'application/json'); + expect(result).to.have.expectedType('application/json'); }); it('has no errors', () => { @@ -166,19 +182,27 @@ describe('validateBody', () => { } ); + it('marks field as invalid', () => { + expect(result).to.not.be.valid; + }); + it('has no validator', () => { - assert.propertyVal(result, 'validator', null); + expect(result).to.have.validator(null); }); it('fallbacks to "text/plain" real type', () => { - assert.propertyVal(result, 'realType', 'text/plain'); + expect(result).to.have.realType('text/plain'); }); it('has "text/plain" expected type', () => { - assert.propertyVal(result, 'expectedType', 'text/plain'); + expect(result).to.have.expectedType('text/plain'); }); describe('produces error', () => { + it('exactly one error', () => { + assert.lengthOf(result.errors, 1); + }); + it('has explanatory message', () => { assert.match( result.errors[0].message, @@ -202,16 +226,20 @@ describe('validateBody', () => { } ); + it('marks field as valid', () => { + expect(result).to.be.valid; + }); + it('has "TextDiff" validator', () => { - assert.propertyVal(result, 'validator', 'TextDiff'); + expect(result).to.have.validator('TextDiff'); }); it('has text/plain real type', () => { - assert.propertyVal(result, 'realType', 'text/plain'); + expect(result).to.have.realType('text/plain'); }); it('has "text/plain" expected type', () => { - assert.propertyVal(result, 'expectedType', 'text/plain'); + expect(result).to.have.expectedType('text/plain'); }); it('has no errors', () => { @@ -229,16 +257,20 @@ describe('validateBody', () => { } ); + it('marks field as invalid', () => { + expect(result).to.not.be.valid; + }); + it('has "TextDiff" validator', () => { - assert.propertyVal(result, 'validator', 'TextDiff'); + expect(result).to.have.validator('TextDiff'); }); it('has "text/plain" real type', () => { - assert.propertyVal(result, 'realType', 'text/plain'); + expect(result).to.have.realType('text/plain'); }); it('has "text/plain" expected type', () => { - assert.propertyVal(result, 'expectedType', 'text/plain'); + expect(result).to.have.expectedType('text/plain'); }); describe('produces validation error', () => { @@ -269,20 +301,24 @@ describe('validateBody', () => { } ); + it('marks field as valid', () => { + expect(result).to.be.valid; + }); + it('has "JsonExample" validator', () => { - assert.propertyVal(result, 'validator', 'JsonExample'); + expect(result).to.have.validator('JsonExample'); }); it('has "application/json" real type', () => { - assert.propertyVal(result, 'realType', 'application/json'); + expect(result).to.have.realType('application/json'); }); it('has "application/json" expected type', () => { - assert.propertyVal(result, 'expectedType', 'application/json'); + expect(result).to.have.expectedType('application/json'); }); it('has no errors', () => { - assert.lengthOf(result.errors, 0); + expect(result.errors).to.have.length(0); }); }); @@ -296,16 +332,20 @@ describe('validateBody', () => { } ); + it('marks field as invalid', () => { + expect(result).to.not.be.valid; + }); + it('has "JsonExample" validator', () => { - assert.propertyVal(result, 'validator', 'JsonExample'); + expect(result).to.have.validator('JsonExample'); }); it('has "application/json" real type', () => { - assert.propertyVal(result, 'realType', 'application/json'); + expect(result).to.have.realType('application/json'); }); it('has "application/json" expected type', () => { - assert.propertyVal(result, 'expectedType', 'application/json'); + expect(result).to.have.expectedType('application/json'); }); describe('produces validation errors', () => { @@ -337,20 +377,20 @@ describe('validateBody', () => { } ); + it('marks field as valid', () => { + expect(result).to.be.valid; + }); + it('has "JsonSchema" validator', () => { - assert.propertyVal(result, 'validator', 'JsonSchema'); + expect(result).to.have.validator('JsonSchema'); }); it('has "application/json" real type', () => { - assert.propertyVal(result, 'realType', 'application/json'); + expect(result).to.have.realType('application/json'); }); it('has "application/schema+json" expected type', () => { - assert.propertyVal( - result, - 'expectedType', - 'application/schema+json' - ); + expect(result).to.have.expectedType('application/schema+json'); }); it('has no errors', () => { @@ -362,28 +402,28 @@ describe('validateBody', () => { const result = validateBody( { bodySchema: { - required: ['doe'] + required: ['firstName'] } }, { - body: '{ "oneTwoThree": "bar" }' + body: '{ "lastName": "Doe" }' } ); + it('marks field as invalid', () => { + expect(result).to.not.be.valid; + }); + it('has "JsonSchema" validator', () => { - assert.propertyVal(result, 'validator', 'JsonSchema'); + expect(result).to.have.validator('JsonSchema'); }); it('has "application/json" real type', () => { - assert.propertyVal(result, 'realType', 'application/json'); + expect(result).to.have.realType('application/json'); }); it('has "application/schema+json" expected type', () => { - assert.propertyVal( - result, - 'expectedType', - 'application/schema+json' - ); + expect(result).to.have.expectedType('application/schema+json'); }); describe('produces an error', () => { @@ -395,7 +435,7 @@ describe('validateBody', () => { assert.propertyVal( result.errors[0], 'message', - `At '/doe' Missing required property: doe` + `At '/firstName' Missing required property: firstName` ); }); }); diff --git a/test/unit/units/validateHeaders.test.js b/test/unit/units/validateHeaders.test.js index 3477feff..60718d0d 100644 --- a/test/unit/units/validateHeaders.test.js +++ b/test/unit/units/validateHeaders.test.js @@ -1,9 +1,9 @@ -const { assert } = require('chai'); +const { expect } = require('../../chai'); const { validateHeaders } = require('../../../lib/units/validateHeaders'); describe('validateHeaders', () => { describe('given matching headers', () => { - const res = validateHeaders( + const result = validateHeaders( { headers: { 'content-type': 'application/json', @@ -18,33 +18,33 @@ describe('validateHeaders', () => { } ); + it('marks field as valid', () => { + expect(result).to.be.valid; + }); + it('has "HeadersJsonExample" validator', () => { - assert.propertyVal(res, 'validator', 'HeadersJsonExample'); + expect(result).to.have.validator('HeadersJsonExample'); }); it('has "application/vnd.apiary.http-headers+json" real type', () => { - assert.propertyVal( - res, - 'realType', + expect(result).to.have.realType( 'application/vnd.apiary.http-headers+json' ); }); it('has "application/vnd.apiary.http-headers+json" expected type', () => { - assert.propertyVal( - res, - 'expectedType', + expect(result).to.have.expectedType( 'application/vnd.apiary.http-headers+json' ); }); it('has no errors', () => { - assert.deepPropertyVal(res, 'errors', []); + expect(result.errors).to.have.length(0); }); }); describe('given non-matching headers', () => { - const res = validateHeaders( + const result = validateHeaders( { headers: { 'accept-language': 'en-US,us', @@ -59,22 +59,22 @@ describe('validateHeaders', () => { } ); + it('marks field as invalid', () => { + expect(result).to.not.be.valid; + }); + it('has "HeadersJsonExample" validator', () => { - assert.propertyVal(res, 'validator', 'HeadersJsonExample'); + expect(result).to.have.validator('HeadersJsonExample'); }); it('has "application/vnd.apiary.http-headers+json" real type', () => { - assert.propertyVal( - res, - 'realType', + expect(result).to.have.realType( 'application/vnd.apiary.http-headers+json' ); }); it('has "application/vnd.apiary.http-headers+json" expected type', () => { - assert.propertyVal( - res, - 'expectedType', + expect(result).to.have.expectedType( 'application/vnd.apiary.http-headers+json' ); }); @@ -82,24 +82,22 @@ describe('validateHeaders', () => { describe('produces errors', () => { const missingHeaders = ['accept-language', 'content-type']; - it('for two missing headers', () => { - assert.lengthOf(res.errors, missingHeaders.length); + it('for each missing headers', () => { + expect(result.errors).to.have.length(missingHeaders.length); }); describe('for each missing header', () => { missingHeaders.forEach((headerName, index) => { describe(headerName, () => { it('has pointer to header name', () => { - assert.propertyVal( - res.errors[index], + expect(result.errors[index]).to.have.property( 'pointer', `/${headerName}` ); }); it('has explanatory message', () => { - assert.propertyVal( - res.errors[index], + expect(result.errors[index]).to.have.property( 'message', `At '/${headerName}' Missing required property: ${headerName}` ); @@ -111,7 +109,7 @@ describe('validateHeaders', () => { }); describe('given non-json headers', () => { - const res = validateHeaders( + const result = validateHeaders( { headers: 'bar' }, @@ -120,31 +118,36 @@ describe('validateHeaders', () => { } ); + it('marks field as invalid', () => { + expect(result).to.not.be.valid; + }); + it('has no validator', () => { - assert.propertyVal(res, 'validator', null); + expect(result).to.have.validator(null); }); it('has no real type', () => { - assert.propertyVal(res, 'realType', null); + expect(result).to.have.realType(null); }); it('has no expected type', () => { - assert.propertyVal(res, 'expectedType', null); + expect(result).to.have.expectedType(null); }); describe('produces an error', () => { it('has one error', () => { - assert.lengthOf(res.errors, 1); + expect(result.errors).to.have.length(1); }); it('has explanatory message', () => { - assert.propertyVal( - res.errors[0], + expect(result.errors[0]).to.have.property( 'message', - `No validator found for real data media type + `\ +No validator found for real data media type "null" and expected data media type -"null".` +"null".\ +` ); }); }); diff --git a/test/unit/units/validateMethod.test.js b/test/unit/units/validateMethod.test.js index 3afef845..a0b129c9 100644 --- a/test/unit/units/validateMethod.test.js +++ b/test/unit/units/validateMethod.test.js @@ -1,4 +1,4 @@ -const { assert } = require('chai'); +const { expect } = require('../../chai'); const { validateMethod } = require('../../../lib/units/validateMethod'); describe('validateMethod', () => { @@ -12,24 +12,24 @@ describe('validateMethod', () => { } ); - it('has "isValid" as "true"', () => { - assert.propertyVal(result, 'isValid', true); + it('marks fields as valid', () => { + expect(result).to.be.valid; }); it('has "null" validator', () => { - assert.isNull(result.validator); + expect(result).to.have.validator(null); }); it('has "text/vnd.apiary.method" real type', () => { - assert.propertyVal(result, 'realType', 'text/vnd.apiary.method'); + expect(result).to.have.realType('text/vnd.apiary.method'); }); it('has "text/vnd.apiary.method" expected type', () => { - assert.propertyVal(result, 'expectedType', 'text/vnd.apiary.method'); + expect(result).to.have.expectedType('text/vnd.apiary.method'); }); it('has no errors', () => { - assert.lengthOf(result.errors, 0); + expect(result.errors).to.have.length(0); }); }); @@ -43,30 +43,29 @@ describe('validateMethod', () => { } ); - it('returns "isValid" as "false"', () => { - assert.propertyVal(result, 'isValid', false); + it('marks field as valid', () => { + expect(result).to.not.be.valid; }); it('has "null" validator', () => { - assert.propertyVal(result, 'validator', null); + expect(result).to.have.validator(null); }); it('has "text/vnd.apiary.method" real type', () => { - assert.propertyVal(result, 'realType', 'text/vnd.apiary.method'); + expect(result).to.have.realType('text/vnd.apiary.method'); }); it('has "text/vnd.apiary.method" expected type', () => { - assert.propertyVal(result, 'expectedType', 'text/vnd.apiary.method'); + expect(result).to.have.expectedType('text/vnd.apiary.method'); }); describe('produces an error', () => { it('exactly one error', () => { - assert.lengthOf(result.errors, 1); + expect(result.errors).to.have.length(1); }); it('has explanatory message', () => { - assert.propertyVal( - result.errors[0], + expect(result.errors[0]).to.have.property( 'message', 'Expected "method" field to equal "POST", but got "GET".' ); @@ -84,30 +83,29 @@ describe('validateMethod', () => { } ); - it('returns "isValid" as "false"', () => { - assert.propertyVal(result, 'isValid', false); + it('marks field as invalid', () => { + expect(result).to.not.be.valid; }); it('has "null" validator', () => { - assert.propertyVal(result, 'validator', null); + expect(result).to.have.validator(null); }); it('has "text/vnd.apiary.method" real type', () => { - assert.propertyVal(result, 'realType', 'text/vnd.apiary.method'); + expect(result).to.have.realType('text/vnd.apiary.method'); }); it('has "text/vnd.apiary.method" expected type', () => { - assert.propertyVal(result, 'expectedType', 'text/vnd.apiary.method'); + expect(result).to.have.expectedType('text/vnd.apiary.method'); }); describe('produces an error', () => { it('exactly one error', () => { - assert.lengthOf(result.errors, 1); + expect(result.errors).to.have.length(1); }); it('has explanatory message', () => { - assert.propertyVal( - result.errors[0], + expect(result.errors[0]).to.have.property( 'message', 'Expected "method" field to equal "PATCH", but got "".' ); diff --git a/test/unit/units/validateStatusCode.test.js b/test/unit/units/validateStatusCode.test.js index 2060e958..eacec997 100644 --- a/test/unit/units/validateStatusCode.test.js +++ b/test/unit/units/validateStatusCode.test.js @@ -1,4 +1,4 @@ -const { assert } = require('chai'); +const { expect } = require('../../chai'); const { validateStatusCode } = require('../../../lib/units/validateStatusCode'); describe('validateStatusCode', () => { @@ -12,20 +12,24 @@ describe('validateStatusCode', () => { } ); + it('marks field as valid', () => { + expect(result).to.be.valid; + }); + it('has "TextDiff" validator', () => { - assert.propertyVal(result, 'validator', 'TextDiff'); + expect(result).to.have.validator('TextDiff'); }); it('has "text/vnd.apiary.status-code" expected type', () => { - assert.propertyVal(result, 'expectedType', 'text/vnd.apiary.status-code'); + expect(result).to.have.expectedType('text/vnd.apiary.status-code'); }); it('has "text/vnd.apiary.status-code" real type', () => { - assert.propertyVal(result, 'realType', 'text/vnd.apiary.status-code'); + expect(result).to.have.realType('text/vnd.apiary.status-code'); }); it('has no errors', () => { - assert.deepPropertyVal(result, 'errors', []); + expect(result.errors).to.have.length(0); }); }); @@ -39,26 +43,29 @@ describe('validateStatusCode', () => { } ); + it('marks field as invalid', () => { + expect(result).to.not.be.valid; + }); + it('has "TextDiff" validator', () => { - assert.propertyVal(result, 'validator', 'TextDiff'); + expect(result).to.have.validator('TextDiff'); }); it('has "text/vnd.apiary.status-code" expected type', () => { - assert.propertyVal(result, 'expectedType', 'text/vnd.apiary.status-code'); + expect(result).to.have.expectedType('text/vnd.apiary.status-code'); }); it('has "text/vnd.apiary.status-code" real type', () => { - assert.propertyVal(result, 'realType', 'text/vnd.apiary.status-code'); + expect(result).to.have.realType('text/vnd.apiary.status-code'); }); describe('produces error', () => { it('exactly one error', () => { - assert.lengthOf(result.errors, 1); + expect(result.errors).to.have.length(1); }); it('has explanatory message', () => { - assert.propertyVal( - result.errors[0], + expect(result.errors[0]).to.have.property( 'message', `Status code is '200' instead of '400'` ); diff --git a/test/unit/units/validateURI.test.js b/test/unit/units/validateURI.test.js index aaeb880b..db794365 100644 --- a/test/unit/units/validateURI.test.js +++ b/test/unit/units/validateURI.test.js @@ -1,4 +1,4 @@ -const { assert } = require('chai'); +const { expect } = require('../../chai'); const { validateURI } = require('../../../lib/units/validateURI'); describe('validateURI', () => { @@ -14,23 +14,23 @@ describe('validateURI', () => { ); it('marks field as valid', () => { - assert.propertyVal(result, 'isValid', true); + expect(result).to.be.valid; }); it('has "null" validator', () => { - assert.propertyVal(result, 'validator', null); + expect(result).to.have.validator(null); }); it('has "text/vnd.apiary.uri" real type', () => { - assert.propertyVal(result, 'realType', 'text/vnd.apiary.uri'); + expect(result).to.have.realType('text/vnd.apiary.uri'); }); it('has "text/vnd.apiary.uri" expected type', () => { - assert.propertyVal(result, 'expectedType', 'text/vnd.apiary.uri'); + expect(result).to.have.expectedType('text/vnd.apiary.uri'); }); it('has no errors', () => { - assert.lengthOf(result.errors, 0); + expect(result.errors).to.have.length(0); }); }); @@ -46,23 +46,23 @@ describe('validateURI', () => { ); it('marks field as valid', () => { - assert.propertyVal(result, 'isValid', true); + expect(result).to.be.valid; }); it('has "null" validator', () => { - assert.propertyVal(result, 'validator', null); + expect(result).to.have.validator(null); }); it('has "text/vnd.apiary.uri" real type', () => { - assert.propertyVal(result, 'realType', 'text/vnd.apiary.uri'); + expect(result).to.have.realType('text/vnd.apiary.uri'); }); it('has "text/vnd.apiary.uri" expected type', () => { - assert.propertyVal(result, 'expectedType', 'text/vnd.apiary.uri'); + expect(result).to.have.expectedType('text/vnd.apiary.uri'); }); it('has no errors', () => { - assert.lengthOf(result.errors, 0); + expect(result.errors).to.have.length(0); }); }); @@ -78,23 +78,23 @@ describe('validateURI', () => { ); it('mark field as valid', () => { - assert.propertyVal(result, 'isValid', true); + expect(result).to.be.valid; }); it('has "null" validator', () => { - assert.propertyVal(result, 'validator', null); + expect(result).to.have.validator(null); }); it('has "text/vnd.apiary.uri" real type', () => { - assert.propertyVal(result, 'realType', 'text/vnd.apiary.uri'); + expect(result).to.have.realType('text/vnd.apiary.uri'); }); it('has "text/vnd.apiary.uri" expected type', () => { - assert.propertyVal(result, 'expectedType', 'text/vnd.apiary.uri'); + expect(result).to.have.expectedType('text/vnd.apiary.uri'); }); it('has no errors', () => { - assert.lengthOf(result.errors, 0); + expect(result.errors).to.have.length(0); }); }); @@ -110,23 +110,23 @@ describe('validateURI', () => { ); it('marks field as valid', () => { - assert.propertyVal(result, 'isValid', true); + expect(result).to.be.valid; }); it('has "null" validator', () => { - assert.propertyVal(result, 'validator', null); + expect(result).to.have.validator(null); }); it('has "text/vnd.apiary.uri" real type', () => { - assert.propertyVal(result, 'realType', 'text/vnd.apiary.uri'); + expect(result).to.have.realType('text/vnd.apiary.uri'); }); it('has "text/vnd.apiary.uri" expected type', () => { - assert.propertyVal(result, 'expectedType', 'text/vnd.apiary.uri'); + expect(result).to.have.expectedType('text/vnd.apiary.uri'); }); it('has no errors', () => { - assert.lengthOf(result.errors, 0); + expect(result.errors).to.have.length(0); }); }); }); @@ -144,29 +144,28 @@ describe('validateURI', () => { ); it('marks field as invalid', () => { - assert.propertyVal(result, 'isValid', false); + expect(result).to.not.be.valid; }); it('has "null" validator', () => { - assert.propertyVal(result, 'validator', null); + expect(result).to.have.validator(null); }); it('has "text/vnd.apiary.uri" real type', () => { - assert.propertyVal(result, 'realType', 'text/vnd.apiary.uri'); + expect(result).to.have.realType('text/vnd.apiary.uri'); }); it('has "text/vnd.apiary.uri" expected type', () => { - assert.propertyVal(result, 'expectedType', 'text/vnd.apiary.uri'); + expect(result).to.have.expectedType('text/vnd.apiary.uri'); }); describe('produces an error', () => { it('exactly one error', () => { - assert.lengthOf(result.errors, 1); + expect(result.errors).to.have.length(1); }); it('has explanatory message', () => { - assert.propertyVal( - result.errors[0], + expect(result.errors[0]).to.have.property( 'message', 'Expected "uri" field to equal "/dashboard", but got: "/profile".' ); @@ -186,29 +185,28 @@ describe('validateURI', () => { ); it('marks field is invalid', () => { - assert.propertyVal(result, 'isValid', false); + expect(result).to.not.be.valid; }); it('has "null" validator', () => { - assert.propertyVal(result, 'validator', null); + expect(result).to.have.validator(null); }); it('has "text/vnd.apiary.uri" real type', () => { - assert.propertyVal(result, 'realType', 'text/vnd.apiary.uri'); + expect(result).to.have.realType('text/vnd.apiary.uri'); }); it('has "text/vnd.apiary.uri" expected type', () => { - assert.propertyVal(result, 'expectedType', 'text/vnd.apiary.uri'); + expect(result).to.have.expectedType('text/vnd.apiary.uri'); }); describe('produces an error', () => { it('exactly one error', () => { - assert.lengthOf(result.errors, 1); + expect(result.errors).to.have.length(1); }); it('has explanatory message', () => { - assert.propertyVal( - result.errors[0], + expect(result.errors[0]).to.have.property( 'message', 'Expected "uri" field to equal "/account?id=123", but got: "/account".' ); @@ -227,29 +225,28 @@ describe('validateURI', () => { ); it('marks field is invalid', () => { - assert.propertyVal(result, 'isValid', false); + expect(result).to.not.be.valid; }); it('has "null" validator', () => { - assert.propertyVal(result, 'validator', null); + expect(result).to.have.validator(null); }); it('has "text/vnd.apiary.uri" real type', () => { - assert.propertyVal(result, 'realType', 'text/vnd.apiary.uri'); + expect(result).to.have.realType('text/vnd.apiary.uri'); }); it('has "text/vnd.apiary.uri" expected type', () => { - assert.propertyVal(result, 'expectedType', 'text/vnd.apiary.uri'); + expect(result).to.have.expectedType('text/vnd.apiary.uri'); }); describe('produces an error', () => { it('exactly one error', () => { - assert.lengthOf(result.errors, 1); + expect(result.errors).to.have.length(1); }); it('has explanatory message', () => { - assert.propertyVal( - result.errors[0], + expect(result.errors[0]).to.have.property( 'message', 'Expected "uri" field to equal "/account?name=user", but got: "/account?nAmE=usEr".' ); @@ -268,29 +265,28 @@ describe('validateURI', () => { ); it('marks field is invalid', () => { - assert.propertyVal(result, 'isValid', false); + expect(result).to.not.be.valid; }); it('has "null" validator', () => { - assert.propertyVal(result, 'validator', null); + expect(result).to.have.validator(null); }); it('has "text/vnd.apiary.uri" real type', () => { - assert.propertyVal(result, 'realType', 'text/vnd.apiary.uri'); + expect(result).to.have.realType('text/vnd.apiary.uri'); }); it('has "text/vnd.apiary.uri" expected type', () => { - assert.propertyVal(result, 'expectedType', 'text/vnd.apiary.uri'); + expect(result).to.have.expectedType('text/vnd.apiary.uri'); }); describe('produces an error', () => { it('exactly one error', () => { - assert.lengthOf(result.errors, 1); + expect(result.errors).to.have.length(1); }); it('has explanatory message', () => { - assert.propertyVal( - result.errors[0], + expect(result.errors[0]).to.have.property( 'message', 'Expected "uri" field to equal "/zoo?type=cats&type=dogs", but got: "/zoo?type=dogs&type=cats".' ); From cbfb18c2dbb9856f00e7458dcd6f940d332168e0 Mon Sep 17 00:00:00 2001 From: artem-zakharchenko Date: Mon, 24 Jun 2019 11:19:55 +0200 Subject: [PATCH 2/3] chore: separates temporary and permanent overrides in eslint --- .eslintrc.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 9d41475f..40361f60 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -12,9 +12,12 @@ module.exports = { 'no-plusplus': 'off', 'func-names': 'off', + // Disabled to allow "expect()" assertions with parameters: + // expect(foo).to.be.valid + 'no-unused-expressions': 'off', + // Temporary overrides. Logic to be rewritten. // TODO https://github.com/apiaryio/gavel.js/issues/150 - 'no-param-reassign': 'off', - 'no-unused-expressions': 'off' + 'no-param-reassign': 'off' } }; From 555f3e454237e0c0a241817233a2fe1e1a99f952 Mon Sep 17 00:00:00 2001 From: artem-zakharchenko Date: Mon, 24 Jun 2019 16:50:11 +0200 Subject: [PATCH 3/3] test: adds custom error assertions --- test/chai.js | 104 ++++++++++++++++- test/integration/validate.test.js | 130 ++++++++++----------- test/unit/units/validateBody.test.js | 79 ++++++------- test/unit/units/validateHeaders.test.js | 33 +++--- test/unit/units/validateMethod.test.js | 22 ++-- test/unit/units/validateStatusCode.test.js | 11 +- test/unit/units/validateURI.test.js | 52 +++++---- 7 files changed, 260 insertions(+), 171 deletions(-) diff --git a/test/chai.js b/test/chai.js index ff299192..43b6ea6f 100644 --- a/test/chai.js +++ b/test/chai.js @@ -6,6 +6,51 @@ const stringify = (obj) => { }; chai.use(({ Assertion }, utils) => { + const createErrorPropertyAssertion = (propName, methodName) => { + Assertion.addMethod(methodName, function(expectedValue) { + const stringifiedObj = stringify(this._obj); + const { currentError: error, currentErrorIndex } = this.__flags; + const target = error[propName]; + const isRegExp = expectedValue instanceof RegExp; + const matchWord = isRegExp ? 'matches' : 'equals'; + + new Assertion(error).to.be.instanceOf(Object); + new Assertion(error).to.have.property(propName); + + this.assert( + isRegExp ? expectedValue.test(target) : target === expectedValue, + ` + Expected the next HTTP message field: + + ${stringifiedObj} + + to have ${propName} at index ${currentErrorIndex} that ${matchWord}: + + ${expectedValue.toString()} + + but got: + + ${target.toString()} + `, + ` + Expected the next HTTP message field: + + ${stringifiedObj} + + not to have ${propName} at index ${currentErrorIndex}, but got: + + ${target.toString()} + `, + expectedValue.toString(), + target.toString(), + true + ); + }); + }; + + createErrorPropertyAssertion('message', 'withMessage'); + createErrorPropertyAssertion('pointer', 'withPointer'); + utils.addProperty(Assertion.prototype, 'valid', function() { const { isValid } = this._obj; const stringifiedObj = stringify(this._obj); @@ -24,8 +69,56 @@ Expected the following HTTP message field: ${stringifiedObj} -to be invalid, but it is actually valid.` +to be invalid, but it is actually valid.`, + { isValid }, + { isValid: true }, + true + ); + }); + + utils.addProperty(Assertion.prototype, 'errors', function() { + const { errors } = this._obj; + const stringifiedObj = stringify(this._obj); + + this.assert( + errors.length > 0, + ` +Expected the following HTTP message field: + +${stringifiedObj} + +to have some errors, but got no errors. +`, + ` +Expected the following HTTP message field: + +${stringifiedObj} + +to have no errors, but got ${errors.length} error(s). + `, + { errors: [] }, + { errors }, + true ); + + utils.flag(this, 'object', errors); + }); + + Assertion.addMethod('errorAtIndex', function(index) { + const { errors } = this._obj; + const errorsCount = errors.length; + + new Assertion(errors).to.be.instanceOf(Array); + new Assertion(errorsCount).to.be.a('number'); + + this.assert( + errors[index], + `Expected to have error at index ${index}`, + `Expected NOT to have error at index ${index}` + ); + + utils.flag(this, 'currentError', errors[index]); + utils.flag(this, 'currentErrorIndex', index); }); Assertion.addMethod('validator', function(expectedValue) { @@ -49,7 +142,8 @@ ${stringifiedObj} to not have validator equal to "${expectedValue}". `, expectedValue, - actualValue + actualValue, + true ); }); @@ -74,7 +168,8 @@ ${stringifiedObj} to not have an "expectedType" of "${expectedValue}". `, expectedValue, - actualValue + actualValue, + true ); }); @@ -99,7 +194,8 @@ ${stringifiedObj} to not have an "realType" of "${expectedValue}". `, expectedValue, - actualValue + actualValue, + true ); }); }); diff --git a/test/integration/validate.test.js b/test/integration/validate.test.js index 2c6712ea..a6e2baaf 100644 --- a/test/integration/validate.test.js +++ b/test/integration/validate.test.js @@ -1,12 +1,6 @@ const { expect } = require('../chai'); const { validate } = require('../../lib/validate'); -const noErrors = (obj) => { - it('has no errors', () => { - expect(obj.errors).to.have.length(0); - }); -}; - describe('validate', () => { describe('with matching requests', () => { const request = { @@ -33,8 +27,7 @@ describe('validate', () => { 'text/vnd.apiary.method' ); expect(result.fields.method).to.have.realType('text/vnd.apiary.method'); - - noErrors(result.fields.method); + expect(result.fields.method).to.not.have.errors; }); describe('headers', () => { @@ -46,8 +39,7 @@ describe('validate', () => { expect(result.fields.headers).to.have.realType( 'application/vnd.apiary.http-headers+json' ); - - noErrors(result.fields.headers); + expect(result.fields.headers).to.not.have.errors; }); describe('body', () => { @@ -55,8 +47,7 @@ describe('validate', () => { expect(result.fields.body).to.have.validator('JsonExample'); expect(result.fields.body).to.have.expectedType('application/json'); expect(result.fields.body).to.have.realType('application/json'); - - noErrors(result.fields.body); + expect(result.fields.body).to.not.have.errors; }); }); @@ -94,14 +85,15 @@ describe('validate', () => { describe('produces one error', () => { it('exactly one error', () => { - expect(result.fields.method.errors).to.have.length(1); + expect(result.fields.method).to.have.errors.lengthOf(1); }); it('has explanatory message', () => { - expect(result.fields.method.errors[0]).to.have.property( - 'message', - 'Expected "method" field to equal "PUT", but got "POST".' - ); + expect(result.fields.method) + .to.have.errorAtIndex(0) + .withMessage( + 'Expected "method" field to equal "PUT", but got "POST".' + ); }); }); }); @@ -115,8 +107,7 @@ describe('validate', () => { expect(result.fields.headers).to.have.realType( 'application/vnd.apiary.http-headers+json' ); - - noErrors(result.fields.headers); + expect(result.fields.headers).to.not.have.errors; }); describe('body', () => { @@ -127,14 +118,13 @@ describe('validate', () => { describe('produces an error', () => { it('exactly one error', () => { - expect(result.fields.body.errors).to.have.length(1); + expect(result.fields.body).to.have.errors.lengthOf(1); }); it('has explanatory message', () => { - expect(result.fields.body.errors[0]).to.have.property( - 'message', - `At '' Invalid type: object (expected integer)` - ); + expect(result.fields.body) + .to.have.errorAtIndex(0) + .withMessage(`At '' Invalid type: object (expected integer)`); }); }); }); @@ -167,8 +157,7 @@ describe('validate', () => { expect(result.fields.statusCode).to.have.realType( 'text/vnd.apiary.status-code' ); - - noErrors(result.fields.statusCode); + expect(result.fields.statusCode).to.not.have.errors; }); describe('headers', () => { @@ -180,8 +169,7 @@ describe('validate', () => { expect(result.fields.headers).to.have.realType( 'application/vnd.apiary.http-headers+json' ); - - noErrors(result.fields.headers); + expect(result.fields.headers).to.not.have.errors; }); describe('body', () => { @@ -189,8 +177,7 @@ describe('validate', () => { expect(result.fields.body).to.have.validator('JsonExample'); expect(result.fields.body).to.have.expectedType('application/json'); expect(result.fields.body).to.have.realType('application/json'); - - noErrors(result.fields.body); + expect(result.fields.body).to.not.have.errors; }); }); @@ -229,14 +216,13 @@ describe('validate', () => { describe('produces an error', () => { it('exactly one error', () => { - expect(result.fields.statusCode.errors).to.have.length(1); + expect(result.fields.statusCode).to.have.errors.lengthOf(1); }); it('has explanatory message', () => { - expect(result.fields.statusCode.errors[0]).to.have.property( - 'message', - `Status code is '400' instead of '200'` - ); + expect(result.fields.statusCode) + .to.have.errorAtIndex(0) + .withMessage(`Status code is '400' instead of '200'`); }); }); }); @@ -253,14 +239,15 @@ describe('validate', () => { describe('produces an error', () => { it('exactly one error', () => { - expect(result.fields.headers.errors).to.have.length(1); + expect(result.fields.headers).to.have.errors.lengthOf(1); }); it('includes missing header in the message', () => { - expect(result.fields.headers.errors[0]).to.have.property( - 'message', - `At '/accept-language' Missing required property: accept-language` - ); + expect(result.fields.headers) + .to.have.errorAtIndex(0) + .withMessage( + `At '/accept-language' Missing required property: accept-language` + ); }); }); }); @@ -296,8 +283,7 @@ describe('validate', () => { expect(result.fields.statusCode).to.have.realType( 'text/vnd.apiary.status-code' ); - - noErrors(result.fields.statusCode); + expect(result.fields.statusCode).to.not.have.errors; }); describe('headers', () => { @@ -312,21 +298,21 @@ describe('validate', () => { describe('produces an error', () => { it('exactly one error', () => { - expect(result.fields.headers.errors).to.have.length(1); + expect(result.fields.headers).to.have.errors.lengthOf(1); }); it('has pointer to missing "Content-Type"', () => { - expect(result.fields.headers.errors[0]).to.have.property( - 'pointer', - '/content-type' - ); + expect(result.fields.headers) + .to.have.errorAtIndex(0) + .withPointer('/content-type'); }); it('has explanatory message', () => { - expect(result.fields.headers.errors[0]).to.have.property( - 'message', - `At '/content-type' Missing required property: content-type` - ); + expect(result.fields.headers) + .to.have.errorAtIndex(0) + .withMessage( + `At '/content-type' Missing required property: content-type` + ); }); }); }); @@ -371,14 +357,15 @@ describe('validate', () => { describe('produces an error', () => { it('exactly one error', () => { - expect(result.fields.method.errors).to.have.length(1); + expect(result.fields.method).to.have.errors.lengthOf(1); }); it('has explanatory message', () => { - expect(result.fields.method.errors[0]).to.have.property( - 'message', - 'Expected "method" field to equal "POST", but got "PUT".' - ); + expect(result.fields.method) + .to.have.errorAtIndex(0) + .withMessage( + 'Expected "method" field to equal "POST", but got "PUT".' + ); }); }); }); @@ -397,14 +384,13 @@ describe('validate', () => { describe('produces an error', () => { it('exactly one error', () => { - expect(result.fields.statusCode.errors).to.have.length(1); + expect(result.fields.statusCode).to.have.errors.lengthOf(1); }); it('has explanatory message', () => { - expect(result.fields.statusCode.errors[0]).to.have.property( - 'message', - `Status code is 'undefined' instead of '200'` - ); + expect(result.fields.statusCode) + .to.have.errorAtIndex(0) + .withMessage(`Status code is 'undefined' instead of '200'`); }); }); }); @@ -421,14 +407,15 @@ describe('validate', () => { describe('produces one error', () => { it('exactly one error', () => { - expect(result.fields.headers.errors).to.have.length(1); + expect(result.fields.headers).to.have.errors.lengthOf(1); }); it('has explanatory message', () => { - expect(result.fields.headers.errors[0]).to.have.property( - 'message', - `At '/content-type' Missing required property: content-type` - ); + expect(result.fields.headers) + .to.have.errorAtIndex(0) + .withMessage( + `At '/content-type' Missing required property: content-type` + ); }); }); }); @@ -441,14 +428,15 @@ describe('validate', () => { describe('produces an error', () => { it('exactly one error', () => { - expect(result.fields.body.errors).to.have.length(1); + expect(result.fields.body).to.have.errors.lengthOf(1); }); it('has explanatory message', () => { - expect(result.fields.body.errors[0]).to.have.property( - 'message', - 'Expected "body" of "application/json" media type, but actual "body" is missing.' - ); + expect(result.fields.body) + .to.have.errorAtIndex(0) + .withMessage( + 'Expected "body" of "application/json" media type, but actual "body" is missing.' + ); }); }); }); diff --git a/test/unit/units/validateBody.test.js b/test/unit/units/validateBody.test.js index 30f72281..7fa92db1 100644 --- a/test/unit/units/validateBody.test.js +++ b/test/unit/units/validateBody.test.js @@ -49,15 +49,15 @@ describe('validateBody', () => { describe('produces validation error', () => { it('exactly one error', () => { - assert.lengthOf(result.errors, 1); + expect(result).to.have.errors.lengthOf(1); }); it('has explanatory message', () => { - assert.propertyVal( - result.errors[0], - 'message', - `Can't validate real media type 'application/json' against expected media type 'text/plain'.` - ); + expect(result) + .to.have.errorAtIndex(0) + .withMessage( + `Can't validate real media type 'application/json' against expected media type 'text/plain'.` + ); }); }); }); @@ -92,7 +92,7 @@ describe('validateBody', () => { }); it('has no errors', () => { - assert.lengthOf(result.errors, 0); + expect(result).to.not.have.errors; }); }); @@ -124,11 +124,16 @@ describe('validateBody', () => { }); describe('produces content-type error', () => { + it('exactly one error', () => { + expect(result).to.have.errors.lengthOf(1); + }); + it('has explanatory message', () => { - assert.match( - result.errors[0].message, - /^Can't validate: real body 'Content-Type' header is 'application\/json' but body is not a parseable JSON:/ - ); + expect(result) + .to.have.errorAtIndex(0) + .withMessage( + /^Can't validate: real body 'Content-Type' header is 'application\/json' but body is not a parseable JSON:/ + ); }); }); }); @@ -165,7 +170,7 @@ describe('validateBody', () => { }); it('has no errors', () => { - assert.lengthOf(result.errors, 0); + expect(result).to.not.have.errors; }); }); @@ -200,14 +205,15 @@ describe('validateBody', () => { describe('produces error', () => { it('exactly one error', () => { - assert.lengthOf(result.errors, 1); + expect(result).to.have.errors.lengthOf(1); }); it('has explanatory message', () => { - assert.match( - result.errors[0].message, - /^Can't validate: real body 'Content-Type' header is 'application\/hal\+json' but body is not a parseable JSON:/ - ); + expect(result) + .to.have.errorAtIndex(0) + .withMessage( + /^Can't validate: real body 'Content-Type' header is 'application\/hal\+json' but body is not a parseable JSON:/ + ); }); }); }); @@ -243,7 +249,7 @@ describe('validateBody', () => { }); it('has no errors', () => { - assert.lengthOf(result.errors, 0); + expect(result).to.not.have.errors; }); }); @@ -275,16 +281,13 @@ describe('validateBody', () => { describe('produces validation error', () => { it('exactly one error', () => { - assert.lengthOf(result.errors, 1); + expect(result).to.have.errors.lengthOf(1); }); it('with explanatory message', () => { - assert.hasAnyKeys(result.errors[0], 'message'); - assert.propertyVal( - result.errors[0], - 'message', - 'Real and expected data does not match.' - ); + expect(result) + .to.have.errorAtIndex(0) + .withMessage('Real and expected data does not match.'); }); }); }); @@ -318,7 +321,7 @@ describe('validateBody', () => { }); it('has no errors', () => { - expect(result.errors).to.have.length(0); + expect(result).to.not.have.errors; }); }); @@ -350,15 +353,13 @@ describe('validateBody', () => { describe('produces validation errors', () => { it('exactly one error', () => { - assert.lengthOf(result.errors, 1); + expect(result).to.have.errors.lengthOf(1); }); it('has explanatory message', () => { - assert.propertyVal( - result.errors[0], - 'message', - `At '/bar' Missing required property: bar` - ); + expect(result) + .to.have.errorAtIndex(0) + .withMessage(`At '/bar' Missing required property: bar`); }); }); }); @@ -394,7 +395,7 @@ describe('validateBody', () => { }); it('has no errors', () => { - assert.lengthOf(result.errors, 0); + expect(result).to.not.have.errors; }); }); @@ -428,15 +429,15 @@ describe('validateBody', () => { describe('produces an error', () => { it('exactly one error', () => { - assert.lengthOf(result.errors, 1); + expect(result).to.have.errors.lengthOf(1); }); it('has explanatory message', () => { - assert.propertyVal( - result.errors[0], - 'message', - `At '/firstName' Missing required property: firstName` - ); + expect(result) + .to.have.errorAtIndex(0) + .withMessage( + `At '/firstName' Missing required property: firstName` + ); }); }); }); diff --git a/test/unit/units/validateHeaders.test.js b/test/unit/units/validateHeaders.test.js index 60718d0d..6a4d4c75 100644 --- a/test/unit/units/validateHeaders.test.js +++ b/test/unit/units/validateHeaders.test.js @@ -39,7 +39,7 @@ describe('validateHeaders', () => { }); it('has no errors', () => { - expect(result.errors).to.have.length(0); + expect(result).to.not.have.errors; }); }); @@ -83,24 +83,24 @@ describe('validateHeaders', () => { const missingHeaders = ['accept-language', 'content-type']; it('for each missing headers', () => { - expect(result.errors).to.have.length(missingHeaders.length); + expect(result).to.have.errors.lengthOf(missingHeaders.length); }); describe('for each missing header', () => { missingHeaders.forEach((headerName, index) => { describe(headerName, () => { it('has pointer to header name', () => { - expect(result.errors[index]).to.have.property( - 'pointer', - `/${headerName}` - ); + expect(result) + .to.have.errorAtIndex(index) + .withPointer(`/${headerName}`); }); it('has explanatory message', () => { - expect(result.errors[index]).to.have.property( - 'message', - `At '/${headerName}' Missing required property: ${headerName}` - ); + expect(result) + .to.have.errorAtIndex(index) + .withMessage( + `At '/${headerName}' Missing required property: ${headerName}` + ); }); }); }); @@ -135,20 +135,21 @@ describe('validateHeaders', () => { }); describe('produces an error', () => { - it('has one error', () => { - expect(result.errors).to.have.length(1); + it('exactly one error', () => { + expect(result).to.have.errors.lengthOf(1); }); it('has explanatory message', () => { - expect(result.errors[0]).to.have.property( - 'message', - `\ + expect(result) + .to.have.errorAtIndex(0) + .withMessage( + `\ No validator found for real data media type "null" and expected data media type "null".\ ` - ); + ); }); }); }); diff --git a/test/unit/units/validateMethod.test.js b/test/unit/units/validateMethod.test.js index a0b129c9..8f206131 100644 --- a/test/unit/units/validateMethod.test.js +++ b/test/unit/units/validateMethod.test.js @@ -29,7 +29,7 @@ describe('validateMethod', () => { }); it('has no errors', () => { - expect(result.errors).to.have.length(0); + expect(result).to.not.have.errors; }); }); @@ -61,14 +61,15 @@ describe('validateMethod', () => { describe('produces an error', () => { it('exactly one error', () => { - expect(result.errors).to.have.length(1); + expect(result).to.have.errors.lengthOf(1); }); it('has explanatory message', () => { - expect(result.errors[0]).to.have.property( - 'message', - 'Expected "method" field to equal "POST", but got "GET".' - ); + expect(result) + .to.have.errorAtIndex(0) + .withMessage( + 'Expected "method" field to equal "POST", but got "GET".' + ); }); }); }); @@ -101,14 +102,13 @@ describe('validateMethod', () => { describe('produces an error', () => { it('exactly one error', () => { - expect(result.errors).to.have.length(1); + expect(result).to.have.errors.lengthOf(1); }); it('has explanatory message', () => { - expect(result.errors[0]).to.have.property( - 'message', - 'Expected "method" field to equal "PATCH", but got "".' - ); + expect(result) + .to.have.errorAtIndex(0) + .withMessage('Expected "method" field to equal "PATCH", but got "".'); }); }); }); diff --git a/test/unit/units/validateStatusCode.test.js b/test/unit/units/validateStatusCode.test.js index eacec997..5985de62 100644 --- a/test/unit/units/validateStatusCode.test.js +++ b/test/unit/units/validateStatusCode.test.js @@ -29,7 +29,7 @@ describe('validateStatusCode', () => { }); it('has no errors', () => { - expect(result.errors).to.have.length(0); + expect(result).to.not.have.errors; }); }); @@ -61,14 +61,13 @@ describe('validateStatusCode', () => { describe('produces error', () => { it('exactly one error', () => { - expect(result.errors).to.have.length(1); + expect(result).to.have.errors.lengthOf(1); }); it('has explanatory message', () => { - expect(result.errors[0]).to.have.property( - 'message', - `Status code is '200' instead of '400'` - ); + expect(result) + .to.have.errorAtIndex(0) + .withMessage(`Status code is '200' instead of '400'`); }); }); }); diff --git a/test/unit/units/validateURI.test.js b/test/unit/units/validateURI.test.js index db794365..27f142b4 100644 --- a/test/unit/units/validateURI.test.js +++ b/test/unit/units/validateURI.test.js @@ -30,7 +30,7 @@ describe('validateURI', () => { }); it('has no errors', () => { - expect(result.errors).to.have.length(0); + expect(result).to.not.have.errors; }); }); @@ -62,7 +62,7 @@ describe('validateURI', () => { }); it('has no errors', () => { - expect(result.errors).to.have.length(0); + expect(result).to.not.have.errors; }); }); @@ -94,7 +94,7 @@ describe('validateURI', () => { }); it('has no errors', () => { - expect(result.errors).to.have.length(0); + expect(result).to.not.have.errors; }); }); @@ -126,7 +126,7 @@ describe('validateURI', () => { }); it('has no errors', () => { - expect(result.errors).to.have.length(0); + expect(result).to.not.have.errors; }); }); }); @@ -161,14 +161,15 @@ describe('validateURI', () => { describe('produces an error', () => { it('exactly one error', () => { - expect(result.errors).to.have.length(1); + expect(result).to.have.errors.lengthOf(1); }); it('has explanatory message', () => { - expect(result.errors[0]).to.have.property( - 'message', - 'Expected "uri" field to equal "/dashboard", but got: "/profile".' - ); + expect(result) + .to.have.errorAtIndex(0) + .withMessage( + 'Expected "uri" field to equal "/dashboard", but got: "/profile".' + ); }); }); }); @@ -202,14 +203,15 @@ describe('validateURI', () => { describe('produces an error', () => { it('exactly one error', () => { - expect(result.errors).to.have.length(1); + expect(result).to.have.errors.lengthOf(1); }); it('has explanatory message', () => { - expect(result.errors[0]).to.have.property( - 'message', - 'Expected "uri" field to equal "/account?id=123", but got: "/account".' - ); + expect(result) + .to.have.errorAtIndex(0) + .withMessage( + 'Expected "uri" field to equal "/account?id=123", but got: "/account".' + ); }); }); }); @@ -242,14 +244,15 @@ describe('validateURI', () => { describe('produces an error', () => { it('exactly one error', () => { - expect(result.errors).to.have.length(1); + expect(result).to.have.errors.lengthOf(1); }); it('has explanatory message', () => { - expect(result.errors[0]).to.have.property( - 'message', - 'Expected "uri" field to equal "/account?name=user", but got: "/account?nAmE=usEr".' - ); + expect(result) + .to.have.errorAtIndex(0) + .withMessage( + 'Expected "uri" field to equal "/account?name=user", but got: "/account?nAmE=usEr".' + ); }); }); }); @@ -282,14 +285,15 @@ describe('validateURI', () => { describe('produces an error', () => { it('exactly one error', () => { - expect(result.errors).to.have.length(1); + expect(result).to.have.errors.lengthOf(1); }); it('has explanatory message', () => { - expect(result.errors[0]).to.have.property( - 'message', - 'Expected "uri" field to equal "/zoo?type=cats&type=dogs", but got: "/zoo?type=dogs&type=cats".' - ); + expect(result) + .to.have.errorAtIndex(0) + .withMessage( + 'Expected "uri" field to equal "/zoo?type=cats&type=dogs", but got: "/zoo?type=dogs&type=cats".' + ); }); }); });