diff --git a/lib/schemaUtils.js b/lib/schemaUtils.js index a252f310e..6e6b91b5a 100644 --- a/lib/schemaUtils.js +++ b/lib/schemaUtils.js @@ -2810,6 +2810,43 @@ module.exports = { return jsonContentType; }, + /** + * Converts existing JSON values of mismatch objects to serialised value based on + * serilasation params defined in schema. + * + * @param {Array} mismatches - Array of mismatch objects + * @param {Array} resolvedSchemaParams - All resolved schema params + * @param {Object} components - Components in the spec that the schema might refer to + * @param {Object} schemaCache - object storing schemaFaker and schmeResolution caches + * @param {Object} options - Global options + * @returns {Array} - Array of mismatch objects with updated value + */ + convertToSerialisedValues: function (mismatches, resolvedSchemaParams, components, schemaCache, options) { + // fetches property name from schem path + let getPropNameFromSchemPath = (schemaPath) => { + let regex = /\.properties\[(.+)\]/gm; + return _.last(regex.exec(schemaPath)); + }; + + return _.map(mismatches, (mismatchObj) => { + if (!_.isEmpty(mismatchObj)) { + let propertyName = getPropNameFromSchemPath(mismatchObj.schemaJsonPath), + schemaParam = _.find(resolvedSchemaParams, (param) => { return param.name === propertyName; }), + serializedParamValue; + + if (schemaParam) { + // serialize param value (to be used in suggested value) + serializedParamValue = _.get(this.convertParamsWithStyle(schemaParam, _.get(mismatchObj, + 'suggestedFix.suggestedValue'), PARAMETER_SOURCE.REQUEST, components, schemaCache, options), + '[0].value'); + _.set(mismatchObj, 'suggestedFix.actualValue', schemaParam.actualValue); + _.set(mismatchObj, 'suggestedFix.suggestedValue', serializedParamValue); + } + } + return mismatchObj; + }); + }, + /** * * @param {String} property - one of QUERYPARAM, PATHVARIABLE, HEADER, BODY, RESPONSE_HEADER, RESPONSE_BODY @@ -3355,7 +3392,8 @@ module.exports = { resolvedSchemaParams.push({ name: propName, schema: propSchema, - isResolvedParam: true, + required: param.required || false, // treat exploded param as required if parent param is required + isResolvedParam: !_.includes(['array', 'object'], _.get(propSchema, 'type')), pathPrefix }); }); @@ -3389,10 +3427,21 @@ module.exports = { // assign parameter example(s) as schema examples; this.assignParameterExamples(schemaParam); - if (!schemaParam.isResolvedParam) { + if (schemaParam.isResolvedParam === false) { + // simply parse value which will be not serialised and is stringified + try { + resolvedParamValue = JSON.parse(pQuery.value); + } + catch (err) { + console.warn(`Unable to parse value for parameter ${schemaParam.name}`); + } + } + else if (!schemaParam.isResolvedParam) { resolvedParamValue = this.deserialiseParamValue(schemaParam, pQuery.value, PARAMETER_SOURCE.REQUEST, components, schemaCache); } + // store existing value to be used in mismatch object + schemaParam.actualValue = pQuery.value; // query found in spec. check query's schema setTimeout(() => { @@ -3414,7 +3463,7 @@ module.exports = { let mismatches = [], mismatchObj; - _.each(_.filter(schemaParams, (q) => { return q.required; }), (qp) => { + _.each(_.filter(resolvedSchemaParams, (q) => { return q.required; }), (qp) => { if (!_.find(requestQueryParams, (param) => { return param.key === qp.name; })) { // assign parameter example(s) as schema examples; @@ -3443,7 +3492,11 @@ module.exports = { mismatches.push(mismatchObj); } }); - return callback(null, _.concat(_.flatten(res), mismatches)); + + mismatches = this.convertToSerialisedValues(_.concat(_.flatten(res), mismatches), resolvedSchemaParams, + components, schemaCache, options); + + return callback(null, mismatches); }); }, @@ -3864,7 +3917,8 @@ module.exports = { resolvedSchemaParams.push({ name: key, schema: value, - isResolvedParam: true + required: resolvedProp.required || false, // treat exploded param as required if parent param is required + isResolvedParam: !_.includes(['array', 'object'], _.get(propSchema, 'type')) }); }); } @@ -3894,7 +3948,16 @@ module.exports = { return cb(null, mismatches); } - if (!schemaParam.isResolvedParam) { + if (schemaParam.isResolvedParam === false) { + // simply parse value which will be not serialised and is stringified + try { + resolvedParamValue = JSON.parse(uParam.value); + } + catch (err) { + console.warn(`Unable to parse value for parameter ${schemaParam.name}`); + } + } + else if (!schemaParam.isResolvedParam) { resolvedParamValue = this.deserialiseParamValue(schemaParam, uParam.value, PARAMETER_SOURCE.REQUEST, components, schemaCache); } @@ -3919,30 +3982,7 @@ module.exports = { }, 0); }, (err, res) => { let mismatches = [], - mismatchObj, - // fetches property name from schem path - getPropNameFromSchemPath = (schemaPath) => { - let regex = /\.properties\[(.+)\]/gm; - return _.last(regex.exec(schemaPath)); - }; - - // update actual value and suggested value from JSON to serialized strings - _.forEach(_.flatten(res), (mismatchObj) => { - if (!_.isEmpty(mismatchObj)) { - let propertyName = getPropNameFromSchemPath(mismatchObj.schemaJsonPath), - schemaParam = _.find(resolvedSchemaParams, (param) => { return param.name === propertyName; }), - serializedParamValue; - - if (schemaParam) { - // serialize param value (to be used in suggested value) - serializedParamValue = _.get(this.convertParamsWithStyle(schemaParam, _.get(mismatchObj, - 'suggestedFix.suggestedValue'), PARAMETER_SOURCE.REQUEST, components, schemaCache, options), - '[0].value'); - _.set(mismatchObj, 'suggestedFix.actualValue', schemaParam.actualValue); - _.set(mismatchObj, 'suggestedFix.suggestedValue', serializedParamValue); - } - } - }); + mismatchObj; _.each(resolvedSchemaParams, (uParam) => { // report mismatches only for reuired properties @@ -3970,7 +4010,12 @@ module.exports = { mismatches.push(mismatchObj); } }); - return callback(null, _.concat(_.flatten(res), mismatches)); + + // update actual value and suggested value from JSON to serialized strings + mismatches = this.convertToSerialisedValues(_.concat(_.flatten(res), mismatches), resolvedSchemaParams, + components, schemaCache, options); + + return callback(null, mismatches); }); } else { diff --git a/test/data/validationData/urlencodedBodySpec.yaml b/test/data/validationData/urlencodedBodySpec.yaml index da507719c..df7215dc9 100644 --- a/test/data/validationData/urlencodedBodySpec.yaml +++ b/test/data/validationData/urlencodedBodySpec.yaml @@ -54,8 +54,17 @@ paths: propSimple: type: integer example: 123 + propObjectMissing: + type: object + properties: + prop1: + type: string + example: hola + prop2: + type: string + example: world! required: - - status + - propObjectMissing encoding: propObjectExplodable: style: form @@ -63,6 +72,9 @@ paths: propObjectNonExplodable: style: form explode: false + propObjectMissing: + style: form + explode: false responses: '200': description: Pet updated. diff --git a/test/unit/validator.test.js b/test/unit/validator.test.js index 49ba378a2..d08247026 100644 --- a/test/unit/validator.test.js +++ b/test/unit/validator.test.js @@ -641,7 +641,7 @@ describe('VALIDATE FUNCTION TESTS ', function () { resultObj, historyRequest = [], schemaPack = new Converter.SchemaPack({ type: 'string', data: urlencodedBodySpec }, - { suggestAvailableFixes: true }); + { suggestAvailableFixes: true, showMissingInSchemaErrors: true }); getAllTransactions(JSON.parse(urlencodedBodyCollection), historyRequest); @@ -649,7 +649,7 @@ describe('VALIDATE FUNCTION TESTS ', function () { expect(err).to.be.null; expect(result).to.be.an('object'); resultObj = result.requests[historyRequest[0].id].endpoints[0]; - expect(resultObj.mismatches).to.have.lengthOf(3); + expect(resultObj.mismatches).to.have.lengthOf(4); // for explodable property of type object named "propObjectExplodable", // second property named "prop2" is incorrect, while property "prop1" is correct @@ -666,6 +666,11 @@ describe('VALIDATE FUNCTION TESTS ', function () { expect(resultObj.mismatches[2].transactionJsonPath).to.eql('$.request.body.urlencoded[4].value'); expect(resultObj.mismatches[2].suggestedFix.actualValue).to.eql('999'); expect(resultObj.mismatches[2].suggestedFix.suggestedValue).to.eql('exampleString'); + + // property named "propObjectMissing" is missing in request + expect(resultObj.mismatches[3].reasonCode).to.eql('MISSING_IN_REQUEST'); + expect(resultObj.mismatches[3].suggestedFix.key).to.eql('propObjectMissing'); + expect(resultObj.mismatches[3].suggestedFix.suggestedValue).to.eql('prop3,hold,prop4,world!'); done(); }); });