Коммит
9c59fd3073
|
@ -11,10 +11,14 @@
|
|||
"program": "${workspaceRoot}/cli.js",
|
||||
"cwd": "${workspaceRoot}",
|
||||
"args": [
|
||||
"validate-spec",
|
||||
"arm-storage/2016-01-01/swagger/storage.json",
|
||||
"validate-example",
|
||||
"..\\upstream\\azure-rest-api-specs\\arm-customer-insights\\2016-01-01\\swagger\\customer-insights.json",
|
||||
"-o",
|
||||
"Interactions_CreateOrUpdate",
|
||||
"-j"
|
||||
]
|
||||
],
|
||||
"env": {
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
|
|
|
@ -3,12 +3,13 @@
|
|||
|
||||
'use strict';
|
||||
|
||||
var Sway = require('sway'),
|
||||
util = require('util'),
|
||||
msRest = require('ms-rest'),
|
||||
HttpRequest = msRest.WebResource,
|
||||
var util = require('util'),
|
||||
path = require('path'),
|
||||
fs = require('fs'),
|
||||
Sway = require('sway'),
|
||||
msRest = require('ms-rest'),
|
||||
JsonRefs = require('json-refs'),
|
||||
HttpRequest = msRest.WebResource,
|
||||
utils = require('./util/utils'),
|
||||
Constants = require('./util/constants'),
|
||||
log = require('./util/logging'),
|
||||
|
@ -44,6 +45,130 @@ 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);
|
||||
log.error(e);
|
||||
throw e;
|
||||
} else {
|
||||
return 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);
|
||||
if (refObj.filePath) {
|
||||
let docPath = utils.joinPath(self.specDir, refObj.filePath);
|
||||
return utils.parseJson(docPath).then(function(result) {
|
||||
return self.findObject(refObj.localReference.accessorProperty, result, refObj.localReference.value, docPath);
|
||||
});
|
||||
} else {
|
||||
return self.findObject(refObj.localReference.accessorProperty, self.specInJson, refObj.localReference.value, self.specPath);
|
||||
}
|
||||
}
|
||||
|
||||
mergeResolvedAllOfObjects(source, target) {
|
||||
let self = this;
|
||||
if (!source || (source && typeof source !== 'object')) {
|
||||
return Promise.reject(new Error(`source must be of type "object".`));
|
||||
}
|
||||
if (!target || (target && typeof target !== 'object')) {
|
||||
return Promise.reject(new Error(`target must be of type "object".`));
|
||||
}
|
||||
//merge the target model's properties
|
||||
source.properties = utils.merge(source.properties, target.properties);
|
||||
//merge the array of required properties
|
||||
if (target.required) {
|
||||
if (!source.required) {
|
||||
source.required = [];
|
||||
}
|
||||
source.required = [...new Set([...source.required, ...target.required])];
|
||||
}
|
||||
//merge x-ms-azure-resource
|
||||
if (target['x-ms-azure-resource']) {
|
||||
source['x-ms-azure-resource'] = target['x-ms-azure-resource'];
|
||||
}
|
||||
return Promise.resolve(source);
|
||||
}
|
||||
|
||||
getRefDefinitionInAllOf(item) {
|
||||
let self = this;
|
||||
if (!item || (item && typeof item !== 'object')) {
|
||||
return Promise.reject(new Error(`item must be of type "object".`));
|
||||
}
|
||||
if (item['$ref']) {
|
||||
return Promise.resolve(self.getDefinitionFromReference(item['$ref']));
|
||||
} else {
|
||||
return Promise.resolve(item);
|
||||
}
|
||||
}
|
||||
|
||||
composeAllOf(model, item) {
|
||||
let self = this;
|
||||
return self.getRefDefinitionInAllOf(item).then(function(result) {
|
||||
if (result && result.allOf) {
|
||||
return self.resolveAllOf(result).then(function(res) {
|
||||
return Promise.resolve(self.mergeResolvedAllOfObjects(model, result));
|
||||
}).catch(function (err) {
|
||||
log.error(err);
|
||||
return Promise.reject(err);
|
||||
});
|
||||
} else {
|
||||
return Promise.resolve(self.mergeResolvedAllOfObjects(model, result));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
resolveAllOf(model) {
|
||||
let self = this;
|
||||
if (!model || (model && typeof model !== 'object')) {
|
||||
return Promise.reject(new Error(`model cannot be null or undefined and must of type object.`));
|
||||
}
|
||||
if (model.allOf) {
|
||||
let allOfItemPromiseFactories = model.allOf.map(function(item) {
|
||||
return Promise.resolve(self.composeAllOf(model, item));
|
||||
});
|
||||
return utils.executePromisesSequentially(allOfItemPromiseFactories);
|
||||
}
|
||||
return Promise.resolve(model);
|
||||
}
|
||||
|
||||
makeModelsStricter() {
|
||||
let self = this;
|
||||
let spec = self.specInJson;
|
||||
let definitions = spec.definitions;
|
||||
let modelNames = Object.keys(self.specInJson.definitions);
|
||||
let modelPromiseFactories = modelNames.map(function (modelName) {
|
||||
let model = definitions[modelName];
|
||||
if (model && !model.additionalProperties) {
|
||||
model.additionalProperties = false;
|
||||
}
|
||||
return self.resolveAllOf(model);
|
||||
});
|
||||
return utils.executePromisesSequentially(modelPromiseFactories);
|
||||
}
|
||||
|
||||
// resolveRelativePaths() {
|
||||
// let self = this;
|
||||
// let spec = self.specInJson;
|
||||
// let options = {
|
||||
// relativeBase: self.specDir,
|
||||
// filter: ['relative', 'remote']
|
||||
// };
|
||||
// let allRefsRemoteRelative = JsonRefs.findRefs(spec, options);
|
||||
// Object.keys(allRefsRemoteRelative).map(function(refName) {
|
||||
// let refDetails = allRefsRemoteRelative[refName];
|
||||
// })
|
||||
|
||||
// }
|
||||
updateValidityStatus(value) {
|
||||
if (!Boolean(value)) {
|
||||
this.specValidationResult.validityStatus = false;
|
||||
|
@ -72,6 +197,16 @@ class SpecValidator {
|
|||
return utils.parseJson(self.specPath).then(function (result) {
|
||||
self.specInJson = result;
|
||||
self.unifyXmsPaths();
|
||||
return self;
|
||||
}).then(function () {
|
||||
return self.makeModelsStricter();
|
||||
}).then(function() {
|
||||
return Object.keys(self.specInJson.definitions).map(function(definitionName){
|
||||
if (self.specInJson.definitions[definitionName].allOf) {
|
||||
delete self.specInJson.definitions[definitionName].allOf;
|
||||
}
|
||||
});
|
||||
}).then(function() {
|
||||
let options = {};
|
||||
options.definition = self.specInJson;
|
||||
options.jsonRefs = {};
|
||||
|
@ -82,9 +217,11 @@ class SpecValidator {
|
|||
self.swaggerApi = api;
|
||||
return Promise.resolve(api);
|
||||
}).catch(function (err) {
|
||||
console.dir(err);
|
||||
let e = self.constructErrorObject(ErrorCodes.ResolveSpecError, err.message, [err]);
|
||||
self.specValidationResult.resolveSpec = e;
|
||||
log.error(`${ErrorCodes.ResolveSpecError}: ${err.message}.`);
|
||||
log.silly(err.stack);
|
||||
return Promise.reject(e);
|
||||
});
|
||||
}
|
||||
|
@ -361,7 +498,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 +507,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;
|
||||
}
|
||||
|
@ -449,20 +586,36 @@ class SpecValidator {
|
|||
if (exampleResponseValue === null || exampleResponseValue === undefined || typeof exampleResponseValue !== 'object') {
|
||||
throw new Error('operation cannot be null or undefined and must be of type \'object\'.');
|
||||
}
|
||||
|
||||
let responsesInSwagger = {};
|
||||
let responses = operation.getResponses().map(function(response) {
|
||||
responsesInSwagger[response.statusCode] = response.statusCode;
|
||||
return response.statusCode;
|
||||
});
|
||||
for (let exampleResponseStatusCode in exampleResponseValue) {
|
||||
let response = operation.getResponse(exampleResponseStatusCode);
|
||||
result[exampleResponseStatusCode] = {errors: [], warnings: []};
|
||||
if (responsesInSwagger[exampleResponseStatusCode]) delete responsesInSwagger[exampleResponseStatusCode];
|
||||
result[exampleResponseStatusCode] = { errors: [], warnings: [] };
|
||||
//have to ensure how to map negative status codes to default. There have been several issues filed in the Autorest repo, w.r.t how
|
||||
//default is handled. While solving that issue, we may come up with some extension. Once that is finalized, we should code accordingly over here.
|
||||
if (!response) {
|
||||
let msg = `${exampleResponseStatusCode} for operation ${operation.operationId} is provided in exampleResponseValue, however it is not present in the swagger spec.`;
|
||||
let e = new Error(msg);
|
||||
e.code = ErrorCodes.ResponseStatusCodeNotInSpec;
|
||||
let msg = `Response statusCode "${exampleResponseStatusCode}" for operation "${operation.operationId}" is provided in exampleResponseValue, ` +
|
||||
`however it is not present in the swagger spec.`;
|
||||
let e = self.constructErrorObject(ErrorCodes.ResponseStatusCodeNotInSpec, msg);
|
||||
result[exampleResponseStatusCode].errors.push(e);
|
||||
log.error(e);
|
||||
continue;
|
||||
}
|
||||
|
||||
let exampleResponseHeaders = exampleResponseValue[exampleResponseStatusCode]['headers'] || {};
|
||||
let exampleResponseBody = exampleResponseValue[exampleResponseStatusCode]['body'];
|
||||
if (exampleResponseBody && !response.schema) {
|
||||
let msg = `Response statusCode "${exampleResponseStatusCode}" for operation "${operation.operationId}" has response body provided in the example, ` +
|
||||
`however the response does not have a "schema" defined in the swagger spec.`;
|
||||
let e = self.constructErrorObject(ErrorCodes.ResponseSchemaNotInSpec, msg);
|
||||
result[exampleResponseStatusCode].errors.push(e);
|
||||
log.error(e);
|
||||
continue;
|
||||
}
|
||||
//ensure content-type header is present
|
||||
if (!(exampleResponseHeaders['content-type'] || exampleResponseHeaders['Content-Type'])) {
|
||||
exampleResponseHeaders['content-type'] = operation.produces[0];
|
||||
|
@ -471,6 +624,18 @@ class SpecValidator {
|
|||
let validationResult = self.validateResponse(operation, exampleResponse);
|
||||
result[exampleResponseStatusCode] = validationResult;
|
||||
}
|
||||
let responseWithoutXmsExamples = Object.keys(responsesInSwagger).filter(function (statusCode) {
|
||||
if (statusCode !== 'default') {
|
||||
//let intStatusCode = parseInt(statusCode);
|
||||
//if (!isNaN(intStatusCode) && intStatusCode < 400) {
|
||||
return statusCode;
|
||||
//}
|
||||
}
|
||||
});
|
||||
if (responseWithoutXmsExamples && responseWithoutXmsExamples.length) {
|
||||
let msg = `Following response status codes "${responseWithoutXmsExamples}" for operation "${operation.operationId}" were present in the swagger spec, ` +
|
||||
`however they were not present in x-ms-examples. Please provide them.`;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@ var Constants = {
|
|||
RequestValidationError: 'REQUEST_VALIDATION_ERROR',
|
||||
ResponseBodyValidationError: 'RESPONSE_BODY_VALIDATION_ERROR',
|
||||
ResponseStatusCodeNotInSpec: 'RESPONSE_STATUS_CODE_NOT_IN_SPEC',
|
||||
ResponseSchemaNotInSpec: 'RESPONSE_SCHEMA_NOT_IN_SPEC',
|
||||
RequiredParameterNotInExampleError: 'REQUIRED_PARAMETER_NOT_IN_EXAMPLE_ERROR',
|
||||
BodyParameterValidationError: 'BODY_PARAMETER_VALIDATION_ERROR',
|
||||
TypeValidationError: 'TYPE_VALIDATION_ERROR',
|
||||
|
|
|
@ -4,8 +4,17 @@
|
|||
'use strict';
|
||||
var fs = require('fs'),
|
||||
util = require('util'),
|
||||
path = require('path'),
|
||||
request = require('request');
|
||||
|
||||
/*
|
||||
* Caches the json docs that were successfully parsed by exports.parseJson(). This avoids, fetching them again.
|
||||
* key: docPath
|
||||
* value: parsed doc in JSON format
|
||||
*/
|
||||
exports.docCache = {};
|
||||
|
||||
|
||||
/*
|
||||
* Removes byte order marker. This catches EF BB BF (the UTF-8 BOM)
|
||||
* because the buffer-to-string conversion in `fs.readFile()`
|
||||
|
@ -35,17 +44,23 @@ exports.parseJson = function parseJson(specPath) {
|
|||
let err = new Error('A (github) url or a local file path to the swagger spec is required and must be of type string.');
|
||||
return Promise.rject(err);
|
||||
}
|
||||
if (exports.docCache[specPath]) {
|
||||
return Promise.resolve(exports.docCache[specPath]);
|
||||
}
|
||||
//url
|
||||
if (specPath.match(/^http.*/ig) !== null) {
|
||||
//If the spec path is a url starting with https://github then let us auto convert it to an https://raw.githubusercontent url.
|
||||
if (specPath.startsWith('https://github')) {
|
||||
specPath = specPath.replace(/^https:\/\/(github.com)(.*)blob\/(.*)/ig, 'https://raw.githubusercontent.com$2$3');
|
||||
}
|
||||
return exports.makeRequest({ url: specPath, errorOnNon200Response: true});
|
||||
let res = exports.makeRequest({ url: specPath, errorOnNon200Response: true});
|
||||
exports.docCache[specPath] = res;
|
||||
return res;
|
||||
} else {
|
||||
//local filepath
|
||||
try {
|
||||
result = JSON.parse(exports.stripBOM(fs.readFileSync(specPath, 'utf8')));
|
||||
exports.docCache[specPath] = result;
|
||||
return Promise.resolve(result);
|
||||
} catch (err) {
|
||||
return Promise.reject(err);
|
||||
|
@ -147,7 +162,8 @@ exports.getTimeStamp = function getTimeStamp() {
|
|||
};
|
||||
|
||||
/*
|
||||
* Executes an array of promises sequentially
|
||||
* Executes an array of promises sequentially. Inspiration of this method is here:
|
||||
* https://pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html. An awesome blog on promises!
|
||||
*
|
||||
* @param {Array} promiseFactories An array of promise factories(A function that return a promise)
|
||||
*
|
||||
|
@ -185,4 +201,81 @@ 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 (let arg in arguments) {
|
||||
finalPath = path.join(finalPath, arguments[arg]);
|
||||
}
|
||||
finalPath = finalPath.replace(/\\/gi, '/');
|
||||
finalPath = finalPath.replace(/^(http|https):\/(.*)/gi, '$1://$2');
|
||||
log.silly(`The final path is: ${finalPath}.`);
|
||||
return finalPath;
|
||||
};
|
||||
|
||||
|
||||
exports.merge = function merge(obj, src) {
|
||||
Object.keys(src).forEach(function(key) { obj[key] = src[key]; });
|
||||
return obj;
|
||||
}
|
||||
|
||||
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;
|
Загрузка…
Ссылка в новой задаче