liveTest support, foundation work to merge allOf and set additionalProperties to false
This commit is contained in:
Родитель
afdadcae54
Коммит
6883f91690
|
@ -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": {
|
||||
|
|
125
validate.js
125
validate.js
|
@ -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;
|
Загрузка…
Ссылка в новой задаче