From 0c8871b73179c31a8a2f8f25dbfaf260db74a205 Mon Sep 17 00:00:00 2001 From: artem-zakharchenko Date: Wed, 26 Jun 2019 15:48:42 +0200 Subject: [PATCH 01/29] feat: adds "kind", "values" field properties, removes deprecations BREAKING CHANGE: - Gavel fields no longer contain the following properties: - `validator` - `expectedType` - `realType` - `rawData` - Gavel fields now contain the next new properies: - `kind` - `values` (optional) --- lib/units/validateBody.js | 42 +++-- lib/units/validateHeaders.js | 11 +- lib/units/validateMethod.js | 18 +- lib/units/validateStatusCode.js | 21 +-- lib/units/validateURI.js | 26 +-- lib/validate.js | 2 +- test/chai.js | 157 ++++++------------ test/integration/validate.test.js | 129 +++----------- test/unit/units/validateBody.test.js | 141 ++++------------ .../validateBody/getBodyValidator.test.js | 1 + test/unit/units/validateHeaders.test.js | 44 +---- test/unit/units/validateMethod.test.js | 54 +++--- test/unit/units/validateStatusCode.test.js | 33 ++-- test/unit/units/validateURI.test.js | 132 ++++++--------- 14 files changed, 268 insertions(+), 543 deletions(-) diff --git a/lib/units/validateBody.js b/lib/units/validateBody.js index 58ed19de..c67b35e8 100644 --- a/lib/units/validateBody.js +++ b/lib/units/validateBody.js @@ -126,16 +126,17 @@ function getBodyValidator(realType, expectedType) { }; const validators = [ - [TextDiff, both(isPlainText)], + [TextDiff, both(isPlainText), 'text'], // List JsonSchema first, because weak predicate of JsonExample // would resolve on "application/schema+json" media type too. [ JsonSchema, (real, expected) => { return isJson(real) && isJsonSchema(expected); - } + }, + 'json' ], - [JsonExample, both(isJson)] + [JsonExample, both(isJson), 'json'] ]; const validator = validators.find(([_name, predicate]) => { @@ -146,10 +147,10 @@ function getBodyValidator(realType, expectedType) { const error = `Can't validate real media type '${mediaTyper.format( realType )}' against expected media type '${mediaTyper.format(expectedType)}'.`; - return [error, null]; + return [error, null, null]; } - return [null, validator[0]]; + return [null, validator[0], validator[2]]; } /** @@ -161,6 +162,10 @@ function validateBody(expected, real) { const errors = []; const realBodyType = typeof real.body; const hasEmptyRealBody = real.body === ''; + const values = { + expected: expected.body, + actual: real.body + }; // Throw when user input for real body is not a string. if (realBodyType !== 'string') { @@ -185,13 +190,15 @@ function validateBody(expected, real) { if (realTypeError) { errors.push({ - message: realTypeError + message: realTypeError, + values }); } if (expectedTypeError) { errors.push({ - message: expectedTypeError + message: expectedTypeError, + values }); } @@ -199,8 +206,8 @@ function validateBody(expected, real) { // Skipping body validation in case errors during // real/expected body type definition. - const [validatorError, ValidatorClass] = hasErrors - ? [null, null] + const [validatorError, ValidatorClass, kind] = hasErrors + ? [null, null, null] : getBodyValidator(realType, expectedType); if (validatorError) { @@ -213,11 +220,13 @@ function validateBody(expected, real) { errors.push({ message: `Expected "body" of "${mediaTyper.format( expectedType - )}" media type, but actual "body" is missing.` + )}" media type, but actual "body" is missing.`, + values }); } else { errors.push({ - message: validatorError + message: validatorError, + values }); } } @@ -229,16 +238,15 @@ function validateBody(expected, real) { real.body, usesJsonSchema ? expected.bodySchema : expected.body ); - const rawData = validator && validator.validate(); + // Without ".validate()" it cannot evaluate output to result. + // TODO Re-do this. + validator && validator.validate(); const validationErrors = validator ? validator.evaluateOutputToResults() : []; errors.push(...validationErrors); return { - isValid: isValidField({ errors }), - validator: ValidatorClass && ValidatorClass.name, - realType: mediaTyper.format(realType), - expectedType: mediaTyper.format(expectedType), - rawData, + valid: isValidField({ errors }), + kind, errors }; } diff --git a/lib/units/validateHeaders.js b/lib/units/validateHeaders.js index bcfed620..4ca8ebc8 100644 --- a/lib/units/validateHeaders.js +++ b/lib/units/validateHeaders.js @@ -26,7 +26,9 @@ function validateHeaders(expected, real) { const validator = hasJsonHeaders ? new HeadersJsonExample(real.headers, expected.headers) : null; - const rawData = validator && validator.validate(); + + // if you don't call ".validate()", it never evaluates any results. + validator && validator.validate(); if (validator) { errors.push(...validator.evaluateOutputToResults()); @@ -42,11 +44,8 @@ and expected data media type } return { - isValid: isValidField({ errors }), - validator: validator && 'HeadersJsonExample', - realType, - expectedType, - rawData, + valid: isValidField({ errors }), + kind: hasJsonHeaders ? 'json' : 'text', errors }; } diff --git a/lib/units/validateMethod.js b/lib/units/validateMethod.js index ef14f144..1390b792 100644 --- a/lib/units/validateMethod.js +++ b/lib/units/validateMethod.js @@ -1,22 +1,22 @@ -const APIARY_METHOD_TYPE = 'text/vnd.apiary.method'; - function validateMethod(expected, real) { const { method: expectedMethod } = expected; const { method: realMethod } = real; - const isValid = realMethod === expectedMethod; + const valid = realMethod === expectedMethod; const errors = []; - if (!isValid) { + if (!valid) { errors.push({ - message: `Expected "method" field to equal "${expectedMethod}", but got "${realMethod}".` + message: `Expected "method" field to equal "${expectedMethod}", but got "${realMethod}".`, + values: { + expected: expectedMethod, + actual: realMethod + } }); } return { - isValid, - validator: null, - realType: APIARY_METHOD_TYPE, - expectedType: APIARY_METHOD_TYPE, + valid, + kind: 'text', errors }; } diff --git a/lib/units/validateStatusCode.js b/lib/units/validateStatusCode.js index 5c757514..36d5ac2a 100644 --- a/lib/units/validateStatusCode.js +++ b/lib/units/validateStatusCode.js @@ -1,28 +1,25 @@ -const APIARY_STATUS_CODE_TYPE = 'text/vnd.apiary.status-code'; - /** * Validates given real and expected status codes. * @param {Object} real * @param {number} expected */ function validateStatusCode(expected, real) { - const isValid = real.statusCode === expected.statusCode; + const valid = real.statusCode === expected.statusCode; const errors = []; - if (!isValid) { + if (!valid) { errors.push({ - message: `Status code is '${real.statusCode}' instead of '${ - expected.statusCode - }'` + message: `Status code is '${real.statusCode}' instead of '${expected.statusCode}'`, + values: { + expected: expected.statusCode, + actual: real.statusCode + } }); } return { - isValid, - validator: 'TextDiff', - realType: APIARY_STATUS_CODE_TYPE, - expectedType: APIARY_STATUS_CODE_TYPE, - rawData: '', + valid, + kind: 'text', errors }; } diff --git a/lib/units/validateURI.js b/lib/units/validateURI.js index 7f769c11..cdb4eb6b 100644 --- a/lib/units/validateURI.js +++ b/lib/units/validateURI.js @@ -1,8 +1,6 @@ const url = require('url'); const deepEqual = require('deep-equal'); -const APIARY_URI_TYPE = 'text/vnd.apiary.uri'; - /** * Parses the given URI and returns the properties * elligible for comparison. Leaves out raw properties like "path" @@ -20,32 +18,34 @@ const parseURI = (uri) => { }; }; -const validateURI = (expected, real) => { +const validateURI = (expected, actual) => { const { uri: expectedURI } = expected; - const { uri: realURI } = real; + const { uri: actualURI } = actual; // Parses URI to perform a correct comparison: // - literal comparison of pathname // - order-insensitive comparison of query parameters - const parsedExpected = parseURI(expectedURI, true); - const parsedReal = parseURI(realURI, true); + const parsedExpected = parseURI(expectedURI); + const parsedActual = parseURI(actualURI); // Note the different order of arguments between // "validateURI" and "deepEqual". - const isValid = deepEqual(parsedReal, parsedExpected); + const valid = deepEqual(parsedActual, parsedExpected); const errors = []; - if (!isValid) { + if (!valid) { errors.push({ - message: `Expected "uri" field to equal "${expectedURI}", but got: "${realURI}".` + message: `Expected "uri" field to equal "${expectedURI}", but got: "${actualURI}".`, + values: { + expected: expectedURI, + actual: actualURI + } }); } return { - isValid, - validator: null, - expectedType: APIARY_URI_TYPE, - realType: APIARY_URI_TYPE, + valid, + kind: 'text', errors }; }; diff --git a/lib/validate.js b/lib/validate.js index ba456b72..74e9a15e 100644 --- a/lib/validate.js +++ b/lib/validate.js @@ -47,7 +47,7 @@ function validate(expectedMessage, realMessage) { } // Indicates the validity of the real message - result.isValid = isValidResult(result); + result.valid = isValidResult(result); return result; } diff --git a/test/chai.js b/test/chai.js index 43b6ea6f..2e18dafa 100644 --- a/test/chai.js +++ b/test/chai.js @@ -1,5 +1,6 @@ /* eslint-disable no-underscore-dangle */ const chai = require('chai'); +const deepEqual = require('deep-equal'); const stringify = (obj) => { return JSON.stringify(obj, null, 2); @@ -18,31 +19,33 @@ chai.use(({ Assertion }, utils) => { new Assertion(error).to.have.property(propName); this.assert( - isRegExp ? expectedValue.test(target) : target === expectedValue, + isRegExp + ? expectedValue.test(target) + : deepEqual(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} + +to have an error at index ${currentErrorIndex} that includes property "${propName}" that ${matchWord}: + +${JSON.stringify(expectedValue)} + +but got: + +${JSON.stringify(target)} `, ` - Expected the next HTTP message field: - - ${stringifiedObj} - - not to have ${propName} at index ${currentErrorIndex}, but got: - - ${target.toString()} +Expected the next HTTP message field: + +${stringifiedObj} + +to have an error at index ${currentErrorIndex} that includes property "${propName}" that not ${matchWord}: + +${JSON.stringify(target)} `, - expectedValue.toString(), - target.toString(), + JSON.stringify(expectedValue), + JSON.stringify(target), true ); }); @@ -50,19 +53,43 @@ chai.use(({ Assertion }, utils) => { createErrorPropertyAssertion('message', 'withMessage'); createErrorPropertyAssertion('pointer', 'withPointer'); + createErrorPropertyAssertion('values', 'withValues'); + + // + // TODO + // Finish the error messages + Assertion.addMethod('kind', function(expectedValue) { + const { kind } = this._obj; + const stringifiedObj = stringify(this._obj); + + this.assert( + kind === expectedValue, + ` +Expected the following HTTP message field: + +${stringifiedObj} + +to have "kind" property equal to "${expectedValue}". + `, + 'asdas', + expectedValue, + kind, + true + ); + }); utils.addProperty(Assertion.prototype, 'valid', function() { - const { isValid } = this._obj; + const { valid } = this._obj; const stringifiedObj = stringify(this._obj); this.assert( - isValid === true, + valid === true, ` Expected the following HTTP message field: ${stringifiedObj} -to have "isValid" equal #{exp}, but got #{act}'. +to have "valid" equal #{exp}, but got #{act}'. `, ` Expected the following HTTP message field: @@ -70,8 +97,8 @@ Expected the following HTTP message field: ${stringifiedObj} to be invalid, but it is actually valid.`, - { isValid }, - { isValid: true }, + { valid }, + { valid: true }, true ); }); @@ -120,84 +147,6 @@ to have no errors, but got ${errors.length} error(s). utils.flag(this, 'currentError', errors[index]); utils.flag(this, 'currentErrorIndex', index); }); - - 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, - true - ); - }); - - 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, - true - ); - }); - - 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, - true - ); - }); }); module.exports = chai; diff --git a/test/integration/validate.test.js b/test/integration/validate.test.js index a6e2baaf..639407ef 100644 --- a/test/integration/validate.test.js +++ b/test/integration/validate.test.js @@ -22,31 +22,19 @@ describe('validate', () => { describe('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'); + expect(result.fields.method).to.have.kind('text'); expect(result.fields.method).to.not.have.errors; }); describe('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' - ); - expect(result.fields.headers).to.have.realType( - 'application/vnd.apiary.http-headers+json' - ); + expect(result.fields.headers).to.have.kind('json'); expect(result.fields.headers).to.not.have.errors; }); describe('body', () => { 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'); + expect(result.fields.body).to.have.kind('json'); expect(result.fields.body).to.not.have.errors; }); }); @@ -77,12 +65,7 @@ describe('validate', () => { describe('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'); - + expect(result.fields.method).to.have.kind('text'); describe('produces one error', () => { it('exactly one error', () => { expect(result.fields.method).to.have.errors.lengthOf(1); @@ -100,21 +83,13 @@ describe('validate', () => { describe('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' - ); - expect(result.fields.headers).to.have.realType( - 'application/vnd.apiary.http-headers+json' - ); + expect(result.fields.headers).to.have.kind('json'); expect(result.fields.headers).to.not.have.errors; }); describe('body', () => { 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'); + expect(result.fields.body).to.have.kind('json'); describe('produces an error', () => { it('exactly one error', () => { @@ -150,33 +125,19 @@ describe('validate', () => { describe('statusCode', () => { 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' - ); + expect(result.fields.statusCode).to.have.kind('text'); expect(result.fields.statusCode).to.not.have.errors; }); describe('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' - ); - expect(result.fields.headers).to.have.realType( - 'application/vnd.apiary.http-headers+json' - ); + expect(result.fields.headers).to.have.kind('json'); expect(result.fields.headers).to.not.have.errors; }); describe('body', () => { 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'); + expect(result.fields.body).to.have.kind('json'); expect(result.fields.body).to.not.have.errors; }); }); @@ -206,14 +167,7 @@ describe('validate', () => { describe('statusCode', () => { 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' - ); - + expect(result.fields.statusCode).to.have.kind('text'); describe('produces an error', () => { it('exactly one error', () => { expect(result.fields.statusCode).to.have.errors.lengthOf(1); @@ -229,13 +183,7 @@ describe('validate', () => { describe('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' - ); - expect(result.fields.headers).to.have.realType( - 'application/vnd.apiary.http-headers+json' - ); + expect(result.fields.headers).to.have.kind('json'); describe('produces an error', () => { it('exactly one error', () => { @@ -276,26 +224,13 @@ describe('validate', () => { describe('statusCode', () => { 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' - ); + expect(result.fields.statusCode).to.have.kind('text'); expect(result.fields.statusCode).to.not.have.errors; }); describe('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' - ); - expect(result.fields.headers).to.have.realType( - 'application/vnd.apiary.http-headers+json' - ); - + expect(result.fields.headers).to.have.kind('json'); describe('produces an error', () => { it('exactly one error', () => { expect(result.fields.headers).to.have.errors.lengthOf(1); @@ -349,11 +284,7 @@ describe('validate', () => { describe('for properties present in both expected and real', () => { describe('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'); + expect(result.fields.method).to.have.kind('text'); describe('produces an error', () => { it('exactly one error', () => { @@ -367,6 +298,15 @@ describe('validate', () => { 'Expected "method" field to equal "POST", but got "PUT".' ); }); + + it('includes values', () => { + expect(result.fields.method) + .to.have.errorAtIndex(0) + .withValues({ + expected: 'POST', + actual: 'PUT' + }); + }); }); }); }); @@ -374,14 +314,7 @@ describe('validate', () => { describe('for properties present in expected, but not in real', () => { describe('statusCode', () => { 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' - ); - + expect(result.fields.statusCode).to.have.kind('text'); describe('produces an error', () => { it('exactly one error', () => { expect(result.fields.statusCode).to.have.errors.lengthOf(1); @@ -397,14 +330,7 @@ describe('validate', () => { describe('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' - ); - expect(result.fields.headers).to.have.realType( - 'application/vnd.apiary.http-headers+json' - ); - + expect(result.fields.headers).to.have.kind('json'); describe('produces one error', () => { it('exactly one error', () => { expect(result.fields.headers).to.have.errors.lengthOf(1); @@ -422,10 +348,7 @@ describe('validate', () => { describe('body', () => { 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'); - + expect(result.fields.body).to.have.kind(null); describe('produces an error', () => { it('exactly one error', () => { expect(result.fields.body).to.have.errors.lengthOf(1); diff --git a/test/unit/units/validateBody.test.js b/test/unit/units/validateBody.test.js index 846de9b6..b5b742bf 100644 --- a/test/unit/units/validateBody.test.js +++ b/test/unit/units/validateBody.test.js @@ -35,16 +35,8 @@ describe('validateBody', () => { expect(result).to.not.be.valid; }); - it('has no validator', () => { - expect(result).to.have.validator(null); - }); - - it('has "application/json" real type', () => { - expect(result).to.have.realType('application/json'); - }); - - it('has "text/plain" expected type', () => { - expect(result).to.have.expectedType('text/plain'); + it('has "null" kind', () => { + expect(result).to.have.kind(null); }); describe('produces validation error', () => { @@ -59,6 +51,15 @@ describe('validateBody', () => { `Can't validate real media type 'application/json' against expected media type 'text/plain'.` ); }); + + it('includes values', () => { + expect(result) + .to.have.errorAtIndex(0) + .withValues({ + expected: '', + actual: '{ "foo": "bar" }' + }); + }); }); }); @@ -79,16 +80,8 @@ describe('validateBody', () => { expect(result).to.be.valid; }); - it('has "JsonExample" validator', () => { - expect(result).to.have.validator('JsonExample'); - }); - - it('has "application/json" real type', () => { - expect(result).to.have.realType('application/json'); - }); - - it('has "application/json" expected type', () => { - expect(result).to.have.expectedType('application/json'); + it('has "json" kind', () => { + expect(result).to.have.kind('json'); }); it('has no errors', () => { @@ -111,16 +104,8 @@ describe('validateBody', () => { expect(result).to.not.be.valid; }); - it('has no validator', () => { - expect(result).to.have.validator(null); - }); - - it('fallbacks to "text/plain" real type', () => { - expect(result).to.have.realType('text/plain'); - }); - - it('has "application/json" expected type', () => { - expect(result).to.have.expectedType('application/json'); + it('has "null" kind', () => { + expect(result).to.have.kind(null); }); describe('produces content-type error', () => { @@ -157,16 +142,8 @@ describe('validateBody', () => { expect(result).to.be.valid; }); - it('has "JsonExample" validator', () => { - expect(result).to.have.validator('JsonExample'); - }); - - it('has "application/hal+json" real type', () => { - expect(result).to.have.realType('application/hal+json'); - }); - - it('has "application/json" expected type', () => { - expect(result).to.have.expectedType('application/json'); + it('has "json" kind', () => { + expect(result).to.have.kind('json'); }); it('has no errors', () => { @@ -191,16 +168,8 @@ describe('validateBody', () => { expect(result).to.not.be.valid; }); - it('has no validator', () => { - expect(result).to.have.validator(null); - }); - - it('fallbacks to "text/plain" real type', () => { - expect(result).to.have.realType('text/plain'); - }); - - it('has "text/plain" expected type', () => { - expect(result).to.have.expectedType('text/plain'); + it('has "null" kind', () => { + expect(result).to.have.kind(null); }); describe('produces error', () => { @@ -236,16 +205,8 @@ describe('validateBody', () => { expect(result).to.be.valid; }); - it('has "TextDiff" validator', () => { - expect(result).to.have.validator('TextDiff'); - }); - - it('has text/plain real type', () => { - expect(result).to.have.realType('text/plain'); - }); - - it('has "text/plain" expected type', () => { - expect(result).to.have.expectedType('text/plain'); + it('has "text" kind', () => { + expect(result).to.have.kind('text'); }); it('has no errors', () => { @@ -267,16 +228,8 @@ describe('validateBody', () => { expect(result).to.not.be.valid; }); - it('has "TextDiff" validator', () => { - expect(result).to.have.validator('TextDiff'); - }); - - it('has "text/plain" real type', () => { - expect(result).to.have.realType('text/plain'); - }); - - it('has "text/plain" expected type', () => { - expect(result).to.have.expectedType('text/plain'); + it('has "text" kind', () => { + expect(result).to.have.kind('text'); }); describe('produces validation error', () => { @@ -308,16 +261,8 @@ describe('validateBody', () => { expect(result).to.be.valid; }); - it('has "JsonExample" validator', () => { - expect(result).to.have.validator('JsonExample'); - }); - - it('has "application/json" real type', () => { - expect(result).to.have.realType('application/json'); - }); - - it('has "application/json" expected type', () => { - expect(result).to.have.expectedType('application/json'); + it('has "json" kind', () => { + expect(result).to.have.kind('json'); }); it('has no errors', () => { @@ -339,16 +284,8 @@ describe('validateBody', () => { expect(result).to.not.be.valid; }); - it('has "JsonExample" validator', () => { - expect(result).to.have.validator('JsonExample'); - }); - - it('has "application/json" real type', () => { - expect(result).to.have.realType('application/json'); - }); - - it('has "application/json" expected type', () => { - expect(result).to.have.expectedType('application/json'); + it('has "json" kind', () => { + expect(result).to.have.kind('json'); }); describe('produces validation errors', () => { @@ -382,16 +319,8 @@ describe('validateBody', () => { expect(result).to.be.valid; }); - it('has "JsonSchema" validator', () => { - expect(result).to.have.validator('JsonSchema'); - }); - - it('has "application/json" real type', () => { - expect(result).to.have.realType('application/json'); - }); - - it('has "application/schema+json" expected type', () => { - expect(result).to.have.expectedType('application/schema+json'); + it('has "json" kind', () => { + expect(result).to.have.kind('json'); }); it('has no errors', () => { @@ -415,16 +344,8 @@ describe('validateBody', () => { expect(result).to.not.be.valid; }); - it('has "JsonSchema" validator', () => { - expect(result).to.have.validator('JsonSchema'); - }); - - it('has "application/json" real type', () => { - expect(result).to.have.realType('application/json'); - }); - - it('has "application/schema+json" expected type', () => { - expect(result).to.have.expectedType('application/schema+json'); + it('has "json" kind', () => { + expect(result).to.have.kind('json'); }); describe('produces an error', () => { diff --git a/test/unit/units/validateBody/getBodyValidator.test.js b/test/unit/units/validateBody/getBodyValidator.test.js index 8406eb90..a83e7579 100644 --- a/test/unit/units/validateBody/getBodyValidator.test.js +++ b/test/unit/units/validateBody/getBodyValidator.test.js @@ -44,6 +44,7 @@ describe('getBodyValidator', () => { }); }); + // TODO Remove or uncomment // describe('when given unknown media type', () => { // const unknownContentTypes = [['text/html', 'text/xml']]; diff --git a/test/unit/units/validateHeaders.test.js b/test/unit/units/validateHeaders.test.js index 6a4d4c75..6d45767e 100644 --- a/test/unit/units/validateHeaders.test.js +++ b/test/unit/units/validateHeaders.test.js @@ -22,20 +22,8 @@ describe('validateHeaders', () => { expect(result).to.be.valid; }); - it('has "HeadersJsonExample" validator', () => { - expect(result).to.have.validator('HeadersJsonExample'); - }); - - it('has "application/vnd.apiary.http-headers+json" real type', () => { - expect(result).to.have.realType( - 'application/vnd.apiary.http-headers+json' - ); - }); - - it('has "application/vnd.apiary.http-headers+json" expected type', () => { - expect(result).to.have.expectedType( - 'application/vnd.apiary.http-headers+json' - ); + it('has "json" kind', () => { + expect(result).to.have.kind('json'); }); it('has no errors', () => { @@ -63,20 +51,8 @@ describe('validateHeaders', () => { expect(result).to.not.be.valid; }); - it('has "HeadersJsonExample" validator', () => { - expect(result).to.have.validator('HeadersJsonExample'); - }); - - it('has "application/vnd.apiary.http-headers+json" real type', () => { - expect(result).to.have.realType( - 'application/vnd.apiary.http-headers+json' - ); - }); - - it('has "application/vnd.apiary.http-headers+json" expected type', () => { - expect(result).to.have.expectedType( - 'application/vnd.apiary.http-headers+json' - ); + it('has "json" kind', () => { + expect(result).to.have.kind('json'); }); describe('produces errors', () => { @@ -122,16 +98,8 @@ describe('validateHeaders', () => { expect(result).to.not.be.valid; }); - it('has no validator', () => { - expect(result).to.have.validator(null); - }); - - it('has no real type', () => { - expect(result).to.have.realType(null); - }); - - it('has no expected type', () => { - expect(result).to.have.expectedType(null); + it('has "text" validator', () => { + expect(result).to.have.kind('text'); }); describe('produces an error', () => { diff --git a/test/unit/units/validateMethod.test.js b/test/unit/units/validateMethod.test.js index 8f206131..30f01d6e 100644 --- a/test/unit/units/validateMethod.test.js +++ b/test/unit/units/validateMethod.test.js @@ -16,16 +16,8 @@ describe('validateMethod', () => { expect(result).to.be.valid; }); - it('has "null" validator', () => { - expect(result).to.have.validator(null); - }); - - it('has "text/vnd.apiary.method" real type', () => { - expect(result).to.have.realType('text/vnd.apiary.method'); - }); - - it('has "text/vnd.apiary.method" expected type', () => { - expect(result).to.have.expectedType('text/vnd.apiary.method'); + it('has "text" kind', () => { + expect(result).to.have.kind('text'); }); it('has no errors', () => { @@ -47,16 +39,8 @@ describe('validateMethod', () => { expect(result).to.not.be.valid; }); - it('has "null" validator', () => { - expect(result).to.have.validator(null); - }); - - it('has "text/vnd.apiary.method" real type', () => { - expect(result).to.have.realType('text/vnd.apiary.method'); - }); - - it('has "text/vnd.apiary.method" expected type', () => { - expect(result).to.have.expectedType('text/vnd.apiary.method'); + it('has "text" kind', () => { + expect(result).to.have.kind('text'); }); describe('produces an error', () => { @@ -71,6 +55,15 @@ describe('validateMethod', () => { 'Expected "method" field to equal "POST", but got "GET".' ); }); + + it('includes values', () => { + expect(result) + .to.have.errorAtIndex(0) + .withValues({ + expected: 'POST', + actual: 'GET' + }); + }); }); }); @@ -88,16 +81,8 @@ describe('validateMethod', () => { expect(result).to.not.be.valid; }); - it('has "null" validator', () => { - expect(result).to.have.validator(null); - }); - - it('has "text/vnd.apiary.method" real type', () => { - expect(result).to.have.realType('text/vnd.apiary.method'); - }); - - it('has "text/vnd.apiary.method" expected type', () => { - expect(result).to.have.expectedType('text/vnd.apiary.method'); + it('has "text" kind', () => { + expect(result).to.have.kind('text'); }); describe('produces an error', () => { @@ -110,6 +95,15 @@ describe('validateMethod', () => { .to.have.errorAtIndex(0) .withMessage('Expected "method" field to equal "PATCH", but got "".'); }); + + it('includes values', () => { + expect(result) + .to.have.errorAtIndex(0) + .withValues({ + expected: 'PATCH', + actual: '' + }); + }); }); }); }); diff --git a/test/unit/units/validateStatusCode.test.js b/test/unit/units/validateStatusCode.test.js index 5985de62..ec2c1932 100644 --- a/test/unit/units/validateStatusCode.test.js +++ b/test/unit/units/validateStatusCode.test.js @@ -16,16 +16,8 @@ describe('validateStatusCode', () => { expect(result).to.be.valid; }); - it('has "TextDiff" validator', () => { - expect(result).to.have.validator('TextDiff'); - }); - - it('has "text/vnd.apiary.status-code" expected type', () => { - expect(result).to.have.expectedType('text/vnd.apiary.status-code'); - }); - - it('has "text/vnd.apiary.status-code" real type', () => { - expect(result).to.have.realType('text/vnd.apiary.status-code'); + it('has "text" kind', () => { + expect(result).to.have.kind('text'); }); it('has no errors', () => { @@ -47,16 +39,8 @@ describe('validateStatusCode', () => { expect(result).to.not.be.valid; }); - it('has "TextDiff" validator', () => { - expect(result).to.have.validator('TextDiff'); - }); - - it('has "text/vnd.apiary.status-code" expected type', () => { - expect(result).to.have.expectedType('text/vnd.apiary.status-code'); - }); - - it('has "text/vnd.apiary.status-code" real type', () => { - expect(result).to.have.realType('text/vnd.apiary.status-code'); + it('has "text" kind', () => { + expect(result).to.have.kind('text'); }); describe('produces error', () => { @@ -69,6 +53,15 @@ describe('validateStatusCode', () => { .to.have.errorAtIndex(0) .withMessage(`Status code is '200' instead of '400'`); }); + + it('includes values', () => { + expect(result) + .to.have.errorAtIndex(0) + .withValues({ + expected: '400', + actual: '200' + }); + }); }); }); }); diff --git a/test/unit/units/validateURI.test.js b/test/unit/units/validateURI.test.js index 27f142b4..99def358 100644 --- a/test/unit/units/validateURI.test.js +++ b/test/unit/units/validateURI.test.js @@ -17,16 +17,8 @@ describe('validateURI', () => { expect(result).to.be.valid; }); - it('has "null" validator', () => { - expect(result).to.have.validator(null); - }); - - it('has "text/vnd.apiary.uri" real type', () => { - expect(result).to.have.realType('text/vnd.apiary.uri'); - }); - - it('has "text/vnd.apiary.uri" expected type', () => { - expect(result).to.have.expectedType('text/vnd.apiary.uri'); + it('has "text" kind', () => { + expect(result).to.have.kind('text'); }); it('has no errors', () => { @@ -49,16 +41,8 @@ describe('validateURI', () => { expect(result).to.be.valid; }); - it('has "null" validator', () => { - expect(result).to.have.validator(null); - }); - - it('has "text/vnd.apiary.uri" real type', () => { - expect(result).to.have.realType('text/vnd.apiary.uri'); - }); - - it('has "text/vnd.apiary.uri" expected type', () => { - expect(result).to.have.expectedType('text/vnd.apiary.uri'); + it('has "text" kind', () => { + expect(result).to.have.kind('text'); }); it('has no errors', () => { @@ -81,16 +65,8 @@ describe('validateURI', () => { expect(result).to.be.valid; }); - it('has "null" validator', () => { - expect(result).to.have.validator(null); - }); - - it('has "text/vnd.apiary.uri" real type', () => { - expect(result).to.have.realType('text/vnd.apiary.uri'); - }); - - it('has "text/vnd.apiary.uri" expected type', () => { - expect(result).to.have.expectedType('text/vnd.apiary.uri'); + it('has "text" kind', () => { + expect(result).to.have.kind('text'); }); it('has no errors', () => { @@ -113,16 +89,8 @@ describe('validateURI', () => { expect(result).to.be.valid; }); - it('has "null" validator', () => { - expect(result).to.have.validator(null); - }); - - it('has "text/vnd.apiary.uri" real type', () => { - expect(result).to.have.realType('text/vnd.apiary.uri'); - }); - - it('has "text/vnd.apiary.uri" expected type', () => { - expect(result).to.have.expectedType('text/vnd.apiary.uri'); + it('has "text" kind', () => { + expect(result).to.have.kind('text'); }); it('has no errors', () => { @@ -147,16 +115,8 @@ describe('validateURI', () => { expect(result).to.not.be.valid; }); - it('has "null" validator', () => { - expect(result).to.have.validator(null); - }); - - it('has "text/vnd.apiary.uri" real type', () => { - expect(result).to.have.realType('text/vnd.apiary.uri'); - }); - - it('has "text/vnd.apiary.uri" expected type', () => { - expect(result).to.have.expectedType('text/vnd.apiary.uri'); + it('has "text" kind', () => { + expect(result).to.have.kind('text'); }); describe('produces an error', () => { @@ -171,6 +131,15 @@ describe('validateURI', () => { 'Expected "uri" field to equal "/dashboard", but got: "/profile".' ); }); + + it('includes values', () => { + expect(result) + .to.have.errorAtIndex(0) + .withValues({ + expected: '/dashboard', + actual: '/profile' + }); + }); }); }); @@ -189,16 +158,8 @@ describe('validateURI', () => { expect(result).to.not.be.valid; }); - it('has "null" validator', () => { - expect(result).to.have.validator(null); - }); - - it('has "text/vnd.apiary.uri" real type', () => { - expect(result).to.have.realType('text/vnd.apiary.uri'); - }); - - it('has "text/vnd.apiary.uri" expected type', () => { - expect(result).to.have.expectedType('text/vnd.apiary.uri'); + it('has "text" kind', () => { + expect(result).to.have.kind('text'); }); describe('produces an error', () => { @@ -213,6 +174,15 @@ describe('validateURI', () => { 'Expected "uri" field to equal "/account?id=123", but got: "/account".' ); }); + + it('includes values', () => { + expect(result) + .to.have.errorAtIndex(0) + .withValues({ + expected: '/account?id=123', + actual: '/account' + }); + }); }); }); @@ -230,16 +200,8 @@ describe('validateURI', () => { expect(result).to.not.be.valid; }); - it('has "null" validator', () => { - expect(result).to.have.validator(null); - }); - - it('has "text/vnd.apiary.uri" real type', () => { - expect(result).to.have.realType('text/vnd.apiary.uri'); - }); - - it('has "text/vnd.apiary.uri" expected type', () => { - expect(result).to.have.expectedType('text/vnd.apiary.uri'); + it('has "text" kind', () => { + expect(result).to.have.kind('text'); }); describe('produces an error', () => { @@ -254,6 +216,15 @@ describe('validateURI', () => { 'Expected "uri" field to equal "/account?name=user", but got: "/account?nAmE=usEr".' ); }); + + it('includes values', () => { + expect(result) + .to.have.errorAtIndex(0) + .withValues({ + expected: '/account?name=user', + actual: '/account?nAmE=usEr' + }); + }); }); }); @@ -271,16 +242,8 @@ describe('validateURI', () => { expect(result).to.not.be.valid; }); - it('has "null" validator', () => { - expect(result).to.have.validator(null); - }); - - it('has "text/vnd.apiary.uri" real type', () => { - expect(result).to.have.realType('text/vnd.apiary.uri'); - }); - - it('has "text/vnd.apiary.uri" expected type', () => { - expect(result).to.have.expectedType('text/vnd.apiary.uri'); + it('has "text" kind', () => { + expect(result).to.have.kind('text'); }); describe('produces an error', () => { @@ -295,6 +258,15 @@ describe('validateURI', () => { 'Expected "uri" field to equal "/zoo?type=cats&type=dogs", but got: "/zoo?type=dogs&type=cats".' ); }); + + it('includes values', () => { + expect(result) + .to.have.errorAtIndex(0) + .withValues({ + expected: '/zoo?type=cats&type=dogs', + actual: '/zoo?type=dogs&type=cats' + }); + }); }); }); }); From b940729ced0b1cb8583657af1d2859a5802fdbd5 Mon Sep 17 00:00:00 2001 From: artem-zakharchenko Date: Mon, 1 Jul 2019 11:23:05 +0200 Subject: [PATCH 02/29] test: adjusts Cucumber step definitions to the new vocabulary --- lib/units/isValid.js | 6 +- lib/units/validateBody.js | 21 +-- lib/units/validateHeaders.js | 20 ++- lib/units/validateMethod.js | 18 +-- lib/units/validateStatusCode.js | 16 ++- lib/units/validateURI.js | 18 +-- lib/validate.js | 24 ++-- scripts/cucumber.js | 3 +- .../cucumber/step_definitions/cli_stepdefs.js | 20 ++- .../step_definitions/validators_steps.js | 20 --- test/cucumber/steps/cli.js | 22 +++ test/cucumber/steps/fields.js | 13 ++ test/cucumber/steps/general.js | 102 +++++++++++++ test/cucumber/support/world.js | 134 +++++++++++++----- .../validateBody/getBodyValidator.test.js | 21 --- 15 files changed, 310 insertions(+), 148 deletions(-) create mode 100644 test/cucumber/steps/cli.js create mode 100644 test/cucumber/steps/fields.js create mode 100644 test/cucumber/steps/general.js diff --git a/lib/units/isValid.js b/lib/units/isValid.js index 6e427cd1..d5a84fab 100644 --- a/lib/units/isValid.js +++ b/lib/units/isValid.js @@ -10,11 +10,11 @@ function isValidField({ errors }) { /** * Returns a boolean indicating the given validation result as valid. - * @param {Object} validationResult + * @param {Object} fields * @returns {boolean} */ -function isValidResult(validationResult) { - return Object.values(validationResult.fields).every(isValidField); +function isValidResult(fields) { + return Object.values(fields).every(isValidField); } module.exports = { diff --git a/lib/units/validateBody.js b/lib/units/validateBody.js index c67b35e8..0c1b94e1 100644 --- a/lib/units/validateBody.js +++ b/lib/units/validateBody.js @@ -144,9 +144,9 @@ function getBodyValidator(realType, expectedType) { }); if (!validator) { - const error = `Can't validate real media type '${mediaTyper.format( + const error = `Can't validate actual media type '${mediaTyper.format( realType - )}' against expected media type '${mediaTyper.format(expectedType)}'.`; + )}' against the expected media type '${mediaTyper.format(expectedType)}'.`; return [error, null, null]; } @@ -158,14 +158,14 @@ function getBodyValidator(realType, expectedType) { * @param {Object} expected * @param {Object} real */ -function validateBody(expected, real) { - const errors = []; - const realBodyType = typeof real.body; - const hasEmptyRealBody = real.body === ''; +function validateBody(expected, actual) { const values = { expected: expected.body, - actual: real.body + actual: actual.body }; + const errors = []; + const realBodyType = typeof actual.body; + const hasEmptyRealBody = actual.body === ''; // Throw when user input for real body is not a string. if (realBodyType !== 'string') { @@ -175,8 +175,8 @@ function validateBody(expected, real) { } const [realTypeError, realType] = getBodyType( - real.body, - real.headers && real.headers['content-type'], + actual.body, + actual.headers && actual.headers['content-type'], 'real' ); @@ -235,7 +235,7 @@ function validateBody(expected, real) { const validator = ValidatorClass && new ValidatorClass( - real.body, + actual.body, usesJsonSchema ? expected.bodySchema : expected.body ); // Without ".validate()" it cannot evaluate output to result. @@ -247,6 +247,7 @@ function validateBody(expected, real) { return { valid: isValidField({ errors }), kind, + values, errors }; } diff --git a/lib/units/validateHeaders.js b/lib/units/validateHeaders.js index 4ca8ebc8..20c6bbe9 100644 --- a/lib/units/validateHeaders.js +++ b/lib/units/validateHeaders.js @@ -14,17 +14,21 @@ function getHeadersType(headers) { * @param {Object} expected * @param {Object} real */ -function validateHeaders(expected, real) { - const expectedType = getHeadersType(expected.headers); - const realType = getHeadersType(real.headers); +function validateHeaders(expected, actual) { + const values = { + expected: expected.headers, + actual: actual.headers + }; + const expectedType = getHeadersType(values.expected); + const actualType = getHeadersType(values.actual); const errors = []; const hasJsonHeaders = - realType === APIARY_JSON_HEADER_TYPE && + actualType === APIARY_JSON_HEADER_TYPE && expectedType === APIARY_JSON_HEADER_TYPE; const validator = hasJsonHeaders - ? new HeadersJsonExample(real.headers, expected.headers) + ? new HeadersJsonExample(values.actual, values.expected) : null; // if you don't call ".validate()", it never evaluates any results. @@ -36,16 +40,18 @@ function validateHeaders(expected, real) { errors.push({ message: `\ No validator found for real data media type -"${realType}" +"${actualType}" and expected data media type "${expectedType}".\ -` +`, + values }); } return { valid: isValidField({ errors }), kind: hasJsonHeaders ? 'json' : 'text', + values, errors }; } diff --git a/lib/units/validateMethod.js b/lib/units/validateMethod.js index 1390b792..ba30ef09 100644 --- a/lib/units/validateMethod.js +++ b/lib/units/validateMethod.js @@ -1,22 +1,22 @@ -function validateMethod(expected, real) { - const { method: expectedMethod } = expected; - const { method: realMethod } = real; - const valid = realMethod === expectedMethod; +function validateMethod(expected, actual) { + const values = { + expected: expected.method, + actual: actual.method + }; + const valid = values.actual === values.expected; const errors = []; if (!valid) { errors.push({ - message: `Expected "method" field to equal "${expectedMethod}", but got "${realMethod}".`, - values: { - expected: expectedMethod, - actual: realMethod - } + message: `Expected method '${values.expected}', but got '${values.actual}'.`, + values }); } return { valid, kind: 'text', + values, errors }; } diff --git a/lib/units/validateStatusCode.js b/lib/units/validateStatusCode.js index 36d5ac2a..9d867dd9 100644 --- a/lib/units/validateStatusCode.js +++ b/lib/units/validateStatusCode.js @@ -3,23 +3,25 @@ * @param {Object} real * @param {number} expected */ -function validateStatusCode(expected, real) { - const valid = real.statusCode === expected.statusCode; +function validateStatusCode(expected, actual) { + const values = { + expected: expected.statusCode, + actual: actual.statusCode + }; + const valid = values.actual === values.expected; const errors = []; if (!valid) { errors.push({ - message: `Status code is '${real.statusCode}' instead of '${expected.statusCode}'`, - values: { - expected: expected.statusCode, - actual: real.statusCode - } + message: `Expected status code '${values.expected}', but got '${values.actual}'.`, + values }); } return { valid, kind: 'text', + values, errors }; } diff --git a/lib/units/validateURI.js b/lib/units/validateURI.js index cdb4eb6b..8c5a6cb9 100644 --- a/lib/units/validateURI.js +++ b/lib/units/validateURI.js @@ -19,14 +19,16 @@ const parseURI = (uri) => { }; const validateURI = (expected, actual) => { - const { uri: expectedURI } = expected; - const { uri: actualURI } = actual; + const values = { + expected: expected.uri, + actual: actual.uri + }; // Parses URI to perform a correct comparison: // - literal comparison of pathname // - order-insensitive comparison of query parameters - const parsedExpected = parseURI(expectedURI); - const parsedActual = parseURI(actualURI); + const parsedExpected = parseURI(values.expected); + const parsedActual = parseURI(values.actual); // Note the different order of arguments between // "validateURI" and "deepEqual". @@ -35,17 +37,15 @@ const validateURI = (expected, actual) => { if (!valid) { errors.push({ - message: `Expected "uri" field to equal "${expectedURI}", but got: "${actualURI}".`, - values: { - expected: expectedURI, - actual: actualURI - } + message: `Expected URI '${values.expected}', but got '${values.actual}'.`, + values }); } return { valid, kind: 'text', + values, errors }; }; diff --git a/lib/validate.js b/lib/validate.js index 74e9a15e..5272c311 100644 --- a/lib/validate.js +++ b/lib/validate.js @@ -8,16 +8,15 @@ const { validateStatusCode } = require('./units/validateStatusCode'); const { validateHeaders } = require('./units/validateHeaders'); const { validateBody } = require('./units/validateBody'); -function validate(expectedMessage, realMessage) { - const result = { - fields: {} - }; +function validate(expectedMessage, actualMessage) { + const result = {}; + const fields = {}; // Uses strict coercion on real message. // Strict coercion ensures that real message always has properties // illegible for validation with the expected message, even if they // are not present in the real message. - const real = normalize(coerce(realMessage)); + const actual = normalize(coerce(actualMessage)); // Use weak coercion on expected message. // Weak coercion will transform only the properties present in the @@ -27,27 +26,28 @@ function validate(expectedMessage, realMessage) { const expected = normalize(coerceWeak(expectedMessage)); if (expected.method) { - result.fields.method = validateMethod(expected, real); + fields.method = validateMethod(expected, actual); } if (expected.uri) { - result.fields.uri = validateURI(expected, real); + fields.uri = validateURI(expected, actual); } if (expected.statusCode) { - result.fields.statusCode = validateStatusCode(expected, real); + fields.statusCode = validateStatusCode(expected, actual); } if (expected.headers) { - result.fields.headers = validateHeaders(expected, real); + fields.headers = validateHeaders(expected, actual); } if (isset(expected.body) || isset(expected.bodySchema)) { - result.fields.body = validateBody(expected, real); + fields.body = validateBody(expected, actual); } - // Indicates the validity of the real message - result.valid = isValidResult(result); + // Indicates the validity of the actual message + result.valid = isValidResult(fields); + result.fields = fields; return result; } diff --git a/scripts/cucumber.js b/scripts/cucumber.js index 4109a69d..ec6271f9 100644 --- a/scripts/cucumber.js +++ b/scripts/cucumber.js @@ -1,3 +1,4 @@ +/* eslint-disable import/no-extraneous-dependencies */ const spawn = require('cross-spawn'); const isWindows = process.platform.match(/^win/); @@ -22,7 +23,7 @@ const cucumber = spawn( '-r', 'test/cucumber/support/', '-r', - 'test/cucumber/step_definitions/', + 'test/cucumber/steps/', '-f', 'pretty', 'node_modules/gavel-spec/features/' diff --git a/test/cucumber/step_definitions/cli_stepdefs.js b/test/cucumber/step_definitions/cli_stepdefs.js index 1feec0ec..2c481e59 100644 --- a/test/cucumber/step_definitions/cli_stepdefs.js +++ b/test/cucumber/step_definitions/cli_stepdefs.js @@ -9,7 +9,7 @@ module.exports = function() { return callback(); }); - this.Given(/^you record real raw HTTP messages:$/, function(cmd, callback) { + this.Given(/^you record actual raw HTTP messages:$/, function(cmd, callback) { this.commandBuffer += `;${cmd}`; return callback(); }); @@ -22,7 +22,10 @@ module.exports = function() { } ); - this.When(/^a header is missing in real messages:$/, function(cmd, callback) { + this.When(/^a header is missing in actual messages:$/, function( + cmd, + callback + ) { this.commandBuffer += `;${cmd}`; return callback(); }); @@ -36,16 +39,11 @@ module.exports = function() { }`; const child = exec(cmd, function(error, stdout, stderr) { if (error) { - if (parseInt(error.code) !== parseInt(expectedExitStatus)) { + if (parseInt(error.code, 10) !== parseInt(expectedExitStatus, 10)) { return callback( new Error( - `Expected exit status ${expectedExitStatus} but got ${ - error.code - }.` + - 'STDERR: ' + - stderr + - 'STDOUT: ' + - stdout + `Expected exit status ${expectedExitStatus} but got ${error.code}. STDERR: ${stderr} + STDOUT: ${stdout}` ) ); } @@ -55,7 +53,7 @@ module.exports = function() { }); return child.on('exit', function(code) { - if (parseInt(code) !== parseInt(expectedExitStatus)) { + if (parseInt(code, 10) !== parseInt(expectedExitStatus, 10)) { callback( new Error( `Expected exit status ${expectedExitStatus} but got ${code}.` diff --git a/test/cucumber/step_definitions/validators_steps.js b/test/cucumber/step_definitions/validators_steps.js index 7eb10a60..b8bf3a25 100644 --- a/test/cucumber/step_definitions/validators_steps.js +++ b/test/cucumber/step_definitions/validators_steps.js @@ -29,7 +29,6 @@ module.exports = function() { try { const result = this.validate(); this.results = JSON.parse(JSON.stringify(result)); - this.booleanResult = result.isValid; return callback(); } catch (error) { callback(new Error(`Got error during validation:\n${error}`)); @@ -170,25 +169,6 @@ module.exports = function() { } }); - this.Then(/^validator "([^"]*)" is used for validation$/, function( - validator, - callback - ) { - const usedValidator = this.componentResults.validator; - if (validator !== usedValidator) { - callback( - new Error( - `Used validator '${usedValidator}'` + - " instead of '" + - validator + - "'. Got validation results: " + - JSON.stringify(this.results, null, 2) - ) - ); - } - return callback(); - }); - this.Then( /^validation key "([^"]*)" looks like the following "([^"]*)":$/, function(key, type, expected, callback) { diff --git a/test/cucumber/steps/cli.js b/test/cucumber/steps/cli.js new file mode 100644 index 00000000..1e2ee772 --- /dev/null +++ b/test/cucumber/steps/cli.js @@ -0,0 +1,22 @@ +const { assert } = require('chai'); + +module.exports = function() { + this.Given( + /^(you record (expected|actual) raw HTTP message:)|(a header is missing in actual message:)$/, + function(_, __, ___, command) { + this.commands.push(command); + } + ); + + this.When( + 'you validate the message using the following Gavel command:', + async function(command) { + this.commands.push(command); + this.status = await this.executeCommands(this.commands); + } + ); + + this.Then(/^exit status is (\d+)$/, function(expectedStatus) { + assert.equal(this.status, expectedStatus, 'Process statuses do not match'); + }); +}; diff --git a/test/cucumber/steps/fields.js b/test/cucumber/steps/fields.js new file mode 100644 index 00000000..6c29facb --- /dev/null +++ b/test/cucumber/steps/fields.js @@ -0,0 +1,13 @@ +const chai = require('chai'); +const jhp = require('json-parse-helpfulerror'); + +chai.config.truncateThreshold = 0; +const { expect } = chai; + +module.exports = function() { + this.Then(/^field "([^"]*)" equals:$/, function(fieldName, expectedJson) { + const expected = jhp.parse(expectedJson); + + expect(this.result.fields[fieldName]).to.deep.equal(expected); + }); +}; diff --git a/test/cucumber/steps/general.js b/test/cucumber/steps/general.js new file mode 100644 index 00000000..9b734184 --- /dev/null +++ b/test/cucumber/steps/general.js @@ -0,0 +1,102 @@ +const { expect } = require('chai'); +const Diff = require('googlediff'); +const jhp = require('json-parse-helpfulerror'); + +module.exports = function() { + this.Given( + /^you expect the following HTTP (message|request|response):$/i, + function(_, expectedMessage) { + this.expected = jhp.parse(expectedMessage); + } + ); + + this.Given(/^actual HTTP (message|request|response) is:$/i, function( + _, + actualMessage + ) { + this.actual = jhp.parse(actualMessage); + }); + + this.Given(/^actual "([^"]*)" field equals "([^"]*)"/, function( + fieldName, + value + ) { + this.actual[fieldName] = value; + }); + + this.Given(/^you expect "([^"]*)" field to equal "([^"]*)"$/, function( + fieldName, + expectedValue + ) { + this.expected[fieldName] = expectedValue; + }); + + this.Given(/^you expect "([^"]*)" field to equal:$/, function( + fieldName, + codeBlock + ) { + // Perform conditional code block parsing (if headers, etc.) + this.expected[fieldName] = this.transformCodeBlock(fieldName, codeBlock); + }); + + this.Given( + /^you expect "body" field to match the following "([^"]*)":$/, + function(bodyType, value) { + switch (bodyType) { + case 'JSON schema': + this.expected.bodySchema = value; + break; + default: + this.expected.body = value; + break; + } + } + ); + + this.Given(/^actual "([^"]*)" field equals:$/, function( + fieldName, + codeBlock + ) { + // Also perform conditional code parsing + this.actual[fieldName] = this.transformCodeBlock(fieldName, codeBlock); + }); + + // Actions + this.When('Gavel validates HTTP message', function() { + this.validate(); + }); + + // Assertions + this.Then(/^HTTP message is( NOT)? valid$/i, function(isInvalid) { + expect(this.result).to.have.property('valid', !isInvalid); + }); + + this.Then('the validation result is:', function(expectedResult) { + const dmp = new Diff(); + const stringifiedActual = JSON.stringify(this.result, null, 2); + + expect(this.result).to.deep.equal( + jhp.parse(expectedResult), + `\ +Expected the following result: + +${stringifiedActual} + +to equal: + +${expectedResult} + +See the text diff patches below: + +${dmp.patch_toText(dmp.patch_make(stringifiedActual, expectedResult))} +` + ); + }); + + this.Then(/^field "(\w+)" is( NOT)? valid$/i, function(fieldName, isInvalid) { + expect(this.result).to.have.nested.property( + `fields.${fieldName}.valid`, + !isInvalid + ); + }); +}; diff --git a/test/cucumber/support/world.js b/test/cucumber/support/world.js index 9e70eb4e..225fb4e2 100644 --- a/test/cucumber/support/world.js +++ b/test/cucumber/support/world.js @@ -5,10 +5,12 @@ * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ /* eslint-disable */ -const gavel = require('../../../lib'); const vm = require('vm'); +const Diff = require('googlediff'); const util = require('util'); const { assert } = require('chai'); +const { exec } = require('child_process'); +const gavel = require('../../../lib'); const HTTP_LINE_DELIMITER = '\n'; @@ -17,35 +19,27 @@ class World { this.codeBuffer = ''; this.commandBuffer = ''; - // Data for creation of: - // - // - ExpecterHttpResponse - // - ExpectedHttpRequest - // - ExpectedHttpMessage - this.expected = {}; + // NEW + this.commands = []; - // Data for creation of: - // - // - HttpResponse - // - HttpRequest - // - HttpMessage - this.real = {}; + this.expected = {}; + this.actual = {}; // Parsed HTTP objects for model valdiation this.model = {}; - // Results of validators + // Gavel validation result this.results = {}; + // CLI: Process exit status + this.status = null; + // Validation verdict for the whole HTTP Message - this.booleanResult = false; + // this.booleanResult = false; // Component relevant to the expectation, e.g. 'body' this.component = null; this.componentResults = null; - - this.expectedType = null; - this.realType = null; } expectBlockEval(block, expectedReturn, callback) { @@ -93,8 +87,8 @@ class World { // http://nodejs.org/docs/v0.8.23/api/all.html#all_all_together const formattedCode = code.replace( - "require('gavel", - "require('../../../lib" + `require('gavel`, + `require('../../../lib` ); try { @@ -111,31 +105,83 @@ class World { } } + executeCommands(commands) { + const commandsBuffer = commands.join(';'); + const cmd = + `PATH=$PATH:${process.cwd()}/bin:${process.cwd()}/node_modules/.bin; cd /tmp/gavel-* ;` + + commandsBuffer; + + return new Promise((resolve) => { + const child = exec(cmd, function(error, stdout, stderr) { + if (error) { + resolve(error.code); + } + }); + + child.on('exit', function(code) { + resolve(code); + }); + }); + } + validate() { - return gavel.validate(this.expected, this.real); + this.result = gavel.validate(this.expected, this.actual); + } + + transformCodeBlock(fieldName, value) { + switch (fieldName) { + case 'headers': + return this.parseHeaders(value); + default: + return value; + } } parseHeaders(headersString) { const lines = headersString.split(HTTP_LINE_DELIMITER); - const headers = {}; - for (let line of Array.from(lines)) { - const parts = line.split(':'); - const key = parts.shift(); - headers[key.toLowerCase()] = parts.join(':').trim(); - } + + const headers = lines.reduce((acc, line) => { + // Using RegExp to parse a header line. + // Splitting by semicolon (:) would split + // Date header's time delimiter: + // > Date: Fri, 13 Dec 3000 23:59:59 GMT + const match = line.match(/^(\S+):\s+(.+)$/); + + assert.isNotNull( + match, + `\ +Failed to parse a header line: +${line} + +Make sure it's in the "Header-Name: value" format. +` + ); + + const [_, key, value] = match; + + return { + ...acc, + [key.toLowerCase()]: value.trim() + }; + }, {}); return headers; } parseRequestLine(parsed, firstLine) { - firstLine = firstLine.split(' '); - parsed.method = firstLine[0]; - parsed.uri = firstLine[1]; + const [method, uri] = firstLine.split(' '); + parsed.method = method; + parsed.uri = uri; + // firstLine = firstLine.split(' '); + // parsed.method = firstLine[0]; + // parsed.uri = firstLine[1]; } parseResponseLine(parsed, firstLine) { - firstLine = firstLine.split(' '); - parsed.statusCode = firstLine[1]; - parsed.statusMessage = firstLine[2]; + const [statusCode] = firstLine.split(' '); + parsed.statusCode = statusCode; + // firstLine = firstLine.split(' '); + // parsed.statusCode = firstLine[1]; + // parsed.statusMessage = firstLine[2]; } parseHttp(type, string) { @@ -144,7 +190,6 @@ class World { } const parsed = {}; - const lines = string.split(HTTP_LINE_DELIMITER); if (type === 'request') { @@ -157,6 +202,7 @@ class World { const bodyLines = []; const headersLines = []; let bodyEntered = false; + for (let line of Array.from(lines)) { if (line === '') { bodyEntered = true; @@ -177,21 +223,24 @@ class World { // Hacky coercion function to parse expcected Boolean values // from Gherkin feature suites. + // + // TODO Replace with the {boolean} placeholder from the + // next version of Cucumber. toBoolean(string) { if (string === 'true') return true; if (string === 'false') return false; return !!string; } - toCamelCase(input) { - const result = input.replace(/\s([a-z])/g, (strings) => + toCamelCase(string) { + const result = string.replace(/\s([a-z])/g, (strings) => strings[1].toUpperCase() ); return result; } - toPascalCase(input) { - let result = input.replace( + toPascalCase(string) { + let result = string.replace( /(\w)(\w*)/g, (g0, g1, g2) => g1.toUpperCase() + g2.toLowerCase() ); @@ -207,6 +256,15 @@ class World { } } + diff(expected, actual) { + const dmp = new Diff(); + console.log( + dmp.patch_toText( + dmp.patch_make(JSON.stringify(actual), JSON.stringify(expected)) + ) + ); + } + // Debugging helper throw(data) { throw new Error(this.inspect(data)); diff --git a/test/unit/units/validateBody/getBodyValidator.test.js b/test/unit/units/validateBody/getBodyValidator.test.js index a83e7579..9a977444 100644 --- a/test/unit/units/validateBody/getBodyValidator.test.js +++ b/test/unit/units/validateBody/getBodyValidator.test.js @@ -43,25 +43,4 @@ describe('getBodyValidator', () => { }); }); }); - - // TODO Remove or uncomment - // describe('when given unknown media type', () => { - // const unknownContentTypes = [['text/html', 'text/xml']]; - - // unknownContentTypes.forEach((contentTypes) => { - // const [realContentType, expectedContentType] = contentTypes; - // const [real, expected] = getMediaTypes( - // realContentType, - // expectedContentType - // ); - - // describe(`${realContentType} + ${expectedContentType}`, () => { - // const [error, validator] = getBodyValidator(real, expected); - - // it('...', () => { - // console.log({ error, validator }); - // }); - // }); - // }); - // }); }); From 7bfd35eb679e661f73640597604fb8e55ae761eb Mon Sep 17 00:00:00 2001 From: artem-zakharchenko Date: Tue, 2 Jul 2019 09:51:56 +0200 Subject: [PATCH 03/29] chore: uses "valid" property in request/response (bin) --- bin/gavel | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/gavel b/bin/gavel index d7f8e779..be54be16 100755 --- a/bin/gavel +++ b/bin/gavel @@ -33,7 +33,7 @@ process.stdin.on('end', function() { const requestResult = gavel.validate(expectedRequest, realRequest); const responseResult = gavel.validate(expectedResponse, realResponse); - if (requestResult.isValid && responseResult.isValid) { + if (requestResult.valid && responseResult.valid) { process.exit(0); } else { process.exit(1); From 123be20ebf0124758a95de70ddbdaaacb5625de2 Mon Sep 17 00:00:00 2001 From: artem-zakharchenko Date: Tue, 2 Jul 2019 10:00:11 +0200 Subject: [PATCH 04/29] chore: removes unused step definitions for cucumber tests --- test/cucumber/step_definitions/body_steps.js | 21 -- .../cucumber/step_definitions/cli_stepdefs.js | 66 ------ .../step_definitions/headers_steps.js | 17 -- .../step_definitions/javascript_steps.js | 54 ----- .../cucumber/step_definitions/method_steps.js | 17 -- test/cucumber/step_definitions/model_steps.js | 57 ----- .../step_definitions/status_code_steps.js | 14 -- test/cucumber/step_definitions/uri_steps.js | 17 -- .../validation_errors_thens.js | 46 ---- .../step_definitions/validators_steps.js | 215 ------------------ 10 files changed, 524 deletions(-) delete mode 100644 test/cucumber/step_definitions/body_steps.js delete mode 100644 test/cucumber/step_definitions/cli_stepdefs.js delete mode 100644 test/cucumber/step_definitions/headers_steps.js delete mode 100644 test/cucumber/step_definitions/javascript_steps.js delete mode 100644 test/cucumber/step_definitions/method_steps.js delete mode 100644 test/cucumber/step_definitions/model_steps.js delete mode 100644 test/cucumber/step_definitions/status_code_steps.js delete mode 100644 test/cucumber/step_definitions/uri_steps.js delete mode 100644 test/cucumber/step_definitions/validation_errors_thens.js delete mode 100644 test/cucumber/step_definitions/validators_steps.js diff --git a/test/cucumber/step_definitions/body_steps.js b/test/cucumber/step_definitions/body_steps.js deleted file mode 100644 index db3b21d0..00000000 --- a/test/cucumber/step_definitions/body_steps.js +++ /dev/null @@ -1,21 +0,0 @@ -module.exports = function() { - this.Given( - /^you define expected HTTP body using the following "([^"]*)":$/, - function(type, body, callback) { - if (type === 'textual example') { - this.expected.body = body; - } else if (type === 'JSON example') { - this.expected.body = body; - } else if (type === 'JSON schema') { - this.expected.bodySchema = JSON.parse(body); - } - - return callback(); - } - ); - - return this.When(/^real HTTP body is following:$/, function(body, callback) { - this.real.body = body; - return callback(); - }); -}; diff --git a/test/cucumber/step_definitions/cli_stepdefs.js b/test/cucumber/step_definitions/cli_stepdefs.js deleted file mode 100644 index 2c481e59..00000000 --- a/test/cucumber/step_definitions/cli_stepdefs.js +++ /dev/null @@ -1,66 +0,0 @@ -const { exec } = require('child_process'); - -module.exports = function() { - this.Given(/^you record expected raw HTTP messages:$/, function( - cmd, - callback - ) { - this.commandBuffer += `;${cmd}`; - return callback(); - }); - - this.Given(/^you record actual raw HTTP messages:$/, function(cmd, callback) { - this.commandBuffer += `;${cmd}`; - return callback(); - }); - - this.When( - /^you validate the message using the following Gavel command:$/, - function(cmd, callback) { - this.commandBuffer += `;${cmd}`; - return callback(); - } - ); - - this.When(/^a header is missing in actual messages:$/, function( - cmd, - callback - ) { - this.commandBuffer += `;${cmd}`; - return callback(); - }); - - return this.Then(/^exit status is (\d+)$/, function( - expectedExitStatus, - callback - ) { - const cmd = `PATH=$PATH:${process.cwd()}/bin:${process.cwd()}/node_modules/.bin; cd /tmp/gavel-* ${ - this.commandBuffer - }`; - const child = exec(cmd, function(error, stdout, stderr) { - if (error) { - if (parseInt(error.code, 10) !== parseInt(expectedExitStatus, 10)) { - return callback( - new Error( - `Expected exit status ${expectedExitStatus} but got ${error.code}. STDERR: ${stderr} - STDOUT: ${stdout}` - ) - ); - } - - return callback(); - } - }); - - return child.on('exit', function(code) { - if (parseInt(code, 10) !== parseInt(expectedExitStatus, 10)) { - callback( - new Error( - `Expected exit status ${expectedExitStatus} but got ${code}.` - ) - ); - } - return callback(); - }); - }); -}; diff --git a/test/cucumber/step_definitions/headers_steps.js b/test/cucumber/step_definitions/headers_steps.js deleted file mode 100644 index 952f1a3b..00000000 --- a/test/cucumber/step_definitions/headers_steps.js +++ /dev/null @@ -1,17 +0,0 @@ -module.exports = function() { - this.Given(/^you expect the following HTTP headers:$/, function( - string, - callback - ) { - this.expected.headers = this.parseHeaders(string); - return callback(); - }); - - return this.When(/^real HTTP headers are following:$/, function( - string, - callback - ) { - this.real.headers = this.parseHeaders(string); - return callback(); - }); -}; diff --git a/test/cucumber/step_definitions/javascript_steps.js b/test/cucumber/step_definitions/javascript_steps.js deleted file mode 100644 index 7fb8314f..00000000 --- a/test/cucumber/step_definitions/javascript_steps.js +++ /dev/null @@ -1,54 +0,0 @@ -/* eslint-disable */ -module.exports = function() { - this.Given( - /^you define following( expected)? HTTP (request|response) object:/, - function(isExpected, messageType, string, callback) { - this.codeBuffer += `${string}\n`; - return callback(); - } - ); - - // - this.Given(/^you define the following "([^"]*)" variable:$/, function( - arg1, - string, - callback - ) { - this.codeBuffer += string + '\n'; - return callback(); - }); - - this.Given(/^you add expected "([^"]*)" to real "([^"]*)":$/, function( - arg1, - arg2, - string, - callback - ) { - this.codeBuffer += string + '\n'; - return callback(); - }); - - this.Given(/^prepare result variable:$/, function(string, callback) { - this.codeBuffer += string + '\n'; - return callback(); - }); - - this.Then(/^"([^"]*)" variable will contain:$/, function( - varName, - string, - callback - ) { - this.codeBuffer += varName + '\n'; - const expected = string; - return this.expectBlockEval(this.codeBuffer, expected, callback); - }); - - this.When(/^you call:$/, function(string, callback) { - this.codeBuffer += string + '\n'; - return callback(); - }); - - return this.Then(/^it will return:$/, function(expected, callback) { - return this.expectBlockEval(this.codeBuffer, expected, callback); - }); -}; diff --git a/test/cucumber/step_definitions/method_steps.js b/test/cucumber/step_definitions/method_steps.js deleted file mode 100644 index 3d6a6cfd..00000000 --- a/test/cucumber/step_definitions/method_steps.js +++ /dev/null @@ -1,17 +0,0 @@ -module.exports = function() { - this.Given(/^you expect HTTP message method "([^"]*)"$/, function( - method, - callback - ) { - this.expected.method = method; - return callback(); - }); - - return this.When(/^real HTTP message method is "([^"]*)"$/, function( - method, - callback - ) { - this.real.method = method; - return callback(); - }); -}; diff --git a/test/cucumber/step_definitions/model_steps.js b/test/cucumber/step_definitions/model_steps.js deleted file mode 100644 index e21f6dac..00000000 --- a/test/cucumber/step_definitions/model_steps.js +++ /dev/null @@ -1,57 +0,0 @@ -/* eslint-disable */ -const deepEqual = require('deep-equal'); -const gavel = require('../../../lib'); - -module.exports = function() { - // TODO consider refactoring for for better acceptace testing to separated steps - // i.e. do not use http parsing, use separate steps for body, headers, code, etc... - this.When(/^you have the following real HTTP request:$/, function( - requestString, - callback - ) { - this.model.request = this.parseHttp('request', requestString); - return callback(); - }); - - this.When(/^you have the following real HTTP response:$/, function( - responseString, - callback - ) { - this.model.response = this.parseHttp('response', responseString); - return callback(); - }); - - return this.Then( - /^"([^"]*)" JSON representation will look like this:$/, - function(objectTypeString, string, callback) { - let data; - const expectedObject = JSON.parse(string); - - if (objectTypeString === 'HTTP Request') { - data = this.model.request; - } else if (objectTypeString === 'HTTP Response') { - data = this.model.response; - } else if (objectTypeString === 'Expected HTTP Request') { - data = this.expected; - } else if (objectTypeString === 'Expected HTTP Response') { - data = this.expected; - } - - const jsonizedInstance = JSON.parse(JSON.stringify(data)); - - if (!deepEqual(expectedObject, jsonizedInstance, { strict: true })) { - callback( - new Error( - 'Objects are not equal: ' + - '\nexpected: \n' + - JSON.stringify(expectedObject, null, 2) + - '\njsonized instance: \n' + - JSON.stringify(jsonizedInstance, null, 2) - ) - ); - } - - return callback(); - } - ); -}; diff --git a/test/cucumber/step_definitions/status_code_steps.js b/test/cucumber/step_definitions/status_code_steps.js deleted file mode 100644 index d57ed23a..00000000 --- a/test/cucumber/step_definitions/status_code_steps.js +++ /dev/null @@ -1,14 +0,0 @@ -module.exports = function() { - this.Given(/^you expect HTTP status code "([^"]*)"$/, function( - code, - callback - ) { - this.expected.statusCode = code; - return callback(); - }); - - return this.When(/^real status code is "([^"]*)"$/, function(code, callback) { - this.real.statusCode = code; - return callback(); - }); -}; diff --git a/test/cucumber/step_definitions/uri_steps.js b/test/cucumber/step_definitions/uri_steps.js deleted file mode 100644 index ffb7c1e6..00000000 --- a/test/cucumber/step_definitions/uri_steps.js +++ /dev/null @@ -1,17 +0,0 @@ -module.exports = function() { - this.Given(/^you expect HTTP message URI "([^"]*)"$/, function( - uri, - callback - ) { - this.expected.uri = uri; - return callback(); - }); - - return this.When(/^real HTTP message URI is "([^"]*)"$/, function( - uri, - callback - ) { - this.real.uri = uri; - return callback(); - }); -}; diff --git a/test/cucumber/step_definitions/validation_errors_thens.js b/test/cucumber/step_definitions/validation_errors_thens.js deleted file mode 100644 index 4758af25..00000000 --- a/test/cucumber/step_definitions/validation_errors_thens.js +++ /dev/null @@ -1,46 +0,0 @@ -const { assert } = require('chai'); - -module.exports = function() { - this.Then(/^field "([^"]*)" is( NOT)? valid$/, function( - fieldName, - isNotValid, - callback - ) { - const result = this.validate(); - - assert.property( - result.fields, - fieldName, - `Expected to have "${fieldName}" field in the validation result, but got none.` - ); - - assert.propertyVal( - result.fields[fieldName], - 'isValid', - !isNotValid, - `Expected "result.fields.${fieldName}" to be valid, but it's not.` - ); - - return callback(); - }); - - this.Then(/^Request or Response is NOT valid$/, function(callback) { - const result = this.validate(); - if (result.isValid) { - callback( - new Error('Request or Response is valid and should NOT be valid.') - ); - } - return callback(); - }); - - return this.Then(/^Request or Response is valid$/, function(callback) { - const result = this.validate(); - if (!result.isValid) { - callback( - new Error('Request or Response is NOT valid and should be valid.') - ); - } - return callback(); - }); -}; diff --git a/test/cucumber/step_definitions/validators_steps.js b/test/cucumber/step_definitions/validators_steps.js deleted file mode 100644 index b8bf3a25..00000000 --- a/test/cucumber/step_definitions/validators_steps.js +++ /dev/null @@ -1,215 +0,0 @@ -/* eslint-disable */ -const tv4 = require('tv4'); -const { assert } = require('chai'); -const deepEqual = require('deep-equal'); - -module.exports = function() { - this.When( - /^you perform a failing validation on any validatable HTTP component$/, - function(callback) { - const json1 = '{"a": "b"}'; - const json2 = '{"c": "d"}'; - - this.component = 'body'; - - this.real = { - headers: { - 'content-type': 'application/json' - }, - body: json1 - }; - - this.expected = { - headers: { - 'content-type': 'application/json' - }, - body: json2 - }; - - try { - const result = this.validate(); - this.results = JSON.parse(JSON.stringify(result)); - return callback(); - } catch (error) { - callback(new Error(`Got error during validation:\n${error}`)); - } - } - ); - - this.Then( - /^the validator output for the HTTP component looks like the following JSON:$/, - function(expectedJson, callback) { - const expected = JSON.parse(expectedJson); - const real = this.results.fields[this.component]; - if (!deepEqual(real, expected, { strict: true })) { - return callback( - new Error( - 'Not matched! Expected:\n' + - JSON.stringify(expected, null, 2) + - '\n' + - 'But got:' + - '\n' + - JSON.stringify(real, null, 2) - ) - ); - } else { - return callback(); - } - } - ); - - this.Then(/^validated HTTP component is considered invalid$/, function( - callback - ) { - assert.isFalse(this.booleanResult); - return callback(); - }); - - this.Then( - /^the validator output for the HTTP component is valid against "([^"]*)" model JSON schema:$/, - function(model, schema, callback) { - const valid = tv4.validate( - this.results.fields[this.component], - JSON.parse(schema) - ); - if (!valid) { - return callback( - new Error( - 'Expected no validation errors on schema but got:\n' + - JSON.stringify(tv4.error, null, 2) - ) - ); - } else { - return callback(); - } - } - ); - - this.Then( - /^each result entry under "([^"]*)" key must contain "([^"]*)" key$/, - function(key1, key2, callback) { - const error = this.results.fields[this.component]; - if (error === undefined) { - callback( - new Error( - 'Validation result for "' + - this.component + - '" is undefined. Validations: ' + - JSON.stringify(this.results, null, 2) - ) - ); - } - - error[key1].forEach((error) => assert.include(Object.keys(error), key2)); - return callback(); - } - ); - - this.Then( - /^the output JSON contains key "([^"]*)" with one of the following values:$/, - function(key, table, callback) { - const error = this.results.fields[this.component]; - - const validators = [].concat.apply([], table.raw()); - - assert.include(validators, error[key]); - return callback(); - } - ); - - this.Given(/^you want validate "([^"]*)" HTTP component$/, function( - component, - callback - ) { - this.component = component; - return callback(); - }); - - this.Given( - /^you express expected data by the following "([^"]*)" example:$/, - function(type, data, callback) { - if (type === 'application/schema+json') { - this.expected['bodySchema'] = data; - } else if (type === 'application/vnd.apiary.http-headers+json') { - this.expected[this.component] = JSON.parse(data); - } else { - this.expected[this.component] = data; - } - - this.expectedType = type; - return callback(); - } - ); - - this.Given(/^you have the following "([^"]*)" real data:$/, function( - type, - data, - callback - ) { - if (type === 'application/vnd.apiary.http-headers+json') { - this.real[this.component] = JSON.parse(data); - } else { - this.real[this.component] = data; - } - - this.realType = type; - return callback(); - }); - - this.When(/^you perform validation on the HTTP component$/, function( - callback - ) { - try { - const result = this.validate(); - this.results = result; - this.componentResults = this.results.fields[this.component]; - return callback(); - } catch (error) { - callback(new Error(`Error during validation: ${error}`)); - } - }); - - this.Then( - /^validation key "([^"]*)" looks like the following "([^"]*)":$/, - function(key, type, expected, callback) { - const real = this.componentResults[key]; - if (type === 'JSON') { - expected = JSON.parse(expected); - } else if (type === 'text') { - // FIXME investigate how does cucumber docstrings handle - // newlines and remove trim and remove this hack - expected = expected + '\n'; - } - - if (type === 'JSON') { - if (!deepEqual(expected, real, { strict: true })) { - callback( - new Error( - 'Not matched! Expected:\n' + - this.inspect(expected) + - '\n' + - 'But got:' + - '\n' + - this.inspect(real) + - '\n' + - 'End' - ) - ); - } - } else if (type === 'text') { - assert.equal(expected, real); - } - return callback(); - } - ); - - return this.Then(/^each result entry must contain "([^"]*)" key$/, function( - key, - callback - ) { - this.componentResults.errors.forEach((error) => - assert.include(Object.keys(error), key) - ); - return callback(); - }); -}; From 2c310377900d19eb6f6965ec757648d80e0c2b8f Mon Sep 17 00:00:00 2001 From: artem-zakharchenko Date: Tue, 2 Jul 2019 10:07:04 +0200 Subject: [PATCH 05/29] test: adjusts test suites to use unified error message format --- test/chai.js | 2 +- test/integration/validate.test.js | 12 ++++-------- test/unit/units/validateBody.test.js | 2 +- test/unit/units/validateMethod.test.js | 6 ++---- test/unit/units/validateStatusCode.test.js | 2 +- test/unit/units/validateURI.test.js | 10 ++++------ 6 files changed, 13 insertions(+), 21 deletions(-) diff --git a/test/chai.js b/test/chai.js index 2e18dafa..1deb78d9 100644 --- a/test/chai.js +++ b/test/chai.js @@ -44,8 +44,8 @@ to have an error at index ${currentErrorIndex} that includes property "${propNam ${JSON.stringify(target)} `, - JSON.stringify(expectedValue), JSON.stringify(target), + JSON.stringify(expectedValue), true ); }); diff --git a/test/integration/validate.test.js b/test/integration/validate.test.js index 639407ef..255a6244 100644 --- a/test/integration/validate.test.js +++ b/test/integration/validate.test.js @@ -74,9 +74,7 @@ describe('validate', () => { it('has explanatory message', () => { expect(result.fields.method) .to.have.errorAtIndex(0) - .withMessage( - 'Expected "method" field to equal "PUT", but got "POST".' - ); + .withMessage(`Expected method 'PUT', but got 'POST'.`); }); }); }); @@ -176,7 +174,7 @@ describe('validate', () => { it('has explanatory message', () => { expect(result.fields.statusCode) .to.have.errorAtIndex(0) - .withMessage(`Status code is '400' instead of '200'`); + .withMessage(`Expected status code '200', but got '400'.`); }); }); }); @@ -294,9 +292,7 @@ describe('validate', () => { it('has explanatory message', () => { expect(result.fields.method) .to.have.errorAtIndex(0) - .withMessage( - 'Expected "method" field to equal "POST", but got "PUT".' - ); + .withMessage(`Expected method 'POST', but got 'PUT'.`); }); it('includes values', () => { @@ -323,7 +319,7 @@ describe('validate', () => { it('has explanatory message', () => { expect(result.fields.statusCode) .to.have.errorAtIndex(0) - .withMessage(`Status code is 'undefined' instead of '200'`); + .withMessage(`Expected status code '200', but got 'undefined'.`); }); }); }); diff --git a/test/unit/units/validateBody.test.js b/test/unit/units/validateBody.test.js index b5b742bf..f8c2ca82 100644 --- a/test/unit/units/validateBody.test.js +++ b/test/unit/units/validateBody.test.js @@ -48,7 +48,7 @@ describe('validateBody', () => { expect(result) .to.have.errorAtIndex(0) .withMessage( - `Can't validate real media type 'application/json' against expected media type 'text/plain'.` + `Can't validate actual media type 'application/json' against the expected media type 'text/plain'.` ); }); diff --git a/test/unit/units/validateMethod.test.js b/test/unit/units/validateMethod.test.js index 30f01d6e..d5629846 100644 --- a/test/unit/units/validateMethod.test.js +++ b/test/unit/units/validateMethod.test.js @@ -51,9 +51,7 @@ describe('validateMethod', () => { it('has explanatory message', () => { expect(result) .to.have.errorAtIndex(0) - .withMessage( - 'Expected "method" field to equal "POST", but got "GET".' - ); + .withMessage(`Expected method 'POST', but got 'GET'.`); }); it('includes values', () => { @@ -93,7 +91,7 @@ describe('validateMethod', () => { it('has explanatory message', () => { expect(result) .to.have.errorAtIndex(0) - .withMessage('Expected "method" field to equal "PATCH", but got "".'); + .withMessage(`Expected method 'PATCH', but got ''.`); }); it('includes values', () => { diff --git a/test/unit/units/validateStatusCode.test.js b/test/unit/units/validateStatusCode.test.js index ec2c1932..d510ed7c 100644 --- a/test/unit/units/validateStatusCode.test.js +++ b/test/unit/units/validateStatusCode.test.js @@ -51,7 +51,7 @@ describe('validateStatusCode', () => { it('has explanatory message', () => { expect(result) .to.have.errorAtIndex(0) - .withMessage(`Status code is '200' instead of '400'`); + .withMessage(`Expected status code '400', but got '200'.`); }); it('includes values', () => { diff --git a/test/unit/units/validateURI.test.js b/test/unit/units/validateURI.test.js index 99def358..de706cba 100644 --- a/test/unit/units/validateURI.test.js +++ b/test/unit/units/validateURI.test.js @@ -127,9 +127,7 @@ describe('validateURI', () => { it('has explanatory message', () => { expect(result) .to.have.errorAtIndex(0) - .withMessage( - 'Expected "uri" field to equal "/dashboard", but got: "/profile".' - ); + .withMessage(`Expected URI '/dashboard', but got '/profile'.`); }); it('includes values', () => { @@ -171,7 +169,7 @@ describe('validateURI', () => { expect(result) .to.have.errorAtIndex(0) .withMessage( - 'Expected "uri" field to equal "/account?id=123", but got: "/account".' + `Expected URI '/account?id=123', but got '/account'.` ); }); @@ -213,7 +211,7 @@ describe('validateURI', () => { expect(result) .to.have.errorAtIndex(0) .withMessage( - 'Expected "uri" field to equal "/account?name=user", but got: "/account?nAmE=usEr".' + `Expected URI '/account?name=user', but got '/account?nAmE=usEr'.` ); }); @@ -255,7 +253,7 @@ describe('validateURI', () => { expect(result) .to.have.errorAtIndex(0) .withMessage( - 'Expected "uri" field to equal "/zoo?type=cats&type=dogs", but got: "/zoo?type=dogs&type=cats".' + `Expected URI '/zoo?type=cats&type=dogs', but got '/zoo?type=dogs&type=cats'.` ); }); From 8791429ffeb4d57865dbfd633e68b020d7fc0e2f Mon Sep 17 00:00:00 2001 From: artem-zakharchenko Date: Tue, 2 Jul 2019 11:29:56 +0200 Subject: [PATCH 06/29] chore: updates readme with the new API --- README.md | 253 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 238 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index cc02e6cf..e9a39957 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,247 @@ -# Gavel.js — Validator of HTTP Transactions +

+ + npm version + + + Build Status + + + Build Status + + + Coverage Status + + + Known Vulnerabilities + +

-[![npm version](https://badge.fury.io/js/gavel.svg)](https://badge.fury.io/js/gavel) -[![Build Status](https://travis-ci.org/apiaryio/gavel.js.svg?branch=master)](https://travis-ci.org/apiaryio/gavel.js) -[![Build status](https://ci.appveyor.com/api/projects/status/0cpnaoakhs8q58tn/branch/master?svg=true)](https://ci.appveyor.com/project/Apiary/gavel-js/branch/master) -[![Coverage Status](https://coveralls.io/repos/apiaryio/gavel.js/badge.svg?branch=master)](https://coveralls.io/r/apiaryio/gavel.js?branch=master) -[![Known Vulnerabilities](https://snyk.io/test/npm/gavel/badge.svg)](https://snyk.io/test/npm/gavel) +
-![Gavel.js - Validator of HTTP Transactions](https://raw.github.com/apiaryio/gavel/master/img/gavel.png?v=1) +

+ Gavel logo +

-Gavel detects important differences between actual and expected HTTP transactions (HTTP request and response pairs). Gavel also decides whether the actual HTTP transaction is valid or not. +

Gavel

-## Installation +

Gavel tells you whether an actual HTTP message is valid against an expected HTTP message.

-```sh -$ npm install gavel +## Install + +```bash +npm install gavel +``` + +## Usage + +### CLI + +```bash +# (Optional) Record HTTP messages +curl -s --trace - http://httpbin.org/ip | curl-trace-parser > expected +curl -s --trace - http://httpbin.org/ip | curl-trace-parser > actual + +# Perform the validation +cat actual | gavel expected +``` + +> **Gavel CLI is not supported on Windows**. Example above uses [`curl-trace-parser`](https://github.com/apiaryio/curl-trace-parser). + +### NodeJS + +```js +const gavel = require('gavel'); + +// Define HTTP messages +const expected = { + statusCode: 200, + headers: { + 'Content-Type': 'application/json' + } +}; + +const actual = { + statusCode: 404, + headers: { + 'Content-Type': 'application/json' + } +}; + +// Perform the validation +const result = gavel.validate(expected, actual); +``` + +The code above would return the following validation `result`: + +```js +{ + valid: false, + fields: { + statusCode: { + valid: false, + kind: 'text', + values: { + expected: '200', + actual: '404' + }, + errors: [ + { + message: `Expected status code '200', but got '404'.`, + values: { + expected: '200', + actual: '404' + } + } + ] + }, + headers: { + valid: true, + kind: 'json', + values: { + expected: { + 'Content-Type': 'application/json' + }, + actual: { + 'Content-Type': 'application/json' + } + }, + errors: [] + } + } +} ``` -## Documentation +### Usage with JSON Schema + +You can describe the body expectations using [JSON Schema](https://json-schema.org/) by providing a valid schema to the `bodySchema` property of the expected HTTP message: + +```js +const gavel = require('gavel'); + +const expected = { + bodySchema: { + type: 'object', + properties: { + fruits: { + type: 'array', + items: { + type: 'string' + } + } + } + } +}; + +const actual = { + body: JSON.stringify({ + fruits: ['apple', 'banana', 2] + }) +}; + +const result = gavel.validate(expected, actual); +``` + +The validation `result` against the given JSON Schema will look as follows: + +```js +{ + valid: false, + fields: { + body: { + valid: false, + kind: 'json', + values: { + actual: "{\"fruits\":[\"apple\",\"banana\",2]}" + }, + errors: [ + { + message: `At '/fruits/2' Invalid type: number (expected string)`, + location: { + pointer: '/fruits/2' + } + } + ] + } + } +} +``` + +> Note that JSON schema V5+ are not currently supported. + +## Examples + +Take a look at the examples of each field validation described in [Gherkin](https://cucumber.io/docs/gherkin/): + +- [`method`](https://github.com/apiaryio/gavel-spec/blob/master/features/javascript/fields/method) +- [`statusCode`](https://github.com/apiaryio/gavel-spec/blob/master/features/javascript/fields/statusCode) +- [`headers`](https://github.com/apiaryio/gavel-spec/blob/master/features/javascript/fields/headers) +- [`body`](https://github.com/apiaryio/gavel-spec/blob/master/features/javascript/fields/body) +- [`bodySchema`](https://github.com/apiaryio/gavel-spec/blob/master/features/javascript/fields/bodySchema) + +## Type definitions + +Type definitions below are described using [TypeScript](https://www.typescriptlang.org/) syntax. + +### Input + +> Gavel makes no assumptions over the validity of a given HTTP message according to the HTTP specification (RFCs [2616](https://www.ietf.org/rfc/rfc2616.txt), [7540](https://httpwg.org/specs/rfc7540.html)) and will accept any input matching the input type definition. Gavel will throw an exception when given malformed input data. + +Both expected and actual HTTP messages (no matter request or response) inherit from a single `HttpMessage` interface: + +```ts +interface HttpMessage { + method?: string; + statusCode?: number; + headers?: Record | string; + body?: string; + bodySchema?: Object | string; +} +``` + +### Output + +```ts +// Field kind describes the type of a field's values +// subjected to the end comparison. +enum FieldKind { + null // non-comparable data (validation didn't happen) + text // compared as text + json // compared as JSON +} + +interface ValidationResult { + valid: boolean // validity of the actual message + fields: { + [fieldName: string]: { + valid: boolean // validity of a single field + kind: FieldKind + values: { // end compared values (coerced, normalized) + actual: any + expected: any + } + errors: FieldError[] + } + } +} + +interface FieldError { + message: string + location?: { // kind-specific additional information + // kind: json + pointer?: string + property?: string + } + values?: { + expected: any + actual: any + } +} +``` + +## API + +- `validate(expected: HttpMessage, actual: HttpMessage): ValidationResult` -Gavel.js is a JavaScript implementation of the [Gavel behavior specification](https://www.relishapp.com/apiary/gavel/) ([repository](https://github.com/apiaryio/gavel-spec)): +## License -- [Gavel.js-specific documentation](https://www.relishapp.com/apiary/gavel/docs/javascript/) -- [CLI documentation](https://www.relishapp.com/apiary/gavel/docs/command-line-interface/) +MIT From bc65242ffac8d6466e30df8a6372f5ea1d1d6105 Mon Sep 17 00:00:00 2001 From: artem-zakharchenko Date: Wed, 3 Jul 2019 14:40:50 +0200 Subject: [PATCH 07/29] chore: include proper type definition of "location.property" in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e9a39957..59d985bc 100644 --- a/README.md +++ b/README.md @@ -229,7 +229,7 @@ interface FieldError { location?: { // kind-specific additional information // kind: json pointer?: string - property?: string + property?: string[] } values?: { expected: any From 87bc3f727289586c971657ac48b6c1ccdef71ff3 Mon Sep 17 00:00:00 2001 From: artem-zakharchenko Date: Wed, 3 Jul 2019 14:51:38 +0200 Subject: [PATCH 08/29] chore: introduces "errors[n].location" for body validation results --- lib/units/validateBody.js | 9 ++++++- lib/validators/json-schema.js | 44 ++++++++++++++-------------------- test/cucumber/steps/fields.js | 1 - test/cucumber/steps/general.js | 4 ++-- 4 files changed, 28 insertions(+), 30 deletions(-) diff --git a/lib/units/validateBody.js b/lib/units/validateBody.js index 0c1b94e1..374affbf 100644 --- a/lib/units/validateBody.js +++ b/lib/units/validateBody.js @@ -3,6 +3,7 @@ const mediaTyper = require('media-typer'); const contentTypeUtils = require('content-type'); const { TextDiff, JsonExample, JsonSchema } = require('../validators'); +const isset = require('../utils/isset'); const { isValidField } = require('./isValid'); function isPlainText(mediaType) { @@ -160,9 +161,15 @@ function getBodyValidator(realType, expectedType) { */ function validateBody(expected, actual) { const values = { - expected: expected.body, actual: actual.body }; + + // Prevent assigning { expected: undefined }. + // Also ignore "bodySchema" as the expected value. + if (isset(expected.body)) { + values.expected = expected.body; + } + const errors = []; const realBodyType = typeof actual.body; const hasEmptyRealBody = actual.body === ''; diff --git a/lib/validators/json-schema.js b/lib/validators/json-schema.js index c43d716e..8a464ee6 100644 --- a/lib/validators/json-schema.js +++ b/lib/validators/json-schema.js @@ -41,17 +41,11 @@ const jsonSchemaOptions = { singleError: false, messages: { minLength: (prop, val, validator) => - `The ${prop} property must be at least ${validator} characters long (currently ${ - val.length - } characters long).`, + `The ${prop} property must be at least ${validator} characters long (currently ${val.length} characters long).`, maxLength: (prop, val, validator) => - `The ${prop} property must not exceed ${validator} characters (currently${ - val.length - } characters long).`, + `The ${prop} property must not exceed ${validator} characters (currently${val.length} characters long).`, length: (prop, val, validator) => - `The ${prop} property must be exactly ${validator} characters long (currently ${ - val.length - } characters long).`, + `The ${prop} property must be exactly ${validator} characters long (currently ${val.length} characters long).`, format: (prop, val, validator) => `The ${prop} property must be ${getArticle( validator[0] @@ -72,13 +66,9 @@ const jsonSchemaOptions = { pattern: (prop, val, validator) => `The ${prop} value (${val}) does not match the ${validator} pattern.`, maxItems: (prop, val, validator) => - `The ${prop} property must not contain more than ${validator} items (currently contains ${ - val.length - } items).`, + `The ${prop} property must not contain more than ${validator} items (currently contains ${val.length} items).`, minItems: (prop, val, validator) => - `The ${prop} property must contain at least ${validator} items (currently contains ${ - val.length - } items).`, + `The ${prop} property must contain at least ${validator} items (currently contains ${val.length} items).`, divisibleBy: (prop, val, validator) => `The ${prop} property is not divisible by ${validator} (current value is ${JSON.stringify( val @@ -138,9 +128,7 @@ class JsonSchema { const validationResult = tv4.validateResult(this.schema, metaSchema); if (!validationResult.valid) { throw new errors.JsonSchemaNotValid( - `JSON schema is not valid draft ${this.jsonSchemaVersion}! ${ - validationResult.error.message - } at path "${validationResult.error.dataPath}"` + `JSON schema is not valid draft ${this.jsonSchemaVersion}! ${validationResult.error.message} at path "${validationResult.error.dataPath}"` ); } } @@ -230,23 +218,27 @@ class JsonSchema { const results = Array.from({ length: data.length }, (_, index) => { const item = data[index]; + const { message, property } = item; let pathArray = []; - if (item.property === null) { + if (property === null) { pathArray = []; } else if ( - Array.isArray(item.property) && - item.property.length === 1 && - [null, undefined].includes(item.property[0]) + Array.isArray(property) && + property.length === 1 && + [null, undefined].includes(property[0]) ) { pathArray = []; } else { - pathArray = item.property; + pathArray = property; } return { - pointer: jsonPointer.compile(pathArray), - message: item.message + message, + location: { + pointer: jsonPointer.compile(pathArray), + property + } }; }); @@ -314,9 +306,9 @@ class JsonSchema { const pointer = jsonPointer.compile(pathArray); amandaCompatibleError[index] = { + message: `At '${pointer}' ${error.message}`, property: pathArray, attributeValue: true, - message: `At '${pointer}' ${error.message}`, validatorName: 'error' }; } diff --git a/test/cucumber/steps/fields.js b/test/cucumber/steps/fields.js index 6c29facb..fd757643 100644 --- a/test/cucumber/steps/fields.js +++ b/test/cucumber/steps/fields.js @@ -7,7 +7,6 @@ const { expect } = chai; module.exports = function() { this.Then(/^field "([^"]*)" equals:$/, function(fieldName, expectedJson) { const expected = jhp.parse(expectedJson); - expect(this.result.fields[fieldName]).to.deep.equal(expected); }); }; diff --git a/test/cucumber/steps/general.js b/test/cucumber/steps/general.js index 9b734184..c489256d 100644 --- a/test/cucumber/steps/general.js +++ b/test/cucumber/steps/general.js @@ -42,8 +42,8 @@ module.exports = function() { this.Given( /^you expect "body" field to match the following "([^"]*)":$/, function(bodyType, value) { - switch (bodyType) { - case 'JSON schema': + switch (bodyType.toLowerCase()) { + case 'json schema': this.expected.bodySchema = value; break; default: From 867698912b38a040507167110e3dbb99f78fa834 Mon Sep 17 00:00:00 2001 From: artem-zakharchenko Date: Wed, 3 Jul 2019 15:09:56 +0200 Subject: [PATCH 09/29] chore: aligns expected/asserted grammar in Gherkin suites --- test/cucumber/steps/fields.js | 5 ++++- test/cucumber/steps/general.js | 15 +++++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/test/cucumber/steps/fields.js b/test/cucumber/steps/fields.js index fd757643..2f1a77b9 100644 --- a/test/cucumber/steps/fields.js +++ b/test/cucumber/steps/fields.js @@ -5,7 +5,10 @@ chai.config.truncateThreshold = 0; const { expect } = chai; module.exports = function() { - this.Then(/^field "([^"]*)" equals:$/, function(fieldName, expectedJson) { + this.Then(/^result field "([^"]*)" equals:$/, function( + fieldName, + expectedJson + ) { const expected = jhp.parse(expectedJson); expect(this.result.fields[fieldName]).to.deep.equal(expected); }); diff --git a/test/cucumber/steps/general.js b/test/cucumber/steps/general.js index c489256d..9f978623 100644 --- a/test/cucumber/steps/general.js +++ b/test/cucumber/steps/general.js @@ -17,21 +17,21 @@ module.exports = function() { this.actual = jhp.parse(actualMessage); }); - this.Given(/^actual "([^"]*)" field equals "([^"]*)"/, function( + this.Given(/^actual field "([^"]*)" equals "([^"]*)"/, function( fieldName, value ) { this.actual[fieldName] = value; }); - this.Given(/^you expect "([^"]*)" field to equal "([^"]*)"$/, function( + this.Given(/^you expect field "([^"]*)" to equal "([^"]*)"$/, function( fieldName, expectedValue ) { this.expected[fieldName] = expectedValue; }); - this.Given(/^you expect "([^"]*)" field to equal:$/, function( + this.Given(/^you expect field "([^"]*)" to equal:$/, function( fieldName, codeBlock ) { @@ -40,7 +40,7 @@ module.exports = function() { }); this.Given( - /^you expect "body" field to match the following "([^"]*)":$/, + /^you expect field "body" to match the following "([^"]*)":$/, function(bodyType, value) { switch (bodyType.toLowerCase()) { case 'json schema': @@ -53,7 +53,7 @@ module.exports = function() { } ); - this.Given(/^actual "([^"]*)" field equals:$/, function( + this.Given(/^actual field "([^"]*)" equals:$/, function( fieldName, codeBlock ) { @@ -93,7 +93,10 @@ ${dmp.patch_toText(dmp.patch_make(stringifiedActual, expectedResult))} ); }); - this.Then(/^field "(\w+)" is( NOT)? valid$/i, function(fieldName, isInvalid) { + this.Then(/^result field "(\w+)" is( NOT)? valid$/i, function( + fieldName, + isInvalid + ) { expect(this.result).to.have.nested.property( `fields.${fieldName}.valid`, !isInvalid From 55ff709725de2960253f7841b04aa55b17f8fe3d Mon Sep 17 00:00:00 2001 From: artem-zakharchenko Date: Thu, 4 Jul 2019 14:34:05 +0200 Subject: [PATCH 10/29] test: adjusts cucumber step definitions to use new vocabulary --- test/cucumber/steps/fields.js | 2 +- test/cucumber/steps/general.js | 51 +++++++++++++++------------------- 2 files changed, 23 insertions(+), 30 deletions(-) diff --git a/test/cucumber/steps/fields.js b/test/cucumber/steps/fields.js index 2f1a77b9..3433e4d4 100644 --- a/test/cucumber/steps/fields.js +++ b/test/cucumber/steps/fields.js @@ -5,7 +5,7 @@ chai.config.truncateThreshold = 0; const { expect } = chai; module.exports = function() { - this.Then(/^result field "([^"]*)" equals:$/, function( + this.Then(/^the result field "([^"]*)" equals:$/, function( fieldName, expectedJson ) { diff --git a/test/cucumber/steps/general.js b/test/cucumber/steps/general.js index 9f978623..f1f1405a 100644 --- a/test/cucumber/steps/general.js +++ b/test/cucumber/steps/general.js @@ -10,28 +10,26 @@ module.exports = function() { } ); - this.Given(/^actual HTTP (message|request|response) is:$/i, function( + this.Given(/^the actual HTTP (message|request|response) equals:$/i, function( _, actualMessage ) { this.actual = jhp.parse(actualMessage); }); - this.Given(/^actual field "([^"]*)" equals "([^"]*)"/, function( - fieldName, - value - ) { + // Inline value assertion. + this.Given(/^the actual "([^"]*)" is "([^"]*)"/, function(fieldName, value) { this.actual[fieldName] = value; }); - this.Given(/^you expect field "([^"]*)" to equal "([^"]*)"$/, function( + this.Given(/^you expect "([^"]*)" to be "([^"]*)"$/, function( fieldName, expectedValue ) { this.expected[fieldName] = expectedValue; }); - this.Given(/^you expect field "([^"]*)" to equal:$/, function( + this.Given(/^you expect "([^"]*)" to equal:$/, function( fieldName, codeBlock ) { @@ -39,35 +37,33 @@ module.exports = function() { this.expected[fieldName] = this.transformCodeBlock(fieldName, codeBlock); }); - this.Given( - /^you expect field "body" to match the following "([^"]*)":$/, - function(bodyType, value) { - switch (bodyType.toLowerCase()) { - case 'json schema': - this.expected.bodySchema = value; - break; - default: - this.expected.body = value; - break; - } + this.Given(/^you expect "body" to match the following "([^"]*)":$/, function( + bodyType, + value + ) { + switch (bodyType.toLowerCase()) { + case 'json schema': + this.expected.bodySchema = value; + break; + default: + this.expected.body = value; + break; } - ); + }); - this.Given(/^actual field "([^"]*)" equals:$/, function( - fieldName, - codeBlock - ) { + // Block value assertion. + this.Given(/^the actual "([^"]*)" equals:$/, function(fieldName, codeBlock) { // Also perform conditional code parsing this.actual[fieldName] = this.transformCodeBlock(fieldName, codeBlock); }); // Actions - this.When('Gavel validates HTTP message', function() { + this.When('Gavel validates the HTTP message', function() { this.validate(); }); // Assertions - this.Then(/^HTTP message is( NOT)? valid$/i, function(isInvalid) { + this.Then(/^the actual HTTP message is( NOT)? valid$/i, function(isInvalid) { expect(this.result).to.have.property('valid', !isInvalid); }); @@ -93,10 +89,7 @@ ${dmp.patch_toText(dmp.patch_make(stringifiedActual, expectedResult))} ); }); - this.Then(/^result field "(\w+)" is( NOT)? valid$/i, function( - fieldName, - isInvalid - ) { + this.Then(/^the "(\w+)" is( NOT)? valid$/i, function(fieldName, isInvalid) { expect(this.result).to.have.nested.property( `fields.${fieldName}.valid`, !isInvalid From 87e6bab89111a6ec8d88bac8a429315b89d35f7f Mon Sep 17 00:00:00 2001 From: artem-zakharchenko Date: Thu, 4 Jul 2019 14:50:45 +0200 Subject: [PATCH 11/29] chore: adds inferred json schema notice to README --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 59d985bc..4544c24d 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,8 @@ The code above would return the following validation `result`: ### Usage with JSON Schema +> When a parsable JSON body is expected without an explicit schema the [default schema](https://github.com/apiaryio/gavel-spec/blob/master/features/expectations/bodyJsonExample.feature) is inferred. + You can describe the body expectations using [JSON Schema](https://json-schema.org/) by providing a valid schema to the `bodySchema` property of the expected HTTP message: ```js @@ -166,7 +168,7 @@ The validation `result` against the given JSON Schema will look as follows: } ``` -> Note that JSON schema V5+ are not currently supported. +> Note that JSON schema V5+ are not currently supported. [Follow the support progress](https://github.com/apiaryio/gavel.js/issues/90). ## Examples From 19e15e73c2a3a60136afd926a08b5a3d950788b7 Mon Sep 17 00:00:00 2001 From: artem-zakharchenko Date: Mon, 8 Jul 2019 10:11:29 +0200 Subject: [PATCH 12/29] test: uses "I" instead of "you" in step definitions --- test/cucumber/steps/cli.js | 4 ++-- test/cucumber/steps/general.js | 16 +++++++++------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/test/cucumber/steps/cli.js b/test/cucumber/steps/cli.js index 1e2ee772..fc34f2f9 100644 --- a/test/cucumber/steps/cli.js +++ b/test/cucumber/steps/cli.js @@ -2,14 +2,14 @@ const { assert } = require('chai'); module.exports = function() { this.Given( - /^(you record (expected|actual) raw HTTP message:)|(a header is missing in actual message:)$/, + /^(I record (expected|actual) raw HTTP message:)|(a header is missing in actual message:)$/, function(_, __, ___, command) { this.commands.push(command); } ); this.When( - 'you validate the message using the following Gavel command:', + 'I validate the message using the following Gavel command:', async function(command) { this.commands.push(command); this.status = await this.executeCommands(this.commands); diff --git a/test/cucumber/steps/general.js b/test/cucumber/steps/general.js index f1f1405a..35666d61 100644 --- a/test/cucumber/steps/general.js +++ b/test/cucumber/steps/general.js @@ -4,7 +4,7 @@ const jhp = require('json-parse-helpfulerror'); module.exports = function() { this.Given( - /^you expect the following HTTP (message|request|response):$/i, + /^I expect the following HTTP (message|request|response):$/i, function(_, expectedMessage) { this.expected = jhp.parse(expectedMessage); } @@ -22,22 +22,19 @@ module.exports = function() { this.actual[fieldName] = value; }); - this.Given(/^you expect "([^"]*)" to be "([^"]*)"$/, function( + this.Given(/^I expect "([^"]*)" to be "([^"]*)"$/, function( fieldName, expectedValue ) { this.expected[fieldName] = expectedValue; }); - this.Given(/^you expect "([^"]*)" to equal:$/, function( - fieldName, - codeBlock - ) { + this.Given(/^I expect "([^"]*)" to equal:$/, function(fieldName, codeBlock) { // Perform conditional code block parsing (if headers, etc.) this.expected[fieldName] = this.transformCodeBlock(fieldName, codeBlock); }); - this.Given(/^you expect "body" to match the following "([^"]*)":$/, function( + this.Given(/^I expect "body" to match the following "([^"]*)":$/, function( bodyType, value ) { @@ -62,6 +59,11 @@ module.exports = function() { this.validate(); }); + // Vocabulary proxy over the previous action for better scenarios readability. + this.When(/^I call "([^"]*)"$/, function(_command) { + this.validate(); + }); + // Assertions this.Then(/^the actual HTTP message is( NOT)? valid$/i, function(isInvalid) { expect(this.result).to.have.property('valid', !isInvalid); From 307016b76a4de187e217ee652721d9e25d2c9804 Mon Sep 17 00:00:00 2001 From: artem-zakharchenko Date: Mon, 8 Jul 2019 10:43:26 +0200 Subject: [PATCH 13/29] fix: simplifies tags handling in cucumber args --- scripts/cucumber.js | 37 ++++++++++++++----------------------- 1 file changed, 14 insertions(+), 23 deletions(-) diff --git a/scripts/cucumber.js b/scripts/cucumber.js index ec6271f9..b51a6868 100644 --- a/scripts/cucumber.js +++ b/scripts/cucumber.js @@ -3,32 +3,23 @@ const spawn = require('cross-spawn'); const isWindows = process.platform.match(/^win/); -// Removing '@cli' behavior from tests due to +// Excludes Cucumber features marked with the "@cli" tag. +// CLI does not work on Windows: // https://github.com/apiaryio/gavel-spec/issues/24 -const tags = [ - '@javascript', - '~@proposal', - '~@draft', - '~@javascript-pending', - isWindows && '~@cli' -].filter(Boolean); +const tags = [isWindows && '~@cli'].filter(Boolean); -const args = tags.reduce((acc, tag) => { - return acc.concat('-t').concat(tag); -}, []); +const args = [ + ...tags, + '-r', + 'test/cucumber/support/', + '-r', + 'test/cucumber/steps/', + '-f', + 'pretty', + 'node_modules/gavel-spec/features/' +]; -const cucumber = spawn( - 'node_modules/.bin/cucumber-js', - args.concat([ - '-r', - 'test/cucumber/support/', - '-r', - 'test/cucumber/steps/', - '-f', - 'pretty', - 'node_modules/gavel-spec/features/' - ]) -); +const cucumber = spawn('node_modules/.bin/cucumber-js', args); cucumber.stdout.on('data', (data) => process.stdout.write(data)); cucumber.stderr.on('data', (data) => process.stderr.write(data)); From 96956ec56b7a9bec3ba1ed6108130904e789c4e6 Mon Sep 17 00:00:00 2001 From: artem-zakharchenko Date: Mon, 8 Jul 2019 11:48:28 +0200 Subject: [PATCH 14/29] chore: moves husky hooks into own config key --- package.json | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 06977618..e0731529 100644 --- a/package.json +++ b/package.json @@ -19,8 +19,12 @@ "coveralls": "nyc --reporter=text-lcov npm run test:server | coveralls", "ci:lint": "npm run lint", "ci:test": "npm run coveralls && npm run test:browser && npm run test:features", - "ci:release": "semantic-release", - "precommit": "lint-staged" + "ci:release": "semantic-release" + }, + "husky": { + "hooks": { + "pre-commit": "lint-staged" + } }, "lint-staged": { "*.js": [ From 4b24fb66e18542174c38003f0a641b602f9ec9bc Mon Sep 17 00:00:00 2001 From: artem-zakharchenko Date: Mon, 8 Jul 2019 11:48:37 +0200 Subject: [PATCH 15/29] test: adjusts tests for "error[n].location" property --- lib/units/validateBody.js | 1 + test/chai.js | 2 +- test/integration/validate.test.js | 5 ++++- test/unit/support/amanda-to-gavel-shared.js | 4 ++-- test/unit/units/validateHeaders.test.js | 5 ++++- 5 files changed, 12 insertions(+), 5 deletions(-) diff --git a/lib/units/validateBody.js b/lib/units/validateBody.js index 374affbf..c121a45d 100644 --- a/lib/units/validateBody.js +++ b/lib/units/validateBody.js @@ -245,6 +245,7 @@ function validateBody(expected, actual) { actual.body, usesJsonSchema ? expected.bodySchema : expected.body ); + // Without ".validate()" it cannot evaluate output to result. // TODO Re-do this. validator && validator.validate(); diff --git a/test/chai.js b/test/chai.js index 1deb78d9..aa798319 100644 --- a/test/chai.js +++ b/test/chai.js @@ -52,7 +52,7 @@ ${JSON.stringify(target)} }; createErrorPropertyAssertion('message', 'withMessage'); - createErrorPropertyAssertion('pointer', 'withPointer'); + createErrorPropertyAssertion('location', 'withLocation'); createErrorPropertyAssertion('values', 'withValues'); // diff --git a/test/integration/validate.test.js b/test/integration/validate.test.js index 255a6244..f39e1f4f 100644 --- a/test/integration/validate.test.js +++ b/test/integration/validate.test.js @@ -237,7 +237,10 @@ describe('validate', () => { it('has pointer to missing "Content-Type"', () => { expect(result.fields.headers) .to.have.errorAtIndex(0) - .withPointer('/content-type'); + .withLocation({ + pointer: '/content-type', + property: ['content-type'] + }); }); it('has explanatory message', () => { diff --git a/test/unit/support/amanda-to-gavel-shared.js b/test/unit/support/amanda-to-gavel-shared.js index 1a233c18..5f3171f4 100644 --- a/test/unit/support/amanda-to-gavel-shared.js +++ b/test/unit/support/amanda-to-gavel-shared.js @@ -45,7 +45,7 @@ exports.shouldBehaveLikeAmandaToGavel = (instance) => { assert.isObject(item); }); - const props = ['message', 'pointer']; + const props = ['message', 'location']; props.forEach((key) => { it('should have "' + key + '"', () => { assert.include(Object.keys(item), key); @@ -55,7 +55,7 @@ exports.shouldBehaveLikeAmandaToGavel = (instance) => { describe('pointer key value', () => { value = null; before(() => { - value = item['pointer']; + value = item.location.pointer; }); it('should be a string', () => { diff --git a/test/unit/units/validateHeaders.test.js b/test/unit/units/validateHeaders.test.js index 6d45767e..4114e4f1 100644 --- a/test/unit/units/validateHeaders.test.js +++ b/test/unit/units/validateHeaders.test.js @@ -68,7 +68,10 @@ describe('validateHeaders', () => { it('has pointer to header name', () => { expect(result) .to.have.errorAtIndex(index) - .withPointer(`/${headerName}`); + .withLocation({ + pointer: `/${headerName}`, + property: [headerName] + }); }); it('has explanatory message', () => { From 48251fdb5229413a4d31f0ffd173ad75dfc5de09 Mon Sep 17 00:00:00 2001 From: artem-zakharchenko Date: Mon, 8 Jul 2019 13:51:10 +0200 Subject: [PATCH 16/29] chore: adapts "actual" namespace in validators --- lib/units/validateBody.js | 4 +- lib/units/validateHeaders.js | 2 +- lib/validators/headers-json-example.js | 12 ++--- lib/validators/json-example.js | 10 ++-- lib/validators/json-schema.js | 6 +-- lib/validators/text-diff.js | 24 ++++----- .../headers-json-example-validator-test.js | 34 ++++++------ test/unit/validators/json-example-test.js | 39 +++++++------- test/unit/validators/json-schema-test.js | 52 +++++++++---------- 9 files changed, 90 insertions(+), 93 deletions(-) diff --git a/lib/units/validateBody.js b/lib/units/validateBody.js index c121a45d..23db0cc3 100644 --- a/lib/units/validateBody.js +++ b/lib/units/validateBody.js @@ -242,8 +242,8 @@ function validateBody(expected, actual) { const validator = ValidatorClass && new ValidatorClass( - actual.body, - usesJsonSchema ? expected.bodySchema : expected.body + usesJsonSchema ? expected.bodySchema : expected.body, + actual.body ); // Without ".validate()" it cannot evaluate output to result. diff --git a/lib/units/validateHeaders.js b/lib/units/validateHeaders.js index 20c6bbe9..5d5aaef2 100644 --- a/lib/units/validateHeaders.js +++ b/lib/units/validateHeaders.js @@ -28,7 +28,7 @@ function validateHeaders(expected, actual) { expectedType === APIARY_JSON_HEADER_TYPE; const validator = hasJsonHeaders - ? new HeadersJsonExample(values.actual, values.expected) + ? new HeadersJsonExample(values.expected, values.actual) : null; // if you don't call ".validate()", it never evaluates any results. diff --git a/lib/validators/headers-json-example.js b/lib/validators/headers-json-example.js index af4b4074..659d72ad 100644 --- a/lib/validators/headers-json-example.js +++ b/lib/validators/headers-json-example.js @@ -38,9 +38,9 @@ const getSchema = (json) => { }; class HeadersJsonExample extends JsonSchema { - constructor(real, expected) { - if (typeof real !== 'object') { - throw new errors.MalformedDataError('Real is not an Object'); + constructor(expected, actual) { + if (typeof actual !== 'object') { + throw new errors.MalformedDataError('Actual is not an Object'); } if (typeof expected !== 'object') { @@ -48,7 +48,7 @@ class HeadersJsonExample extends JsonSchema { } const preparedExpected = prepareHeaders(expected); - const preparedReal = prepareHeaders(real); + const preparedActual = prepareHeaders(actual); const preparedSchema = getSchema(preparedExpected); if (preparedSchema && preparedSchema.properties) { @@ -60,10 +60,10 @@ class HeadersJsonExample extends JsonSchema { }); } - super(preparedReal, preparedSchema); + super(preparedSchema, preparedActual); this.expected = preparedExpected; - this.real = preparedReal; + this.actual = preparedActual; this.schema = preparedSchema; } diff --git a/lib/validators/json-example.js b/lib/validators/json-example.js index 84b8d323..0e2b2573 100644 --- a/lib/validators/json-example.js +++ b/lib/validators/json-example.js @@ -26,12 +26,12 @@ class JsonExample extends JsonSchema { * @throw {SchemaNotJsonParsableError} when given schema is not a json parsable string or valid json * @throw {NotEnoughDataError} when at least one of expected data and json schema is not given */ - constructor(real, expected) { - if (typeof real !== 'string') { + constructor(expected, actual) { + if (typeof actual !== 'string') { const outError = new errors.MalformedDataError( - 'JsonExample validator: provided real data is not string' + 'JsonExample validator: provided actual data is not string' ); - outError.data = real; + outError.data = actual; throw outError; } @@ -44,7 +44,7 @@ class JsonExample extends JsonSchema { } const schema = getSchema(expected); - super(real, schema); + super(schema, actual); } } diff --git a/lib/validators/json-schema.js b/lib/validators/json-schema.js index 8a464ee6..14b533a2 100644 --- a/lib/validators/json-schema.js +++ b/lib/validators/json-schema.js @@ -80,12 +80,12 @@ const jsonSchemaOptions = { class JsonSchema { /** * Constructs a JsonValidator and validates given data. - * @param {Object | string} data * @param {Object | string} schema + * @param {Object | string} data */ - constructor(data, schema) { - this.data = data; + constructor(schema, data) { this.schema = schema; + this.data = data; if (typeof this.data === 'string') { try { diff --git a/lib/validators/text-diff.js b/lib/validators/text-diff.js index 0fc73f92..6c8ddbc2 100644 --- a/lib/validators/text-diff.js +++ b/lib/validators/text-diff.js @@ -1,13 +1,17 @@ const DiffMatchPatch = require('googlediff'); const errors = require('../errors'); +const sanitizeSurrogatePairs = (data) => { + return data.replace(/[\uD800-\uDBFF]/g, '').replace(/[\uDC00-\uDFFF]/g, ''); +}; + class TextDiff { - constructor(real, expected) { - if (typeof real !== 'string') { + constructor(expected, actual) { + if (typeof actual !== 'string') { const outError = new errors.DataNotStringError( - 'String validator real: input data is not string' + 'String validator actual: input data is not string' ); - outError.data = real; + outError.data = actual; throw outError; } @@ -19,28 +23,22 @@ class TextDiff { throw outError; } - this.real = real; this.expected = expected; + this.actual = actual; } validate() { - const sanitizeSurrogatePairs = (data) => { - return data - .replace(/[\uD800-\uDBFF]/g, '') - .replace(/[\uDC00-\uDFFF]/g, ''); - }; - this.output = null; const dmp = new DiffMatchPatch(); try { - const patch = dmp.patch_make(this.real, this.expected); + const patch = dmp.patch_make(this.actual, this.expected); this.output = dmp.patch_toText(patch); return this.output; } catch (error) { if (error instanceof URIError) { const patch = dmp.patch_make( - sanitizeSurrogatePairs(this.real), + sanitizeSurrogatePairs(this.actual), sanitizeSurrogatePairs(this.expected) ); this.output = dmp.patch_toText(patch); diff --git a/test/unit/validators/headers-json-example-validator-test.js b/test/unit/validators/headers-json-example-validator-test.js index 1319c8a7..7c879552 100644 --- a/test/unit/validators/headers-json-example-validator-test.js +++ b/test/unit/validators/headers-json-example-validator-test.js @@ -16,7 +16,7 @@ describe('HeadersJsonExample', () => { describe('when I provede real data as non obejct', () => { it('should throw an exception', () => { const fn = () => { - headersValidator = new HeadersJsonExample('', { header1: 'value1' }); + headersValidator = new HeadersJsonExample({ header1: 'value1' }, ''); }; assert.throw(fn, 'is not an Object'); }); @@ -25,7 +25,7 @@ describe('HeadersJsonExample', () => { describe('when I provede expected data as non obejct', () => { it('should throw an exception', () => { const fn = () => { - headersValidator = new HeadersJsonExample({ header1: 'value1' }, ''); + headersValidator = new HeadersJsonExample('', { header1: 'value1' }); }; assert.throw(fn, 'is not an Object'); }); @@ -62,8 +62,8 @@ describe('HeadersJsonExample', () => { describe('when provided real and expected headers differ in upper/lower-case state of keys', () => { before(() => { headersValidator = new HeadersJsonExample( - fixtures.sampleHeadersMixedCase, - fixtures.sampleHeaders + fixtures.sampleHeaders, + fixtures.sampleHeadersMixedCase ); }); @@ -78,8 +78,8 @@ describe('HeadersJsonExample', () => { describe('when provided real and expected headers differ in one value (real change) of a key different by upper/lower', () => { before(() => { headersValidator = new HeadersJsonExample( - fixtures.sampleHeadersMixedCaseDiffers, - fixtures.sampleHeaders + fixtures.sampleHeaders, + fixtures.sampleHeadersMixedCaseDiffers ); }); describe('and I run validate()', () => { @@ -93,8 +93,8 @@ describe('HeadersJsonExample', () => { describe('when key is missing in provided headers', () => { beforeEach(() => { headersValidator = new HeadersJsonExample( - fixtures.sampleHeadersMissing, - fixtures.sampleHeaders + fixtures.sampleHeaders, + fixtures.sampleHeadersMissing ); }); describe('and i run validate()', () => { @@ -113,8 +113,8 @@ describe('HeadersJsonExample', () => { describe('when value of content negotiation header in provided headers differs', () => { beforeEach(() => { headersValidator = new HeadersJsonExample( - fixtures.sampleHeadersDiffers, - fixtures.sampleHeaders + fixtures.sampleHeaders, + fixtures.sampleHeadersDiffers ); }); @@ -138,8 +138,8 @@ describe('HeadersJsonExample', () => { describe('when key is added to provided headers', () => { before(() => { headersValidator = new HeadersJsonExample( - fixtures.sampleHeadersAdded, - fixtures.sampleHeaders + fixtures.sampleHeaders, + fixtures.sampleHeadersAdded ); }); @@ -153,7 +153,7 @@ describe('HeadersJsonExample', () => { describe('when real is empty object and expected is proper object', () => { before(() => { - headersValidator = new HeadersJsonExample({}, fixtures.sampleHeaders); + headersValidator = new HeadersJsonExample(fixtures.sampleHeaders, {}); }); describe('and i run validate()', () => { @@ -167,8 +167,8 @@ describe('HeadersJsonExample', () => { describe('when non content negotiation header header values differs', () => { before(() => { headersValidator = new HeadersJsonExample( - fixtures.sampleHeadersWithNonContentNegotiationChanged, - fixtures.sampleHeadersNonContentNegotiation + fixtures.sampleHeadersNonContentNegotiation, + fixtures.sampleHeadersWithNonContentNegotiationChanged ); }); @@ -184,8 +184,8 @@ describe('HeadersJsonExample', () => { output = null; before(() => { headersValidator = new HeadersJsonExample( - fixtures.sampleHeadersMissing, - fixtures.sampleHeaders + fixtures.sampleHeaders, + fixtures.sampleHeadersMissing ); output = headersValidator.validate(); }); diff --git a/test/unit/validators/json-example-test.js b/test/unit/validators/json-example-test.js index 748ae47f..16add7e1 100644 --- a/test/unit/validators/json-example-test.js +++ b/test/unit/validators/json-example-test.js @@ -12,10 +12,9 @@ describe('JsonExample', () => { describe('when I provide non string real data', () => { it('should throw exception', () => { const fn = () => { - bodyValidator = new JsonExample( - { malformed: 'malformed ' }, - "{'header1': 'value1'}" - ); + bodyValidator = new JsonExample("{'header1': 'value1'}", { + malformed: 'malformed ' + }); }; assert.throws(fn); }); @@ -25,8 +24,8 @@ describe('JsonExample', () => { it('should not throw exception', () => { fn = () => { bodyValidator = new JsonExample( - '"Number of profiles deleted: com.viacom.auth.infrastructure.DocumentsUpdated@1"', - '{"header1": "value1"}' + '{"header1": "value1"}', + '"Number of profiles deleted: com.viacom.auth.infrastructure.DocumentsUpdated@1"' ); }; assert.doesNotThrow(fn); @@ -37,8 +36,8 @@ describe('JsonExample', () => { it('should not throw exception', () => { const fn = () => { bodyValidator = new JsonExample( - '{"header1": "value1"}', - '"Number of profiles deleted: com.viacom.auth.infrastructure.DocumentsUpdated@1"' + '"Number of profiles deleted: com.viacom.auth.infrastructure.DocumentsUpdated@1"', + '{"header1": "value1"}' ); }; assert.doesNotThrow(fn); @@ -109,8 +108,8 @@ describe('JsonExample', () => { describe('when key is missing in provided real data', () => { before(() => { bodyValidator = new JsonExample( - fixtures.sampleJsonSimpleKeyMissing, - fixtures.sampleJson + fixtures.sampleJson, + fixtures.sampleJsonSimpleKeyMissing ); }); describe('and i run validate()', () => { @@ -122,7 +121,7 @@ describe('JsonExample', () => { describe.skip('when value has different primitive type', () => { before(() => { - bodyValidator = new JsonExample('{"a": "a"}', '{"a": 1}'); + bodyValidator = new JsonExample('{"a": 1}', '{"a": "a"}'); }); describe('and i run validate()', () => { it('PROPOSAL: should return 1 errors', () => { @@ -150,8 +149,8 @@ describe('JsonExample', () => { describe('when key is added to provided data', () => { before(() => { bodyValidator = new JsonExample( - fixtures.sampleJsonComplexKeyAdded, - fixtures.sampleJson + fixtures.sampleJson, + fixtures.sampleJsonComplexKeyAdded ); }); describe('and i run validate()', () => { @@ -180,8 +179,8 @@ describe('JsonExample', () => { describe('when key value is a null', () => { before(() => { bodyValidator = new JsonExample( - '{"a": "a","b": null }', - '{"a":"a", "b": null}' + '{"a":"a", "b": null}', + '{"a": "a","b": null }' ); }); describe('and i run validate()', () => { @@ -195,7 +194,7 @@ describe('JsonExample', () => { describe('when expected and real data are different on root level', () => { describe('when expected is object and real is array', () => { before(() => { - bodyValidator = new JsonExample('[{"a":1}]', '{"a":1}'); + bodyValidator = new JsonExample('{"a":1}', '[{"a":1}]'); }); describe('and i run validate()', () => { it('should not throw exception', () => { @@ -210,7 +209,7 @@ describe('JsonExample', () => { describe('when expected is array and real is object', () => { before(() => { - bodyValidator = new JsonExample('{"a":1}', '[{"a":1}]'); + bodyValidator = new JsonExample('[{"a":1}]', '{"a":1}'); }); describe('and i run validate()', () => { it('should not throw exception', () => { @@ -224,7 +223,7 @@ describe('JsonExample', () => { describe('when expected is primitive and real is object', () => { before(() => { - bodyValidator = new JsonExample('0', '{"a":1}'); + bodyValidator = new JsonExample('{"a":1}', '0'); }); describe('and i run validate()', () => { it('should not throw exception', () => { @@ -238,7 +237,7 @@ describe('JsonExample', () => { describe('when expected array and real is object', () => { before(() => { - bodyValidator = new JsonExample('[0,1,2]', '{"a":1}'); + bodyValidator = new JsonExample('{"a":1}', '[0,1,2]'); }); describe('and i run validate()', () => { it('should not throw exception', () => { @@ -252,7 +251,7 @@ describe('JsonExample', () => { describe('when real is empty object and expected is non-empty object', () => { before(() => { - bodyValidator = new JsonExample('{}', '{"a":1}'); + bodyValidator = new JsonExample('{"a":1}', '{}'); }); describe('and i run validate()', () => { diff --git a/test/unit/validators/json-schema-test.js b/test/unit/validators/json-schema-test.js index 0a130a8e..64d86e4d 100644 --- a/test/unit/validators/json-schema-test.js +++ b/test/unit/validators/json-schema-test.js @@ -13,11 +13,11 @@ describe('JsonSchema', () => { const dataForTypes = { string: { - real: fixtures.sampleJsonComplexKeyMissing, + actual: fixtures.sampleJsonComplexKeyMissing, schema: fixtures.sampleJsonSchemaNonStrict }, object: { - real: JSON.parse(fixtures.sampleJsonComplexKeyMissing), + actual: JSON.parse(fixtures.sampleJsonComplexKeyMissing), schema: JSON.parse(fixtures.sampleJsonSchemaNonStrict) } }; @@ -34,12 +34,12 @@ describe('JsonSchema', () => { let validator = null; beforeEach(() => { - validator = new JsonSchema(data.real, data.schema); + validator = new JsonSchema(data.schema, data.actual); }); it('should not throw an exception', () => { const fn = () => { - new JsonSchema(data.real, data.schema); + new JsonSchema(data.schema, data.actual); }; assert.doesNotThrow(fn); }); @@ -134,11 +134,11 @@ describe('JsonSchema', () => { } ); - describe('when validation performed on real empty object', () => { + describe('when validation performed on actual empty object', () => { it('should return some errors', () => { validator = new JsonSchema( - {}, - JSON.parse(fixtures.sampleJsonSchemaNonStrict) + JSON.parse(fixtures.sampleJsonSchemaNonStrict), + {} ); result = validator.validate(); assert.notEqual(validator.validate().length, 0); @@ -176,7 +176,7 @@ describe('JsonSchema', () => { it('should throw an error for "schema"', () => { const invalidStringifiedSchema = require('../../fixtures/invalid-stringified-schema'); const fn = () => { - new JsonSchema({}, invalidStringifiedSchema); + new JsonSchema(invalidStringifiedSchema, {}); }; assert.throw(fn); }); @@ -192,8 +192,8 @@ describe('JsonSchema', () => { fixtures.sampleJsonBodyTestingAmandaMessages ).length; validator = new JsonSchema( - fixtures.sampleJsonBodyTestingAmandaMessages, - fixtures.sampleJsonSchemaTestingAmandaMessages + fixtures.sampleJsonSchemaTestingAmandaMessages, + fixtures.sampleJsonBodyTestingAmandaMessages ); results = validator.validate(); }); @@ -220,7 +220,7 @@ describe('JsonSchema', () => { before(() => { const invalidSchema = require('../../fixtures/invalid-schema-v3'); fn = () => { - validator = new JsonSchema({}, invalidSchema); + validator = new JsonSchema(invalidSchema, {}); }; }); @@ -242,7 +242,7 @@ describe('JsonSchema', () => { before(() => { const validSchema = require('../../fixtures/valid-schema-v3'); fn = () => { - validator = new JsonSchema({}, validSchema); + validator = new JsonSchema(validSchema, {}); }; }); @@ -263,7 +263,7 @@ describe('JsonSchema', () => { before(() => { const invalidSchema = require('../../fixtures/invalid-schema-v4'); fn = () => { - validator = new JsonSchema({}, invalidSchema); + validator = new JsonSchema(invalidSchema, {}); }; }); @@ -285,7 +285,7 @@ describe('JsonSchema', () => { before(() => { validSchema = require('../../fixtures/valid-schema-v4'); fn = () => { - validator = new JsonSchema({}, validSchema); + validator = new JsonSchema(validSchema, {}); }; }); @@ -306,7 +306,7 @@ describe('JsonSchema', () => { validSchema = require('../../fixtures/valid-schema-v3'); delete validSchema['$schema']; fn = () => { - validator = new JsonSchema({}, validSchema); + validator = new JsonSchema(validSchema, {}); }; }); @@ -326,7 +326,7 @@ describe('JsonSchema', () => { validSchema = require('../../fixtures/valid-schema-v4'); delete validSchema['$schema']; fn = () => { - validator = new JsonSchema({}, validSchema); + validator = new JsonSchema(validSchema, {}); }; }); @@ -346,7 +346,7 @@ describe('JsonSchema', () => { validSchema = require('../../fixtures/invalid-schema-v3-v4'); delete validSchema['$schema']; fn = () => { - validator = new JsonSchema({}, validSchema); + validator = new JsonSchema(validSchema, {}); }; }); @@ -375,7 +375,7 @@ describe('JsonSchema', () => { before(() => { validSchema = require('../../fixtures/valid-schema-v3'); delete validSchema['$schema']; - validator = new JsonSchema({}, validSchema); + validator = new JsonSchema(validSchema, {}); v3 = sinon.stub(validator, 'validateSchemaV3'); v4 = sinon.stub(validator, 'validateSchemaV4'); @@ -404,7 +404,7 @@ describe('JsonSchema', () => { before(() => { const validSchema = require('../../fixtures/valid-schema-v4'); delete validSchema['$schema']; - validator = new JsonSchema({}, validSchema); + validator = new JsonSchema(validSchema, {}); sinon.stub(validator, 'validateSchemaV3'); sinon.stub(validator, 'validateSchemaV4'); @@ -434,9 +434,9 @@ describe('JsonSchema', () => { before(() => { const validSchema = require('../../fixtures/valid-schema-v4-with-refs'); - const real = JSON.parse('{ "foo": "bar" }'); + const actual = JSON.parse('{ "foo": "bar" }'); fn = () => { - validator = new JsonSchema(real, validSchema); + validator = new JsonSchema(validSchema, actual); return validator.validatePrivate(); }; }); @@ -452,9 +452,9 @@ describe('JsonSchema', () => { before(() => { const validSchema = require('../../fixtures/valid-schema-v4-with-refs'); - const real = JSON.parse('{ "foo": 1 }'); + const actual = JSON.parse('{ "foo": 1 }'); fn = () => { - validator = new JsonSchema(real, validSchema); + validator = new JsonSchema(validSchema, actual); return validator.validatePrivate(); }; }); @@ -475,9 +475,9 @@ describe('JsonSchema', () => { before(() => { const invalidSchema = require('../../fixtures/invalid-schema-v4-with-refs'); - const real = JSON.parse('{ "foo": "bar" }'); + const actual = JSON.parse('{ "foo": "bar" }'); fn = () => { - validator = new JsonSchema(real, invalidSchema); + validator = new JsonSchema(invalidSchema, actual); return validator.validatePrivate(); }; }); @@ -500,7 +500,7 @@ describe('JsonSchema', () => { const validSchema = require('../../fixtures/valid-schema-v3'); delete validSchema['$schema']; fn = () => { - validator = new JsonSchema({}, validSchema); + validator = new JsonSchema(validSchema, {}); validator.jsonSchemaVersion = null; validator.validatePrivate(); }; From a95de9458ec9c7f2e4be4c6ebe5b8af558ff1243 Mon Sep 17 00:00:00 2001 From: artem-zakharchenko Date: Mon, 8 Jul 2019 14:18:50 +0200 Subject: [PATCH 17/29] feat: removes diff from TextDiff validator --- lib/validators/text-diff.js | 43 +++++++------------------- test/unit/units/validateBody.test.js | 2 +- test/unit/validators/text-diff-test.js | 28 +++-------------- 3 files changed, 18 insertions(+), 55 deletions(-) diff --git a/lib/validators/text-diff.js b/lib/validators/text-diff.js index 6c8ddbc2..3e806e2a 100644 --- a/lib/validators/text-diff.js +++ b/lib/validators/text-diff.js @@ -1,9 +1,9 @@ -const DiffMatchPatch = require('googlediff'); +// const DiffMatchPatch = require('googlediff'); const errors = require('../errors'); -const sanitizeSurrogatePairs = (data) => { - return data.replace(/[\uD800-\uDBFF]/g, '').replace(/[\uDC00-\uDFFF]/g, ''); -}; +// const sanitizeSurrogatePairs = (data) => { +// return data.replace(/[\uD800-\uDBFF]/g, '').replace(/[\uDC00-\uDFFF]/g, ''); +// }; class TextDiff { constructor(expected, actual) { @@ -28,40 +28,21 @@ class TextDiff { } validate() { - this.output = null; - const dmp = new DiffMatchPatch(); - - try { - const patch = dmp.patch_make(this.actual, this.expected); - this.output = dmp.patch_toText(patch); - return this.output; - } catch (error) { - if (error instanceof URIError) { - const patch = dmp.patch_make( - sanitizeSurrogatePairs(this.actual), - sanitizeSurrogatePairs(this.expected) - ); - this.output = dmp.patch_toText(patch); - return this.output; - } - - throw error; - } + this.valid = this.actual === this.expected; } - evaluateOutputToResults(data) { - if (!data) { - data = this.output; - } - - if (!data) { + evaluateOutputToResults() { + if (this.valid) { return []; } return [ { - // TODO Improve the message to contain real and expected data - message: 'Real and expected data does not match.' + message: 'Actual and expected data do not match.', + values: { + expected: this.expected, + actual: this.actual + } } ]; } diff --git a/test/unit/units/validateBody.test.js b/test/unit/units/validateBody.test.js index f8c2ca82..480daac9 100644 --- a/test/unit/units/validateBody.test.js +++ b/test/unit/units/validateBody.test.js @@ -240,7 +240,7 @@ describe('validateBody', () => { it('with explanatory message', () => { expect(result) .to.have.errorAtIndex(0) - .withMessage('Real and expected data does not match.'); + .withMessage('Actual and expected data do not match.'); }); }); }); diff --git a/test/unit/validators/text-diff-test.js b/test/unit/validators/text-diff-test.js index b9947726..d94318f1 100644 --- a/test/unit/validators/text-diff-test.js +++ b/test/unit/validators/text-diff-test.js @@ -88,14 +88,10 @@ describe('TextDiff', () => { }); it('should set output property', () => { - assert.isDefined(validator.output); + assert.isDefined(validator.valid); - it('output should be a string', () => { - assert.isString(validator.output); - }); - - it('output should be empty string', () => { - assert.equal(validator.output, ''); + it('output should be marked as valid', () => { + assert.isTrue(validator.vaild); }); }); }); @@ -108,22 +104,8 @@ describe('TextDiff', () => { validationResult = validator.validate(); }); - it('output property should be a string', () => { - assert.isString(validator.output); - }); - - it('output property should not be empty string', () => { - assert.notEqual(validator.output, ''); - }); - - it('output property should contain + and -', () => { - assert.include(validator.output, '-'); - assert.include(validator.output, '+'); - }); - - it('output property should be persed by googlediff to an array', () => { - dmp = new DiffMatchPatch(); - assert.isArray(dmp.patch_fromText(validator.output)); + it('output property should not be marked as valid', () => { + assert.isNotTrue(validator.valid); }); }); }); From c4814e02248d8b65d48c00b678c98e8d916c5407 Mon Sep 17 00:00:00 2001 From: artem-zakharchenko Date: Tue, 9 Jul 2019 09:42:27 +0200 Subject: [PATCH 18/29] chore: updates "gavel-spec" to "4.0.0" --- package-lock.json | 47 ++++++++++++++--------------------------------- package.json | 2 +- 2 files changed, 15 insertions(+), 34 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7011e32c..17b826f6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3483,8 +3483,7 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "aproba": { "version": "1.2.0", @@ -3505,14 +3504,12 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -3527,20 +3524,17 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -3657,8 +3651,7 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -3670,7 +3663,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -3685,7 +3677,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -3693,14 +3684,12 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -3719,7 +3708,6 @@ "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -3800,8 +3788,7 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -3813,7 +3800,6 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -3899,8 +3885,7 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -3936,7 +3921,6 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -3956,7 +3940,6 @@ "version": "3.0.1", "bundled": true, "dev": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -4000,14 +3983,12 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "yallist": { "version": "3.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true } } }, @@ -4024,9 +4005,9 @@ "dev": true }, "gavel-spec": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/gavel-spec/-/gavel-spec-3.0.2.tgz", - "integrity": "sha512-r8EZvdety8qc80Vf/zJbKCPHYMPBs2Wucg1+qbhTyw20I4doeZZXaB01XQhP8M9qgBnjP7oSJEbNfLputSJ4Dg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/gavel-spec/-/gavel-spec-4.0.0.tgz", + "integrity": "sha512-+8hBF2qyrCpos47y9+PjL0FL5ZwIwvfW7W/RQKLq6X1GJ8C5/KHPLrjOcuRdpwMrz1C7HYPGgYCBuaE3a4iWew==", "dev": true }, "get-assigned-identifiers": { diff --git a/package.json b/package.json index e0731529..9744b1ca 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,7 @@ "eslint-config-airbnb-base": "13.2.0", "eslint-config-prettier": "6.0.0", "eslint-plugin-import": "2.18.0", - "gavel-spec": "3.0.2", + "gavel-spec": "4.0.0", "husky": "3.0.0", "lint-staged": "9.0.2", "mocha": "6.1.4", From 09bd99708902d88db909516cdf1d6090f67d379d Mon Sep 17 00:00:00 2001 From: artem-zakharchenko Date: Tue, 9 Jul 2019 10:04:29 +0200 Subject: [PATCH 19/29] chore: removes "googlediff" package --- lib/validators/text-diff.js | 1 - package-lock.json | 5 ----- package.json | 1 - test/cucumber/steps/general.js | 5 ----- test/cucumber/support/world.js | 10 ---------- test/unit/validators/text-diff-test.js | 1 - 6 files changed, 23 deletions(-) diff --git a/lib/validators/text-diff.js b/lib/validators/text-diff.js index 3e806e2a..b9540a0f 100644 --- a/lib/validators/text-diff.js +++ b/lib/validators/text-diff.js @@ -1,4 +1,3 @@ -// const DiffMatchPatch = require('googlediff'); const errors = require('../errors'); // const sanitizeSurrogatePairs = (data) => { diff --git a/package-lock.json b/package-lock.json index 17b826f6..d1f20d4e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4155,11 +4155,6 @@ "pinkie-promise": "^2.0.0" } }, - "googlediff": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/googlediff/-/googlediff-0.1.0.tgz", - "integrity": "sha1-mazwXMBiI+tmwpAI2B+bLRjCRT0=" - }, "graceful-fs": { "version": "4.1.15", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", diff --git a/package.json b/package.json index 9744b1ca..14ab9c39 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,6 @@ "content-type": "1.0.4", "curl-trace-parser": "0.0.10", "deep-equal": "1.0.1", - "googlediff": "0.1.0", "http-string-parser": "0.0.6", "json-parse-helpfulerror": "1.0.3", "json-pointer": "0.6.0", diff --git a/test/cucumber/steps/general.js b/test/cucumber/steps/general.js index 35666d61..3062d964 100644 --- a/test/cucumber/steps/general.js +++ b/test/cucumber/steps/general.js @@ -1,5 +1,4 @@ const { expect } = require('chai'); -const Diff = require('googlediff'); const jhp = require('json-parse-helpfulerror'); module.exports = function() { @@ -83,10 +82,6 @@ ${stringifiedActual} to equal: ${expectedResult} - -See the text diff patches below: - -${dmp.patch_toText(dmp.patch_make(stringifiedActual, expectedResult))} ` ); }); diff --git a/test/cucumber/support/world.js b/test/cucumber/support/world.js index 225fb4e2..c02e9a6b 100644 --- a/test/cucumber/support/world.js +++ b/test/cucumber/support/world.js @@ -6,7 +6,6 @@ */ /* eslint-disable */ const vm = require('vm'); -const Diff = require('googlediff'); const util = require('util'); const { assert } = require('chai'); const { exec } = require('child_process'); @@ -256,15 +255,6 @@ Make sure it's in the "Header-Name: value" format. } } - diff(expected, actual) { - const dmp = new Diff(); - console.log( - dmp.patch_toText( - dmp.patch_make(JSON.stringify(actual), JSON.stringify(expected)) - ) - ); - } - // Debugging helper throw(data) { throw new Error(this.inspect(data)); diff --git a/test/unit/validators/text-diff-test.js b/test/unit/validators/text-diff-test.js index d94318f1..8531da63 100644 --- a/test/unit/validators/text-diff-test.js +++ b/test/unit/validators/text-diff-test.js @@ -1,6 +1,5 @@ /* eslint-disable */ const { assert } = require('chai'); -const DiffMatchPatch = require('googlediff'); const fixtures = require('../../fixtures'); const { TextDiff } = require('../../../lib/validators/text-diff'); From 3db2db6c48340439f0b7a1a37b82a2f057a91bc7 Mon Sep 17 00:00:00 2001 From: artem-zakharchenko Date: Tue, 9 Jul 2019 11:31:45 +0200 Subject: [PATCH 20/29] test: rewrites TextDiff unit tests --- lib/validators/text-diff.js | 1 + test/cucumber/steps/general.js | 1 - test/unit/validators/text-diff-test.js | 166 ++++++++++--------------- 3 files changed, 64 insertions(+), 104 deletions(-) diff --git a/lib/validators/text-diff.js b/lib/validators/text-diff.js index b9540a0f..1b12f7ff 100644 --- a/lib/validators/text-diff.js +++ b/lib/validators/text-diff.js @@ -28,6 +28,7 @@ class TextDiff { validate() { this.valid = this.actual === this.expected; + return this.valid; } evaluateOutputToResults() { diff --git a/test/cucumber/steps/general.js b/test/cucumber/steps/general.js index 3062d964..54c12689 100644 --- a/test/cucumber/steps/general.js +++ b/test/cucumber/steps/general.js @@ -69,7 +69,6 @@ module.exports = function() { }); this.Then('the validation result is:', function(expectedResult) { - const dmp = new Diff(); const stringifiedActual = JSON.stringify(this.result, null, 2); expect(this.result).to.deep.equal( diff --git a/test/unit/validators/text-diff-test.js b/test/unit/validators/text-diff-test.js index 8531da63..9bff88f9 100644 --- a/test/unit/validators/text-diff-test.js +++ b/test/unit/validators/text-diff-test.js @@ -1,147 +1,107 @@ -/* eslint-disable */ -const { assert } = require('chai'); - -const fixtures = require('../../fixtures'); +/* eslint-disable no-new */ +const { expect } = require('chai'); const { TextDiff } = require('../../../lib/validators/text-diff'); -const { - ValidationErrors -} = require('../../../lib/validators/validation-errors'); describe('TextDiff', () => { - validator = null; - - describe('when i create new instance of validator with incorrect "data" (first argument)', () => { - validator = null; - - it('should throw exception', () => { + describe('when expected non-string data', () => { + it('should throw an exception', () => { const fn = () => { - validator = new TextDiff(null, ''); + new TextDiff(null, ''); }; - assert.throws(fn); + expect(fn).to.throw(); }); }); - describe('when i create new instance of validator with incorrect "expected" (second argument)', () => { - validator = null; - - it('should throw exception', () => { - fn = () => { - validator = new TextDiff('', null); + describe('when given non-string actual data', () => { + it('should throw an exception', () => { + const fn = () => { + new TextDiff('', null); }; - assert.throws(fn); + expect(fn).to.throw(); }); }); - describe('when i create new instance of validator with "Iñtërnâtiônàlizætiøn☃" string as "data"', () => { - validator = null; + describe('when expected internationalized string', () => { + const expected = 'Iñtërnâtiônàlizætiøn☃'; - it('should not throw exception', () => { - const fn = () => { - validator = new TextDiff('Iñtërnâtiônàlizætiøn☃', ''); - }; - assert.doesNotThrow(fn); + it('should resolve on matching actual string', () => { + const validator = new TextDiff(expected, expected); + expect(validator.validate()).to.be.true; }); - describe('when I run validate', () => { - it('should not throw exception', () => { - const fn = () => validator.validate(); - assert.doesNotThrow(fn); - }); + it('should reject on non-matching actual string', () => { + const validator = new TextDiff(expected, 'Nâtiônàl'); + expect(validator.validate()).to.be.false; }); }); - describe('when i create new instance of validator with surrogate pair in data', () => { - validator = null; + describe('when expected a string with surrogate pair', () => { + const expected = 'text1\uD800'; - it('should not throw exception', () => { - const fn = () => { - validator = new TextDiff('text1\uD800', '\uD800text1'); - }; - assert.doesNotThrow(fn); + it('should resolve on matching string', () => { + const validator = new TextDiff(expected, 'text1\uD800'); + expect(validator.validate()).to.be.true; }); - describe('when I run validate', () => { - it('should not throw exception', () => { - const fn = () => validator.validate(); - assert.doesNotThrow(fn); - }); + it('should reject on non-matching string', () => { + // This is not considered as non-matching strings + // due to surrogate pairs in it. Rewrite the test. + const validator = new TextDiff(expected, 'barry'); + expect(validator.validate()).to.be.false; }); }); - describe('when i create new instance of validator with correct data', () => { - validator = null; + describe('when expected textual data', () => { + const expected = 'john'; - it('should not throw exception', () => { - const fn = () => { - validator = new TextDiff('text1', 'text1'); - }; - assert.doesNotThrow(fn); + it('should resolve when given matching actual data', () => { + const validator = new TextDiff(expected, 'john'); + expect(validator.validate()).to.be.true; }); - describe('when data are same and I run validate', () => { - validationResult = null; - - before(() => { - validator = new TextDiff('text1', 'text1'); - validationResult = validator.validate(); - }); - - it('should set output property', () => { - assert.isDefined(validator.valid); - - it('output should be marked as valid', () => { - assert.isTrue(validator.vaild); - }); - }); + it('should reject when given non-matching actual data', () => { + const validator = new TextDiff(expected, 'barry'); + expect(validator.validate()).to.be.false; }); + }); - describe('when data differs and I run validate', () => { - validationResult = null; - - before(() => { - validator = new TextDiff('text1', 'text2'); - validationResult = validator.validate(); - }); + describe('when evaluating output to results', () => { + describe('when expected and actual data match', () => { + const validator = new TextDiff('john', 'john'); + validator.validate(); + const result = validator.evaluateOutputToResults(); - it('output property should not be marked as valid', () => { - assert.isNotTrue(validator.valid); + it('should return an empty array', () => { + expect(result).to.be.instanceOf(Array); + expect(result).to.have.lengthOf(0); }); }); - }); - describe('.evaluateOutputToResults', () => { - data = null; - results = null; - - describe('empty validation result', () => { - before(() => { - validator = new TextDiff('', ''); - validator.validate(); - results = validator.evaluateOutputToResults(); - }); + describe('when expected and actual data do not match', () => { + const validator = new TextDiff('john', 'barry'); + validator.validate(); + const result = validator.evaluateOutputToResults(); it('should return an array', () => { - assert.isArray(results); + expect(result).to.be.instanceOf(Array); }); - it('should has no results', () => { - assert.equal(results.length, 0); + it('should contain exactly one error', () => { + expect(result).to.have.lengthOf(1); }); - }); - describe('non empty validation result', () => { - before(() => { - validator = new TextDiff('abc', 'cde'); - validator.validate(); - results = validator.evaluateOutputToResults(); + it('error should include the "message"', () => { + expect(result[0]).to.have.property( + 'message', + 'Actual and expected data do not match.' + ); }); - it('should return an array', () => { - assert.isArray(results); - }); - - it('should contain one error', () => { - assert.lengthOf(results, 1); + it('error should contain compared values', () => { + expect(result[0]).to.have.deep.property('values', { + expected: 'john', + actual: 'barry' + }); }); }); }); From 54d3f12bfdefac133eda3d701d1a7391c33ed6a4 Mon Sep 17 00:00:00 2001 From: artem-zakharchenko Date: Wed, 10 Jul 2019 14:45:55 +0200 Subject: [PATCH 21/29] fix: excludes "@cli" features on win platform --- scripts/cucumber.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/cucumber.js b/scripts/cucumber.js index b51a6868..c986f967 100644 --- a/scripts/cucumber.js +++ b/scripts/cucumber.js @@ -6,7 +6,7 @@ const isWindows = process.platform.match(/^win/); // Excludes Cucumber features marked with the "@cli" tag. // CLI does not work on Windows: // https://github.com/apiaryio/gavel-spec/issues/24 -const tags = [isWindows && '~@cli'].filter(Boolean); +const tags = [isWindows && '-t ~@cli'].filter(Boolean); const args = [ ...tags, From 73b7a95696fb619f02d6bdd79ba331c0efc05abe Mon Sep 17 00:00:00 2001 From: artem-zakharchenko Date: Wed, 10 Jul 2019 15:02:00 +0200 Subject: [PATCH 22/29] chore: adds ".nvmrc" and contribution guidelines --- .nvmrc | 1 + CONTRIBUTING.md | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 .nvmrc create mode 100644 CONTRIBUTING.md diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 00000000..368fe859 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +v8.12.0 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..62af5cf7 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,23 @@ +# Contributing + +Thank you for deciding to contribute to Gavel.js. Please read the guidelines below to ensure the smoothest developer's experience during the involvement. + +## Workflow + +1. Clone the repository. +2. Install dependencies: + +```bash +npm install +``` + +3. Use the correct NodeJS version: + +```bash +nvm use +``` + +4. Create a branch for a feature or a bugfix. +5. Follow the [Conventional Commit Messages](https://github.com/angular/angular.js/blob/master/DEVELOPERS.md#user-content--git-commit-guidelines) for commit messages. +6. Issue a pull request and undergo a code review. +7. Upon approval, merge the pull request. From d16d8bf43aa90b185bf5fe8a40a41d4d7faf1384 Mon Sep 17 00:00:00 2001 From: artem-zakharchenko Date: Thu, 11 Jul 2019 10:53:04 +0200 Subject: [PATCH 23/29] test: removes unused Cucumber world utils --- test/cucumber/support/world.js | 63 ---------------------------------- 1 file changed, 63 deletions(-) diff --git a/test/cucumber/support/world.js b/test/cucumber/support/world.js index c02e9a6b..8082f9f1 100644 --- a/test/cucumber/support/world.js +++ b/test/cucumber/support/world.js @@ -41,69 +41,6 @@ class World { this.componentResults = null; } - expectBlockEval(block, expectedReturn, callback) { - const realOutput = this.safeEval(block, callback); - - // I'm terribly sorry, but curly braces not asigned to any - // variable in evaled string are interpreted as code block - // not an Object literal, so I'm wrapping expected code output - // with brackets. - // see: http://stackoverflow.com/questions/8949274/javascript-calling-eval-on-an-object-literal-with-functions - - const expectedOutput = this.safeEval(`(${expectedReturn})`, callback); - - const realOutputInspect = util.inspect(realOutput); - const expectedOutputInspect = util.inspect(expectedOutput); - - try { - assert.deepEqual(realOutput, expectedOutput); - } catch (error) { - callback( - new Error( - 'Output of code buffer does not equal. Expected output:\n' + - expectedOutputInspect + - '\nbut got: \n' + - realOutputInspect + - '\n' + - 'Evaled code block:' + - '\n' + - '- - - \n' + - block + - '\n' + - '- - - ' - ) - ); - } - return callback(); - } - - safeEval(code, callback) { - // I'm terribly sorry, it's no longer possible to manipulate module require/load - // path inside node's process. So I'm prefixing require path by hard - // substitution in code to pretend to 'hit' is packaged module. - // - // further reading on node.js load paths: - // http://nodejs.org/docs/v0.8.23/api/all.html#all_all_together - - const formattedCode = code.replace( - `require('gavel`, - `require('../../../lib` - ); - - try { - return eval(formattedCode); - } catch (error) { - return callback( - new Error( - 'Eval failed. Code buffer: \n\n' + - formattedCode + - '\nWith error: ' + - error - ) - ); - } - } - executeCommands(commands) { const commandsBuffer = commands.join(';'); const cmd = From feaf319f433232634d6f1f08a13a1b366ed9ced2 Mon Sep 17 00:00:00 2001 From: artem-zakharchenko Date: Thu, 11 Jul 2019 12:07:07 +0200 Subject: [PATCH 24/29] test: removes surrogate pairs test from TextDiff --- lib/validators/text-diff.js | 4 ---- test/unit/validators/text-diff-test.js | 16 ---------------- 2 files changed, 20 deletions(-) diff --git a/lib/validators/text-diff.js b/lib/validators/text-diff.js index 1b12f7ff..b5923dba 100644 --- a/lib/validators/text-diff.js +++ b/lib/validators/text-diff.js @@ -1,9 +1,5 @@ const errors = require('../errors'); -// const sanitizeSurrogatePairs = (data) => { -// return data.replace(/[\uD800-\uDBFF]/g, '').replace(/[\uDC00-\uDFFF]/g, ''); -// }; - class TextDiff { constructor(expected, actual) { if (typeof actual !== 'string') { diff --git a/test/unit/validators/text-diff-test.js b/test/unit/validators/text-diff-test.js index 9bff88f9..ca1df7ed 100644 --- a/test/unit/validators/text-diff-test.js +++ b/test/unit/validators/text-diff-test.js @@ -35,22 +35,6 @@ describe('TextDiff', () => { }); }); - describe('when expected a string with surrogate pair', () => { - const expected = 'text1\uD800'; - - it('should resolve on matching string', () => { - const validator = new TextDiff(expected, 'text1\uD800'); - expect(validator.validate()).to.be.true; - }); - - it('should reject on non-matching string', () => { - // This is not considered as non-matching strings - // due to surrogate pairs in it. Rewrite the test. - const validator = new TextDiff(expected, 'barry'); - expect(validator.validate()).to.be.false; - }); - }); - describe('when expected textual data', () => { const expected = 'john'; From d193fc800fd30edc51fb2e6d5ba97b8dbb4e7675 Mon Sep 17 00:00:00 2001 From: artem-zakharchenko Date: Thu, 11 Jul 2019 12:15:20 +0200 Subject: [PATCH 25/29] test: declares a proper variable for amanda-to-gavel assertions --- test/unit/support/amanda-to-gavel-shared.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/test/unit/support/amanda-to-gavel-shared.js b/test/unit/support/amanda-to-gavel-shared.js index 5f3171f4..9c76ae01 100644 --- a/test/unit/support/amanda-to-gavel-shared.js +++ b/test/unit/support/amanda-to-gavel-shared.js @@ -63,9 +63,8 @@ exports.shouldBehaveLikeAmandaToGavel = (instance) => { }); it('should be a parseable JSON poitner', () => { - parsed = jsonPointer.parse(value); - - assert.isArray(parsed); + const parsedPointer = jsonPointer.parse(value); + assert.isArray(parsedPointer); }); }); }); From 6148b4782307e048fb4f6056380e029c1c21a9b2 Mon Sep 17 00:00:00 2001 From: artem-zakharchenko Date: Fri, 12 Jul 2019 10:21:37 +0200 Subject: [PATCH 26/29] chore: marks validators refactoring with GitHub issue --- lib/units/validateBody.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/units/validateBody.js b/lib/units/validateBody.js index 23db0cc3..e7c3b687 100644 --- a/lib/units/validateBody.js +++ b/lib/units/validateBody.js @@ -246,8 +246,10 @@ function validateBody(expected, actual) { actual.body ); - // Without ".validate()" it cannot evaluate output to result. - // TODO Re-do this. + // Calling "validate()" often updates an internal state of a validator. + // That state is later used to output the gavel-compliant results. + // Cannot remove until validators are refactored into simple functions. + // @see https://github.com/apiaryio/gavel.js/issues/150 validator && validator.validate(); const validationErrors = validator ? validator.evaluateOutputToResults() : []; errors.push(...validationErrors); From 7ff8e79f295a488f7e17fbd09393528c9b87a13c Mon Sep 17 00:00:00 2001 From: artem-zakharchenko Date: Fri, 12 Jul 2019 10:23:13 +0200 Subject: [PATCH 27/29] chore: removes unused properties from Cucumber world --- test/cucumber/steps/cli.js | 12 ++++++++---- test/cucumber/steps/general.js | 2 +- test/cucumber/support/world.js | 27 +++------------------------ 3 files changed, 12 insertions(+), 29 deletions(-) diff --git a/test/cucumber/steps/cli.js b/test/cucumber/steps/cli.js index fc34f2f9..e5453d7d 100644 --- a/test/cucumber/steps/cli.js +++ b/test/cucumber/steps/cli.js @@ -3,7 +3,7 @@ const { assert } = require('chai'); module.exports = function() { this.Given( /^(I record (expected|actual) raw HTTP message:)|(a header is missing in actual message:)$/, - function(_, __, ___, command) { + function(_1, _2, _3, command) { this.commands.push(command); } ); @@ -12,11 +12,15 @@ module.exports = function() { 'I validate the message using the following Gavel command:', async function(command) { this.commands.push(command); - this.status = await this.executeCommands(this.commands); + this.exitCode = await this.executeCommands(this.commands); } ); - this.Then(/^exit status is (\d+)$/, function(expectedStatus) { - assert.equal(this.status, expectedStatus, 'Process statuses do not match'); + this.Then(/^exit status is (\d+)$/, function(expectedExitCode) { + assert.equal( + this.exitCode, + expectedExitCode, + `Expected process to exit with code ${expectedExitCode}, but got ${this.exitCode}.` + ); }); }; diff --git a/test/cucumber/steps/general.js b/test/cucumber/steps/general.js index 54c12689..efa804d8 100644 --- a/test/cucumber/steps/general.js +++ b/test/cucumber/steps/general.js @@ -59,7 +59,7 @@ module.exports = function() { }); // Vocabulary proxy over the previous action for better scenarios readability. - this.When(/^I call "([^"]*)"$/, function(_command) { + this.When(/^I call "gavel.validate(([^"]*))"$/, function(_, _command) { this.validate(); }); diff --git a/test/cucumber/support/world.js b/test/cucumber/support/world.js index 8082f9f1..d9916598 100644 --- a/test/cucumber/support/world.js +++ b/test/cucumber/support/world.js @@ -15,30 +15,15 @@ const HTTP_LINE_DELIMITER = '\n'; class World { constructor() { - this.codeBuffer = ''; - this.commandBuffer = ''; - - // NEW - this.commands = []; - this.expected = {}; this.actual = {}; - // Parsed HTTP objects for model valdiation - this.model = {}; - // Gavel validation result this.results = {}; - // CLI: Process exit status - this.status = null; - - // Validation verdict for the whole HTTP Message - // this.booleanResult = false; - - // Component relevant to the expectation, e.g. 'body' - this.component = null; - this.componentResults = null; + // CLI + this.commands = []; + this.exitCode = null; } executeCommands(commands) { @@ -107,17 +92,11 @@ Make sure it's in the "Header-Name: value" format. const [method, uri] = firstLine.split(' '); parsed.method = method; parsed.uri = uri; - // firstLine = firstLine.split(' '); - // parsed.method = firstLine[0]; - // parsed.uri = firstLine[1]; } parseResponseLine(parsed, firstLine) { const [statusCode] = firstLine.split(' '); parsed.statusCode = statusCode; - // firstLine = firstLine.split(' '); - // parsed.statusCode = firstLine[1]; - // parsed.statusMessage = firstLine[2]; } parseHttp(type, string) { From d3603f524f8a4e981c9dc73cd3e58c3bdecac4b7 Mon Sep 17 00:00:00 2001 From: artem-zakharchenko Date: Fri, 12 Jul 2019 10:29:50 +0200 Subject: [PATCH 28/29] chore: provides assertion error messages for "kind" in Chai --- test/chai.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/test/chai.js b/test/chai.js index aa798319..afdfca48 100644 --- a/test/chai.js +++ b/test/chai.js @@ -55,9 +55,6 @@ ${JSON.stringify(target)} createErrorPropertyAssertion('location', 'withLocation'); createErrorPropertyAssertion('values', 'withValues'); - // - // TODO - // Finish the error messages Assertion.addMethod('kind', function(expectedValue) { const { kind } = this._obj; const stringifiedObj = stringify(this._obj); @@ -69,11 +66,17 @@ Expected the following HTTP message field: ${stringifiedObj} -to have "kind" property equal to "${expectedValue}". +to have "kind" property equal "${expectedValue}", but got ${kind}. + `, + ` +Expected the following HTTP message field: + +${stringifiedObj} + +to not have "kind" property equal "${expectedValue}". `, - 'asdas', - expectedValue, kind, + expectedValue, true ); }); From 7c67e519cfb9f6302b2f4b28310c7d249b1f0b64 Mon Sep 17 00:00:00 2001 From: artem-zakharchenko Date: Fri, 12 Jul 2019 10:37:15 +0200 Subject: [PATCH 29/29] chore: adds license link, adjusts gherkin examples wording in README --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 4544c24d..da5d41cf 100644 --- a/README.md +++ b/README.md @@ -168,11 +168,11 @@ The validation `result` against the given JSON Schema will look as follows: } ``` -> Note that JSON schema V5+ are not currently supported. [Follow the support progress](https://github.com/apiaryio/gavel.js/issues/90). +> Note that JSON schema Draft-05+ are not currently supported. [Follow the support progress](https://github.com/apiaryio/gavel.js/issues/90). ## Examples -Take a look at the examples of each field validation described in [Gherkin](https://cucumber.io/docs/gherkin/): +Take a look at the [Gherkin](https://cucumber.io/docs/gherkin/) specification, which describes on examples how validation of each field behaves: - [`method`](https://github.com/apiaryio/gavel-spec/blob/master/features/javascript/fields/method) - [`statusCode`](https://github.com/apiaryio/gavel-spec/blob/master/features/javascript/fields/statusCode) @@ -205,7 +205,7 @@ interface HttpMessage { ```ts // Field kind describes the type of a field's values // subjected to the end comparison. -enum FieldKind { +enum ValidationKind { null // non-comparable data (validation didn't happen) text // compared as text json // compared as JSON @@ -216,7 +216,7 @@ interface ValidationResult { fields: { [fieldName: string]: { valid: boolean // validity of a single field - kind: FieldKind + kind: ValidationKind values: { // end compared values (coerced, normalized) actual: any expected: any @@ -246,4 +246,4 @@ interface FieldError { ## License -MIT +[MIT](LICENSE)