mirror of
https://github.com/postmanlabs/openapi-to-postman.git
synced 2022-11-29 22:05:00 +03:00
864 lines
40 KiB
JavaScript
864 lines
40 KiB
JavaScript
var expect = require('chai').expect,
|
|
Converter = require('../../index.js'),
|
|
fs = require('fs'),
|
|
path = require('path'),
|
|
async = require('async'),
|
|
_ = require('lodash'),
|
|
schemaUtils = require('../../lib/schemaUtils'),
|
|
VALIDATION_DATA_FOLDER_PATH = '../data/validationData',
|
|
VALID_OPENAPI_FOLDER_PATH = '../data/valid_openapi';
|
|
|
|
/**
|
|
* Extract all transaction from collection and appends them into array
|
|
*
|
|
* @param {*} collection - Postman Collection
|
|
* @param {*} allRequests - Array to which transactions are appended
|
|
* @returns {*} - null
|
|
*/
|
|
function getAllTransactions (collection, allRequests) {
|
|
if (!_.has(collection, 'item') || !_.isArray(collection.item)) {
|
|
return;
|
|
}
|
|
_.forEach(collection.item, (item) => {
|
|
if (_.has(item, 'request') || _.has(item, 'response')) {
|
|
allRequests.push(item);
|
|
}
|
|
else {
|
|
getAllTransactions(item, allRequests);
|
|
}
|
|
});
|
|
}
|
|
|
|
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'],
|
|
checkMismatch = (mismatch) => {
|
|
expect(['REQUEST_NAME', 'REQUEST_DESCRIPTION', 'PATHVARIABLE', 'QUERYPARAM', 'HEADER', 'RESPONSE_HEADER',
|
|
'BODY', 'RESPONSE_BODY', 'ENDPOINT']).to.include(mismatch.property);
|
|
expect(mismatch).to.include.keys('transactionJsonPath');
|
|
expect(mismatch).to.include.keys('schemaJsonPath');
|
|
expect(mismatch.reason).to.be.a('string');
|
|
expect(['MISSING_IN_REQUEST', 'INVALID_TYPE', 'MISSING_IN_SCHEMA', 'INVALID_VALUE', 'INVALID_BODY',
|
|
'INVALID_RESPONSE_BODY', 'BODY_SCHEMA_NOT_FOUND', 'MISSING_ENDPOINT']).to.include(mismatch.reasonCode);
|
|
};
|
|
|
|
async.each(validOpenapiFolder, function (file, cb) {
|
|
it('correctly for schema: ' + file, function () {
|
|
let fileData = fs.readFileSync(path.join(__dirname, VALID_OPENAPI_FOLDER_PATH + '/' + file), 'utf8'),
|
|
options = {
|
|
requestParametersResolution: 'Example',
|
|
exampleParametersResolution: 'Example',
|
|
showMissingInSchemaErrors: true,
|
|
strictRequestMatching: true,
|
|
ignoreUnresolvedVariables: true,
|
|
validateMetadata: true,
|
|
suggestAvailableFixes: true,
|
|
detailedBlobValidation: false
|
|
},
|
|
schemaPack = new Converter.SchemaPack({ type: 'string', data: fileData }, options);
|
|
|
|
// Increase timeout for larger schema
|
|
this.timeout(15000);
|
|
|
|
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');
|
|
|
|
// check for result.requests structure
|
|
_.forEach(result.requests, (req) => {
|
|
expect(req.requestId).to.be.a('string');
|
|
|
|
_.forEach(req.endpoints, (endpoint) => {
|
|
expect(endpoint.matched).to.be.a('boolean');
|
|
expect(endpoint.endpointMatchScore).to.be.a('number');
|
|
expect(endpoint.endpoint).to.be.a('string');
|
|
expect(endpoint.endpoint).to.be.a('string');
|
|
|
|
_.forEach(endpoint.mismatches, (mismatch) => {
|
|
// console.log(file, ' ----------');
|
|
// console.log(JSON.stringify(mismatch, null, 2));
|
|
checkMismatch(mismatch);
|
|
if (mismatch.suggestedFix) {
|
|
expect(mismatch.suggestedFix).to.have.all.keys(suggestedFixProps);
|
|
}
|
|
});
|
|
|
|
_.forEach(endpoint.responses, (response) => {
|
|
expect(response.id).to.be.a('string');
|
|
expect(response.matched).to.be.a('boolean');
|
|
|
|
_.forEach(response.mismatches, (mismatch) => {
|
|
// console.log(file, ' **********');
|
|
// console.log(JSON.stringify(mismatch, null, 2));
|
|
checkMismatch(mismatch);
|
|
if (mismatch.suggestedFix) {
|
|
expect(mismatch.suggestedFix).to.have.all.keys(suggestedFixProps);
|
|
}
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
// check for result.missingEndpoints structure
|
|
_.forEach(result.missingEndpoints, (endpoint) => {
|
|
checkMismatch(endpoint);
|
|
expect(endpoint.property).to.eql('ENDPOINT');
|
|
if (endpoint.suggestedFix) {
|
|
expect(endpoint.suggestedFix).to.have.all.keys(suggestedFixProps);
|
|
}
|
|
});
|
|
return cb(null);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('The Validation option', function () {
|
|
var strictRequestMatchingSpec = path.join(__dirname, VALIDATION_DATA_FOLDER_PATH +
|
|
'/strictRequestMatchingSpec.yaml'),
|
|
strictRequestMatchingCollection = path.join(__dirname, VALIDATION_DATA_FOLDER_PATH +
|
|
'/strictRequestMatchingCollection.json'),
|
|
ignoreUnresolvedVariablesSpec = path.join(__dirname, VALIDATION_DATA_FOLDER_PATH +
|
|
'/ignoreUnresolvedVariablesSpec.yaml'),
|
|
ignoreUnresolvedVariablesCollection = path.join(__dirname, VALIDATION_DATA_FOLDER_PATH +
|
|
'/ignoreUnresolvedVariablesCollection.json'),
|
|
validateMetadataSpec = path.join(__dirname, VALIDATION_DATA_FOLDER_PATH +
|
|
'/validateMetadataSpec.yaml'),
|
|
validateMetadataCollection = path.join(__dirname, VALIDATION_DATA_FOLDER_PATH +
|
|
'/validateMetadataCollection.json'),
|
|
suggestAvailableFixesSpec = path.join(__dirname, VALIDATION_DATA_FOLDER_PATH +
|
|
'/suggestAvailableFixesSpec.yaml'),
|
|
suggestAvailableFixesCollection = path.join(__dirname, VALIDATION_DATA_FOLDER_PATH +
|
|
'/suggestAvailableFixesCollection.json'),
|
|
detailedBlobValidationSpec = path.join(__dirname, VALIDATION_DATA_FOLDER_PATH +
|
|
'/detailedBlobValidationSpec.yaml'),
|
|
detailedBlobValidationCollection = path.join(__dirname, VALIDATION_DATA_FOLDER_PATH +
|
|
'/detailedBlobValidationCollection.json');
|
|
|
|
describe('strictRequestMatching ', function () {
|
|
it('should strictly match collection request with corresponding schema endpoint/s', function (done) {
|
|
var schema = fs.readFileSync(strictRequestMatchingSpec, 'utf8'),
|
|
collection = fs.readFileSync(strictRequestMatchingCollection, 'utf8'),
|
|
schemaPack = new Converter.SchemaPack({ type: 'string', data: schema },
|
|
{ strictRequestMatching: true }),
|
|
historyRequest = [];
|
|
|
|
getAllTransactions(JSON.parse(collection), historyRequest);
|
|
schemaPack.validateTransaction(historyRequest, (err, result) => {
|
|
expect(err).to.be.null;
|
|
|
|
/**
|
|
Collection request contains
|
|
- GET /users/admin/:userId (userId = 12345)
|
|
- GET /users/admin/profile
|
|
|
|
Schema Endpoints contains
|
|
- GET /users/admin/profile
|
|
- GET /users/admin/{userId}
|
|
- GET /admin/{adminId}
|
|
|
|
For strictRequestMatching = false, both collection request matches with all 3 endpoints from schema,
|
|
and no endpoint will be present in missingEndpoints
|
|
For strictRequestMatching = true, we have matches as following
|
|
*/
|
|
// for endpoint "/users/admin/:userId" we should only one match with "/users/admin/{userId}"
|
|
expect(result.requests[historyRequest[0].id].endpoints).to.have.lengthOf(1);
|
|
|
|
// for endpoint "/users/admin/profile" we should have two matches first match with "/users/admin/profile"
|
|
// and second with "/users/admin/{userId}" as first match has more fixed matched segments
|
|
expect(result.requests[historyRequest[1].id].endpoints).to.have.lengthOf(2);
|
|
|
|
// endpoint "/admin/{adminId}"" should be present as missing endpoint
|
|
expect(result.missingEndpoints).to.have.lengthOf(1);
|
|
expect(result.missingEndpoints[0].endpoint).to.eql('GET /admin/{adminId}');
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('ignoreUnresolvedVariables ', function () {
|
|
it('should ignore all mismatches happening due to collection/environment variables present in request',
|
|
function (done) {
|
|
var schema = fs.readFileSync(ignoreUnresolvedVariablesSpec, 'utf8'),
|
|
collection = fs.readFileSync(ignoreUnresolvedVariablesCollection, 'utf8'),
|
|
schemaPack = new Converter.SchemaPack({ type: 'string', data: schema },
|
|
{ ignoreUnresolvedVariables: true }),
|
|
historyRequest = [];
|
|
|
|
getAllTransactions(JSON.parse(collection), historyRequest);
|
|
schemaPack.validateTransaction(historyRequest, (err, result) => {
|
|
var reqResultObj;
|
|
expect(err).to.be.null;
|
|
|
|
/**
|
|
Schema/Collection tested here contains pm variables in all parts checked.
|
|
i.e. path variable, query params, headers, req/res body etc
|
|
*/
|
|
reqResultObj = result.requests[_.keys(result.requests)[0]];
|
|
expect(reqResultObj.endpoints).to.have.lengthOf(1);
|
|
// checking for no mismatches in req and responses
|
|
expect(reqResultObj.endpoints[0].matched).to.be.true;
|
|
expect(reqResultObj.endpoints[0].mismatches).to.have.lengthOf(0);
|
|
_.forEach(reqResultObj.endpoints[0].responses, (response) => {
|
|
expect(response.matched).to.be.true;
|
|
expect(response.mismatches).to.have.lengthOf(0);
|
|
});
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('validateMetadata ', function () {
|
|
var schema = fs.readFileSync(validateMetadataSpec, 'utf8'),
|
|
collection = fs.readFileSync(validateMetadataCollection, 'utf8'),
|
|
schemaPack = new Converter.SchemaPack({ type: 'string', data: schema },
|
|
{ validateMetadata: true, suggestAvailableFixes: true }),
|
|
historyRequest = [],
|
|
resultObj1,
|
|
resultObj2;
|
|
|
|
before(function (done) {
|
|
getAllTransactions(JSON.parse(collection), historyRequest);
|
|
schemaPack.validateTransaction(historyRequest, (err, result) => {
|
|
expect(err).to.be.null;
|
|
resultObj1 = result.requests[historyRequest[0].id].endpoints[0];
|
|
resultObj2 = result.requests[historyRequest[1].id].endpoints[0];
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('should validate request name and description according to schema', function () {
|
|
expect(resultObj1.mismatches).to.have.lengthOf(2);
|
|
_.forEach(resultObj1.mismatches, (mismatch) => {
|
|
// check for suggested value to be one in schema
|
|
if (mismatch.property === 'REQUEST_NAME') {
|
|
expect(mismatch.reasonCode).to.eql('INVALID_VALUE');
|
|
expect(mismatch.reason).to.eql('The request name didn\'t match with specified schema');
|
|
expect(mismatch.suggestedFix.suggestedValue).to.eql('List all pets Updated');
|
|
}
|
|
else if (mismatch.property === 'REQUEST_DESCRIPTION') {
|
|
expect(mismatch.reasonCode).to.eql('INVALID_VALUE');
|
|
expect(mismatch.reason).to.eql('The request description didn\'t match with specified schema');
|
|
expect(mismatch.suggestedFix.suggestedValue).to.eql('Description for GET /pets - List all pets');
|
|
}
|
|
else {
|
|
throw Error('Unhandled mismatch property in test');
|
|
}
|
|
});
|
|
});
|
|
|
|
it('should handle empty and null request name and description in request', function () {
|
|
expect(resultObj2.mismatches).to.have.lengthOf(1);
|
|
expect(resultObj2.mismatches[0].property).to.eql('REQUEST_DESCRIPTION');
|
|
expect(resultObj2.mismatches[0].reasonCode).to.eql('INVALID_VALUE');
|
|
expect(resultObj2.mismatches[0].reason).to.eql('The request description didn\'t match with specified schema');
|
|
expect(resultObj2.mismatches[0].suggestedFix.actualValue).to.be.null;
|
|
expect(resultObj2.mismatches[0].suggestedFix.suggestedValue)
|
|
.to.eql('Description for POST /pets - Create a pet');
|
|
});
|
|
});
|
|
|
|
describe('suggestAvailableFixes ', function () {
|
|
var schema = fs.readFileSync(suggestAvailableFixesSpec, 'utf8'),
|
|
collection = fs.readFileSync(suggestAvailableFixesCollection, 'utf8'),
|
|
schemaPack = new Converter.SchemaPack({ type: 'string', data: schema },
|
|
{ suggestAvailableFixes: true }),
|
|
historyRequest = [],
|
|
resultObj,
|
|
responseResult,
|
|
propertyMismatchMap = {};
|
|
|
|
before(function (done) {
|
|
getAllTransactions(JSON.parse(collection), historyRequest);
|
|
schemaPack.validateTransaction(historyRequest, (err, result) => {
|
|
expect(err).to.be.null;
|
|
resultObj = result.requests[historyRequest[0].id].endpoints[0];
|
|
responseResult = resultObj.responses[historyRequest[0].response[0].id];
|
|
|
|
// check for expected mismatches length
|
|
expect(resultObj.mismatches).to.have.lengthOf(4);
|
|
expect(responseResult.mismatches).to.have.lengthOf(2);
|
|
|
|
// map all mismatch objects with it's property
|
|
_.forEach(_.concat(resultObj.mismatches, responseResult.mismatches), (mismatch) => {
|
|
propertyMismatchMap[mismatch.property] = mismatch;
|
|
});
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('should suggest valid available fix for all kind of violated properties', function () {
|
|
// check for all suggested value to be according to schema
|
|
expect(_.isInteger(propertyMismatchMap.PATHVARIABLE.suggestedFix.suggestedValue)).to.eql(true);
|
|
expect(propertyMismatchMap.QUERYPARAM.suggestedFix.suggestedValue).to.be.a('number');
|
|
expect(propertyMismatchMap.HEADER.suggestedFix.suggestedValue.value).to.be.a('boolean');
|
|
expect(propertyMismatchMap.HEADER.suggestedFix.suggestedValue.description)
|
|
.to.eql('(Required) Quantity of pets available');
|
|
expect(propertyMismatchMap.BODY.suggestedFix.suggestedValue.name).to.be.a('string');
|
|
expect(propertyMismatchMap.BODY.suggestedFix.suggestedValue.name.length >= 30).to.eql(true);
|
|
expect(propertyMismatchMap.BODY.suggestedFix.suggestedValue.tag).to.be.a('string');
|
|
expect(_.includes(['Bulldog', 'Retriever', 'Timberwolf', 'Grizzly', 'Husky'],
|
|
propertyMismatchMap.BODY.suggestedFix.suggestedValue.breeds[2])).to.eql(true);
|
|
expect(_.isInteger(propertyMismatchMap.RESPONSE_HEADER.suggestedFix.suggestedValue)).to.eql(true);
|
|
expect(_.isInteger(propertyMismatchMap.RESPONSE_BODY.suggestedFix.suggestedValue.code)).to.eql(true);
|
|
expect(propertyMismatchMap.RESPONSE_BODY.suggestedFix.suggestedValue.code % 7).to.equal(0);
|
|
expect(propertyMismatchMap.RESPONSE_BODY.suggestedFix.suggestedValue.message).to.be.a('string');
|
|
});
|
|
|
|
it('should maintain valid properties/items in suggested value', function () {
|
|
expect(propertyMismatchMap.BODY.suggestedFix.suggestedValue.petId).to.eql(
|
|
propertyMismatchMap.BODY.suggestedFix.actualValue.petId
|
|
);
|
|
expect(propertyMismatchMap.BODY.suggestedFix.suggestedValue.breeds[0]).to.eql(
|
|
propertyMismatchMap.BODY.suggestedFix.actualValue.breeds[0]
|
|
);
|
|
expect(propertyMismatchMap.BODY.suggestedFix.suggestedValue.breeds[1]).to.eql(
|
|
propertyMismatchMap.BODY.suggestedFix.actualValue.breeds[1]
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('detailedBlobValidation ', function () {
|
|
it('should provide detailed mismatches for each schema keyword violation', function (done) {
|
|
var schema = fs.readFileSync(detailedBlobValidationSpec, 'utf8'),
|
|
collection = fs.readFileSync(detailedBlobValidationCollection, 'utf8'),
|
|
schemaPack = new Converter.SchemaPack({ type: 'string', data: schema },
|
|
{ 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'
|
|
];
|
|
|
|
getAllTransactions(JSON.parse(collection), historyRequest);
|
|
schemaPack.validateTransaction(historyRequest, (err, result) => {
|
|
expect(err).to.be.null;
|
|
resultObj = result.requests[historyRequest[0].id].endpoints[0];
|
|
|
|
// map all mismatch objects with it's property
|
|
_.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);
|
|
|
|
// mark matched path as empty to ensure repetition does'n occur
|
|
violatedKeywords[_.indexOf(violatedKeywords, localJsonPath)] = '';
|
|
});
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('VALIDATE FUNCTION TESTS ', function () {
|
|
describe('validateTransaction function', function () {
|
|
it('Should not fail if spec to validate contains empty parameters', function (done) {
|
|
let emptyParameterSpec = fs.readFileSync(path.join(__dirname, VALIDATION_DATA_FOLDER_PATH +
|
|
'/emptyParameterSpec.yaml'), 'utf-8'),
|
|
emptyParameterCollection = fs.readFileSync(path.join(__dirname, VALIDATION_DATA_FOLDER_PATH +
|
|
'/emptyParameterCollection.json'), 'utf-8'),
|
|
resultObj,
|
|
historyRequest = [],
|
|
schemaPack = new Converter.SchemaPack({ type: 'string', data: emptyParameterSpec }, {});
|
|
|
|
getAllTransactions(JSON.parse(emptyParameterCollection), historyRequest);
|
|
|
|
schemaPack.validateTransaction(historyRequest, (err, result) => {
|
|
// Schema is sample petsore with one of parameter as empty, expect no mismatch / error
|
|
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(0);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('Should correctly handle transactionPath property when Implicit headers are present', function (done) {
|
|
let implicitHeaderSpec = fs.readFileSync(path.join(__dirname, VALIDATION_DATA_FOLDER_PATH +
|
|
'/implicitHeaderSpec.yaml'), 'utf-8'),
|
|
implicitHeaderCollection = fs.readFileSync(path.join(__dirname, VALIDATION_DATA_FOLDER_PATH +
|
|
'/implicitHeaderCollection.json'), 'utf-8'),
|
|
resultObj,
|
|
historyRequest = [],
|
|
schemaPack = new Converter.SchemaPack({ type: 'string', data: implicitHeaderSpec }, {});
|
|
|
|
getAllTransactions(JSON.parse(implicitHeaderCollection), 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];
|
|
|
|
expect(resultObj.mismatches).to.have.lengthOf(1);
|
|
|
|
/**
|
|
header-1 is invalid according to schema, as request contains other 2 implicit headers(Content-Type and Accept)
|
|
the mismatch for header-1 should contain correct index as in request.
|
|
*/
|
|
expect(_.endsWith(resultObj.mismatches[0].transactionJsonPath, '[2].value')).to.eql(true);
|
|
_.forEach(resultObj.responses, (response) => {
|
|
expect(response.matched).to.be.true;
|
|
expect(response.mismatches).to.have.lengthOf(0);
|
|
});
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('Should correctly suggest value when violated keyword is at root level', function (done) {
|
|
let rootKeywordViolationSpec = fs.readFileSync(path.join(__dirname, VALIDATION_DATA_FOLDER_PATH +
|
|
'/rootKeywordViolationSpec.yaml'), 'utf-8'),
|
|
rootKeywordViolationCollection = fs.readFileSync(path.join(__dirname, VALIDATION_DATA_FOLDER_PATH +
|
|
'/rootKeywordViolationCollection.json'), 'utf-8'),
|
|
options = { suggestAvailableFixes: true },
|
|
resultObj,
|
|
historyRequest = [],
|
|
schemaPack = new Converter.SchemaPack({ type: 'string', data: rootKeywordViolationSpec }, options);
|
|
|
|
getAllTransactions(JSON.parse(rootKeywordViolationCollection), 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];
|
|
|
|
expect(resultObj.mismatches).to.have.lengthOf(1);
|
|
|
|
/**
|
|
The spec contains "POST /pet" endpoint with request body as Pet object which requires minimum property of 4
|
|
as this property is root (Json path to prop is ''(empty), we expect suggested value to be according to spec)
|
|
*/
|
|
expect(_.keys(resultObj.mismatches[0].suggestedFix.actualValue)).to.have.lengthOf(3);
|
|
expect(_.keys(resultObj.mismatches[0].suggestedFix.suggestedValue)).to.have.lengthOf(4);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('Should correctly suggest value when property is vioalting multiple keywords', function (done) {
|
|
let doubleValidationFixSpec = fs.readFileSync(path.join(__dirname, VALIDATION_DATA_FOLDER_PATH +
|
|
'/doubleValidationFixSpec.yaml'), 'utf-8'),
|
|
options = { requestParametersResolution: 'Example', suggestAvailableFixes: true },
|
|
resultObj,
|
|
schemaPack = new Converter.SchemaPack({ type: 'string', data: doubleValidationFixSpec }, 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');
|
|
resultObj = result.requests[historyRequest[0].id].endpoints[0];
|
|
|
|
expect(resultObj.mismatches).to.have.lengthOf(1);
|
|
|
|
/**
|
|
The spec contains request body which has name and identifier props as required which is
|
|
violated in collection. We expect both props to be present and valid according to schema.
|
|
*/
|
|
expect(resultObj.mismatches[0].suggestedFix.suggestedValue).to.contain.keys(['name', 'identifier']);
|
|
expect(resultObj.mismatches[0].suggestedFix.suggestedValue.name).to.be.a('string');
|
|
expect(resultObj.mismatches[0].suggestedFix.suggestedValue.identifier).to.be.a('string');
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
|
|
it('Should correctly handle internal $ref when present', function (done) {
|
|
let internalRefsSpec = fs.readFileSync(path.join(__dirname, VALIDATION_DATA_FOLDER_PATH +
|
|
'/internalRefsSpec.yaml'), 'utf-8'),
|
|
internalRefsCollection = fs.readFileSync(path.join(__dirname, VALIDATION_DATA_FOLDER_PATH +
|
|
'/internalRefsCollection.json'), 'utf-8'),
|
|
resultObj,
|
|
options = {
|
|
showMissingInSchemaErrors: true,
|
|
strictRequestMatching: true,
|
|
ignoreUnresolvedVariables: true,
|
|
validateMetadata: true,
|
|
suggestAvailableFixes: true,
|
|
detailedBlobValidation: false
|
|
},
|
|
historyRequest = [],
|
|
schemaPack = new Converter.SchemaPack({ type: 'string', data: internalRefsSpec }, options);
|
|
|
|
getAllTransactions(JSON.parse(internalRefsCollection), 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];
|
|
|
|
// no mismatches should be found when resolved correctly
|
|
expect(resultObj.matched).to.be.true;
|
|
expect(resultObj.mismatches).to.have.lengthOf(0);
|
|
_.forEach(resultObj.responses, (response) => {
|
|
expect(response.matched).to.be.true;
|
|
expect(response.mismatches).to.have.lengthOf(0);
|
|
});
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('Should correctly match and validate valid json content type with collection req/res body', function (done) {
|
|
let differentContentTypesSpec = fs.readFileSync(path.join(__dirname, VALIDATION_DATA_FOLDER_PATH +
|
|
'/differentContentTypesSpec.yaml'), 'utf-8'),
|
|
differentContentTypesCollection = fs.readFileSync(path.join(__dirname, VALIDATION_DATA_FOLDER_PATH +
|
|
'/differentContentTypesCollection.json'), 'utf-8'),
|
|
resultObj,
|
|
historyRequest = [],
|
|
options = {
|
|
suggestAvailableFixes: true
|
|
},
|
|
schemaPack = new Converter.SchemaPack({ type: 'string', data: differentContentTypesSpec }, options);
|
|
|
|
getAllTransactions(JSON.parse(differentContentTypesCollection), 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];
|
|
|
|
/**
|
|
* Both req and res body should match with schema content object and each have one mismatch
|
|
*/
|
|
expect(resultObj.mismatches).to.have.lengthOf(1);
|
|
expect(resultObj.mismatches[0].property).to.equal('BODY');
|
|
expect(resultObj.responses[_.keys(resultObj.responses)[0]].mismatches).to.have.lengthOf(1);
|
|
expect(resultObj.responses[_.keys(resultObj.responses)[0]].mismatches[0].property).to.equal('RESPONSE_BODY');
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('Should be able to validate and suggest correct value for body with primitive data type', function (done) {
|
|
let primitiveDataTypeBodySpec = fs.readFileSync(path.join(__dirname, VALIDATION_DATA_FOLDER_PATH +
|
|
'/primitiveDataTypeBodySpec.yaml'), 'utf-8'),
|
|
primitiveDataTypeBodyCollection = fs.readFileSync(path.join(__dirname, VALIDATION_DATA_FOLDER_PATH +
|
|
'/primitiveDataTypeBodyCollection.json'), 'utf-8'),
|
|
resultObj,
|
|
responseObj,
|
|
historyRequest = [],
|
|
options = {
|
|
showMissingInSchemaErrors: true,
|
|
strictRequestMatching: true,
|
|
ignoreUnresolvedVariables: true,
|
|
validateMetadata: true,
|
|
suggestAvailableFixes: true,
|
|
detailedBlobValidation: false
|
|
},
|
|
schemaPack = new Converter.SchemaPack({ type: 'string', data: primitiveDataTypeBodySpec }, options);
|
|
|
|
getAllTransactions(JSON.parse(primitiveDataTypeBodyCollection), historyRequest);
|
|
|
|
schemaPack.validateTransaction(historyRequest, (err, result) => {
|
|
expect(err).to.be.null;
|
|
expect(result).to.be.an('object');
|
|
|
|
// request body is boolean
|
|
resultObj = result.requests[historyRequest[0].id].endpoints[0];
|
|
expect(resultObj.mismatches).to.have.lengthOf(0);
|
|
|
|
// request body is integer
|
|
responseObj = resultObj.responses[_.keys(resultObj.responses)[0]];
|
|
expect(responseObj.mismatches).to.have.lengthOf(1);
|
|
expect(responseObj.mismatches[0].suggestedFix.suggestedValue).to.be.within(5, 10);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('Should correctly validate schema having path with various path variables', function (done) {
|
|
let multiplePathVarSpec = fs.readFileSync(path.join(__dirname, VALIDATION_DATA_FOLDER_PATH +
|
|
'/multiplePathVarSpec.json'), 'utf-8'),
|
|
multiplePathVarCollection = fs.readFileSync(path.join(__dirname, VALIDATION_DATA_FOLDER_PATH +
|
|
'/multiplePathVarCollection.json'), 'utf-8'),
|
|
resultObj1,
|
|
resultObj2,
|
|
historyRequest = [],
|
|
options = {
|
|
showMissingInSchemaErrors: true,
|
|
strictRequestMatching: true,
|
|
ignoreUnresolvedVariables: true,
|
|
suggestAvailableFixes: true
|
|
},
|
|
schemaPack = new Converter.SchemaPack({ type: 'string', data: multiplePathVarSpec }, options);
|
|
|
|
getAllTransactions(JSON.parse(multiplePathVarCollection), historyRequest);
|
|
|
|
schemaPack.validateTransaction(historyRequest, (err, result) => {
|
|
expect(err).to.be.null;
|
|
expect(result).to.be.an('object');
|
|
|
|
resultObj1 = result.requests[historyRequest[0].id].endpoints[0];
|
|
expect(resultObj1.mismatches).to.have.lengthOf(0);
|
|
|
|
resultObj2 = result.requests[historyRequest[1].id].endpoints[0];
|
|
expect(resultObj2.mismatches).to.have.lengthOf(1);
|
|
expect(resultObj2.mismatches[0].reasonCode).to.eql('MISSING_IN_REQUEST');
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('Should ignore mismatches for nested objects in parameters', function (done) {
|
|
let nestedObjectParamsSpec = fs.readFileSync(path.join(__dirname, VALIDATION_DATA_FOLDER_PATH +
|
|
'/nestedObjectParamsSpec.yaml'), 'utf-8'),
|
|
nestedObjectParamsCollection = fs.readFileSync(path.join(__dirname, VALIDATION_DATA_FOLDER_PATH +
|
|
'/nestedObjectParamsCollection.json'), 'utf-8'),
|
|
resultObj,
|
|
historyRequest = [],
|
|
options = {
|
|
showMissingInSchemaErrors: true,
|
|
strictRequestMatching: true,
|
|
ignoreUnresolvedVariables: true,
|
|
suggestAvailableFixes: true
|
|
},
|
|
schemaPack = new Converter.SchemaPack({ type: 'string', data: nestedObjectParamsSpec }, options);
|
|
|
|
getAllTransactions(JSON.parse(nestedObjectParamsCollection), 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];
|
|
expect(resultObj.mismatches).to.have.lengthOf(0);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('Should be able to validate schema with deepObject style query params against corresponding ' +
|
|
'transactions', function (done) {
|
|
let queryParamDeepObjectSpec = fs.readFileSync(path.join(__dirname, VALIDATION_DATA_FOLDER_PATH +
|
|
'/queryParamDeepObjectSpec.yaml'), 'utf-8'),
|
|
queryParamDeepObjectCollection = fs.readFileSync(path.join(__dirname, VALIDATION_DATA_FOLDER_PATH +
|
|
'/queryParamDeepObjectCollection.json'), 'utf-8'),
|
|
resultObj,
|
|
historyRequest = [],
|
|
schemaPack = new Converter.SchemaPack({ type: 'string', data: queryParamDeepObjectSpec },
|
|
{ suggestAvailableFixes: true, showMissingInSchemaErrors: true });
|
|
|
|
getAllTransactions(JSON.parse(queryParamDeepObjectCollection), 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];
|
|
expect(resultObj.mismatches).to.have.lengthOf(2);
|
|
|
|
/**
|
|
* no mismatches should be found for complex array type params as validation is skipped for them,
|
|
* even though corresponding value is of incorrect type
|
|
*/
|
|
_.forEach(resultObj.mismatches, (mismatch) => {
|
|
expect(mismatch.suggestedFix.key).to.not.eql('propArrayComplex[0][prop1ArrayComp]');
|
|
});
|
|
|
|
// for deepObject param "user", child param "user[id]" is of incorrect type
|
|
expect(resultObj.mismatches[0].reasonCode).to.eql('INVALID_TYPE');
|
|
expect(resultObj.mismatches[0].transactionJsonPath).to.eql('$.request.url.query[0].value');
|
|
expect(resultObj.mismatches[0].suggestedFix.actualValue).to.eql('notAnInteger');
|
|
expect(resultObj.mismatches[0].suggestedFix.suggestedValue).to.eql(123);
|
|
|
|
// for deepObject param "user", child param "user[address][country]" is missing in transaction
|
|
expect(resultObj.mismatches[1].reasonCode).to.eql('MISSING_IN_REQUEST');
|
|
expect(resultObj.mismatches[1].suggestedFix.key).to.eql('user[address][country]');
|
|
expect(resultObj.mismatches[1].suggestedFix.actualValue).to.be.null;
|
|
expect(resultObj.mismatches[1].suggestedFix.suggestedValue).to.eql({
|
|
key: 'user[address][country]',
|
|
value: 'India',
|
|
description: '(Required) info about user'
|
|
});
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('Should be able to correctly validate composite schemas with anyOf, oneOf and allOf keywords correctly ' +
|
|
'against corresponding transactions', function (done) {
|
|
let compositeSchemaSpec = fs.readFileSync(path.join(__dirname, VALIDATION_DATA_FOLDER_PATH +
|
|
'/compositeSchemaSpec.yaml'), 'utf-8'),
|
|
compositeSchemaCollection = fs.readFileSync(path.join(__dirname, VALIDATION_DATA_FOLDER_PATH +
|
|
'/compositeSchemaCollection.json'), 'utf-8'),
|
|
resultObjAnyOf,
|
|
resultObjOneOf,
|
|
resultObjAllOf,
|
|
historyRequest = [],
|
|
schemaPack = new Converter.SchemaPack({ type: 'string', data: compositeSchemaSpec },
|
|
{ suggestAvailableFixes: true, showMissingInSchemaErrors: true });
|
|
|
|
getAllTransactions(JSON.parse(compositeSchemaCollection), historyRequest);
|
|
|
|
schemaPack.validateTransaction(historyRequest, (err, result) => {
|
|
expect(err).to.be.null;
|
|
expect(result).to.be.an('object');
|
|
resultObjAnyOf = result.requests[historyRequest[0].id].endpoints[0];
|
|
resultObjOneOf = result.requests[historyRequest[1].id].endpoints[0];
|
|
resultObjAllOf = result.requests[historyRequest[2].id].endpoints[0];
|
|
|
|
/**
|
|
* no mismatches should be found here even though value present in collection
|
|
* is only valid as per 2nd element of anyOf keyword here
|
|
*/
|
|
expect(resultObjAnyOf.mismatches).to.have.lengthOf(0);
|
|
|
|
/**
|
|
* no mismatches should be found here even though key present in collection request body
|
|
* is only valid as per 2nd element of oneOf keyword here
|
|
*/
|
|
expect(resultObjOneOf.mismatches).to.have.lengthOf(0);
|
|
|
|
//
|
|
expect(resultObjAllOf.mismatches).to.have.lengthOf(1);
|
|
expect(resultObjAllOf.mismatches[0].reasonCode).to.eql('INVALID_BODY');
|
|
expect(resultObjAllOf.mismatches[0].transactionJsonPath).to.eql('$.request.body');
|
|
expect(resultObjAllOf.mismatches[0].schemaJsonPath).to
|
|
.eql('$.paths[/pets/allOf].post.requestBody.content[application/json].schema');
|
|
expect(resultObjAllOf.mismatches[0].suggestedFix.actualValue).to.eql({
|
|
objectType: 'not an integer',
|
|
objectType2: 'prop named objectType2'
|
|
});
|
|
expect(resultObjAllOf.mismatches[0].suggestedFix.suggestedValue).to.eql({
|
|
objectType: 4321,
|
|
objectType2: 'prop named objectType2'
|
|
});
|
|
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('getPostmanUrlSuffixSchemaScore function', function () {
|
|
it('Should maintain correct order in which path vaiables occur in result', function (done) {
|
|
let pmSuffix = ['pets', '123', '456', '789'],
|
|
schemaPath = ['pets', '{petId1}', '{petId2}', '{petId3}'],
|
|
result;
|
|
|
|
result = schemaUtils.getPostmanUrlSuffixSchemaScore(pmSuffix, schemaPath, { strictRequestMatching: true });
|
|
|
|
expect(result.match).to.be.true;
|
|
expect(result.pathVars).to.have.lengthOf(3);
|
|
expect(result.pathVars[0]).to.deep.equal({ key: 'petId1', value: pmSuffix[1] });
|
|
expect(result.pathVars[1]).to.deep.equal({ key: 'petId2', value: pmSuffix[2] });
|
|
expect(result.pathVars[2]).to.deep.equal({ key: 'petId3', value: pmSuffix[3] });
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('Should be able to validate schema with request body of content type "application/x-www-form-urlencoded" ' +
|
|
'against transaction with valid UrlEncoded body correctly', function (done) {
|
|
let urlencodedBodySpec = fs.readFileSync(path.join(__dirname, VALIDATION_DATA_FOLDER_PATH +
|
|
'/urlencodedBodySpec.yaml'), 'utf-8'),
|
|
urlencodedBodyCollection = fs.readFileSync(path.join(__dirname, VALIDATION_DATA_FOLDER_PATH +
|
|
'/urlencodedBodyCollection.json'), 'utf-8'),
|
|
resultObj,
|
|
historyRequest = [],
|
|
schemaPack = new Converter.SchemaPack({ type: 'string', data: urlencodedBodySpec },
|
|
{ suggestAvailableFixes: true, showMissingInSchemaErrors: true });
|
|
|
|
getAllTransactions(JSON.parse(urlencodedBodyCollection), 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];
|
|
expect(resultObj.mismatches).to.have.lengthOf(5);
|
|
|
|
/**
|
|
* no mismatches should be found for complex array type params as validation is skipped for them,
|
|
* even though corresponding value is of incorrect type
|
|
*/
|
|
_.forEach(resultObj.mismatches, (mismatch) => {
|
|
expect(mismatch.suggestedFix.key).to.not.eql('propArrayComplex[0][prop1ArrayComp]');
|
|
});
|
|
|
|
// for explodable property of type object named "propObjectExplodable",
|
|
// second property named "prop2" is incorrect, while property "prop1" is correct
|
|
expect(resultObj.mismatches[0].transactionJsonPath).to.eql('$.request.body.urlencoded[1].value');
|
|
expect(resultObj.mismatches[0].suggestedFix.actualValue).to.eql('false');
|
|
expect(resultObj.mismatches[0].suggestedFix.suggestedValue).to.eql('world');
|
|
|
|
// for non explodable property of type object, entire property with updated value should be suggested
|
|
expect(resultObj.mismatches[1].transactionJsonPath).to.eql('$.request.body.urlencoded[2].value');
|
|
expect(resultObj.mismatches[1].suggestedFix.actualValue).to.eql('prop3,hello,prop4,true');
|
|
expect(resultObj.mismatches[1].suggestedFix.suggestedValue).to.eql('prop3,hello,prop4,world');
|
|
|
|
// for type array property named "propArray" second element is incorrect
|
|
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');
|
|
|
|
// for deepObject property named "propDeepObject" child param "propDeepObject[address][city]" is of incorrect type
|
|
expect(resultObj.mismatches[3].transactionJsonPath).to.eql('$.request.body.urlencoded[8].value');
|
|
expect(resultObj.mismatches[3].suggestedFix.actualValue).to.eql('123');
|
|
expect(resultObj.mismatches[3].suggestedFix.suggestedValue).to.eql('Delhi');
|
|
|
|
// property named "propMissingInReq" is missing in request
|
|
expect(resultObj.mismatches[4].reasonCode).to.eql('MISSING_IN_REQUEST');
|
|
expect(resultObj.mismatches[4].suggestedFix.actualValue).to.eql(null);
|
|
expect(resultObj.mismatches[4].suggestedFix.suggestedValue.key).to.eql('propMissingInReq');
|
|
expect(resultObj.mismatches[4].suggestedFix.suggestedValue.description)
|
|
.to.eql('(Required) This property is not available in matched collection.');
|
|
done();
|
|
});
|
|
});
|
|
|
|
describe('findMatchingRequestFromSchema function', function () {
|
|
it('#GITHUB-9396 Should maintain correct order of matched endpoint', function (done) {
|
|
let schema = {
|
|
paths: {
|
|
'/lookups': {
|
|
'get': { 'summary': 'Lookup Job Values' }
|
|
},
|
|
'/{jobid}': {
|
|
'get': {
|
|
'summary': 'Get Job by ID',
|
|
'parameters': [
|
|
{
|
|
'in': 'path',
|
|
'name': 'jobid',
|
|
'schema': {
|
|
'type': 'string'
|
|
},
|
|
'required': true,
|
|
'description': 'Unique identifier for a job to retrieve.',
|
|
'example': '{{jobid}}'
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
},
|
|
schemaPath = '{{baseUrl}}/{{jobid}}',
|
|
result;
|
|
|
|
result = schemaUtils.findMatchingRequestFromSchema('GET', schemaPath, schema, { strictRequestMatching: true });
|
|
|
|
expect(result).to.have.lengthOf(2);
|
|
expect(result[0].name).to.eql('GET /{jobid}');
|
|
expect(result[1].name).to.eql('GET /lookups');
|
|
done();
|
|
});
|
|
});
|
|
});
|