Skip to content

Commit ce72352

Browse files
authored
Merge pull request #694 from postmanlabs/feature/validation-schemacache-hit
Added support for suggestion of type parametersResolution=Schema for validateTransactionV2().
2 parents 4230274 + 2d035e4 commit ce72352

File tree

4 files changed

+111
-35
lines changed

4 files changed

+111
-35
lines changed

libV2/validationUtils.js

Lines changed: 39 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,6 @@ const _ = require('lodash'),
6464
'accept',
6565
'authorization'
6666
],
67-
DEFAULT_SCHEMA_UTILS = require('../lib/30XUtils/schemaUtils30X'),
6867

6968
OAS_NOT_SUPPORTED = '<Error: Not supported in OAS>',
7069

@@ -133,26 +132,30 @@ function shouldAddDeprecatedOperation (operation, options) {
133132
* removes things that might make schemaFaker crash
134133
* @param {Object} context - Required context from related SchemaPack function
135134
* @param {*} oldSchema the schema to fake
136-
* @param {string} resolveTo The desired JSON-generation mechanism (schema: prefer using the JSONschema to
137135
* generate a fake object, example: use specified examples as-is). Default: schema
138136
* @param {*} resolveFor - resolve refs for flow validation/conversion (value to be one of VALIDATION/CONVERSION)
139137
* @param {string} parameterSourceOption Specifies whether the schema being faked is from a request or response.
140138
* @param {*} components list of predefined components (with schemas)
141139
* @param {string} schemaFormat default or xml
142140
* @param {object} schemaCache - object storing schemaFaker and schemaResolution caches
143-
* @param {object} options - a standard list of options that's globally passed around. Check options.js for more.
144141
* @returns {object} fakedObject
145142
*/
146-
function safeSchemaFaker (context, oldSchema, resolveTo, resolveFor, parameterSourceOption, components,
147-
schemaFormat, schemaCache, options) {
148-
var prop, key, resolvedSchema, fakedSchema,
149-
schemaFakerCache = _.get(schemaCache, 'schemaFakerCache', {});
150-
let concreteUtils = components && components.hasOwnProperty('concreteUtils') ?
151-
components.concreteUtils :
152-
DEFAULT_SCHEMA_UTILS;
153-
const indentCharacter = options.indentCharacter;
143+
function safeSchemaFaker (context, oldSchema, resolveFor, parameterSourceOption, components,
144+
schemaFormat, schemaCache) {
145+
let prop, key, resolvedSchema, fakedSchema,
146+
schemaFakerCache = _.get(schemaCache, 'schemaFakerCache', {}),
147+
concreteUtils = context.concreteUtils;
154148

155-
resolvedSchema = resolveSchema(context, oldSchema, 0, PROCESSING_TYPE.VALIDATION);
149+
const options = context.computedOptions,
150+
resolveTo = _.get(options, 'parametersResolution', 'example'),
151+
indentCharacter = options.indentCharacter;
152+
153+
/**
154+
* Schema is cloned here as resolveSchema() when called for CONVERSION use cases, will mutate schema in certain way.
155+
* i.e. For array it'll add maxItems = 2. This should be avoided as we'll again be needing non-mutated schema
156+
* in further VALIDATION use cases as needed.
157+
*/
158+
resolvedSchema = resolveSchema(context, _.cloneDeep(oldSchema), 0, _.toLower(PROCESSING_TYPE.CONVERSION));
156159

157160
resolvedSchema = concreteUtils.fixExamplesByVersion(resolvedSchema);
158161
key = JSON.stringify(resolvedSchema);
@@ -173,7 +176,6 @@ function safeSchemaFaker (context, oldSchema, resolveTo, resolveFor, parameterSo
173176

174177
if (resolveFor === PROCESSING_TYPE.VALIDATION) {
175178
schemaFaker.option({
176-
useDefaultValue: false,
177179
avoidExampleItemsLength: false
178180
});
179181
}
@@ -1426,8 +1428,8 @@ function checkValueAgainstSchema (context, property, jsonPathPrefix, txnParamNam
14261428
mismatchObj.suggestedFix = {
14271429
key: txnParamName,
14281430
actualValue: valueToUse,
1429-
suggestedValue: safeSchemaFaker(context, openApiSchemaObj || {}, 'example', PROCESSING_TYPE.VALIDATION,
1430-
parameterSourceOption, components, SCHEMA_FORMATS.DEFAULT, schemaCache, options.includeDeprecated)
1431+
suggestedValue: safeSchemaFaker(context, schema || {}, PROCESSING_TYPE.VALIDATION,
1432+
parameterSourceOption, components, SCHEMA_FORMATS.DEFAULT, schemaCache)
14311433
};
14321434
}
14331435

@@ -1442,8 +1444,8 @@ function checkValueAgainstSchema (context, property, jsonPathPrefix, txnParamNam
14421444
if (!_.isEmpty(filteredValidationError)) {
14431445
let mismatchObj,
14441446
suggestedValue,
1445-
fakedValue = safeSchemaFaker(context, openApiSchemaObj || {}, 'example', PROCESSING_TYPE.VALIDATION,
1446-
parameterSourceOption, components, SCHEMA_FORMATS.DEFAULT, schemaCache, options);
1447+
fakedValue = safeSchemaFaker(context, schema || {}, PROCESSING_TYPE.VALIDATION,
1448+
parameterSourceOption, components, SCHEMA_FORMATS.DEFAULT, schemaCache);
14471449

14481450
// Show detailed validation mismatches for only request/response body
14491451
if (options.detailedBlobValidation && needJsonMatching) {
@@ -1698,13 +1700,15 @@ function checkPathVariables (context, matchedPathData, transactionPathPrefix, sc
16981700
};
16991701

17001702
if (options.suggestAvailableFixes) {
1703+
const resolvedSchema = resolveSchema(context, pathVar.schema, 0, PROCESSING_TYPE.VALIDATION);
1704+
17011705
mismatchObj.suggestedFix = {
17021706
key: pathVar.name,
17031707
actualValue,
17041708
suggestedValue: {
17051709
key: pathVar.name,
1706-
value: safeSchemaFaker(context, pathVar.schema || {}, 'example', PROCESSING_TYPE.VALIDATION,
1707-
PARAMETER_SOURCE.REQUEST, components, SCHEMA_FORMATS.DEFAULT, schemaCache, options),
1710+
value: safeSchemaFaker(context, resolvedSchema || {}, PROCESSING_TYPE.VALIDATION,
1711+
PARAMETER_SOURCE.REQUEST, components, SCHEMA_FORMATS.DEFAULT, schemaCache),
17081712
description: getParameterDescription(pathVar)
17091713
}
17101714
};
@@ -1836,13 +1840,15 @@ function checkQueryParams (context, queryParams, transactionPathPrefix, schemaPa
18361840
};
18371841

18381842
if (options.suggestAvailableFixes) {
1843+
const resolvedSchema = resolveSchema(context, qp.schema, 0, PROCESSING_TYPE.VALIDATION);
1844+
18391845
mismatchObj.suggestedFix = {
18401846
key: qp.name,
18411847
actualValue: null,
18421848
suggestedValue: {
18431849
key: qp.name,
1844-
value: safeSchemaFaker(context, qp.schema || {}, 'example', PROCESSING_TYPE.VALIDATION,
1845-
PARAMETER_SOURCE.REQUEST, components, SCHEMA_FORMATS.DEFAULT, schemaCache, options),
1850+
value: safeSchemaFaker(context, resolvedSchema || {}, PROCESSING_TYPE.VALIDATION,
1851+
PARAMETER_SOURCE.REQUEST, components, SCHEMA_FORMATS.DEFAULT, schemaCache),
18461852
description: getParameterDescription(qp)
18471853
}
18481854
};
@@ -1958,13 +1964,15 @@ function checkRequestHeaders (context, headers, transactionPathPrefix, schemaPat
19581964
};
19591965

19601966
if (options.suggestAvailableFixes) {
1967+
const resolvedSchema = resolveSchema(context, header.schema, 0, PROCESSING_TYPE.VALIDATION);
1968+
19611969
mismatchObj.suggestedFix = {
19621970
key: header.name,
19631971
actualValue: null,
19641972
suggestedValue: {
19651973
key: header.name,
1966-
value: safeSchemaFaker(context, header.schema || {}, 'example', PROCESSING_TYPE.VALIDATION,
1967-
PARAMETER_SOURCE.REQUEST, components, SCHEMA_FORMATS.DEFAULT, schemaCache, options),
1974+
value: safeSchemaFaker(context, resolvedSchema || {}, PROCESSING_TYPE.VALIDATION,
1975+
PARAMETER_SOURCE.REQUEST, components, SCHEMA_FORMATS.DEFAULT, schemaCache),
19681976
description: getParameterDescription(header)
19691977
}
19701978
};
@@ -2076,13 +2084,15 @@ function checkResponseHeaders (context, schemaResponse, headers, transactionPath
20762084
};
20772085

20782086
if (options.suggestAvailableFixes) {
2087+
const resolvedSchema = resolveSchema(context, header.schema, 0, PROCESSING_TYPE.VALIDATION);
2088+
20792089
mismatchObj.suggestedFix = {
20802090
key: header.name,
20812091
actualValue: null,
20822092
suggestedValue: {
20832093
key: header.name,
2084-
value: safeSchemaFaker(context, header.schema || {}, 'example', PROCESSING_TYPE.VALIDATION,
2085-
PARAMETER_SOURCE.REQUEST, components, SCHEMA_FORMATS.DEFAULT, schemaCache, options),
2094+
value: safeSchemaFaker(context, resolvedSchema || {}, PROCESSING_TYPE.VALIDATION,
2095+
PARAMETER_SOURCE.REQUEST, components, SCHEMA_FORMATS.DEFAULT, schemaCache),
20862096
description: getParameterDescription(header)
20872097
}
20882098
};
@@ -2246,13 +2256,15 @@ function checkRequestBody (context, requestBody, transactionPathPrefix, schemaPa
22462256
};
22472257

22482258
if (options.suggestAvailableFixes) {
2259+
const resolvedSchema = resolveSchema(context, uParam.schema, 0, PROCESSING_TYPE.VALIDATION);
2260+
22492261
mismatchObj.suggestedFix = {
22502262
key: uParam.name,
22512263
actualValue: null,
22522264
suggestedValue: {
22532265
key: uParam.name,
2254-
value: safeSchemaFaker(context, uParam.schema || {}, 'example', PROCESSING_TYPE.VALIDATION,
2255-
PARAMETER_SOURCE.REQUEST, components, SCHEMA_FORMATS.DEFAULT, schemaCache, options),
2266+
value: safeSchemaFaker(context, resolvedSchema || {}, PROCESSING_TYPE.VALIDATION,
2267+
PARAMETER_SOURCE.REQUEST, components, SCHEMA_FORMATS.DEFAULT, schemaCache),
22562268
description: getParameterDescription(uParam)
22572269
}
22582270
};

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "openapi-to-postmanv2",
3-
"version": "4.9.0",
3+
"version": "4.9.0-beta.36",
44
"description": "Convert a given OpenAPI specification to Postman Collection v2.0",
55
"homepage": "https://github.com/postmanlabs/openapi-to-postman",
66
"bugs": "https://github.com/postmanlabs/openapi-to-postman/issues",

test/unit/ValidateV2.test.js

Lines changed: 69 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -532,12 +532,75 @@ describe('The Validation option', function () {
532532
});
533533
});
534534

535-
describe('suggestAvailableFixes ', function () {
535+
describe('suggestAvailableFixes for parametersResolution=Schema', function () {
536536
suggestAvailableFixesSpecs.forEach((specData) => {
537537
let schema = fs.readFileSync(specData.path, 'utf8'),
538538
collection = fs.readFileSync(suggestAvailableFixesCollection, 'utf8'),
539539
schemaPack = new Converter.SchemaPack({ type: 'string', data: schema },
540-
{ suggestAvailableFixes: true }),
540+
{ suggestAvailableFixes: true, parametersResolution: 'Schema' }),
541+
historyRequest = [],
542+
resultObj,
543+
responseResult,
544+
propertyMismatchMap = {};
545+
546+
before(function (done) {
547+
getAllTransactions(JSON.parse(collection), historyRequest);
548+
schemaPack.validateTransactionV2(historyRequest, (err, result) => {
549+
expect(err).to.be.null;
550+
resultObj = result.requests[historyRequest[0].id].endpoints[0];
551+
responseResult = resultObj.responses[historyRequest[0].response[0].id];
552+
553+
// check for expected mismatches length
554+
expect(resultObj.mismatches).to.have.lengthOf(4);
555+
expect(responseResult.mismatches).to.have.lengthOf(2);
556+
557+
// map all mismatch objects with it's property
558+
_.forEach(_.concat(resultObj.mismatches, responseResult.mismatches), (mismatch) => {
559+
propertyMismatchMap[mismatch.property] = mismatch;
560+
});
561+
done();
562+
});
563+
});
564+
565+
it('should suggest valid available fix for all kind of violated properties - version: ' +
566+
specData.version, function () {
567+
// check for all suggested value to be according to schema
568+
expect(propertyMismatchMap.PATHVARIABLE.suggestedFix.suggestedValue).to.eql('<integer>');
569+
expect(propertyMismatchMap.QUERYPARAM.suggestedFix.suggestedValue).to.eql('<number>');
570+
expect(propertyMismatchMap.HEADER.suggestedFix.suggestedValue.value).to.eql('<boolean>');
571+
expect(propertyMismatchMap.HEADER.suggestedFix.suggestedValue.description)
572+
.to.eql('(Required) Quantity of pets available');
573+
expect(propertyMismatchMap.BODY.suggestedFix.suggestedValue.name).to.eql('<string>');
574+
expect(propertyMismatchMap.BODY.suggestedFix.suggestedValue.tag).to.eql('<string>');
575+
// For enum, actual values in enum should be used
576+
expect(_.includes(['Bulldog', 'Retriever', 'Timberwolf', 'Grizzly', 'Husky'],
577+
propertyMismatchMap.BODY.suggestedFix.suggestedValue.breeds[2])).to.eql(true);
578+
expect(propertyMismatchMap.RESPONSE_HEADER.suggestedFix.suggestedValue).to.eql('<integer>');
579+
expect(propertyMismatchMap.RESPONSE_BODY.suggestedFix.suggestedValue.code).to.eql('<integer>');
580+
expect(propertyMismatchMap.RESPONSE_BODY.suggestedFix.suggestedValue.message).to.eql('<string>');
581+
});
582+
583+
it('should maintain valid properties/items in suggested value - version:' +
584+
specData.version, function () {
585+
expect(propertyMismatchMap.BODY.suggestedFix.suggestedValue.petId).to.eql(
586+
propertyMismatchMap.BODY.suggestedFix.actualValue.petId
587+
);
588+
expect(propertyMismatchMap.BODY.suggestedFix.suggestedValue.breeds[0]).to.eql(
589+
propertyMismatchMap.BODY.suggestedFix.actualValue.breeds[0]
590+
);
591+
expect(propertyMismatchMap.BODY.suggestedFix.suggestedValue.breeds[1]).to.eql(
592+
propertyMismatchMap.BODY.suggestedFix.actualValue.breeds[1]
593+
);
594+
});
595+
});
596+
});
597+
598+
describe('suggestAvailableFixes for parametersResolution=Example', function () {
599+
suggestAvailableFixesSpecs.forEach((specData) => {
600+
let schema = fs.readFileSync(specData.path, 'utf8'),
601+
collection = fs.readFileSync(suggestAvailableFixesCollection, 'utf8'),
602+
schemaPack = new Converter.SchemaPack({ type: 'string', data: schema },
603+
{ suggestAvailableFixes: true, parametersResolution: 'Example' }),
541604
historyRequest = [],
542605
resultObj,
543606
responseResult,
@@ -980,7 +1043,8 @@ describe('VALIDATE FUNCTION TESTS ', function () {
9801043
ignoreUnresolvedVariables: true,
9811044
validateMetadata: true,
9821045
suggestAvailableFixes: true,
983-
detailedBlobValidation: true
1046+
detailedBlobValidation: true,
1047+
parametersResolution: 'Example'
9841048
},
9851049
schemaPack = new Converter.SchemaPack({ type: 'string', data: primitiveDataTypeBodySpec }, options);
9861050

@@ -1103,7 +1167,7 @@ describe('VALIDATE FUNCTION TESTS ', function () {
11031167
resultObj,
11041168
historyRequest = [],
11051169
schemaPack = new Converter.SchemaPack({ type: 'string', data: queryParamDeepObjectSpec },
1106-
{ suggestAvailableFixes: true, showMissingInSchemaErrors: true });
1170+
{ suggestAvailableFixes: true, showMissingInSchemaErrors: true, parametersResolution: 'Example' });
11071171

11081172
getAllTransactions(JSON.parse(queryParamDeepObjectCollection), historyRequest);
11091173

@@ -1152,7 +1216,7 @@ describe('VALIDATE FUNCTION TESTS ', function () {
11521216
resultObjAllOf,
11531217
historyRequest = [],
11541218
schemaPack = new Converter.SchemaPack({ type: 'string', data: compositeSchemaSpec },
1155-
{ suggestAvailableFixes: true, showMissingInSchemaErrors: true });
1219+
{ suggestAvailableFixes: true, showMissingInSchemaErrors: true, parametersResolution: 'Example' });
11561220

11571221
getAllTransactions(JSON.parse(compositeSchemaCollection), historyRequest);
11581222

0 commit comments

Comments
 (0)