From d91284547cd88892b1186016915b301123d624e3 Mon Sep 17 00:00:00 2001 From: Luis Tejeda <46000487+LuisTejedaS@users.noreply.github.com> Date: Mon, 28 Feb 2022 16:23:22 -0600 Subject: [PATCH 1/9] Add type validation in params, queries headers Add type validation in params, queries headers. Extract common functionality. --- lib/ajValidation/ajvValidation.js | 111 +--------------- lib/common/schemaUtilsCommon.js | 185 +++++++++++++++++++++++++- lib/schemaUtils.js | 15 +-- test/data/valid_openapi/issue#479.yml | 97 ++++++++++++++ test/unit/schemaUtilsCommon.test.js | 101 +++++++++++++- test/unit/xajvValidation.test.js | 81 ----------- 6 files changed, 385 insertions(+), 205 deletions(-) create mode 100644 test/data/valid_openapi/issue#479.yml diff --git a/lib/ajValidation/ajvValidation.js b/lib/ajValidation/ajvValidation.js index d984ba0..309bd2b 100644 --- a/lib/ajValidation/ajvValidation.js +++ b/lib/ajValidation/ajvValidation.js @@ -1,6 +1,6 @@ const { formatDataPath, formatSchemaPathFromAJVErrorToConvertToDataPath, - typesMap } = require('../common/schemaUtilsCommon'); + isTypeValue } = require('../common/schemaUtilsCommon'); var _ = require('lodash'); const IGNORED_KEYWORDS = ['propertyNames', 'const', 'additionalItems', 'dependencies'], @@ -8,17 +8,6 @@ const IGNORED_KEYWORDS = ['propertyNames', 'const', 'additionalItems', 'dependen { validateSchemaAJVDraft04 } = require('./ajvValidatorDraft04'), specialDraft = 'http://json-schema.org/draft-04/schema#'; -/** - * Checks if value is postman variable or not - * - * @param {string} type - type to look for - * @param {string} format - format from schema - * @returns {Boolean} postman variable or not - */ -function getDefaultFromTypeAndFormat(type, format) { - return typesMap[type][format]; -} - /** * Checks if value is postman variable or not * @@ -30,104 +19,6 @@ function isPmVariable (value) { return _.isString(value) && _.startsWith(value, '{{') && _.endsWith(value, '}}'); } -/** - * Checks if value is the representation of its type like: - * "" - * - * @param {string} value - Value to check for - * @param {string} type - The type in the schemna - * @returns {Boolean} the value is the representation of its type - */ -function compareType(value, type) { - return value === '<' + type + '>'; -} - -/** - * Checks if value is the representation of its type like: - * "" - * Works in array types - * @param {string} value - Value to check for - * @param {*} types - The types in the schemna - * @returns {Boolean} the value is the representation of its type - */ -function isTypeValueArrayCheck(value, types) { - return types.find((type) => { - return compareType(value, type); - }) !== undefined; -} - -/** - * Checks if value is the representation of its type like: - * "" - * Works in array types - * @param {string} value - Value to check for - * @param {*} types - The types in the schemna - * @returns {Boolean} the value is the representation of its type - */ -function checkValueOnlyTypes(value, types) { - return Array.isArray(types) ? isTypeValueArrayCheck(value, types) : compareType(value, types); -} - -/** - * Checks if value is the representation of its type like: - * "" - * Works in array types - * @param {string} value - Value to check for - * @param {*} types - The types in the schema - * @param {*} format - format from the schema - * @returns {Boolean} the value is the representation of its type - */ -function checkValueTypesAndFormat(value, types, format) { - let typesNotInMapp = [], - typesArray = Array.isArray(types) ? types : [types], - found = typesArray.find((type) => { - let defaultValue; - if (typesMap.hasOwnProperty(type)) { - defaultValue = getDefaultFromTypeAndFormat(type, format); - - // in case the format is a custom format (email, hostname etc.) - // https://swagger.io/docs/specification/data-models/data-types/#string - if (!defaultValue && format) { - defaultValue = '<' + format + '>'; - } - } - else { - typesNotInMapp.push(type); - } - return defaultValue === value; - }); - - if (found) { - return true; - } - - found = typesNotInMapp.find((type) => { - let defaultValue; - defaultValue = '<' + type + (format ? ('-' + format) : '') + '>'; - return defaultValue === value; - }); - - return found !== undefined; -} - -/** - * Checks if value is the representation of its type like: - * "" - * Works in array types - * @param {string} value - Value to check for - * @param {*} schema - The schema portion used in validation - * @returns {Boolean} the value is the representation of its type - */ -function isTypeValue(value, schema) { - if (schema.hasOwnProperty('type') && !schema.hasOwnProperty('format')) { - return checkValueOnlyTypes(value, schema.type); - } - if (schema.hasOwnProperty('type') && schema.hasOwnProperty('format')) { - return checkValueTypesAndFormat(value, schema.type, schema.format); - } -} - - /** * returns the local $schema value * diff --git a/lib/common/schemaUtilsCommon.js b/lib/common/schemaUtilsCommon.js index ead54f8..d63b8c2 100644 --- a/lib/common/schemaUtilsCommon.js +++ b/lib/common/schemaUtilsCommon.js @@ -3,6 +3,7 @@ */ const parse = require('../parse.js'), + _ = require('lodash'), typesMap = { integer: { int32: '', @@ -20,6 +21,16 @@ const parse = require('../parse.js'), password: '' }, boolean: '' + }, + + /* eslint-disable arrow-body-style */ + schemaTypeToJsValidator = { + 'string': (d) => typeof d === 'string', + 'number': (d) => !isNaN(d), + 'integer': (d) => !isNaN(d) && Number.isInteger(Number(d)), + 'boolean': (d) => _.isBoolean(d) || d === 'true' || d === 'false', + 'array': (d) => Array.isArray(d), + 'object': (d) => typeof d === 'object' && !Array.isArray(d) }; /** @@ -44,6 +55,152 @@ function removeTypeFromLastPosition(schemaPath) { return splittedDataPath.join('/'); } +/** + * Checks if value is the representation of its type like: + * "" + * + * @param {string} value - Value to check for + * @param {string} type - The type in the schemna + * @returns {Boolean} the value is the representation of its type + */ +function compareType(value, type) { + return value === '<' + type + '>'; +} + +/** + * Checks if value is the representation of its type like: + * "" + * Works in array types + * @param {string} value - Value to check for + * @param {*} types - The types in the schemna + * @returns {Boolean} the value is the representation of its type + */ +function isTypeValueArrayCheck(value, types) { + return types.find((type) => { + return compareType(value, type); + }) !== undefined; +} + +/** + * Checks if value is the representation of its type like: + * "" + * Works in array types + * @param {string} value - Value to check for + * @param {*} types - The types in the schemna + * @returns {Boolean} the value is the representation of its type + */ +function checkValueOnlyTypes(value, types) { + return Array.isArray(types) ? isTypeValueArrayCheck(value, types) : compareType(value, types); +} + +/** + * Checks if value is postman variable or not + * + * @param {string} type - type to look for + * @param {string} format - format from schema + * @returns {Boolean} postman variable or not + */ +function getDefaultFromTypeAndFormat(type, format) { + return typesMap[type][format]; +} + + +/** + * Checks if value is the representation of its type like: + * "" + * Works in array types + * @param {string} value - Value to check for + * @param {*} types - The types in the schema + * @param {*} format - format from the schema + * @returns {Boolean} the value is the representation of its type + */ +function checkValueTypesAndFormat(value, types, format) { + let typesNotInMapp = [], + typesArray = Array.isArray(types) ? types : [types], + found = typesArray.find((type) => { + let defaultValue; + if (typesMap.hasOwnProperty(type)) { + defaultValue = getDefaultFromTypeAndFormat(type, format); + + // in case the format is a custom format (email, hostname etc.) + // https://swagger.io/docs/specification/data-models/data-types/#string + if (!defaultValue && format) { + defaultValue = '<' + format + '>'; + } + } + else { + typesNotInMapp.push(type); + } + return defaultValue === value; + }); + + if (found) { + return true; + } + + found = typesNotInMapp.find((type) => { + let defaultValue; + defaultValue = '<' + type + (format ? ('-' + format) : '') + '>'; + return defaultValue === value; + }); + + return found !== undefined; +} + +/** + * Checks if value is equal to the defined default + * Works in array types + * @param {string} value - Value to check for + * @param {*} definedDefault - The defined default value of the schema + * @returns {Boolean} wheter value is equal to the defined or not + */ +function checkValueEqualsDefault(value, definedDefault) { + return value === definedDefault; +} + +/** + * Checks if value is the representation of its type like: + * "" + * Works in array types + * @param {string} value - Value to check for + * @param {*} schema - The schema portion used in validation + * @returns {Boolean} the value is the representation of its type + */ +function isTypeValue(value, schema) { + if (schema.hasOwnProperty('type') && schema.hasOwnProperty('default')) { + const isDefault = checkValueEqualsDefault(value, schema.default); + if (isDefault) { + return true; + } + } + if (schema.hasOwnProperty('type') && !schema.hasOwnProperty('format')) { + return checkValueOnlyTypes(value, schema.type); + } + if (schema.hasOwnProperty('type') && schema.hasOwnProperty('format')) { + return checkValueTypesAndFormat(value, schema.type, schema.format); + } +} + +/** + * Checks if value is correcta according to schema + * If the value should be numeric, it tries to convert and then validate + * also validates if the value is a correct representation in the form of + * or etc for integers format 32 or format 64 + * @param {string} value - Value to check for + * @param {*} schema - The schema portion used in validation + * @returns {Boolean} the value is the representation of its type + */ +function checkIsCorrectType(value, schema) { + if (schema.hasOwnProperty('type') && + typeof schemaTypeToJsValidator[schema.type] === 'function') { + const isCorrectType = schemaTypeToJsValidator[schema.type](value); + if (isCorrectType) { + return true; + } + } + return isTypeValue(value, schema); +} + module.exports = { /** @@ -158,5 +315,31 @@ module.exports = { return removeTypeFromLastPosition(removeSharpAndSlashFromFirstPosition(schemaPath)); }, - typesMap + typesMap, + + /** + * Checks if value is the representation of its type like: + * "" + * Works in array types + * @param {string} value - Value to check for + * @param {*} schema - The schema portion used in validation + * @returns {Boolean} the value is the representation of its type + */ + isTypeValue, + + /** + * Checks if value is correcta according to schema + * If the value should be numeric, it tries to convert and then validate + * also validates if the value is a correct representation in the form of + * or etc for integers format 32 or format 64 + * @param {string} value - Value to check for + * @param {*} schema - The schema portion used in validation + * @returns {Boolean} the value is the representation of its type + */ + checkIsCorrectType, + + + isKnownType: function(schema) { + return typeof schemaTypeToJsValidator[schema.type] === 'function'; + } }; diff --git a/lib/schemaUtils.js b/lib/schemaUtils.js index b3ff35a..d2e9358 100644 --- a/lib/schemaUtils.js +++ b/lib/schemaUtils.js @@ -3,7 +3,7 @@ * utils.js contains other util functions */ -const { formatDataPath } = require('./common/schemaUtilsCommon.js'), +const { formatDataPath, checkIsCorrectType, isKnownType } = require('./common/schemaUtilsCommon.js'), { getConcreteSchemaUtils } = require('./common/versionUtils.js'), async = require('async'), sdk = require('postman-collection'), @@ -85,15 +85,6 @@ const { formatDataPath } = require('./common/schemaUtilsCommon.js'), 'authorization' ], - /* eslint-disable arrow-body-style */ - schemaTypeToJsValidator = { - 'string': (d) => typeof d === 'string', - 'number': (d) => !isNaN(d), - 'integer': (d) => !isNaN(d) && Number.isInteger(Number(d)), - 'boolean': (d) => _.isBoolean(d) || d === 'true' || d === 'false', - 'array': (d) => Array.isArray(d), - 'object': (d) => typeof d === 'object' && !Array.isArray(d) - }, crypto = require('crypto'), DEFAULT_SCHEMA_UTILS = require('./30XUtils/schemaUtils30X'); /* eslint-enable */ @@ -3088,7 +3079,7 @@ module.exports = { } // When processing a reference, schema.type could also be undefined else if (schema && schema.type) { - if (typeof schemaTypeToJsValidator[schema.type] === 'function') { + if (isKnownType(schema)) { let isCorrectType; // Treat unresolved postman collection/environment variable as correct type @@ -3096,7 +3087,7 @@ module.exports = { isCorrectType = true; } else { - isCorrectType = schemaTypeToJsValidator[schema.type](valueToUse); + isCorrectType = checkIsCorrectType(valueToUse, schema); } if (!isCorrectType) { diff --git a/test/data/valid_openapi/issue#479.yml b/test/data/valid_openapi/issue#479.yml new file mode 100644 index 0000000..08ad9dd --- /dev/null +++ b/test/data/valid_openapi/issue#479.yml @@ -0,0 +1,97 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Swagger Petstore + license: + name: MIT +servers: + - url: http://petstore.swagger.io/v1 +paths: + /pets: + get: + summary: List all pets + operationId: listPets + tags: + - pets + parameters: + - name: limit + in: query + description: How many items to return at one time (max 100) + required: false + schema: + type: integer + format: int32 + - name: header-1 + in: header + required: false + schema: + type: integer + format: int64 + responses: + '200': + description: A paged array of pets + headers: + x-next: + description: A link to the next page of responses + schema: + type: integer + format: int64 + content: + application/json: + schema: + $ref: "#/components/schemas/Pets" + /pets2/{pathVar}: + get: + summary: List all pets + operationId: listPets + tags: + - pets + parameters: + - name: pathVar + in: path + required: false + schema: + type: integer + format: int32 + responses: + '200': + description: A paged array of pets + headers: + x-next: + description: A link to the next page of responses + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/Pets" +components: + schemas: + Pet: + type: object + required: + - id + - name + properties: + id: + type: integer + format: int64 + name: + type: string + tag: + type: string + Pets: + type: array + items: + $ref: "#/components/schemas/Pet" + Error: + type: object + required: + - code + - message + properties: + code: + type: integer + format: int32 + message: + type: string diff --git a/test/unit/schemaUtilsCommon.test.js b/test/unit/schemaUtilsCommon.test.js index dfa4ca2..e46cc89 100644 --- a/test/unit/schemaUtilsCommon.test.js +++ b/test/unit/schemaUtilsCommon.test.js @@ -1,5 +1,7 @@ const schemaUtilsCommon = require('../../lib/common/schemaUtilsCommon'), - { formatDataPath, formatSchemaPathFromAJVErrorToConvertToDataPath } = require('../../lib/common/schemaUtilsCommon'), + { formatDataPath, + formatSchemaPathFromAJVErrorToConvertToDataPath, + isTypeValue } = require('../../lib/common/schemaUtilsCommon'), expect = require('chai').expect; describe('formatData method', function() { @@ -165,3 +167,100 @@ describe('formatSchemaPathFromAJVErrorToConvertToDataPath method', function () { expect(result).to.equal('properties/automatic/items/properties/configs/items'); }); }); + +describe('isTypeValue method', function () { + it('should return true when value is and type is integer', function () { + const result = isTypeValue('', { + type: [ + 'integer' + ] + }); + expect(result).to.be.true; + }); + + it('should return true when input is type integer and format int64', function () { + const result = isTypeValue('', { + format: 'int64', + type: ['integer'] + }); + expect(result).to.be.true; + }); + + it('should return true when value is type is string format is uuid', function () { + const result = isTypeValue('', { + format: 'uuid', + type: ['string'] + }); + expect(result).to.be.true; + }); + + + it('should return true when value is type is otherType and there is not format', function () { + const result = isTypeValue('', { + type: ['otherType'] + }); + expect(result).to.be.true; + }); + + it('should return true value is type is otherType and format is otherFormat', function () { + const result = isTypeValue('', { + format: 'otherFormat', + type: ['otherType'] + }); + expect(result).to.be.true; + }); + + it('should return false when value is and type is boolean', function () { + const result = isTypeValue('', { + type: ['boolean'] + }); + expect(result).to.be.false; + }); + + it('should return true when value is and type is string', function () { + const result = isTypeValue('', { + type: ['string'] + }); + expect(result).to.be.true; + }); + + it('should return true when value is and type is ["boolean", "integer"]', function () { + const result = isTypeValue('', { + type: ['boolean', 'integer'] + }); + expect(result).to.be.true; + }); + + it('should return true when value is and type is integer not array', function () { + const result = isTypeValue('', { + type: 'integer' + }); + expect(result).to.be.true; + }); + + it('should return true when value is and type is integer not array format int64', function () { + const result = isTypeValue('', { + format: 'int64', + type: 'integer' + }); + expect(result).to.be.true; + }); + + it('should return true when value is and type is integer, format not present default is ', function () { + const result = isTypeValue('', { + default: '', + type: 'integer' + }); + expect(result).to.be.true; + }); + + it('should return false when value is and type is integer,' + + ' there is no format default is ', function () { + const result = isTypeValue('', { + default: '', + type: 'integer' + }); + expect(result).to.be.false; + }); + +}); diff --git a/test/unit/xajvValidation.test.js b/test/unit/xajvValidation.test.js index 17f615a..73ef43e 100644 --- a/test/unit/xajvValidation.test.js +++ b/test/unit/xajvValidation.test.js @@ -1,7 +1,6 @@ const { getLocalDraft, getAjvValidator, validateSchema, - isTypeValue, getDraftToUse } = require('../../lib/ajValidation/ajvValidation'), { validateSchemaAJVDraft04 } = require('../../lib/ajValidation/ajvValidatorDraft04'), expect = require('chai').expect; @@ -433,83 +432,3 @@ describe('getDraftToUse', function() { expect(draftToUse).to.equal(undefined); }); }); - -describe('isTypeValue method', function () { - it('should return true when value is and type is integer', function () { - const result = isTypeValue('', { - type: [ - 'integer' - ] - }); - expect(result).to.be.true; - }); - - it('should return true when input is type integer and format int64', function () { - const result = isTypeValue('', { - format: 'int64', - type: ['integer'] - }); - expect(result).to.be.true; - }); - - it('should return true when value is type is string format is uuid', function () { - const result = isTypeValue('', { - format: 'uuid', - type: ['string'] - }); - expect(result).to.be.true; - }); - - - it('should return true when value is type is otherType and there is not format', function () { - const result = isTypeValue('', { - type: ['otherType'] - }); - expect(result).to.be.true; - }); - - it('should return true value is type is otherType and format is otherFormat', function () { - const result = isTypeValue('', { - format: 'otherFormat', - type: ['otherType'] - }); - expect(result).to.be.true; - }); - - it('should return false when value is and type is boolean', function () { - const result = isTypeValue('', { - type: ['boolean'] - }); - expect(result).to.be.false; - }); - - it('should return true when value is and type is string', function () { - const result = isTypeValue('', { - type: ['string'] - }); - expect(result).to.be.true; - }); - - it('should return true when value is and type is ["boolean", "integer"]', function () { - const result = isTypeValue('', { - type: ['boolean', 'integer'] - }); - expect(result).to.be.true; - }); - - it('should return true when value is and type is integer not array', function () { - const result = isTypeValue('', { - type: 'integer' - }); - expect(result).to.be.true; - }); - - it('should return true when value is and type is integer not array format int64', function () { - const result = isTypeValue('', { - format: 'int64', - type: 'integer' - }); - expect(result).to.be.true; - }); - -}); From d82aee00d2982e8d7585f31f6c9e2def4c01230a Mon Sep 17 00:00:00 2001 From: Luis Tejeda <46000487+LuisTejedaS@users.noreply.github.com> Date: Tue, 1 Mar 2022 09:46:28 -0600 Subject: [PATCH 2/9] Fix merging errors Fix errors on merge. --- lib/common/schemaUtilsCommon.js | 4 +++- lib/deref.js | 10 +++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/common/schemaUtilsCommon.js b/lib/common/schemaUtilsCommon.js index d63b8c2..c7d95f0 100644 --- a/lib/common/schemaUtilsCommon.js +++ b/lib/common/schemaUtilsCommon.js @@ -20,7 +20,9 @@ const parse = require('../parse.js'), 'date-time': '', password: '' }, - boolean: '' + boolean: '', + array: '', + object: '' }, /* eslint-disable arrow-body-style */ diff --git a/lib/deref.js b/lib/deref.js index 9515350..b41d35d 100644 --- a/lib/deref.js +++ b/lib/deref.js @@ -5,6 +5,14 @@ const _ = require('lodash'), REQUEST: 'REQUEST', RESPONSE: 'RESPONSE' }, + SCHEMA_TYPES = { + array: 'array', + boolean: 'boolean', + integer: 'integer', + number: 'number', + object: 'object', + string: 'string' + }, // All formats supported by both ajv and json-schema-faker SUPPORTED_FORMATS = [ 'date', 'time', 'date-time', @@ -246,7 +254,7 @@ module.exports = { // Override deefault value to appropriate type representation for parameter resolution to schema if (resolveFor === 'CONVERSION' && resolveTo === 'schema') { - schema.default = type.object; + schema.default = typesMap.object; } } else if (concreteUtils.compareTypes(schema.type, SCHEMA_TYPES.array) && schema.items) { From 8927e4b528bceece25f40d9316f5897e40ac0e55 Mon Sep 17 00:00:00 2001 From: Luis Tejeda <46000487+LuisTejedaS@users.noreply.github.com> Date: Tue, 1 Mar 2022 12:12:56 -0600 Subject: [PATCH 3/9] Add test for transaction path in validation Add test for transaction path in validation --- .../invalidTypeProperty.yaml | 117 ++++++ .../validationData/invalidTypeProperty.json | 375 ++++++++++++++++++ .../validationData/invalidTypeProperty.yaml | 118 ++++++ test/unit/validator.test.js | 78 +++- 4 files changed, 669 insertions(+), 19 deletions(-) create mode 100644 test/data/31CollectionTransactions/validate30Scenarios/invalidTypeProperty.yaml create mode 100644 test/data/validationData/invalidTypeProperty.json create mode 100644 test/data/validationData/invalidTypeProperty.yaml diff --git a/test/data/31CollectionTransactions/validate30Scenarios/invalidTypeProperty.yaml b/test/data/31CollectionTransactions/validate30Scenarios/invalidTypeProperty.yaml new file mode 100644 index 0000000..3fbb827 --- /dev/null +++ b/test/data/31CollectionTransactions/validate30Scenarios/invalidTypeProperty.yaml @@ -0,0 +1,117 @@ +openapi: "3.1.0" +info: + version: 1.0.0 + title: Swagger Petstore + license: + name: MIT +servers: + - url: https://postman-echo.com/get +paths: + /pets: + get: + summary: List all pets + operationId: listPets + tags: + - pets + parameters: + - name: limit + in: query + description: How many items to return at one time (max 100) + required: false + schema: + type: integer + format: int32 + responses: + '200': + description: A paged array of pets + headers: + x-next: + description: A link to the next page of responses + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/Pets" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + post: + summary: Create a pet + operationId: createPets + tags: + - pets + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" + responses: + '201': + description: Null response + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + /pets/{petId}: + get: + summary: Info for a specific pet + operationId: showPetById + tags: + - pets + parameters: + - name: petId + in: path + required: true + description: The id of the pet to retrieve + schema: + type: string + responses: + '200': + description: Expected response to a valid request + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" +components: + schemas: + Pet: + type: object + required: + - id + - name + properties: + id: + type: integer + format: int64 + name: + type: string + tag: + type: string + format: date + Pets: + type: array + items: + $ref: "#/components/schemas/Pet" + Error: + type: object + required: + - code + - message + properties: + code: + type: integer + format: int32 + message: + type: string` diff --git a/test/data/validationData/invalidTypeProperty.json b/test/data/validationData/invalidTypeProperty.json new file mode 100644 index 0000000..e35fcc6 --- /dev/null +++ b/test/data/validationData/invalidTypeProperty.json @@ -0,0 +1,375 @@ +{ + "item": [ + { + "id": "ec09078b-8861-4367-9e9e-22f8c3d31f82", + "name": "pets", + "description": { + "content": "", + "type": "text/plain" + }, + "item": [ + { + "id": "2c8762c9-54bc-4981-8d2c-09b71fadcebc", + "name": "List all pets", + "request": { + "name": "List all pets", + "description": {}, + "url": { + "path": [ + "pets" + ], + "host": [ + "{{baseUrl}}" + ], + "query": [ + { + "disabled": false, + "key": "limit", + "value": "82526723", + "description": "How many items to return at one time (max 100)" + } + ], + "variable": [] + }, + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "auth": null + }, + "response": [ + { + "id": "2d0c2488-0d26-4029-9294-fbc5c9b8363f", + "name": "A paged array of pets", + "originalRequest": { + "url": { + "path": [ + "pets" + ], + "host": [ + "{{baseUrl}}" + ], + "query": [ + { + "key": "limit", + "value": "82526723" + } + ], + "variable": [] + }, + "method": "GET", + "body": {} + }, + "status": "OK", + "code": 200, + "header": [ + { + "disabled": false, + "description": "A link to the next page of responses", + "key": "x-next", + "value": "in nulla Lorem ex" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": "[\n {\n \"id\": 41014273,\n \"name\": \"dolor eu minim in\",\n \"tag\": \"urn:uuid:94b326e4-ed11-bd40-c87d-1a1524b6bc2f\"\n },\n {\n \"id\": -74291521,\n \"name\": \"pariatur dolor e\",\n \"tag\": \"urn:uuid:092c825a-7a1d-1adc-71da-b9dbe24c2da6\"\n }\n]", + "cookie": [], + "_postman_previewlanguage": "json" + }, + { + "id": "eb93bc02-1380-433a-9a6e-acd8875a8334", + "name": "unexpected error", + "originalRequest": { + "url": { + "path": [ + "pets" + ], + "host": [ + "{{baseUrl}}" + ], + "query": [ + { + "key": "limit", + "value": "82526723" + } + ], + "variable": [] + }, + "method": "GET", + "body": {} + }, + "status": "Internal Server Error", + "code": 500, + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": "{\n \"code\": -57418069,\n \"message\": \"non quis proident\"\n}", + "cookie": [], + "_postman_previewlanguage": "json" + } + ], + "event": [] + }, + { + "id": "87611012-e59c-40e1-90f2-24a50fbbff1d", + "name": "Create a pet", + "request": { + "name": "Create a pet", + "description": {}, + "url": { + "path": [ + "pets" + ], + "host": [ + "{{baseUrl}}" + ], + "query": [], + "variable": [] + }, + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "POST", + "auth": null, + "body": { + "mode": "raw", + "raw": "{\n \"id\": -56104459,\n \"name\": \"dolore incididunt pariatur occaecat\",\n \"tag\": \"9e6b59af-239e-b38b-74a7-f3110c87227e\"\n}", + "options": { + "raw": { + "language": "json" + } + } + } + }, + "response": [ + { + "id": "813809b9-3290-43e3-8c43-f939db815d2c", + "name": "Null response", + "originalRequest": { + "url": { + "path": [ + "pets" + ], + "host": [ + "{{baseUrl}}" + ], + "query": [], + "variable": [] + }, + "method": "POST", + "body": { + "mode": "raw", + "raw": "{\n \"id\": -56104459,\n \"name\": \"dolore incididunt pariatur occaecat\",\n \"tag\": \"9e6b59af-239e-b38b-74a7-f3110c87227e\"\n}", + "options": { + "raw": { + "language": "json" + } + } + } + }, + "status": "Created", + "code": 201, + "header": [ + { + "key": "Content-Type", + "value": "text/plain" + } + ], + "body": "", + "cookie": [], + "_postman_previewlanguage": "text" + }, + { + "id": "436ebb0e-5c7a-4cba-883e-f36089ccd5e2", + "name": "unexpected error", + "originalRequest": { + "url": { + "path": [ + "pets" + ], + "host": [ + "{{baseUrl}}" + ], + "query": [], + "variable": [] + }, + "method": "POST", + "body": { + "mode": "raw", + "raw": "{\n \"id\": -56104459,\n \"name\": \"dolore incididunt pariatur occaecat\",\n \"tag\": \"9e6b59af-239e-b38b-74a7-f3110c87227e\"\n}", + "options": { + "raw": { + "language": "json" + } + } + } + }, + "status": "Internal Server Error", + "code": 500, + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": "{\n \"code\": -57418069,\n \"message\": \"non quis proident\"\n}", + "cookie": [], + "_postman_previewlanguage": "json" + } + ], + "event": [], + "protocolProfileBehavior": { + "disableBodyPruning": true + } + }, + { + "id": "ab623305-0ccf-4edc-9210-c3ed68d4e1dc", + "name": "Info for a specific pet", + "request": { + "name": "Info for a specific pet", + "description": {}, + "url": { + "path": [ + "pets", + ":petId" + ], + "host": [ + "{{baseUrl}}" + ], + "query": [], + "variable": [ + { + "disabled": false, + "type": "any", + "value": "in nulla Lorem ex", + "key": "petId", + "description": "(Required) The id of the pet to retrieve" + } + ] + }, + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "auth": null + }, + "response": [ + { + "id": "6de63e43-7e59-4713-8893-53bdbddeddef", + "name": "Expected response to a valid request", + "originalRequest": { + "url": { + "path": [ + "pets", + ":petId" + ], + "host": [ + "{{baseUrl}}" + ], + "query": [], + "variable": [ + { + "disabled": false, + "type": "any", + "value": "in nulla Lorem ex", + "key": "petId", + "description": "(Required) The id of the pet to retrieve" + } + ] + }, + "method": "GET", + "body": {} + }, + "status": "OK", + "code": 200, + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": "{\n \"id\": -56104459,\n \"name\": \"dolore incididunt pariatur occaecat\",\n \"tag\": \"9e6b59af-239e-b38b-74a7-f3110c87227e\"\n}", + "cookie": [], + "_postman_previewlanguage": "json" + }, + { + "id": "d221e68f-af12-4042-935e-ae2709ef52e7", + "name": "unexpected error", + "originalRequest": { + "url": { + "path": [ + "pets", + ":petId" + ], + "host": [ + "{{baseUrl}}" + ], + "query": [], + "variable": [ + { + "disabled": false, + "type": "any", + "value": "in nulla Lorem ex", + "key": "petId", + "description": "(Required) The id of the pet to retrieve" + } + ] + }, + "method": "GET", + "body": {} + }, + "status": "Internal Server Error", + "code": 500, + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": "{\n \"code\": -57418069,\n \"message\": \"non quis proident\"\n}", + "cookie": [], + "_postman_previewlanguage": "json" + } + ], + "event": [] + } + ], + "event": [] + } + ], + "event": [], + "variable": [ + { + "type": "string", + "value": "https://postman-echo.com/get", + "key": "baseUrl" + } + ], + "info": { + "_postman_id": "5baba942-df02-45da-9e3d-540db4667bc4", + "name": "Swagger Petstore", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + "description": { + "content": "", + "type": "text/plain" + } + } +} diff --git a/test/data/validationData/invalidTypeProperty.yaml b/test/data/validationData/invalidTypeProperty.yaml new file mode 100644 index 0000000..e5fcef0 --- /dev/null +++ b/test/data/validationData/invalidTypeProperty.yaml @@ -0,0 +1,118 @@ + +openapi: "3.0.0" +info: + version: 1.0.0 + title: Swagger Petstore + license: + name: MIT +servers: + - url: https://postman-echo.com/get +paths: + /pets: + get: + summary: List all pets + operationId: listPets + tags: + - pets + parameters: + - name: limit + in: query + description: How many items to return at one time (max 100) + required: false + schema: + type: integer + format: int32 + responses: + '200': + description: A paged array of pets + headers: + x-next: + description: A link to the next page of responses + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/Pets" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + post: + summary: Create a pet + operationId: createPets + tags: + - pets + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" + responses: + '201': + description: Null response + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + /pets/{petId}: + get: + summary: Info for a specific pet + operationId: showPetById + tags: + - pets + parameters: + - name: petId + in: path + required: true + description: The id of the pet to retrieve + schema: + type: string + responses: + '200': + description: Expected response to a valid request + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" +components: + schemas: + Pet: + type: object + required: + - id + - name + properties: + id: + type: integer + format: int64 + name: + type: string + tag: + type: string + format: date + Pets: + type: array + items: + $ref: "#/components/schemas/Pet" + Error: + type: object + required: + - code + - message + properties: + code: + type: integer + format: int32 + message: + type: string` diff --git a/test/unit/validator.test.js b/test/unit/validator.test.js index a1f9655..73d0f3d 100644 --- a/test/unit/validator.test.js +++ b/test/unit/validator.test.js @@ -391,21 +391,21 @@ describe('The Validation option', function () { { detailedBlobValidation: true }), historyRequest = [], resultObj, - violatedKeywords = [ - 'data.items.minProperties', - 'data.items.required', - 'data.items.properties.entityId.maxLength', - 'data.items.properties.accountNumber.minLength', - 'data.items.properties.entityName.format', - 'data.items.properties.incType.enum', - 'data.items.properties.companyNumber.exclusiveMinimum', - 'data.items.properties.website.type', - 'data.items.properties.turnover.multipleOf', - 'data.items.properties.description.pattern', - 'data.items.properties.wants.uniqueItems', - 'meta.maxProperties', - 'meta.additionalProperties' - ]; + violatedKeywords = { + 'data.items.minProperties': '$.request.body.data[0]', + 'data.items.required': '$.request.body.data[0]', + 'data.items.properties.entityId.maxLength': '$.request.body.data[0].entityId', + 'data.items.properties.accountNumber.minLength': '$.request.body.data[0].accountNumber', + 'data.items.properties.entityName.format': '$.request.body.data[0].entityName', + 'data.items.properties.incType.enum': '$.request.body.data[0].incType', + 'data.items.properties.companyNumber.exclusiveMinimum': '$.request.body.data[0].companyNumber', + 'data.items.properties.website.type': '$.request.body.data[0].website', + 'data.items.properties.turnover.multipleOf': '$.request.body.data[0].turnover', + 'data.items.properties.description.pattern': '$.request.body.data[0].description', + 'data.items.properties.wants.uniqueItems': '$.request.body.data[0].wants', + 'meta.maxProperties': '$.request.body.meta', + 'meta.additionalProperties': '$.request.body.meta' + }; getAllTransactions(JSON.parse(collection), historyRequest); schemaPack.validateTransaction(historyRequest, (err, result) => { @@ -416,7 +416,8 @@ describe('The Validation option', function () { _.forEach(resultObj.mismatches, (mismatch) => { // remove starting string '$.paths[/user].post.requestBody.content[application/json].schema.properties.' let localJsonPath = mismatch.schemaJsonPath.slice(76); - expect(_.includes(violatedKeywords, localJsonPath)).to.eql(true); + expect(_.includes(_.keys(violatedKeywords), localJsonPath)).to.eql(true); + expect(_.includes(_.values(violatedKeywords), mismatch.transactionJsonPath)).to.eql(true); // mark matched path as empty to ensure repetition does'n occur violatedKeywords[_.indexOf(violatedKeywords, localJsonPath)] = ''; @@ -506,6 +507,13 @@ describe('VALIDATE FUNCTION TESTS ', function () { VALIDATION_DATA_SCENARIOS_FOLDER_31_PATH ), '/compositeSchemaSpec.yaml' + ), + invalidTypeProperty = getSpecsPathByVersion( + getFoldersByVersion( + VALIDATION_DATA_FOLDER_PATH, + VALIDATION_DATA_SCENARIOS_FOLDER_31_PATH + ), + '/invalidTypeProperty.yaml' ); emptyParameterSpecs.forEach((specData) => { @@ -749,7 +757,7 @@ describe('VALIDATE FUNCTION TESTS ', function () { ignoreUnresolvedVariables: true, validateMetadata: true, suggestAvailableFixes: true, - detailedBlobValidation: false + detailedBlobValidation: true }, schemaPack = new Converter.SchemaPack({ type: 'string', data: primitiveDataTypeBodySpec }, options); @@ -762,11 +770,13 @@ describe('VALIDATE FUNCTION TESTS ', function () { // request body is boolean resultObj = result.requests[historyRequest[0].id].endpoints[0]; expect(resultObj.mismatches).to.have.lengthOf(0); - + const responseId = _.keys(resultObj.responses)[0]; // request body is integer - responseObj = resultObj.responses[_.keys(resultObj.responses)[0]]; + responseObj = resultObj.responses[responseId]; expect(responseObj.mismatches).to.have.lengthOf(1); expect(responseObj.mismatches[0].suggestedFix.suggestedValue).to.be.within(5, 10); + expect(responseObj.mismatches[0].transactionJsonPath).to + .equal(`$.responses[${responseId}].body`); done(); }); }); @@ -961,6 +971,36 @@ describe('VALIDATE FUNCTION TESTS ', function () { }); }); }); + invalidTypeProperty.forEach((specData) => { + it('Should correctly suggest value and report transactionJsonPath on a body property with incorrect value ' + + specData.version, function (done) { + let invalidTypePropertySpec = fs.readFileSync(specData.path, 'utf-8'), + invalidTypePropertyCollection = fs.readFileSync(path.join(__dirname, VALIDATION_DATA_FOLDER_PATH + + '/invalidTypeProperty.json'), 'utf-8'), + options = { suggestAvailableFixes: true, detailedBlobValidation: true }, + resultObj, + historyRequest = [], + schemaPack = new Converter.SchemaPack({ type: 'string', data: invalidTypePropertySpec }, options); + + getAllTransactions(JSON.parse(invalidTypePropertyCollection), historyRequest); + + schemaPack.validateTransaction(historyRequest, (err, result) => { + expect(err).to.be.null; + expect(result).to.be.an('object'); + resultObj = result.requests[historyRequest[0].id].endpoints[0]; + const responseId = _.keys(resultObj.responses)[0], + responseMissmatches = resultObj.responses[responseId].mismatches; + expect(responseMissmatches).to.have.lengthOf(2); + expect(responseMissmatches[0].transactionJsonPath) + .to.equal(`$.responses[${responseId}].body[0].tag`); + expect(responseMissmatches[0].suggestedFix.key).to.equal('tag'); + expect(responseMissmatches[1].transactionJsonPath) + .to.equal(`$.responses[${responseId}].body[1].tag`); + expect(responseMissmatches[1].suggestedFix.key).to.equal('tag'); + done(); + }); + }); + }); }); describe('getPostmanUrlSuffixSchemaScore function', function () { From b0f57b76886f5959e1aa75847088177b9ae32164 Mon Sep 17 00:00:00 2001 From: Luis Tejeda <46000487+LuisTejedaS@users.noreply.github.com> Date: Thu, 3 Mar 2022 10:42:57 -0600 Subject: [PATCH 4/9] Test for checking deeper properties Test for checking deeper properties in detailed blob validation --- .../detailedBlobValidationSpec.yaml | 48 +++++++++++-------- .../detailedBlobValidationCollection.json | 2 +- .../detailedBlobValidationSpec.yaml | 6 +++ test/unit/validator.test.js | 1 + 4 files changed, 35 insertions(+), 22 deletions(-) diff --git a/test/data/31CollectionTransactions/validateOptions/detailedBlobValidationSpec.yaml b/test/data/31CollectionTransactions/validateOptions/detailedBlobValidationSpec.yaml index 9e823de..78f8685 100644 --- a/test/data/31CollectionTransactions/validateOptions/detailedBlobValidationSpec.yaml +++ b/test/data/31CollectionTransactions/validateOptions/detailedBlobValidationSpec.yaml @@ -17,33 +17,33 @@ paths: description: ID of the user required: true schema: - type: + type: - integer format: int32 requestBody: content: application/json: schema: - type: + type: - object properties: data: - type: + type: - array items: $ref: '#/components/schemas/Entity' meta: - type: + type: - object maxProperties: 1 additionalProperties: false properties: numberOfResults: - type: + type: - number format: int32 totalPages: - type: + type: - number format: int32 responses: @@ -52,7 +52,7 @@ paths: components: schemas: Entity: - type: + type: - object title: Entity description: A single and unique entity linked to a user @@ -62,42 +62,42 @@ components: id: [needThis, accountNumber] properties: entityId: - type: + type: - string maxLength: 5 accountNumber: - type: + type: - string minLength: 50 entityName: - type: + type: - string format: ipv4 entityPhone: - type: + type: - string incType: - type: + type: - string enum: ['Postman'] companyNumber: - type: + type: - number exclusiveMinimum: 10000 website: - type: + type: - number turnover: - type: + type: - integer format: int32 multipleOf: 7 description: - type: + type: - string pattern: '[abc]+' status: - type: + type: - string enum: - pending @@ -107,15 +107,21 @@ components: - tradingAccepted - tradingRejected wants: - type: + type: - array uniqueItems: true items: - type: + type: - string + user: + type: object + properties: + entityId: + type: string + maxLength: 5 isFavorite: - type: + type: - boolean needThis: - type: + type: - string diff --git a/test/data/validationData/detailedBlobValidationCollection.json b/test/data/validationData/detailedBlobValidationCollection.json index 5d6effb..514fb86 100644 --- a/test/data/validationData/detailedBlobValidationCollection.json +++ b/test/data/validationData/detailedBlobValidationCollection.json @@ -37,7 +37,7 @@ }, "body": { "mode": "raw", - "raw": "{\n \"data\": [\n {\n \"entityId\": \"5e5792b234d88e12b8511b92\",\n \"accountNumber\": \"1YNykgIi3T2NDeElON0IqcPOpPI\",\n \"entityName\": \"Farmer Freddy's Veg\",\n \"entityPhone\": \"+4420832132132\",\n \"incType\": \"sole\",\n \"companyNumber\": 10000,\n \"needThisNot\": \"hello\",\n \"website\": \"https://farmer-freddy.null\",\n \"turnover\": 10000,\n \"description\": \"def\",\n \"status\": \"tradingAccepted\",\n \"wants\": [\n \"carpentry\",\n \"beer\",\n \"beer\"\n ],\n \"isFavorite\": true\n }\n ],\n \"meta\": {\n \"notNeeded\": 1,\n \"numberOfResults\": 1,\n \"totalPages\": 1\n }\n}" + "raw": "{\n \"data\": [\n {\n \"entityId\": \"5e5792b234d88e12b8511b92\",\n \"accountNumber\": \"1YNykgIi3T2NDeElON0IqcPOpPI\",\n \"entityName\": \"Farmer Freddy's Veg\",\n \"entityPhone\": \"+4420832132132\",\n \"incType\": \"sole\",\n \"companyNumber\": 10000,\n \"needThisNot\": \"hello\",\n \"website\": \"https://farmer-freddy.null\",\n \"turnover\": 10000,\n \"description\": \"def\",\n \"status\": \"tradingAccepted\",\n \"wants\": [\n \"carpentry\",\n \"beer\",\n \"beer\"\n ],\n \"user\": {\"entityId\": \"5e5792b234d88e12b8511b92\"},\n \"isFavorite\": true\n }\n ],\n \"meta\": {\n \"notNeeded\": 1,\n \"numberOfResults\": 1,\n \"totalPages\": 1\n }\n}" } }, "response": [ diff --git a/test/data/validationData/detailedBlobValidationSpec.yaml b/test/data/validationData/detailedBlobValidationSpec.yaml index 39161e2..007cab9 100644 --- a/test/data/validationData/detailedBlobValidationSpec.yaml +++ b/test/data/validationData/detailedBlobValidationSpec.yaml @@ -94,6 +94,12 @@ components: uniqueItems: true items: type: string + user: + type: object + properties: + entityId: + type: string + maxLength: 5 isFavorite: type: boolean needThis: diff --git a/test/unit/validator.test.js b/test/unit/validator.test.js index 73d0f3d..3970a5a 100644 --- a/test/unit/validator.test.js +++ b/test/unit/validator.test.js @@ -403,6 +403,7 @@ describe('The Validation option', function () { 'data.items.properties.turnover.multipleOf': '$.request.body.data[0].turnover', 'data.items.properties.description.pattern': '$.request.body.data[0].description', 'data.items.properties.wants.uniqueItems': '$.request.body.data[0].wants', + 'data.items.properties.user.properties.entityId.maxLength': '$.request.body.data[0].user.entityId', 'meta.maxProperties': '$.request.body.meta', 'meta.additionalProperties': '$.request.body.meta' }; From 3442d824b690e729423922c9307ce963088f84ff Mon Sep 17 00:00:00 2001 From: Luis Tejeda <46000487+LuisTejedaS@users.noreply.github.com> Date: Fri, 4 Mar 2022 14:49:02 -0600 Subject: [PATCH 5/9] test for test for --- .../validate30Scenarios/longScenarios.yaml | 97 ++++++++++ test/unit/validator.test.js | 169 ++++++++++++++++++ 2 files changed, 266 insertions(+) create mode 100644 test/data/31CollectionTransactions/validate30Scenarios/longScenarios.yaml diff --git a/test/data/31CollectionTransactions/validate30Scenarios/longScenarios.yaml b/test/data/31CollectionTransactions/validate30Scenarios/longScenarios.yaml new file mode 100644 index 0000000..7e7cc22 --- /dev/null +++ b/test/data/31CollectionTransactions/validate30Scenarios/longScenarios.yaml @@ -0,0 +1,97 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Swagger Petstore + license: + name: MIT +servers: + - url: http://petstore.swagger.io/v1 +paths: + /pets: + get: + summary: List all pets + operationId: listPets + tags: + - pets + parameters: + - name: limit + in: query + description: How many items to return at one time (max 100) + required: false + schema: + type: integer + format: int32 + responses: + '200': + description: A paged array of pets + headers: + x-next: + description: A link to the next page of responses + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/Pets" + post: + summary: Create a pet + operationId: createPets + tags: + - pets + responses: + '201': + description: Null response + content: + application/json: + schema: + $ref: "#/components/schemas/Pets" + /pets/{petId}: + get: + summary: Info for a specific pet + operationId: showPetById + tags: + - pets + parameters: + - name: petId + in: path + required: true + description: The id of the pet to retrieve + schema: + type: string + responses: + '200': + description: Expected response to a valid request + content: + application/json: + schema: + $ref: "#/components/schemas/Pets" +components: + schemas: + Pet: + type: object + required: + - id + - name + properties: + id: + type: integer + format: int64 + name: + type: string + tag: + type: string + Pets: + type: array + items: + $ref: "#/components/schemas/Pet" + Error: + type: object + required: + - code + - message + properties: + code: + type: integer + format: int32 + message: + type: string diff --git a/test/unit/validator.test.js b/test/unit/validator.test.js index 3970a5a..ed25185 100644 --- a/test/unit/validator.test.js +++ b/test/unit/validator.test.js @@ -63,6 +63,175 @@ function getFoldersByVersion(folder30Path, folder31Path) { }]; } + +describe('Validation with different resolution parameters options', function () { + + it('Should validate correctly with request and example parameters as Schema', function () { + let fileData = fs.readFileSync(path.join(__dirname, VALIDATION_DATA_SCENARIOS_FOLDER_31_PATH, + '/longScenarios.yaml'), 'utf8'), + expectedBody = + '[{"id":"","name":"","tag":""},{"id":"","name":"","tag":""}]', + options = { + requestParametersResolution: 'Schema', + exampleParametersResolution: 'Schema', + showMissingInSchemaErrors: true, + strictRequestMatching: true, + ignoreUnresolvedVariables: true, + validateMetadata: true, + suggestAvailableFixes: true, + detailedBlobValidation: true + }, + schemaPack = new Converter.SchemaPack({ type: 'string', data: fileData }, options); + schemaPack.convert((err, conversionResult) => { + expect(err).to.be.null; + expect(conversionResult.result).to.equal(true); + + let historyRequest = []; + + getAllTransactions(conversionResult.output[0].data, historyRequest); + + historyRequest.forEach((request) => { + const fixedBody = request.response[0].body.replace(/\s/g, ''); + expect(fixedBody).to.equal(expectedBody); + }); + + schemaPack.validateTransaction(historyRequest, (err, result) => { + expect(err).to.be.null; + expect(result).to.be.an('object'); + let requestIds = Object.keys(result.requests); + expect(err).to.be.null; + requestIds.forEach((requestId) => { + expect(result.requests[requestId].endpoints[0].matched).to.be.true; + const responsesIds = Object.keys(result.requests[requestId].endpoints[0].responses); + responsesIds.forEach((responseId) => { + expect(result.requests[requestId].endpoints[0].responses[responseId].matched).to.be.true; + }); + }); + }); + }); + }); + + it('Should validate correctly with request and example parameters as Example', function () { + let fileData = fs.readFileSync(path.join(__dirname, VALIDATION_DATA_SCENARIOS_FOLDER_31_PATH, + '/longScenarios.yaml'), 'utf8'), + options = { + requestParametersResolution: 'Example', + exampleParametersResolution: 'Example', + showMissingInSchemaErrors: true, + strictRequestMatching: true, + ignoreUnresolvedVariables: true, + validateMetadata: true, + suggestAvailableFixes: true, + detailedBlobValidation: true + }, + schemaPack = new Converter.SchemaPack({ type: 'string', data: fileData }, options); + schemaPack.convert((err, conversionResult) => { + expect(err).to.be.null; + expect(conversionResult.result).to.equal(true); + + let historyRequest = []; + + getAllTransactions(conversionResult.output[0].data, historyRequest); + + schemaPack.validateTransaction(historyRequest, (err, result) => { + expect(err).to.be.null; + expect(result).to.be.an('object'); + let requestIds = Object.keys(result.requests); + expect(err).to.be.null; + requestIds.forEach((requestId) => { + expect(result.requests[requestId].endpoints[0].matched).to.be.true; + const responsesIds = Object.keys(result.requests[requestId].endpoints[0].responses); + responsesIds.forEach((responseId) => { + expect(result.requests[requestId].endpoints[0].responses[responseId].matched).to.be.true; + }); + }); + }); + }); + }); + + it('Should validate correctly with request params as Schema and example parameters as Example', function () { + let fileData = fs.readFileSync(path.join(__dirname, VALIDATION_DATA_SCENARIOS_FOLDER_31_PATH, + '/longScenarios.yaml'), 'utf8'), + options = { + requestParametersResolution: 'Schema', + exampleParametersResolution: 'Example', + showMissingInSchemaErrors: true, + strictRequestMatching: true, + ignoreUnresolvedVariables: true, + validateMetadata: true, + suggestAvailableFixes: true, + detailedBlobValidation: false + }, + schemaPack = new Converter.SchemaPack({ type: 'string', data: fileData }, options); + schemaPack.convert((err, conversionResult) => { + expect(err).to.be.null; + expect(conversionResult.result).to.equal(true); + + let historyRequest = []; + + getAllTransactions(conversionResult.output[0].data, historyRequest); + + schemaPack.validateTransaction(historyRequest, (err, result) => { + expect(err).to.be.null; + expect(result).to.be.an('object'); + let requestIds = Object.keys(result.requests); + expect(err).to.be.null; + requestIds.forEach((requestId) => { + expect(result.requests[requestId].endpoints[0].matched).to.be.true; + const responsesIds = Object.keys(result.requests[requestId].endpoints[0].responses); + responsesIds.forEach((responseId) => { + expect(result.requests[requestId].endpoints[0].responses[responseId].matched).to.be.true; + }); + }); + }); + }); + }); + + it('Should validate correctly with request params as Example and example params as Schema', function () { + let fileData = fs.readFileSync(path.join(__dirname, VALIDATION_DATA_SCENARIOS_FOLDER_31_PATH, + '/longScenarios.yaml'), 'utf8'), + expectedBody = + '[{"id":"","name":"","tag":""},{"id":"","name":"","tag":""}]', + options = { + requestParametersResolution: 'Example', + exampleParametersResolution: 'Schema', + showMissingInSchemaErrors: true, + strictRequestMatching: true, + ignoreUnresolvedVariables: true, + validateMetadata: true, + suggestAvailableFixes: true, + detailedBlobValidation: false + }, + schemaPack = new Converter.SchemaPack({ type: 'string', data: fileData }, options); + schemaPack.convert((err, conversionResult) => { + expect(err).to.be.null; + expect(conversionResult.result).to.equal(true); + + let historyRequest = []; + + getAllTransactions(conversionResult.output[0].data, historyRequest); + + historyRequest.forEach((request) => { + const fixedBody = request.response[0].body.replace(/\s/g, ''); + expect(fixedBody).to.equal(expectedBody); + }); + + schemaPack.validateTransaction(historyRequest, (err, result) => { + expect(err).to.be.null; + expect(result).to.be.an('object'); + let requestIds = Object.keys(result.requests); + expect(err).to.be.null; + requestIds.forEach((requestId) => { + expect(result.requests[requestId].endpoints[0].matched).to.be.true; + const responsesIds = Object.keys(result.requests[requestId].endpoints[0].responses); + responsesIds.forEach((responseId) => { + expect(result.requests[requestId].endpoints[0].responses[responseId].matched).to.be.true; + }); + }); + }); + }); + }); +}); describe('The validator must validate generated collection from schema against schema itself', function () { var validOpenapiFolder = fs.readdirSync(path.join(__dirname, VALID_OPENAPI_FOLDER_PATH)), suggestedFixProps = ['key', 'actualValue', 'suggestedValue'], From a22700bc96d3c738d94c5f3c6ec1c1fe6ded0538 Mon Sep 17 00:00:00 2001 From: Luis Tejeda <46000487+LuisTejedaS@users.noreply.github.com> Date: Fri, 4 Mar 2022 17:43:12 -0600 Subject: [PATCH 6/9] Add support for int64 format Add support for int 64 format --- lib/deref.js | 3 +- .../issue#479_2.yaml} | 0 test/data/valid_openapi/issue#479_3.yaml | 60 ++++++++++++++++++ test/unit/deref.test.js | 10 +-- test/unit/validator.test.js | 61 ++++++++++++++++--- 5 files changed, 120 insertions(+), 14 deletions(-) rename test/data/{31CollectionTransactions/validate30Scenarios/longScenarios.yaml => valid_openapi/issue#479_2.yaml} (100%) create mode 100644 test/data/valid_openapi/issue#479_3.yaml diff --git a/lib/deref.js b/lib/deref.js index b41d35d..1e714be 100644 --- a/lib/deref.js +++ b/lib/deref.js @@ -22,7 +22,8 @@ const _ = require('lodash'), 'ipv4', 'ipv6', 'regex', 'uuid', - 'json-pointer' + 'json-pointer', + 'int64' ], DEFAULT_SCHEMA_UTILS = require('./30XUtils/schemaUtils30X'); diff --git a/test/data/31CollectionTransactions/validate30Scenarios/longScenarios.yaml b/test/data/valid_openapi/issue#479_2.yaml similarity index 100% rename from test/data/31CollectionTransactions/validate30Scenarios/longScenarios.yaml rename to test/data/valid_openapi/issue#479_2.yaml diff --git a/test/data/valid_openapi/issue#479_3.yaml b/test/data/valid_openapi/issue#479_3.yaml new file mode 100644 index 0000000..9ba75c9 --- /dev/null +++ b/test/data/valid_openapi/issue#479_3.yaml @@ -0,0 +1,60 @@ +openapi: 3.1.0 +info: + version: 1 + title: test for types +servers: + - url: 'http://localhost:3000' +paths: + /user: + post: + summary: 'Sample endpoint: Returns details about a particular user' + operationId: listUser + tags: + - user + parameters: + - name: id + in: query + description: ID of the user + required: true + schema: + type: + - integer + format: int32 + requestBody: + content: + application/json: + schema: + type: + - object + properties: + data: + type: + - array + items: + $ref: '#/components/schemas/Entity' + responses: + 200: + description: OK +components: + schemas: + Entity: + type: + - object + title: Entity + description: A single and unique entity linked to a user + properties: + entityId: + type: + - string + user: + type: object + properties: + id: + type: integer + format: int64 + isFavorite: + type: integer + format: int32 + needThis: + type: + - string diff --git a/test/unit/deref.test.js b/test/unit/deref.test.js index d1a86db..e37d4a8 100644 --- a/test/unit/deref.test.js +++ b/test/unit/deref.test.js @@ -132,13 +132,13 @@ describe('DEREF FUNCTION TESTS ', function() { expect(output).to.deep.include({ type: 'object', required: ['id'], - properties: { id: { default: '', type: 'integer' } } }); + properties: { id: { default: '', format: 'int64', type: 'integer' } } }); expect(output_validation).to.deep.include({ anyOf: [ { type: 'object', required: ['id'], description: 'Schema 2', - properties: { id: { type: 'integer' } } + properties: { id: { format: 'int64', type: 'integer' } } }, { type: 'object', properties: { emailField: { type: 'string', format: 'email' } } @@ -147,7 +147,7 @@ describe('DEREF FUNCTION TESTS ', function() { expect(output_withdot).to.deep.include({ type: 'object', required: ['id'], - properties: { id: { default: '', type: 'integer' } } }); + properties: { id: { default: '', format: 'int64', type: 'integer' } } }); expect(output_customFormat).to.deep.include({ type: 'object', properties: { emailField: { default: '', format: 'email', type: 'string' } } }); @@ -156,7 +156,7 @@ describe('DEREF FUNCTION TESTS ', function() { type: 'object', description: 'Schema 2', properties: { - id: { default: '', type: 'integer' }, + id: { default: '', format: 'int64', type: 'integer' }, test_prop: { default: '', type: 'string' } } }); @@ -201,7 +201,7 @@ describe('DEREF FUNCTION TESTS ', function() { ], nonSupportedFormats = [ { type: 'integer', format: 'int32' }, - { type: 'integer', format: 'int64' }, + // { type: 'integer', format: 'int64' }, { type: 'number', format: 'float' }, { type: 'number', format: 'double' }, { type: 'string', format: 'byte' }, diff --git a/test/unit/validator.test.js b/test/unit/validator.test.js index ed25185..09a172c 100644 --- a/test/unit/validator.test.js +++ b/test/unit/validator.test.js @@ -67,8 +67,8 @@ function getFoldersByVersion(folder30Path, folder31Path) { describe('Validation with different resolution parameters options', function () { it('Should validate correctly with request and example parameters as Schema', function () { - let fileData = fs.readFileSync(path.join(__dirname, VALIDATION_DATA_SCENARIOS_FOLDER_31_PATH, - '/longScenarios.yaml'), 'utf8'), + let fileData = fs.readFileSync(path.join(__dirname, VALID_OPENAPI_FOLDER_PATH, + '/issue#479_2.yaml'), 'utf8'), expectedBody = '[{"id":"","name":"","tag":""},{"id":"","name":"","tag":""}]', options = { @@ -112,8 +112,8 @@ describe('Validation with different resolution parameters options', function () }); it('Should validate correctly with request and example parameters as Example', function () { - let fileData = fs.readFileSync(path.join(__dirname, VALIDATION_DATA_SCENARIOS_FOLDER_31_PATH, - '/longScenarios.yaml'), 'utf8'), + let fileData = fs.readFileSync(path.join(__dirname, VALID_OPENAPI_FOLDER_PATH, + '/issue#479_2.yaml'), 'utf8'), options = { requestParametersResolution: 'Example', exampleParametersResolution: 'Example', @@ -150,8 +150,8 @@ describe('Validation with different resolution parameters options', function () }); it('Should validate correctly with request params as Schema and example parameters as Example', function () { - let fileData = fs.readFileSync(path.join(__dirname, VALIDATION_DATA_SCENARIOS_FOLDER_31_PATH, - '/longScenarios.yaml'), 'utf8'), + let fileData = fs.readFileSync(path.join(__dirname, VALID_OPENAPI_FOLDER_PATH, + '/issue#479_2.yaml'), 'utf8'), options = { requestParametersResolution: 'Schema', exampleParametersResolution: 'Example', @@ -188,8 +188,8 @@ describe('Validation with different resolution parameters options', function () }); it('Should validate correctly with request params as Example and example params as Schema', function () { - let fileData = fs.readFileSync(path.join(__dirname, VALIDATION_DATA_SCENARIOS_FOLDER_31_PATH, - '/longScenarios.yaml'), 'utf8'), + let fileData = fs.readFileSync(path.join(__dirname, VALID_OPENAPI_FOLDER_PATH, + '/issue#479_2.yaml'), 'utf8'), expectedBody = '[{"id":"","name":"","tag":""},{"id":"","name":"","tag":""}]', options = { @@ -231,6 +231,51 @@ describe('Validation with different resolution parameters options', function () }); }); }); + + it('Should validate correctly example 2 with request and example parameters as Schema', function () { + let fileData = fs.readFileSync(path.join(__dirname, VALID_OPENAPI_FOLDER_PATH, + '/issue#479_3.yaml'), 'utf8'), + expectedBody = + '{"data":[{"entityId":"","user":{"id":""},"isFavorite":"","needThis":""},' + + '{"entityId":"","user":{"id":""},"isFavorite":"","needThis":""}]}', + options = { + requestParametersResolution: 'Schema', + exampleParametersResolution: 'Schema', + showMissingInSchemaErrors: true, + strictRequestMatching: true, + ignoreUnresolvedVariables: true, + validateMetadata: true, + suggestAvailableFixes: true, + detailedBlobValidation: true + }, + schemaPack = new Converter.SchemaPack({ type: 'string', data: fileData }, options); + schemaPack.convert((err, conversionResult) => { + expect(err).to.be.null; + expect(conversionResult.result).to.equal(true); + + let historyRequest = []; + + getAllTransactions(conversionResult.output[0].data, historyRequest); + + const fixedBody = historyRequest[0].request.body.raw.replace(/\s/g, ''); + expect(fixedBody).to.equal(expectedBody); + + schemaPack.validateTransaction(historyRequest, (err, result) => { + expect(err).to.be.null; + expect(result).to.be.an('object'); + let requestIds = Object.keys(result.requests); + expect(err).to.be.null; + requestIds.forEach((requestId) => { + expect(result.requests[requestId].endpoints[0].matched).to.be.true; + const responsesIds = Object.keys(result.requests[requestId].endpoints[0].responses); + responsesIds.forEach((responseId) => { + expect(result.requests[requestId].endpoints[0].responses[responseId].matched).to.be.true; + }); + }); + }); + }); + }); + }); describe('The validator must validate generated collection from schema against schema itself', function () { var validOpenapiFolder = fs.readdirSync(path.join(__dirname, VALID_OPENAPI_FOLDER_PATH)), From aec94b7f0348486c2f84a16ed9f0db869e72cb85 Mon Sep 17 00:00:00 2001 From: Luis Tejeda <46000487+LuisTejedaS@users.noreply.github.com> Date: Mon, 7 Mar 2022 10:07:30 -0600 Subject: [PATCH 7/9] Add testing scenarios and simplify into one file Add testing scenarios and simplify into one file (compose object in prams) --- test/data/valid_openapi/issue#479_2.yaml | 113 +++++------ test/data/valid_openapi/issue#479_3.yaml | 60 ------ test/unit/validator.test.js | 229 ++++++++++------------- 3 files changed, 148 insertions(+), 254 deletions(-) delete mode 100644 test/data/valid_openapi/issue#479_3.yaml diff --git a/test/data/valid_openapi/issue#479_2.yaml b/test/data/valid_openapi/issue#479_2.yaml index 7e7cc22..78a12e4 100644 --- a/test/data/valid_openapi/issue#479_2.yaml +++ b/test/data/valid_openapi/issue#479_2.yaml @@ -1,63 +1,37 @@ -openapi: "3.0.0" +openapi: 3.1.0 info: - version: 1.0.0 - title: Swagger Petstore - license: - name: MIT + version: 1 + title: test for types servers: - - url: http://petstore.swagger.io/v1 + - url: 'http://localhost:3000' paths: - /pets: - get: - summary: List all pets - operationId: listPets - tags: - - pets - parameters: - - name: limit - in: query - description: How many items to return at one time (max 100) - required: false - schema: - type: integer - format: int32 - responses: - '200': - description: A paged array of pets - headers: - x-next: - description: A link to the next page of responses - schema: - type: string - content: - application/json: - schema: - $ref: "#/components/schemas/Pets" + /user: post: - summary: Create a pet - operationId: createPets + summary: 'Sample endpoint: Returns details about a particular user' + operationId: listUser tags: - - pets - responses: - '201': - description: Null response - content: - application/json: - schema: - $ref: "#/components/schemas/Pets" - /pets/{petId}: - get: - summary: Info for a specific pet - operationId: showPetById - tags: - - pets + - user parameters: - - name: petId - in: path + - name: id + in: query + description: ID of the user required: true - description: The id of the pet to retrieve schema: - type: string + type: + - integer + format: int32 + requestBody: + content: + application/json: + schema: + type: + - object + properties: + data: + type: + - array + items: + $ref: '#/components/schemas/Entity' responses: '200': description: Expected response to a valid request @@ -67,6 +41,30 @@ paths: $ref: "#/components/schemas/Pets" components: schemas: + Entity: + type: + - object + title: Entity + description: A single and unique entity linked to a user + properties: + entityId: + type: + - string + user: + type: object + properties: + id: + type: integer + format: int64 + age: + type: integer + format: int32 + isFavorite: + type: integer + format: int32 + needThis: + type: + - string Pet: type: object required: @@ -84,14 +82,3 @@ components: type: array items: $ref: "#/components/schemas/Pet" - Error: - type: object - required: - - code - - message - properties: - code: - type: integer - format: int32 - message: - type: string diff --git a/test/data/valid_openapi/issue#479_3.yaml b/test/data/valid_openapi/issue#479_3.yaml deleted file mode 100644 index 9ba75c9..0000000 --- a/test/data/valid_openapi/issue#479_3.yaml +++ /dev/null @@ -1,60 +0,0 @@ -openapi: 3.1.0 -info: - version: 1 - title: test for types -servers: - - url: 'http://localhost:3000' -paths: - /user: - post: - summary: 'Sample endpoint: Returns details about a particular user' - operationId: listUser - tags: - - user - parameters: - - name: id - in: query - description: ID of the user - required: true - schema: - type: - - integer - format: int32 - requestBody: - content: - application/json: - schema: - type: - - object - properties: - data: - type: - - array - items: - $ref: '#/components/schemas/Entity' - responses: - 200: - description: OK -components: - schemas: - Entity: - type: - - object - title: Entity - description: A single and unique entity linked to a user - properties: - entityId: - type: - - string - user: - type: object - properties: - id: - type: integer - format: int64 - isFavorite: - type: integer - format: int32 - needThis: - type: - - string diff --git a/test/unit/validator.test.js b/test/unit/validator.test.js index 09a172c..7f906e8 100644 --- a/test/unit/validator.test.js +++ b/test/unit/validator.test.js @@ -65,11 +65,15 @@ function getFoldersByVersion(folder30Path, folder31Path) { describe('Validation with different resolution parameters options', function () { - it('Should validate correctly with request and example parameters as Schema', function () { let fileData = fs.readFileSync(path.join(__dirname, VALID_OPENAPI_FOLDER_PATH, '/issue#479_2.yaml'), 'utf8'), - expectedBody = + expectedRequestBody = + '{"data":[{"entityId":"","user":{"id":"","age":""},' + + '"isFavorite":"","needThis":""},' + + '{"entityId":"","user":{"id":"","age":""},' + + '"isFavorite":"","needThis":""}]}', + expectedResponseBody = '[{"id":"","name":"","tag":""},{"id":"","name":"","tag":""}]', options = { requestParametersResolution: 'Schema', @@ -90,10 +94,99 @@ describe('Validation with different resolution parameters options', function () getAllTransactions(conversionResult.output[0].data, historyRequest); - historyRequest.forEach((request) => { - const fixedBody = request.response[0].body.replace(/\s/g, ''); - expect(fixedBody).to.equal(expectedBody); + const fixedResponseBody = historyRequest[0].response[0].body.replace(/\s/g, ''), + fixedRequestBody = historyRequest[0].request.body.raw.replace(/\s/g, ''); + expect(fixedResponseBody).to.equal(expectedResponseBody); + expect(fixedRequestBody).to.equal(expectedRequestBody); + + schemaPack.validateTransaction(historyRequest, (err, result) => { + expect(err).to.be.null; + expect(result).to.be.an('object'); + let requestIds = Object.keys(result.requests); + expect(err).to.be.null; + requestIds.forEach((requestId) => { + expect(result.requests[requestId].endpoints[0].matched).to.be.true; + const responsesIds = Object.keys(result.requests[requestId].endpoints[0].responses); + responsesIds.forEach((responseId) => { + expect(result.requests[requestId].endpoints[0].responses[responseId].matched).to.be.true; + }); + }); }); + }); + }); + + it('Should validate correctly with request as schema and example parameters as Example', function () { + let fileData = fs.readFileSync(path.join(__dirname, VALID_OPENAPI_FOLDER_PATH, + '/issue#479_2.yaml'), 'utf8'), + expectedBody = + '{"data":[{"entityId":"","user":{"id":"","age":""},' + + '"isFavorite":"","needThis":""},' + + '{"entityId":"","user":{"id":"","age":""},' + + '"isFavorite":"","needThis":""}]}', + options = { + requestParametersResolution: 'Schema', + exampleParametersResolution: 'Example', + showMissingInSchemaErrors: true, + strictRequestMatching: true, + ignoreUnresolvedVariables: true, + validateMetadata: true, + suggestAvailableFixes: true, + detailedBlobValidation: true + }, + schemaPack = new Converter.SchemaPack({ type: 'string', data: fileData }, options); + schemaPack.convert((err, conversionResult) => { + expect(err).to.be.null; + expect(conversionResult.result).to.equal(true); + + let historyRequest = []; + + getAllTransactions(conversionResult.output[0].data, historyRequest); + + const fixedBody = historyRequest[0].request.body.raw.replace(/\s/g, ''); + expect(fixedBody).to.equal(expectedBody); + + schemaPack.validateTransaction(historyRequest, (err, result) => { + expect(err).to.be.null; + expect(result).to.be.an('object'); + let requestIds = Object.keys(result.requests); + expect(err).to.be.null; + requestIds.forEach((requestId) => { + expect(result.requests[requestId].endpoints[0].matched).to.be.true; + const responsesIds = Object.keys(result.requests[requestId].endpoints[0].responses); + responsesIds.forEach((responseId) => { + expect(result.requests[requestId].endpoints[0].responses[responseId].matched).to.be.true; + }); + }); + }); + }); + }); + + it('Should validate correctly with request as Example and example parameters as Schema', function () { + let fileData = fs.readFileSync(path.join(__dirname, VALID_OPENAPI_FOLDER_PATH, + '/issue#479_2.yaml'), 'utf8'), + expectedResponseBody = + '[{"id":"","name":"","tag":""},{"id":"","name":"","tag":""}]', + options = { + requestParametersResolution: 'Example', + exampleParametersResolution: 'Schema', + showMissingInSchemaErrors: true, + strictRequestMatching: true, + ignoreUnresolvedVariables: true, + validateMetadata: true, + suggestAvailableFixes: true, + detailedBlobValidation: true + }, + schemaPack = new Converter.SchemaPack({ type: 'string', data: fileData }, options); + schemaPack.convert((err, conversionResult) => { + expect(err).to.be.null; + expect(conversionResult.result).to.equal(true); + + let historyRequest = []; + + getAllTransactions(conversionResult.output[0].data, historyRequest); + + const fixedResponseBody = historyRequest[0].response[0].body.replace(/\s/g, ''); + expect(fixedResponseBody).to.equal(expectedResponseBody); schemaPack.validateTransaction(historyRequest, (err, result) => { expect(err).to.be.null; @@ -149,132 +242,6 @@ describe('Validation with different resolution parameters options', function () }); }); - it('Should validate correctly with request params as Schema and example parameters as Example', function () { - let fileData = fs.readFileSync(path.join(__dirname, VALID_OPENAPI_FOLDER_PATH, - '/issue#479_2.yaml'), 'utf8'), - options = { - requestParametersResolution: 'Schema', - exampleParametersResolution: 'Example', - showMissingInSchemaErrors: true, - strictRequestMatching: true, - ignoreUnresolvedVariables: true, - validateMetadata: true, - suggestAvailableFixes: true, - detailedBlobValidation: false - }, - schemaPack = new Converter.SchemaPack({ type: 'string', data: fileData }, options); - schemaPack.convert((err, conversionResult) => { - expect(err).to.be.null; - expect(conversionResult.result).to.equal(true); - - let historyRequest = []; - - getAllTransactions(conversionResult.output[0].data, historyRequest); - - schemaPack.validateTransaction(historyRequest, (err, result) => { - expect(err).to.be.null; - expect(result).to.be.an('object'); - let requestIds = Object.keys(result.requests); - expect(err).to.be.null; - requestIds.forEach((requestId) => { - expect(result.requests[requestId].endpoints[0].matched).to.be.true; - const responsesIds = Object.keys(result.requests[requestId].endpoints[0].responses); - responsesIds.forEach((responseId) => { - expect(result.requests[requestId].endpoints[0].responses[responseId].matched).to.be.true; - }); - }); - }); - }); - }); - - it('Should validate correctly with request params as Example and example params as Schema', function () { - let fileData = fs.readFileSync(path.join(__dirname, VALID_OPENAPI_FOLDER_PATH, - '/issue#479_2.yaml'), 'utf8'), - expectedBody = - '[{"id":"","name":"","tag":""},{"id":"","name":"","tag":""}]', - options = { - requestParametersResolution: 'Example', - exampleParametersResolution: 'Schema', - showMissingInSchemaErrors: true, - strictRequestMatching: true, - ignoreUnresolvedVariables: true, - validateMetadata: true, - suggestAvailableFixes: true, - detailedBlobValidation: false - }, - schemaPack = new Converter.SchemaPack({ type: 'string', data: fileData }, options); - schemaPack.convert((err, conversionResult) => { - expect(err).to.be.null; - expect(conversionResult.result).to.equal(true); - - let historyRequest = []; - - getAllTransactions(conversionResult.output[0].data, historyRequest); - - historyRequest.forEach((request) => { - const fixedBody = request.response[0].body.replace(/\s/g, ''); - expect(fixedBody).to.equal(expectedBody); - }); - - schemaPack.validateTransaction(historyRequest, (err, result) => { - expect(err).to.be.null; - expect(result).to.be.an('object'); - let requestIds = Object.keys(result.requests); - expect(err).to.be.null; - requestIds.forEach((requestId) => { - expect(result.requests[requestId].endpoints[0].matched).to.be.true; - const responsesIds = Object.keys(result.requests[requestId].endpoints[0].responses); - responsesIds.forEach((responseId) => { - expect(result.requests[requestId].endpoints[0].responses[responseId].matched).to.be.true; - }); - }); - }); - }); - }); - - it('Should validate correctly example 2 with request and example parameters as Schema', function () { - let fileData = fs.readFileSync(path.join(__dirname, VALID_OPENAPI_FOLDER_PATH, - '/issue#479_3.yaml'), 'utf8'), - expectedBody = - '{"data":[{"entityId":"","user":{"id":""},"isFavorite":"","needThis":""},' + - '{"entityId":"","user":{"id":""},"isFavorite":"","needThis":""}]}', - options = { - requestParametersResolution: 'Schema', - exampleParametersResolution: 'Schema', - showMissingInSchemaErrors: true, - strictRequestMatching: true, - ignoreUnresolvedVariables: true, - validateMetadata: true, - suggestAvailableFixes: true, - detailedBlobValidation: true - }, - schemaPack = new Converter.SchemaPack({ type: 'string', data: fileData }, options); - schemaPack.convert((err, conversionResult) => { - expect(err).to.be.null; - expect(conversionResult.result).to.equal(true); - - let historyRequest = []; - - getAllTransactions(conversionResult.output[0].data, historyRequest); - - const fixedBody = historyRequest[0].request.body.raw.replace(/\s/g, ''); - expect(fixedBody).to.equal(expectedBody); - - schemaPack.validateTransaction(historyRequest, (err, result) => { - expect(err).to.be.null; - expect(result).to.be.an('object'); - let requestIds = Object.keys(result.requests); - expect(err).to.be.null; - requestIds.forEach((requestId) => { - expect(result.requests[requestId].endpoints[0].matched).to.be.true; - const responsesIds = Object.keys(result.requests[requestId].endpoints[0].responses); - responsesIds.forEach((responseId) => { - expect(result.requests[requestId].endpoints[0].responses[responseId].matched).to.be.true; - }); - }); - }); - }); - }); }); describe('The validator must validate generated collection from schema against schema itself', function () { From 1d5b475cfa7e5c5980ec33af059127e6b30ce658 Mon Sep 17 00:00:00 2001 From: Luis Tejeda <46000487+LuisTejedaS@users.noreply.github.com> Date: Mon, 7 Mar 2022 10:26:31 -0600 Subject: [PATCH 8/9] Update deref.test.js remove commented line --- test/unit/deref.test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/unit/deref.test.js b/test/unit/deref.test.js index e37d4a8..8aac8b2 100644 --- a/test/unit/deref.test.js +++ b/test/unit/deref.test.js @@ -201,7 +201,6 @@ describe('DEREF FUNCTION TESTS ', function() { ], nonSupportedFormats = [ { type: 'integer', format: 'int32' }, - // { type: 'integer', format: 'int64' }, { type: 'number', format: 'float' }, { type: 'number', format: 'double' }, { type: 'string', format: 'byte' }, From adde11a1d5744e0745396df0d5cf7dba3d22c061 Mon Sep 17 00:00:00 2001 From: Luis Tejeda <46000487+LuisTejedaS@users.noreply.github.com> Date: Wed, 9 Mar 2022 13:02:12 -0600 Subject: [PATCH 9/9] add format scenarios added support for: date-time, date, float, double, and verification for byte, binary and password --- lib/ajValidation/ajvValidation.js | 2 +- lib/common/schemaUtilsCommon.js | 10 +++++--- lib/deref.js | 4 ++- test/data/valid_openapi/issue#479_2.yaml | 24 ++++++++++++++++++ test/unit/deref.test.js | 2 -- test/unit/schemaUtilsCommon.test.js | 31 ++++++++++++++++++++++++ test/unit/validator.test.js | 22 ++++++++++++----- 7 files changed, 81 insertions(+), 14 deletions(-) diff --git a/lib/ajValidation/ajvValidation.js b/lib/ajValidation/ajvValidation.js index 309bd2b..5768bad 100644 --- a/lib/ajValidation/ajvValidation.js +++ b/lib/ajValidation/ajvValidation.js @@ -100,7 +100,7 @@ function validateSchema (schema, valueToUse, options = {}, jsonSchemaDialect) { return false; } - if (validationError.keyword === 'type') { + if (validationError.keyword === 'type' || validationError.keyword === 'format') { let schemaDataPath = formatDataPath(formatSchemaPathFromAJVErrorToConvertToDataPath(validationError.schemaPath)), schemaToUse = schemaDataPath ? _.get(schema, schemaDataPath) : schema, valueToValidate = dataPath ? _.get(valueToUse, dataPath) : valueToUse; diff --git a/lib/common/schemaUtilsCommon.js b/lib/common/schemaUtilsCommon.js index c7d95f0..3e50340 100644 --- a/lib/common/schemaUtilsCommon.js +++ b/lib/common/schemaUtilsCommon.js @@ -45,13 +45,14 @@ function removeSharpAndSlashFromFirstPosition(schemaPath) { } /** - * Remove the type from the last position of a schema path + * Remove the defined word from the last position of a schema path * @param {string} schemaPath - a defined schemaPath + * @param {string} word - word to remove * @returns {string} - the schema path with type removed */ -function removeTypeFromLastPosition(schemaPath) { +function removeWordFromLastPosition(schemaPath, word) { let splittedDataPath = schemaPath.split('/'); - if (splittedDataPath[splittedDataPath.length - 1] === 'type') { + if (splittedDataPath[splittedDataPath.length - 1] === word) { splittedDataPath.splice(-1); } return splittedDataPath.join('/'); @@ -314,7 +315,8 @@ module.exports = { * @returns {string} - The schemaPath with initial #/ and last "/type" removed */ formatSchemaPathFromAJVErrorToConvertToDataPath: function (schemaPath) { - return removeTypeFromLastPosition(removeSharpAndSlashFromFirstPosition(schemaPath)); + return removeWordFromLastPosition(removeWordFromLastPosition(removeSharpAndSlashFromFirstPosition(schemaPath), + 'type'), 'format'); }, typesMap, diff --git a/lib/deref.js b/lib/deref.js index 1e714be..d48d109 100644 --- a/lib/deref.js +++ b/lib/deref.js @@ -23,7 +23,9 @@ const _ = require('lodash'), 'regex', 'uuid', 'json-pointer', - 'int64' + 'int64', + 'float', + 'double' ], DEFAULT_SCHEMA_UTILS = require('./30XUtils/schemaUtils30X'); diff --git a/test/data/valid_openapi/issue#479_2.yaml b/test/data/valid_openapi/issue#479_2.yaml index 78a12e4..d68d1b2 100644 --- a/test/data/valid_openapi/issue#479_2.yaml +++ b/test/data/valid_openapi/issue#479_2.yaml @@ -59,6 +59,9 @@ components: age: type: integer format: int32 + created_at: + type: string + format: date-time isFavorite: type: integer format: int32 @@ -78,6 +81,27 @@ components: type: string tag: type: string + created_at: + type: string + format: date-time + birthday: + type: string + format: date + floatField: + type: number + format: float + doubleField: + type: number + format: double + content: + format: byte + type: string + file: + type: string + format: binary + root_pass: + type: string + format: password Pets: type: array items: diff --git a/test/unit/deref.test.js b/test/unit/deref.test.js index 8aac8b2..3886c89 100644 --- a/test/unit/deref.test.js +++ b/test/unit/deref.test.js @@ -201,8 +201,6 @@ describe('DEREF FUNCTION TESTS ', function() { ], nonSupportedFormats = [ { type: 'integer', format: 'int32' }, - { type: 'number', format: 'float' }, - { type: 'number', format: 'double' }, { type: 'string', format: 'byte' }, { type: 'string', format: 'binary' }, { type: 'string', format: 'password' }, diff --git a/test/unit/schemaUtilsCommon.test.js b/test/unit/schemaUtilsCommon.test.js index e46cc89..c728fa7 100644 --- a/test/unit/schemaUtilsCommon.test.js +++ b/test/unit/schemaUtilsCommon.test.js @@ -263,4 +263,35 @@ describe('isTypeValue method', function () { expect(result).to.be.false; }); + + it('should return true when value is and type is string,' + + ' and format is date-time', function () { + const result = isTypeValue('', { type: 'string', format: 'date-time' }); + expect(result).to.be.true; + }); + + it('should return true when value is and type is string,' + + ' and format is date', function () { + const result = isTypeValue('', { type: 'string', format: 'date' }); + expect(result).to.be.true; + }); + + it('should return true when value is and type is number,' + + ' and format is float', function () { + const result = isTypeValue('', { type: 'number', format: 'float' }); + expect(result).to.be.true; + }); + + it('should return true when value is and type is number,' + + ' and format is double', function () { + const result = isTypeValue('', { type: 'number', format: 'double' }); + expect(result).to.be.true; + }); + + it('should return true when value is and type is number,' + + ' and format is double', function () { + const result = isTypeValue('', { type: 'number', format: 'double' }); + expect(result).to.be.true; + }); + }); diff --git a/test/unit/validator.test.js b/test/unit/validator.test.js index 7f906e8..c1a6f45 100644 --- a/test/unit/validator.test.js +++ b/test/unit/validator.test.js @@ -69,12 +69,17 @@ describe('Validation with different resolution parameters options', function () let fileData = fs.readFileSync(path.join(__dirname, VALID_OPENAPI_FOLDER_PATH, '/issue#479_2.yaml'), 'utf8'), expectedRequestBody = - '{"data":[{"entityId":"","user":{"id":"","age":""},' + + '{"data":[{"entityId":"","user":{"id":"","age":"","created_at":""},' + '"isFavorite":"","needThis":""},' + - '{"entityId":"","user":{"id":"","age":""},' + + '{"entityId":"","user":{"id":"","age":"","created_at":""},' + '"isFavorite":"","needThis":""}]}', expectedResponseBody = - '[{"id":"","name":"","tag":""},{"id":"","name":"","tag":""}]', + '[{"id":"","name":"","tag":"","created_at":"","birthday":""' + + ',"floatField":"","doubleField":"","content":"","file":"",' + + '"root_pass":""},' + + '{"id":"","name":"","tag":"","created_at":"","birthday":""' + + ',"floatField":"","doubleField":"","content":"","file":"",' + + '"root_pass":""}]', options = { requestParametersResolution: 'Schema', exampleParametersResolution: 'Schema', @@ -119,9 +124,9 @@ describe('Validation with different resolution parameters options', function () let fileData = fs.readFileSync(path.join(__dirname, VALID_OPENAPI_FOLDER_PATH, '/issue#479_2.yaml'), 'utf8'), expectedBody = - '{"data":[{"entityId":"","user":{"id":"","age":""},' + + '{"data":[{"entityId":"","user":{"id":"","age":"","created_at":""},' + '"isFavorite":"","needThis":""},' + - '{"entityId":"","user":{"id":"","age":""},' + + '{"entityId":"","user":{"id":"","age":"","created_at":""},' + '"isFavorite":"","needThis":""}]}', options = { requestParametersResolution: 'Schema', @@ -165,7 +170,12 @@ describe('Validation with different resolution parameters options', function () let fileData = fs.readFileSync(path.join(__dirname, VALID_OPENAPI_FOLDER_PATH, '/issue#479_2.yaml'), 'utf8'), expectedResponseBody = - '[{"id":"","name":"","tag":""},{"id":"","name":"","tag":""}]', + '[{"id":"","name":"","tag":"","created_at":"","birthday":""' + + ',"floatField":"","doubleField":"","content":"","file":"",' + + '"root_pass":""},' + + '{"id":"","name":"","tag":"","created_at":"","birthday":""' + + ',"floatField":"","doubleField":"","content":"","file":"",' + + '"root_pass":""}]', options = { requestParametersResolution: 'Example', exampleParametersResolution: 'Schema',