liveTest support, foundation work to merge allOf and set additionalProperties to false

This commit is contained in:
Amar Zavery 2017-01-19 20:56:42 -08:00
Родитель afdadcae54
Коммит 6883f91690
4 изменённых файлов: 245 добавлений и 19 удалений

Просмотреть файл

@ -13,6 +13,7 @@ var Sway = require('sway'),
Constants = require('./util/constants'),
log = require('./util/logging'),
ResponseWrapper = require('./responseWrapper'),
jsonRefs = require('json-refs'),
ErrorCodes = Constants.ErrorCodes;
class SpecValidator {
@ -44,6 +45,55 @@ class SpecValidator {
}
}
findObject(what, where, actualReference, docPath) {
let self = this;
let result = eval(where[what]);
if (!result) {
let msg = `Object '${what}' from the given reference ` +
`'${actualReference}' is not found in the swagger spec '${docPath}'.`;
let e = self.constructErrorObject('OBJECT_NOT_FOUND', msg);
return Promise.reject(e);
} else {
return Promise.resolve(result);
}
}
getDefinitionFromReference(reference) {
let self = this;
if (!reference || (reference && reference.trim().length > 0)) {
throw new Error('reference cannot be null or undefined and it must be a non-empty string.');
}
let refObj = utils.parseReferenceInSwagger(reference);
let result;
if (refObj.filePath) {
let docPath = utils.joinPath(self.specDir, refObj.filePath);
utils.parseJson(docPath).then(function(result) {
return self.findObject(refObj.localReference.accessorProperty, result, refObj.localReference.value, docPath);
}).catch(function (err) {
return Promise.reject(err);
});
} else {
return self.findObject(refObj.localReference.accessorProperty, self.specInJson, refObj.localReference.value, self.specPath);
}
}
makeModelsStricter() {
let self = this;
let spec = self.specInJson;
for (let model in spec) {
if (spec[model] && spec[model].allOf) {
spec.model.allOf.forEach(function(item) {
self.getDefinitionFromReference(item).then(function(result) {
//do something with this model.
}).catch(function(err) {
return Promise.reject(err);
})
});
}
}
return;
}
updateValidityStatus(value) {
if (!Boolean(value)) {
this.specValidationResult.validityStatus = false;
@ -72,6 +122,13 @@ class SpecValidator {
return utils.parseJson(self.specPath).then(function (result) {
self.specInJson = result;
self.unifyXmsPaths();
return self;
}).then(function () {
let options = {subDocPath: '#/definitions'};
return jsonRefs.findRefs(self.specInJson, options);
}).then(function(res) {
console.log(res);
}).then(function() {
let options = {};
options.definition = self.specInJson;
options.jsonRefs = {};
@ -361,7 +418,7 @@ class SpecValidator {
}
if (exampleParameterValues === null || exampleParameterValues === undefined || typeof exampleParameterValues !== 'object') {
throw new Error('exampleParameterValues cannot be null or undefined and must be of type \'object\' (A dictionary of key-value pairs of parameter-names and their values).');
throw new Error(`In operation "${operation.operationId}", exampleParameterValues cannot be null or undefined and must be of type "object" (A dictionary of key-value pairs of parameter-names and their values).`);
}
let parameters = operation.getParameters();
let options = {};
@ -370,7 +427,7 @@ class SpecValidator {
parameters.forEach(function(parameter) {
if (!exampleParameterValues[parameter.name]) {
if (parameter.required) {
throw new Error(`Parameter ${parameter.name} is required in the swagger spec but is not present in the provided example parameter values.`);
throw new Error(`In operation "${operation.operationId}", parameter ${parameter.name} is required in the swagger spec but is not present in the provided example parameter values.`);
}
return;
}

Просмотреть файл

@ -185,4 +185,75 @@ exports.generateRandomId = function generateRandomId(prefix, existingIds) {
return randomStr;
};
/*
* Parses a [inline|relative] [model|parameter] reference in the swagger spec.
* This method does not handle parsing paths "/subscriptions/{subscriptionId}/etc.".
*
* @param {string} reference Reference to be parsed.
*
* @return {object} result
* {string} [result.filePath] Filepath present in the reference. Examples are:
* - '../newtwork.json#/definitions/Resource' => '../network.json'
* - '../examples/nic_create.json' => '../examples/nic_create.json'
* {object} [result.localReference] Provides information about the local reference in the json document.
* {string} [result.localReference.value] The json reference value. Examples are:
* - '../newtwork.json#/definitions/Resource' => '#/definitions/Resource'
* - '#/parameters/SubscriptionId' => '#/parameters/SubscriptionId'
* {string} [result.localReference.accessorProperty] The json path expression that can be used by
* eval() to access the desired object. Examples are:
* - '../newtwork.json#/definitions/Resource' => 'definitions.Resource'
* - '#/parameters/SubscriptionId' => 'parameters,SubscriptionId'
*/
exports.parseReferenceInSwagger = function parseReferenceInSwagger(reference) {
if (!reference || (reference && reference.trim().length > 0)) {
throw new Error('reference cannot be null or undefined and it must be a non-empty string.');
}
let result = {};
if (reference.includes('#')) {
//local reference in the doc
if (reference.startsWith('#/')) {
result.localReference = {};
result.localReference.value = reference;
result.localReference.accessorProperty = reference.slice(2).replace('/', '.');
} else {
//filePath+localReference
let segments = reference.split('#');
result.filePath = segments[0];
result.localReference = {};
result.localReference.value = '#' + segments[1];
result.localReference.accessorProperty = segments[1].slice(1).replace('/', '.');
}
} else {
//we are assuming that the string is a relative filePath
result.filePath = reference;
}
return result;
};
/*
* Same as path.join(), however, it converts backward slashes to forward slashes.
* This is required because path.join() joins the paths and converts all the
* forward slashes to backward slashes if executed on a windows system. This can
* be problematic while joining a url. For example:
* path.join('https://github.com/Azure/openapi-validation-tools/blob/master/lib', '../examples/foo.json') returns
* 'https:\\github.com\\Azure\\openapi-validation-tools\\blob\\master\\examples\\foo.json' instead of
* 'https://github.com/Azure/openapi-validation-tools/blob/master/examples/foo.json'
*
* @param variable number of arguments and all the arguments must be of type string. Similar to the API
* provided by path.join() https://nodejs.org/dist/latest-v6.x/docs/api/path.html#path_path_join_paths
* @return {string} resolved path
*/
exports.joinPath = function joinPath() {
let finalPath = '';
for (arg in arguments) {
finalPath = path.join(finalPath, arguments[arg]);
}
finalPath = finalPath.replace(/\\/gi, '/');
finalPath = finalPath.replace(/^(http|https):\/(.*)/gi, '$1://$2');
console.log(finalPath);
return finalPath;
};
exports = module.exports;

Просмотреть файл

@ -10,14 +10,15 @@
"description": "Validate Azure REST API Specifications",
"license": "MIT",
"dependencies": {
"azure-arm-resource": "^1.6.1-preview",
"moment": "^2.14.1",
"ms-rest": "^1.15.2",
"ms-rest-azure": "^1.15.2",
"request": "^2.79.0",
"swagger-tools": "^0.10.1",
"sway": "^1.0.0",
"winston": "^2.3.0",
"yargs": "^6.6.0",
"ms-rest": "^1.15.2",
"ms-rest-azure": "^1.15.2"
"yargs": "^6.6.0"
},
"homepage": "https://github.com/azure/openapi-validator-tools",
"repository": {

Просмотреть файл

@ -3,9 +3,14 @@
'use strict';
var log = require('./lib/util/logging'),
var msrest = require('ms-rest'),
msrestazure = require('ms-rest-azure'),
ResourceManagementClient = require('azure-arm-resource').ResourceManagementClient,
log = require('./lib/util/logging'),
utils = require('./lib/util/utils'),
Constants = require('./lib/util/constants'),
path = require('path'),
util = require('util'),
SpecValidator = require('./lib/specValidator');
exports.finalValidationResult = { validityStatus: true };
@ -42,7 +47,7 @@ exports.validateSpec = function validateSpec(specPath, json) {
let validator = new SpecValidator(specPath);
exports.finalValidationResult[specPath] = validator.specValidationResult;
validator.initialize().then(function() {
log.info(`\n> Semantically validating ${specPath}:\n`);
log.info(`Semantically validating ${specPath}:\n`);
validator.validateSpec();
exports.updateEndResultOfSingleValidation(validator);
exports.logDetailedInfo(validator, json);
@ -53,20 +58,12 @@ exports.validateSpec = function validateSpec(specPath, json) {
});
};
exports.executeSequentially = function executeSequentially(promiseFactories) {
var result = Promise.resolve();
promiseFactories.forEach(function (promiseFactory) {
result = result.then(promiseFactory);
});
return result;
};
exports.validateCompositeSpec = function validateCompositeSpec(compositeSpecPath, json){
return exports.getDocumentsFromCompositeSwagger(compositeSpecPath).then(function(docs) {
let promiseFactories = docs.map(function(doc) {
return exports.validateSpec(doc, json);
});
return exports.executeSequentially(promiseFactories);
return utils.executePromisesSequentially(promiseFactories);
}).catch(function (err) {
log.error(err);
});
@ -76,7 +73,7 @@ exports.validateExamples = function validateExamples(specPath, operationIds, jso
let validator = new SpecValidator(specPath);
exports.finalValidationResult[specPath] = validator.specValidationResult;
validator.initialize().then(function() {
log.info(`\n> Validating "examples" and "x-ms-examples" in ${specPath}:\n`);
log.info(`Validating "examples" and "x-ms-examples" in ${specPath}:\n`);
validator.validateOperations(operationIds);
exports.updateEndResultOfSingleValidation(validator);
exports.logDetailedInfo(validator, json);
@ -91,7 +88,7 @@ exports.validateExamplesInCompositeSpec = function validateExamplesInCompositeSp
let promiseFactories = docs.map(function(doc) {
return exports.validateExamples(doc, json);
});
return exports.executeSequentially(promiseFactories);
return utils.executePromisesSequentially(promiseFactories);
}).catch(function (err) {
log.error(err);
});
@ -107,7 +104,7 @@ exports.updateEndResultOfSingleValidation = function updateEndResultOfSingleVali
exports.finalValidationResult.validityStatus = validator.specValidationResult.validityStatus;
}
return;
}
};
exports.logDetailedInfo = function logDetailedInfo(validator, json) {
if (json) {
@ -123,4 +120,104 @@ exports.logDetailedInfo = function logDetailedInfo(validator, json) {
}
};
exports.sanitizeParameters = function sanitizeParameters(exampleParameters) {
if (exampleParameters) {
if (exampleParameters.subscriptionId) {
exampleParameters.subscriptionId = process.env['AZURE_SUBSCRIPTION_ID'];
}
if (exampleParameters.resourceGroupName) {
exampleParameters.resourceGroupName = process.env['AZURE_RESOURCE_GROUP'];
}
}
return exampleParameters;
};
exports.liveTest = function liveTest(specPath, operationId) {
exports.validateEnvironmentVariables();
log.transports.console.level = 'info';
let clientId = process.env['CLIENT_ID'];
let domain = process.env['DOMAIN'];
let secret = process.env['APPLICATION_SECRET'];
let subscriptionId = process.env['AZURE_SUBSCRIPTION_ID'];
let location = process.env['AZURE_LOCATION'] || 'westus';
let resourceGroupName = process.env['AZURE_RESOURCE_GROUP'] || utils.generateRandomId('testrg');
let validator = new SpecValidator(specPath);
let operation, xmsExamples;
validator.initialize().then(function() {
log.info(`Running live test using x-ms-examples in ${operationId} in ${specPath}:\n`);
operation = validator.getOperationById(operationId);
xmsExamples = operation[Constants.xmsExamples];
if (xmsExamples) {
for (let scenario in xmsExamples) {
let xmsExample = xmsExamples[scenario];
let parameters = exports.sanitizeParameters(xmsExample.parameters);
let result = validator.validateRequest(operation, xmsExample.parameters);
if (result.validationResult && result.validationResult.errors && result.validationResult.errors.length) {
let msg = `Cannot proceed ahead with the live test for operation: ${operationId}. Found validation errors in the request.\n` +
`${util.inspect(result.validationResult.errors, {depth: null})}`;
throw new Error(msg);
}
let req = result.request;
if (!req) {
throw new Error(`Cannot proceed ahead with the live test for operation: ${operationId}. The request object is undefined.`);
}
if (req.body !== null && req.body !== undefined) {
req.body = JSON.stringify(req.body);
}
msrestazure.loginWithServicePrincipalSecret(clientId, secret, domain, function(err, creds, subscriptions) {
if (err) {
throw err;
}
let resourceClient = new ResourceManagementClient(creds, subscriptionId);
let client = new msrestazure.AzureServiceClient(creds);
exports.createResourceGroup(resourceClient, location, resourceGroupName, function(err, resourceGroup) {
if (err) {
throw err;
}
client.sendRequest(req, function(err, result, request, response) {
log.info(request);
log.info(response);
if (err) {
throw err;
}
log.info(result);
});
});
});
}
}
}).catch(function (err) {
log.error(err);
});
};
exports.createResourceGroup = function createResourceGroup(resourceClient, location, resourceGroupName, callback) {
resourceClient.resourceGroups.get(resourceGroupName, function (err, result, request, response) {
if (err && err.statusCode === 404) {
log.info(`Creating resource group: \'${resourceGroupName}\', if not present.`);
let groupParameters = { location: location, tags: { 'live-test': 'live-test'} };
return resourceClient.resourceGroups.createOrUpdate(resourceGroupName, groupParameters, callback);
} else {
return callback(null, result);
}
});
};
exports.getResourceGroup = function getResourceGroup(resourceClient, resourceGroupName, callback) {
log.info(`Searching ResourceGroup: ${resourceGroupName}.`)
resourceClient.resourceGroups.get(resourceGroupName, callback);
}
exports.validateEnvironmentVariables = function validateEnvironmentVariables() {
var envs = [];
if (!process.env['CLIENT_ID']) envs.push('CLIENT_ID');
if (!process.env['DOMAIN']) envs.push('DOMAIN');
if (!process.env['APPLICATION_SECRET']) envs.push('APPLICATION_SECRET');
if (!process.env['AZURE_SUBSCRIPTION_ID']) envs.push('AZURE_SUBSCRIPTION_ID');
if (envs.length > 0) {
throw new Error(util.format('please set/export the following environment variables: %s', envs.toString()));
}
};
exports = module.exports;