Correct JSONPaths for schema/txn objects

This commit is contained in:
Abhijit Kane
2019-12-17 15:05:54 +05:30
parent 35c76cd2f2
commit 18e00de729
3 changed files with 104 additions and 60 deletions

View File

@@ -1687,7 +1687,7 @@ module.exports = {
// look for specific method
if (pathItemObject[method.toLowerCase()]) {
matchedPath = pathItemObject[method.toLowerCase()];
matchedPathJsonPath = `$.paths[${path}].${method}.`.replace(/\.\./g, '.');
matchedPathJsonPath = `$.paths[${path}]`;
if (!matchedPath.parameters) {
matchedPath.parameters = [];
@@ -1697,14 +1697,15 @@ module.exports = {
matchedPath.parameters = _.map(matchedPath.parameters, (commonParam) => {
// for path-specifix params that are added to the path, have a way to identify them
// when the schemaPath is required
commonParam.pathPrefix = `${matchedPathJsonPath}.parameters.`.replace(/\.\./g, '.');
// method is lowercased because OAS methods are always lowercase
commonParam.pathPrefix = `${matchedPathJsonPath}.${method.toLowerCase()}.parameters`;
return commonParam;
}).concat(
_.map(pathItemObject.parameters || [], (commonParam) => {
// for common params that are added to the path, have a way to identify them
// when the schemaPath is required
commonParam.pathPrefix = '$.parameters.';
commonParam.pathPrefix = matchedPathJsonPath + '.parameters';
return commonParam;
})
);
@@ -1712,7 +1713,7 @@ module.exports = {
retVal.push({
name: matchedPath.operationId || matchedPath.summary || (method + ' ' + path),
path: matchedPath,
jsonPath: matchedPathJsonPath,
jsonPath: matchedPathJsonPath + '.' + method.toLowerCase(),
pathVariables: pathItemValues
});
}
@@ -1726,23 +1727,59 @@ module.exports = {
checkValueAgainstSchema: function (property, jsonPathPrefix, value, schemaPathPrefix, schema,
components, options, callback) {
if (schema.hasOwnProperty('$ref')) {
schema = this.getRefObject(schema.$ref, components, options);
}
if (!schemaTypeToJsValidator[schema.type](value)) {
callback(null, [{
property,
transactionJsonPath: jsonPathPrefix,
schemaJsonPath: schemaPathPrefix.replace(/\.\./g, '.'),
reasonCode: 'INVALID_TYPE',
reason: `Value must be a token of type ${schema.type}, found ${value}`
}]);
let mismatches = [];
if (schema) {
if (!schemaTypeToJsValidator[schema.type](value)) {
// if type didn't match, no point checking for AJV
return callback(null, [{
property,
transactionJsonPath: jsonPathPrefix,
schemaJsonPath: schemaPathPrefix,
reasonCode: 'INVALID_TYPE',
reason: `Value must be a token of type ${schema.type}, found ${value}`
}]);
}
// only do AJV if type is array or object
// simpler cases are handled by a type check
if (schema.type === 'array' || schema.type === 'object') {
let ajv = new Ajv({ unknownFormats: ['int32', 'int64'], allErrors: true }),
validate = ajv.compile(schema),
res = validate(value);
if (!res) {
mismatches.push({
property: property,
transactionJsonPath: jsonPathPrefix,
schemaJsonPath: schemaPathPrefix,
reasonCode: 'INVALID_TYPE',
reason: 'The property didn\'t match the specified schema'
});
// only return AJV mismatches
return callback(null, mismatches);
}
// result passed. No AJV mismatch
}
// Schema was not AJV or object
}
// Schema not defined
return callback(null, []);
// if (!schemaTypeToJsValidator[schema.type](value)) {
// callback(null, [{
// property,
// transactionJsonPath: jsonPathPrefix,
// schemaJsonPath: schemaPathPrefix,
// reasonCode: 'INVALID_TYPE',
// reason: `Value must be a token of type ${schema.type}, found ${value}`
// }]);
// }
// TODO: Further checks for object type
else {
callback(null, []);
}
// else {
// callback(null, []);
// }
},
/**
@@ -1783,7 +1820,8 @@ module.exports = {
// extra pathVar present in given request.
mismatches.push({
property: mismatchProperty,
transactionJsonPath: transactionPathPrefix + '[' + pathVar.key + ']',
// not adding the pathVar name to the jsonPath because URL is just a string
transactionJsonPath: transactionPathPrefix,
schemaJsonPath: null,
reasonCode: 'MISSING_IN_SCHEMA',
reason: `The path variable ${pathVar.key} was not found in the schema`
@@ -1791,9 +1829,12 @@ module.exports = {
return cb(null);
}
this.checkValueAgainstSchema(mismatchProperty, transactionPathPrefix + '[' + pathVar.key + ']',
this.checkValueAgainstSchema(mismatchProperty,
transactionPathPrefix,
pathVar.value,
schemaPathVar.pathPrefix + schemaPathVar.name, schemaPathVar.schema, components, options, cb);
schemaPathVar.pathPrefix + '[?(@.name==\'' + schemaPathVar.name + '\')]',
deref.resolveRefs(schemaPathVar.schema, 'request', components),
components, options, cb);
}, (err, res) => {
if (err) {
@@ -1825,13 +1866,13 @@ module.exports = {
mismatches = [];
// 1. for each header, find relevant schemaPath property
async.map(headers, (pHeader, cb) => {
return async.map(headers, (pHeader, cb) => {
schemaHeader = _.find(schemaHeaders, (header) => { return header.name === pHeader.key; });
if (!schemaHeader) {
// no schema header found
mismatches.push({
property: mismatchProperty,
transactionJsonPath: transactionPathPrefix + '[' + pHeader.key + ']',
transactionJsonPath: transactionPathPrefix + '[?(@.key==\'' + pHeader.key + '\')]',
schemaJsonPath: null,
reasonCode: 'MISSING_IN_SCHEMA',
reason: `The header ${pHeader.key} was not found in the schema`
@@ -1841,10 +1882,10 @@ module.exports = {
// header found in spec. check header's schema
this.checkValueAgainstSchema(mismatchProperty,
transactionPathPrefix + '[' + pHeader.key + ']',
transactionPathPrefix + '[?(@.key==\'' + pHeader.key + '\')]',
pHeader.value,
schemaHeader.pathPrefix + '[' + schemaHeader.name + ']',
schemaHeader.schema,
schemaHeader.pathPrefix + '[?(@.name==\'' + schemaHeader.name + '\')]',
deref.resolveRefs(schemaHeader.schema, 'request', components),
components, options,
cb
);
@@ -1854,13 +1895,13 @@ module.exports = {
mismatches.push({
property: mismatchProperty,
transactionJsonPath: null,
schemaJsonPath: header.pathPrefix + header.name,
schemaJsonPath: header.pathPrefix + '[?(@.name==\'' + header.name + '\')]',
reasonCode: 'MISSING_IN_REQUEST',
reason: `The requried header ${header.name} was not found in the transaction`
});
}
});
callback(null, _.concat(_.flatten(res), mismatches));
return callback(null, _.concat(_.flatten(res), mismatches));
});
},
@@ -1906,18 +1947,21 @@ module.exports = {
transactionPathPrefix + '/' + pHeader.key,
pHeader.value,
schemaPathPrefix + responsePathPrefix + '.headers[' + pHeader.key + ']',
schemaHeader.schema,
deref.resolveRefs(schemaHeader.schema, 'response', components),
components,
options,
cb
);
}, (err, res) => {
_.each(_.filter(schemaHeaders, (h) => { return h.required; }), (header) => {
_.each(_.filter(schemaHeaders, (h, hName) => {
h.name = hName;
return h.required;
}), (header) => {
if (!_.find(headers, (param) => { return param.key === header.name; })) {
mismatches.push({
property: mismatchProperty,
transactionJsonPath: null,
schemaJsonPath: header.pathPrefix + header.name,
schemaJsonPath: schemaPathPrefix + '.headers[\'' + header.name + '\']',
reasonCode: 'MISSING_IN_REQUEST',
reason: `The requried header ${header.name} was not found in the transaction`
});
@@ -1942,16 +1986,25 @@ module.exports = {
validate = ajv.compile(jsonSchemaBody),
res = validate(JSON.parse(requestBody.raw));
if (!res) {
_.each(validate.errors, (error) => {
// error.keyword can be https://ajv.js.org/keywords.html
mismatches.push({
property: 'REQUEST_BODY',
transactionJsonPath: transactionPathPrefix + error.dataPath,
schemaJsonPath: schemaPathPrefix + 'requestBody.content.application.json.schema.' + error.schemaPath,
reasonCode: error.keyword.toUpperCase(),
reason: error.message
});
mismatches.push({
property: 'REQUEST_BODY',
transactionJsonPath: transactionPathPrefix,
schemaJsonPath: schemaPathPrefix + 'requestBody.content.application.json.schema',
reasonCode: 'INVALID_TYPE',
reason: 'The request body didn\'t match the specified schema'
});
// Not validating parts of the body for now
// _.each(validate.errors, (error) => {
// // error.keyword can be https://ajv.js.org/keywords.html
// mismatches.push({
// property: 'REQUEST_BODY',
// transactionJsonPath: transactionPathPrefix + error.dataPath,
// schemaJsonPath: schemaPathPrefix + 'requestBody.content.application.json.schema.' + error.schemaPath,
// reasonCode: error.keyword.toUpperCase(),
// reason: error.message
// });
// });
return callback(null, mismatches);
}
}
@@ -1980,10 +2033,10 @@ module.exports = {
return this.checkValueAgainstSchema(mismatchProperty,
transactionPathPrefix + '/body',
transactionPathPrefix + '.body',
body,
schemaPathPrefix,
schemaContent,
schemaPathPrefix + '.content[application/json].schema',
deref.resolveRefs(schemaContent, 'response', components),
components,
options,
callback
@@ -2053,15 +2106,14 @@ module.exports = {
async.parallel({
headers: (cb) => {
this.checkResponseHeaders(thisSchemaResponse, response.header,
transactionPathPrefix + '.' + response.id + '.headers.',
schemaPathPrefix + '.' + responsePathPrefix, components, options, cb);
transactionPathPrefix + '.' + response.id + '.header',
schemaPathPrefix + '.responses.' + responsePathPrefix, components, options, cb);
},
body: (cb) => {
// assume it's JSON at this point
this.checkResponseBody(thisSchemaResponse, response.header,
transactionPathPrefix + '.' + response.id + '.body.',
schemaPathPrefix + '.' + responsePathPrefix, components, options, cb);
this.checkResponseBody(thisSchemaResponse, response.body,
transactionPathPrefix + '.' + response.id + '.body',
schemaPathPrefix + '.responses.' + responsePathPrefix, components, options, cb);
}
}, (err, result) => {
return responseCallback(null, {

View File

@@ -262,7 +262,7 @@ class SchemaPack {
componentsAndPaths, options, cb);
},
headers: function(cb) {
schemaUtils.checkRequestHeaders(transaction.request.header, '$.request.headers', matchedPath.path,
schemaUtils.checkRequestHeaders(transaction.request.header, '$.request.header', matchedPath.path,
componentsAndPaths, options, cb);
},
requestBody: function(cb) {
@@ -273,17 +273,9 @@ class SchemaPack {
schemaUtils.checkResponses(transaction.response, '$.responses', matchedPath.jsonPath,
matchedPath.path, componentsAndPaths, options, cb);
}
// responseHeaders: function(cb) {
// schemaUtils.checkResponseHeaders(transaction.response.code, transaction.response.headers,
// '$.response.headers', matchedPath.jsonPath, matchedPath.path, cb);
// },
// responseBody: function(cb) {
// schemaUtils.checkResponseBody(transaction.response.code, transaction.response.headers,
// '$.response.body', matchedPath.jsonPath, matchedPath.path, cb);
// }
}, (err, result) => {
console.log('Checked all properties for txn ' + transaction.id + ' for path ' + matchedPath.name);
let allMismatches = _.concat(result.headers, result.path, result.requestBody, result.responseHeaders),
let allMismatches = _.concat(result.headers, result.path, result.requestBody),
retVal = { matched: true }; // no mismatch
if (allMismatches.length > 0) {

View File

@@ -17,7 +17,7 @@ describe('The converter must validate a history request against the schema', fun
it('correctly', function(done) {
let schemaPack = new Converter.SchemaPack({ type: 'json', data: openapi }, {});
schemaPack.validateTransaction(historyRequest, (err, result) => {
debugger;
console.log('Final result: ', JSON.stringify(result, null, 2));
done();
});
});