diff --git a/lib/ajValidation/ajvValidation.js b/lib/ajValidation/ajvValidation.js index bd80363..5768bad 100644 --- a/lib/ajValidation/ajvValidation.js +++ b/lib/ajValidation/ajvValidation.js @@ -1,4 +1,6 @@ -const { formatDataPath } = require('../common/schemaUtilsCommon'); +const { formatDataPath, + formatSchemaPathFromAJVErrorToConvertToDataPath, + isTypeValue } = require('../common/schemaUtilsCommon'); var _ = require('lodash'); const IGNORED_KEYWORDS = ['propertyNames', 'const', 'additionalItems', 'dependencies'], @@ -97,6 +99,14 @@ function validateSchema (schema, valueToUse, options = {}, jsonSchemaDialect) { isPmVariable(dataPath === '' ? valueToUse : _.get(valueToUse, dataPath))) { return false; } + + 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; + return !isTypeValue(valueToValidate, schemaToUse); + } + return true; }); @@ -110,5 +120,6 @@ module.exports = { validateSchema, getLocalDraft, getAjvValidator, - getDraftToUse + getDraftToUse, + isTypeValue }; diff --git a/lib/common/schemaUtilsCommon.js b/lib/common/schemaUtilsCommon.js index d9619ad..3e50340 100644 --- a/lib/common/schemaUtilsCommon.js +++ b/lib/common/schemaUtilsCommon.js @@ -2,7 +2,207 @@ * This file contains util functions that are common between versions */ -const parse = require('../parse.js'); +const parse = require('../parse.js'), + _ = require('lodash'), + typesMap = { + integer: { + int32: '', + int64: '' + }, + number: { + float: '', + double: '' + }, + string: { + byte: '', + binary: '', + date: '', + 'date-time': '', + password: '' + }, + boolean: '', + array: '', + object: '' + }, + + /* 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) + }; + +/** + * Remove the # character from the beginning of a schema path + * @param {string} schemaPath - a defined schemaPath + * @returns {string} - the schema path with # removed + */ +function removeSharpAndSlashFromFirstPosition(schemaPath) { + return schemaPath[0] === '#' ? schemaPath.slice(2) : schemaPath; +} + +/** + * 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 removeWordFromLastPosition(schemaPath, word) { + let splittedDataPath = schemaPath.split('/'); + if (splittedDataPath[splittedDataPath.length - 1] === word) { + splittedDataPath.splice(-1); + } + 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 = { @@ -54,15 +254,21 @@ module.exports = { isANumber = (value) => { return !isNaN(value); }, - formattedElements = splittedDataPath.map((element) => { + formattedElements = splittedDataPath.map((element, index) => { if (element !== '' && isANumber(element)) { return `[${element}]`; } - return element; + if (element === '' || element[0] === '.') { + return element; + } + if (index === 0 && !initialDotIfExist) { + return `${element}`; + } + return `.${element}`; }), formattedDataPath = formattedElements.join(''); - return `${initialDotIfExist}${formattedDataPath}`; + return `${formattedDataPath}`; }, handleExclusiveMaximum: function(schema, max) { @@ -101,5 +307,43 @@ module.exports = { } } return min; + }, + + /** + * Removes initial "#/" from a schema path and the last "/type" segment + * @param {string} schemaPath - The OAS 3.x specification specified in either YAML or JSON + * @returns {string} - The schemaPath with initial #/ and last "/type" removed + */ + formatSchemaPathFromAJVErrorToConvertToDataPath: function (schemaPath) { + return removeWordFromLastPosition(removeWordFromLastPosition(removeSharpAndSlashFromFirstPosition(schemaPath), + 'type'), 'format'); + }, + + 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/deref.js b/lib/deref.js index aa21127..7945454 100644 --- a/lib/deref.js +++ b/lib/deref.js @@ -1,24 +1,9 @@ const _ = require('lodash'), mergeAllOf = require('json-schema-merge-allof'), - type = { - integer: { - int32: '', - int64: '' - }, - number: { - float: '', - double: '' - }, - string: { - byte: '', - binary: '', - date: '', - 'date-time': '', - password: '' - }, - boolean: '', - array: '', - object: '' + { typesMap } = require('./common/schemaUtilsCommon'), + PARAMETER_SOURCE = { + REQUEST: 'REQUEST', + RESPONSE: 'RESPONSE' }, SCHEMA_TYPES = { array: 'array', @@ -28,10 +13,6 @@ const _ = require('lodash'), object: 'object', string: 'string' }, - PARAMETER_SOURCE = { - REQUEST: 'REQUEST', - RESPONSE: 'RESPONSE' - }, // All formats supported by both ajv and json-schema-faker SUPPORTED_FORMATS = [ 'date', 'time', 'date-time', @@ -41,7 +22,10 @@ const _ = require('lodash'), 'ipv4', 'ipv6', 'regex', 'uuid', - 'json-pointer' + 'json-pointer', + 'int64', + 'float', + 'double' ], DEFAULT_SCHEMA_UTILS = require('./30XUtils/schemaUtils30X'); @@ -281,7 +265,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) { @@ -321,8 +305,8 @@ module.exports = { if (!schema.hasOwnProperty('format')) { schema.default = '<' + schema.type + '>'; } - else if (type.hasOwnProperty(schema.type)) { - schema.default = type[schema.type][schema.format]; + else if (typesMap.hasOwnProperty(schema.type)) { + schema.default = typesMap[schema.type][schema.format]; // in case the format is a custom format (email, hostname etc.) // https://swagger.io/docs/specification/data-models/data-types/#string diff --git a/lib/schemaUtils.js b/lib/schemaUtils.js index c9e6377..220710d 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 */ @@ -3105,7 +3096,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 @@ -3113,7 +3104,7 @@ module.exports = { isCorrectType = true; } else { - isCorrectType = schemaTypeToJsValidator[schema.type](valueToUse); + isCorrectType = checkIsCorrectType(valueToUse, schema); } if (!isCorrectType) { diff --git a/test/data/31CollectionTransactions/479.yaml b/test/data/31CollectionTransactions/479.yaml new file mode 100644 index 0000000..3b9acdf --- /dev/null +++ b/test/data/31CollectionTransactions/479.yaml @@ -0,0 +1,32 @@ +openapi: "3.1.0" +info: + version: 1.0.0 + title: Swagger Petstore + license: + name: MIT +servers: + - url: http://petstore.swagger.io/v1 +paths: + /pets/anyOf: + post: + summary: issue 479 + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/example" + responses: + default: + description: ok +components: + schemas: + example: + type: + - object + properties: + id: + type: + - integer + hasPet: + type: + - boolean diff --git a/test/data/31CollectionTransactions/479col.json b/test/data/31CollectionTransactions/479col.json new file mode 100644 index 0000000..715c104 --- /dev/null +++ b/test/data/31CollectionTransactions/479col.json @@ -0,0 +1,105 @@ +{ + "item": [ + { + "id": "c3a2f66d-f91e-45ec-9116-4ce926acb630", + "name": "pets", + "item": [ + { + "id": "25758366-55ed-4370-90fd-1e1be8536b0b", + "name": "issue 479", + "request": { + "name": "composite schema with anyOf keyword", + "description": {}, + "url": { + "path": [ + "pets", + "anyOf" + ], + "host": [ + "{{baseUrl}}" + ], + "query": [], + "variable": [] + }, + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "method": "POST", + "auth": null, + "body": { + "mode": "raw", + "raw": "{ \"id\": \"\", \"hasPet\": \"\" }", + "options": { + "raw": { + "language": "json" + } + } + } + }, + "response": [ + { + "id": "f4f6ddf5-d259-45b7-89fe-086cc22a6549", + "name": "ok", + "originalRequest": { + "url": { + "path": [ + "pets", + "anyOf" + ], + "host": [ + "{{baseUrl}}" + ], + "query": [], + "variable": [] + }, + "method": "POST", + "body": { + "mode": "raw", + "raw": "{ \"id\": \"\", \"hasPet\": \"\" }", + "options": { + "raw": { + "language": "json" + } + } + } + }, + "status": "Internal Server Error", + "code": 500, + "header": [ + { + "key": "Content-Type", + "value": "text/plain" + } + ], + "body": "", + "cookie": [], + "_postman_previewlanguage": "text" + } + ], + "event": [] + } + ], + "event": [] + } + ], + "event": [], + "variable": [ + { + "type": "string", + "value": "http://petstore.swagger.io/v1", + "key": "baseUrl" + } + ], + "info": { + "_postman_id": "b498d8c3-e6e5-4a72-92de-e5c455786a8e", + "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/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/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/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/data/valid_openapi/issue#479_2.yaml b/test/data/valid_openapi/issue#479_2.yaml new file mode 100644 index 0000000..d68d1b2 --- /dev/null +++ b/test/data/valid_openapi/issue#479_2.yaml @@ -0,0 +1,108 @@ +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: Expected response to a valid request + content: + application/json: + schema: + $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 + created_at: + type: string + format: date-time + isFavorite: + type: integer + format: int32 + needThis: + type: + - string + Pet: + type: object + required: + - id + - name + properties: + id: + type: integer + format: int64 + name: + 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: + $ref: "#/components/schemas/Pet" 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/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/deref.test.js b/test/unit/deref.test.js index ebf6168..8fb314f 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,9 +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' }, { type: 'string', format: 'binary' }, { type: 'string', format: 'password' }, diff --git a/test/unit/schemaUtilsCommon.test.js b/test/unit/schemaUtilsCommon.test.js index 04c9779..c728fa7 100644 --- a/test/unit/schemaUtilsCommon.test.js +++ b/test/unit/schemaUtilsCommon.test.js @@ -1,5 +1,7 @@ -const schemaUtilsCommon = require('../../lib/common/schemaUtilsCommon'); -const { formatDataPath } = require('../../lib/common/schemaUtilsCommon'), +const schemaUtilsCommon = require('../../lib/common/schemaUtilsCommon'), + { formatDataPath, + formatSchemaPathFromAJVErrorToConvertToDataPath, + isTypeValue } = require('../../lib/common/schemaUtilsCommon'), expect = require('chai').expect; describe('formatData method', function() { @@ -31,6 +33,14 @@ describe('formatData method', function() { expect(formattedDataPath).to.be.equal(expectedDataPath); }); + it('Should return "properties.automatic.items.properties.configs.items"' + + ' when input is "#/properties/automatic/items/properties/configs/items/type"', function() { + const input = 'properties/automatic/items/properties/configs/items', + expectedDataPath = 'properties.automatic.items.properties.configs.items', + formattedDataPath = formatDataPath(input); + expect(formattedDataPath).to.be.equal(expectedDataPath); + }); + }); describe('handleExclusiveMaximum method', function() { @@ -149,4 +159,139 @@ describe('handleExclusiveMinimum method', function() { }); }); +describe('formatSchemaPathFromAJVErrorToConvertToDataPath method', function () { + it('should return properties/automatic/items/properties/configs/items ' + + 'when entry is #/properties/automatic/items/properties/configs/items/type', function () { + const result = + formatSchemaPathFromAJVErrorToConvertToDataPath('#/properties/automatic/items/properties/configs/items/type'); + 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; + }); + + + 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 a1f9655..c1a6f45 100644 --- a/test/unit/validator.test.js +++ b/test/unit/validator.test.js @@ -63,6 +63,197 @@ 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'), + expectedRequestBody = + '{"data":[{"entityId":"","user":{"id":"","age":"","created_at":""},' + + '"isFavorite":"","needThis":""},' + + '{"entityId":"","user":{"id":"","age":"","created_at":""},' + + '"isFavorite":"","needThis":""}]}', + expectedResponseBody = + '[{"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', + 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, ''), + 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":"","created_at":""},' + + '"isFavorite":"","needThis":""},' + + '{"entityId":"","user":{"id":"","age":"","created_at":""},' + + '"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":"","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', + 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; + 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, VALID_OPENAPI_FOLDER_PATH, + '/issue#479_2.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; + }); + }); + }); + }); + }); + + +}); 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'], @@ -391,21 +582,22 @@ 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', + 'data.items.properties.user.properties.entityId.maxLength': '$.request.body.data[0].user.entityId', + 'meta.maxProperties': '$.request.body.meta', + 'meta.additionalProperties': '$.request.body.meta' + }; getAllTransactions(JSON.parse(collection), historyRequest); schemaPack.validateTransaction(historyRequest, (err, result) => { @@ -416,7 +608,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 +699,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 +949,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 +962,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 +1163,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 () { diff --git a/test/unit/x31schemapack.test.js b/test/unit/x31schemapack.test.js index 42fc467..91f4fef 100644 --- a/test/unit/x31schemapack.test.js +++ b/test/unit/x31schemapack.test.js @@ -145,6 +145,27 @@ describe('Openapi 3.1 schema pack validateTransactions', function() { }); } + it('Fix for GITHUB#479: Should accept values like in validation', function() { + const collectionSource = path.join(__dirname, OPENAPI_31_COLLECTIONS + '/479col.json'), + collectionData = fs.readFileSync(collectionSource, 'utf8'), + schemaSource = path.join(__dirname, OPENAPI_31_COLLECTIONS + '/479.yaml'), + schemaData = fs.readFileSync(schemaSource, 'utf8'), + validator = new SchemaPack({ + type: 'string', + data: schemaData + }); + let transactions = []; + getAllTransactions(JSON.parse(collectionData), transactions); + + validator.validateTransaction(transactions, (err, result) => { + let requestIds = Object.keys(result.requests); + expect(err).to.be.null; + requestIds.forEach((requestId) => { + expect(result.requests[requestId].endpoints[0].matched).to.be.true; + }); + }); + }); + it('Should not generate any mismatch with a correct file', function() { const collectionSource = path.join(__dirname, OPENAPI_31_COLLECTIONS + '/compositeSchemaCollection.json'), collectionData = fs.readFileSync(collectionSource, 'utf8'), diff --git a/test/unit/xajvValidation.test.js b/test/unit/xajvValidation.test.js index 7daad7d..73ef43e 100644 --- a/test/unit/xajvValidation.test.js +++ b/test/unit/xajvValidation.test.js @@ -302,6 +302,112 @@ describe('validateSchema', function () { result = validateSchemaAJVDraft04(null, valueToUse); expect(result.filteredValidationError).to.be.undefined; }); + + it('Fix for GITHUB#479: should validate as correct input for type integer', function () { + const schema = { + type: 'object', + properties: { + id: { + type: [ + 'integer', + 'boolean' + ], + examples: [ + 111111 + ] + }, + hasPet: { + type: [ + 'boolean' + ] + } + } + }, + valueToUse = { + 'id': '', + 'hasPet': '' + }, + result = validateSchema(schema, valueToUse); + expect(result).to.be.empty; + }); + + it('Fix for GITHUB#479: should validate as incorrect input for type integer', function () { + const schema = { + type: 'object', + properties: { + id: { + type: [ + 'integer' + ], + examples: [ + 111111 + ] + }, + hasPet: { + type: [ + 'boolean' + ] + } + } + }, + valueToUse = { + 'id': '', + 'hasPet': '' + }, + result = validateSchema(schema, valueToUse); + expect(result[0].instancePath).equal('/id'); + }); + + it('Fix for GITHUB#479: should validate as correct input for type integer format int64', function () { + const schema = { + type: 'object', + properties: { + id: { + type: [ + 'integer' + ], + format: 'int64' + }, + hasPet: { + type: [ + 'boolean' + ] + } + } + }, + valueToUse = { + 'id': '', + 'hasPet': '' + }, + result = validateSchema(schema, valueToUse); + expect(result).to.be.empty; + }); + + it('Fix for GITHUB#479: should validate as correct input for type integer boolean format int64', function () { + const schema = { + type: 'object', + properties: { + id: { + type: [ + 'integer', + 'boolean' + ], + format: 'int64' + }, + hasPet: { + type: [ + 'boolean' + ] + } + } + }, + valueToUse = { + 'id': '', + 'hasPet': '' + }, + result = validateSchema(schema, valueToUse); + expect(result).to.be.empty; + }); }); describe('getDraftToUse', function() {