This commit is contained in:
Vlad Barosan 2018-01-31 12:40:00 -08:00
Родитель aaa3851d03
Коммит 99c21fb260
18 изменённых файлов: 195 добавлений и 154 удалений

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

@ -7,7 +7,7 @@
"forin": true,
"immed": true,
"indent": 2,
"latedef": true,
"latedef": false,
"maxparams": false,
"maxdepth": false,
"maxstatements": false,

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

@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
Object.defineProperty(exports, "__esModule", { value: true });
const vscode_jsonrpc_1 = require("vscode-jsonrpc");
const vscodeJsonRpc = require("vscode-jsonrpc");
const linq = require('linq');
const jsonPath = require('jsonpath');
const yaml = require("js-yaml");
@ -28,20 +28,27 @@ function FormattedOutput(channel, details, code, text, source) {
this.source = source;
}
/**
* Returns a promise with the examples validation of the swagger.
*/
function analyzeSwagger(swaggerFileName, autoRestApi) {
autoRestApi.ReadFile(swaggerFileName).then((swaggerFile) => {
const swagger = yaml.safeLoad(swaggerFile);
return exports.openApiValidationExample(swagger, swaggerFileName).then(function (exampleValidationResults) {
for (const result of exampleValidationResults) {
autoRestApi.Message({ Channel: result.channel, Text: result.text, Details: result.details, Key: result.code, Source: result.source });
}
// console.error(JSON.stringify(exampleValidationResults, null, 2));
});
});
}
extension.Add(modelValidatorPluginName, autoRestApi => {
return autoRestApi.ListInputs().then((swaggerFileNames) => {
const promises = [];
for (const swaggerFileName of swaggerFileNames) {
promises.push(
autoRestApi.ReadFile(swaggerFileName).then((swaggerFile) => {
const swagger = yaml.safeLoad(swaggerFile);
return exports.openApiValidationExample(swagger, swaggerFileName).then(function (exampleValidationResults) {
for (const result of exampleValidationResults) {
autoRestApi.Message({ Channel: result.channel, Text: result.text, Details: result.details, Key: result.code, Source: result.source });
}
// console.error(JSON.stringify(exampleValidationResults, null, 2));
});
})
analyzeSwagger(swaggerFileName)
);
}
return Promise.all(promises).then(_ => true);

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

@ -36,6 +36,6 @@ exports.handler = function (argv) {
vOptions.matchApiVersion = argv.matchApiVersion;
return validate.extractXMsExamples(specPath, recordings, vOptions);
}
};
exports = module.exports;

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

@ -51,6 +51,6 @@ exports.handler = function (argv) {
}
}
return execWireFormat().catch((err) => { process.exitCode = 1; });
}
};
exports = module.exports;

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

@ -13,9 +13,9 @@ exports.describe = 'Performs validation of x-ms-examples and examples present in
exports.builder = {
o: {
alias: 'operationIds',
describe: 'A comma separated string of operationIds for which the examples ' +
'need to be validated. If operationIds are not provided then the entire spec will be validated. ' +
'Example: "StorageAccounts_Create, StorageAccounts_List, Usages_List".',
describe: 'A comma separated string of operationIds for which the examples ' +
'need to be validated. If operationIds are not provided then the entire spec will be validated. ' +
'Example: "StorageAccounts_Create, StorageAccounts_List, Usages_List".',
string: true
}
};
@ -32,6 +32,6 @@ exports.handler = function (argv) {
} else {
return validate.validateExamples(specPath, operationIds, vOptions);
}
}
};
exports = module.exports;

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

@ -50,7 +50,7 @@ class MarkdownHttpTemplate extends HttpTemplate {
}
}
if (!gotContentType) {
result += `Content-Type: application/json; charset=utf-8`
result += `Content-Type: application/json; charset=utf-8`;
}
return result;
}
@ -122,7 +122,7 @@ curl -X ${this.request.method} '${this.request.url}' \\\n-H 'authorization: bear
template += this.populateResponse(this.responses.longrunning.initialResponse, 'Initial Response');
}
if (this.responses.longrunning.finalResponse) {
template += this.populateResponse(this.responses.longrunning.finalResponse, 'Final Response after polling is complete and successful')
template += this.populateResponse(this.responses.longrunning.finalResponse, 'Final Response after polling is complete and successful');
}
} else {
template += this.populateResponse(this.responses.standard.finalResponse, 'Response');

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

@ -10,7 +10,7 @@ const url = require('url'),
class YamlHttpTemplate extends HttpTemplate {
constructor(request, responses) {
super(request, responses)
super(request, responses);
}
getRequestHeaders() {
@ -50,7 +50,7 @@ class YamlHttpTemplate extends HttpTemplate {
}
}
if (!gotContentType) {
result += ` Content-Type: application/json; charset=utf-8`
result += ` Content-Type: application/json; charset=utf-8`;
}
return result;
}
@ -116,7 +116,7 @@ curl: |
template += this.populateResponse(this.responses.longrunning.initialResponse, 'Initial Response');
}
if (this.responses.longrunning.finalResponse) {
template += this.populateResponse(this.responses.longrunning.finalResponse, 'Final Response after polling is complete and successful')
template += this.populateResponse(this.responses.longrunning.finalResponse, 'Final Response after polling is complete and successful');
}
} else {
template += this.populateResponse(this.responses.standard.finalResponse, 'Response');

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

@ -48,15 +48,19 @@ class UmlGenerator {
let definitions = spec.definitions;
for (let modelName of utils.getKeys(definitions)) {
let model = definitions[modelName];
if (model.allOf) {
model.allOf.map((item) => {
let referencedModel = item;
let ref = item['$ref'];
let segments = ref.split('/');
let parent = segments[segments.length - 1];
this.graphDefinition += `\n[${parent}${this.bg}]^-.-allOf[${modelName}${this.bg}]`;
});
}
this.generateAllOfForModel(modelName, model);
}
}
generateAllOfForModel(modelName, model) {
if (model.allOf) {
model.allOf.map((item) => {
let referencedModel = item;
let ref = item['$ref'];
let segments = ref.split('/');
let parent = segments[segments.length - 1];
this.graphDefinition += `\n[${parent}${this.bg}]^-.-allOf[${modelName}${this.bg}]`;
});
}
}

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

@ -46,12 +46,6 @@ class ExecutionEngine {
let self = this;
self.validateEnvironmentVariables();
log.transports.console.level = 'info';
let clientId = process.env[EnvironmentVariables.ClientId];
let domain = process.env[EnvironmentVariables.Domain];
let secret = process.env[EnvironmentVariables.ApplicationSecret];
let subscriptionId = process.env[EnvironmentVariables.AzureSubscriptionId];
let location = process.env[EnvironmentVariables.AzureLocation] || 'westus';
let resourceGroupName = process.env[EnvironmentVariables.AzureResourcegroup] || utils.generateRandomId('testrg');
let validator = new SpecValidator(specPath);
let operation, xmsExamples;
validator.initialize().then(function () {
@ -60,40 +54,7 @@ class ExecutionEngine {
xmsExamples = operation[Constants.xmsExamples];
if (xmsExamples) {
for (let xmsExample of utils.get(xmsExamples)) {
let parameters = self.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, (err, creds, subscriptions) => {
if (err) {
throw err;
}
let resourceClient = new ResourceManagementClient(creds, subscriptionId);
let client = new msrestazure.AzureServiceClient(creds);
self.createResourceGroup(resourceClient, location, resourceGroupName, (err, resourceGroup) => {
if (err) {
throw err;
}
client.sendRequest(req, (err, result, request, response) => {
log.info(request);
log.info(response);
if (err) {
throw err;
}
log.info(result);
});
});
});
this.testExample(xmsExample, operation, operationId, validator);
}
}
}).catch(function (err) {
@ -101,6 +62,50 @@ class ExecutionEngine {
});
}
testExampleForOperation(xmsExample, operation, operationId, validator) {
let clientId = process.env[EnvironmentVariables.ClientId];
let domain = process.env[EnvironmentVariables.Domain];
let secret = process.env[EnvironmentVariables.ApplicationSecret];
let subscriptionId = process.env[EnvironmentVariables.AzureSubscriptionId];
let location = process.env[EnvironmentVariables.AzureLocation] || 'westus';
let resourceGroupName = process.env[EnvironmentVariables.AzureResourcegroup] || utils.generateRandomId('testrg');
let parameters = this.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, (err, creds, subscriptions) => {
if (err) {
throw err;
}
let resourceClient = new ResourceManagementClient(creds, subscriptionId);
let client = new msrestazure.AzureServiceClient(creds);
this.createResourceGroup(resourceClient, location, resourceGroupName, (err, resourceGroup) => {
if (err) {
throw err;
}
client.sendRequest(req, (err, result, request, response) => {
log.info(request);
log.info(response);
if (err) {
throw err;
}
log.info(result);
});
});
});
}
createResourceGroup(resourceClient, location, resourceGroupName, callback) {
let self = this;
resourceClient.resourceGroups.get(resourceGroupName, function (err, result, request, response) {

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

@ -26,14 +26,15 @@ function getTimeStamp() {
}
var now = new Date();
return pad(now.getFullYear())
+ pad(now.getMonth() + 1)
+ pad(now.getDate())
+ "_"
+ pad(now.getHours())
+ pad(now.getMinutes())
+ pad(now.getSeconds());
return pad(now.getFullYear()) +
pad(now.getMonth() + 1) +
pad(now.getDate()) +
"_" +
pad(now.getHours()) +
pad(now.getMinutes()) +
pad(now.getSeconds());
}
var customLogLevels = {
off: 0,
json: 1,
@ -102,7 +103,7 @@ Object.defineProperties(logger, {
},
set: function (logFilePath) {
if (!logFilePath || logFilePath && typeof logFilePath.valueOf() !== 'string') {
throw new Error('filepath cannot be null or undefined and must be of type string. It must be an absolute file path.')
throw new Error('filepath cannot be null or undefined and must be of type string. It must be an absolute file path.');
}
currentLogFile = logFilePath;
this.directory = path.dirname(logFilePath);

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

@ -567,12 +567,12 @@ exports.relaxModelLikeEntities = function relaxModelLikeEntities(model) {
model = exports.relaxEntityType(model);
if (model.properties) {
let modelProperties = model.properties;
for (let propName of exports.getKeys(modelProperties)) {
let isPropRequired = model.required ? model.required.some((p) => { return p == propName; }) : false;
if (modelProperties[propName].properties) {
modelProperties[propName] = exports.relaxModelLikeEntities(modelProperties[propName]);
} else {
modelProperties[propName] = exports.relaxEntityType(modelProperties[propName], isPropRequired);
modelProperties[propName] = exports.relaxEntityType(modelProperties[propName], isPropertyRequired(propName, model));
}
}
}
@ -657,13 +657,12 @@ exports.allowNullableTypes = function allowNullableTypes(model) {
}
if (model && model.properties) {
let modelProperties = model.properties;
for (let propName in modelProperties) {
let isPropRequired = model.required ? model.required.some((p) => { return p == propName; }) : false;
for (let propName of exports.getKeys(modelProperties)) {
// process properties if present
if (modelProperties[propName].properties || modelProperties[propName].additionalProperties) {
modelProperties[propName] = exports.allowNullableTypes(modelProperties[propName]);
}
modelProperties[propName] = exports.allowNullType(modelProperties[propName], isPropRequired);
modelProperties[propName] = exports.allowNullType(modelProperties[propName], isPropertyRequired(propName, model));
}
}
@ -752,4 +751,11 @@ exports.getKeys = function getKeys(obj) {
}
return Object.keys(obj);
};
};
/**
* Checks if the property is required in the model.
*/
function isPropertyRequired(propName, model) {
return model.required ? model.required.some((p) => { return p == propName; }) : false;
}

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

@ -8,6 +8,7 @@ exports = module.exports;
function foo(type, code, message, innerErrors, jsonref, jsonpath, id, validationCategory, providerNamespace, resourceType) {
/* jshint validthis: true */
this.code = code;
this.code = code;
this.message = message;

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

@ -303,22 +303,25 @@ class SpecResolver {
//resolving relative swaggers.
if (result && result.definitions) {
let unresolvedDefinitions = [];
for (const defName of utils.getKeys(result.definitions)) {
unresolvedDefinitions.push(() => {
if (result.definitions[defName].allOf) {
let matchFound = result.definitions[defName].allOf.some((item) => {
return (!self.visitedEntities[`/definitions/${defName}`]);
});
if (matchFound) {
let slicedDefinitionRef = `/definitions/${defName}`;
let definitionObj = result.definitions[defName];
utils.setObject(self.specInJson, slicedDefinitionRef, definitionObj);
self.visitedEntities[slicedDefinitionRef] = definitionObj;
return self.resolveRelativePaths(definitionObj, docPath, 'all');
}
return Promise.resolve();
let processDefinition = (defName) => {
if (result.definitions[defName].allOf) {
let matchFound = result.definitions[defName].allOf.some((item) => {
return (!self.visitedEntities[`/definitions/${defName}`]);
});
if (matchFound) {
let slicedDefinitionRef = `/definitions/${defName}`;
let definitionObj = result.definitions[defName];
utils.setObject(self.specInJson, slicedDefinitionRef, definitionObj);
self.visitedEntities[slicedDefinitionRef] = definitionObj;
return self.resolveRelativePaths(definitionObj, docPath, 'all');
}
});
return Promise.resolve();
}
};
for (const defName of utils.getKeys(result.definitions)) {
unresolvedDefinitions.push(processDefinition(defName));
}
return utils.executePromisesSequentially(unresolvedDefinitions);
}
@ -516,55 +519,63 @@ class SpecResolver {
utils.relaxModelLikeEntities(model);
}
//scan every operation
for (let pathObj of utils.getValues(spec.paths)) {
for (let operation of utils.getValues(pathObj)) {
//scan every parameter in the operation
let consumes = _.isUndefined(operation.consumes) ?
_.isUndefined(spec.consumes) ?
['application/json']
: spec.consumes
: operation.consumes;
let resolveOperation = (operation) => {
//scan every parameter in the operation
let consumes = _.isUndefined(operation.consumes) ?
_.isUndefined(spec.consumes) ?
['application/json']
: spec.consumes
: operation.consumes;
let produces = _.isUndefined(operation.produces) ?
_.isUndefined(spec.produces) ?
['application/json']
: spec.produces
: operation.produces;
let produces = _.isUndefined(operation.produces) ?
_.isUndefined(spec.produces) ?
['application/json']
: spec.produces
: operation.produces;
function octetStream(elements) {
return elements.some(e => {
return e.toLowerCase() === 'application/octet-stream';
});
let octetStream = (elements) => {
return elements.some(e => {
return e.toLowerCase() === 'application/octet-stream';
});
};
let resolveParameter = (param) => {
if (param.in && param.in === 'body' && param.schema && !octetStream(consumes)) {
param.schema = utils.relaxModelLikeEntities(param.schema);
} else {
param = utils.relaxEntityType(param, param.required);
}
};
if (operation.parameters) {
operation.parameters.forEach((param) => {
if (param.in && param.in === 'body' && param.schema && !octetStream(consumes)) {
param.schema = utils.relaxModelLikeEntities(param.schema);
} else {
param = utils.relaxEntityType(param, param.required);
}
});
}
//scan every response in the operation
if (operation.responses) {
for (let response of utils.getValues(operation.responses)) {
if (response.schema && !octetStream(produces)) {
response.schema = utils.relaxModelLikeEntities(response.schema);
}
if (operation.parameters) {
operation.parameters.forEach(resolveParameter);
}
//scan every response in the operation
if (operation.responses) {
for (let response of utils.getValues(operation.responses)) {
if (response.schema && !octetStream(produces)) {
response.schema = utils.relaxModelLikeEntities(response.schema);
}
}
}
};
let resolveParameter = (param) => {
if (param.in && param.in === 'body' && param.schema) {
param.schema = utils.relaxModelLikeEntities(param.schema);
} else {
param = utils.relaxEntityType(param, param.required);
}
};
//scan every operation
for (let pathObj of utils.getValues(spec.paths)) {
for (let operation of utils.getValues(pathObj)) {
resolveOperation(operation);
}
//scan path level parameters if any
if (pathObj.parameters) {
pathObj.parameters.forEach((param) => {
if (param.in && param.in === 'body' && param.schema) {
param.schema = utils.relaxModelLikeEntities(param.schema);
} else {
param = utils.relaxEntityType(param, param.required);
}
});
pathObj.parameters.forEach(resolveParameter);
}
}
//scan global parameters
@ -769,7 +780,8 @@ class SpecResolver {
let definitions = this.specInJson.definitions;
let reference = `#/definitions/${name}`;
let result = new Set();
for (let definitionName of utils.getKeys(definitions)) {
let findReferences = (definitionName) => {
let definition = definitions[definitionName];
if (definition && definition.allOf) {
definition.allOf.forEach((item) => {
@ -780,7 +792,12 @@ class SpecResolver {
}
});
}
};
for (let definitionName of utils.getKeys(definitions)) {
findReferences(definitionName);
}
return result;
}

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

@ -649,11 +649,11 @@ class SpecValidator {
if (operation.consumes) {
if (!options.headers) options.headers = {};
function isOctetStream(consumes) {
let isOctetStream = (consumes) => {
return consumes.some((contentType) => {
return contentType === 'application/octet-stream';
});
}
};
if (parameter.schema.format === 'file' && isOctetStream(operation.consumes)) {
options.headers['Content-Type'] = 'application/octet-stream';
@ -666,11 +666,11 @@ class SpecValidator {
options.headers[parameter.name] = parameterValue;
} else if (location === 'formData') {
// helper function
function isFormUrlEncoded(consumes) {
let isFormUrlEncoded = (consumes) => {
return consumes.some((contentType) => {
return contentType === 'application/x-www-form-urlencoded';
});
}
};
if (!options.formData) options.formData = {};
options.formData[parameter.name] = parameterValue;
@ -816,7 +816,7 @@ class SpecValidator {
responsesInSwagger[response.statusCode] = response.statusCode;
return response.statusCode;
});
for (let exampleResponseStatusCode in exampleResponseValue) {
for (let exampleResponseStatusCode of utils.getKeys(exampleResponseValue)) {
let response = operation.getResponse(exampleResponseStatusCode);
if (responsesInSwagger[exampleResponseStatusCode]) delete responsesInSwagger[exampleResponseStatusCode];
result[exampleResponseStatusCode] = { errors: [], warnings: [] };

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

@ -118,9 +118,9 @@ class WireFormatGenerator {
if (innerErrors) {
err.innerErrors = innerErrors;
}
if (!skipValidityStatusUpdate) {
//this.updateValidityStatus();
}
//if (!skipValidityStatusUpdate) {
//this.updateValidityStatus();
//}
return err;
}

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

@ -92,7 +92,7 @@ class xMsExampleExtractor {
let paths = api.paths;
let pathIndex = 0;
var pathParams = {};
for (let path in paths) {
for (let path of utils.getKeys(paths)) {
pathIndex++;
let searchResult = path.match(/\/{\w*\}/g);
let pathParts = path.split('/');
@ -187,7 +187,7 @@ class xMsExampleExtractor {
}
}
let responses = infoFromOperation["responses"];
for (var response in responses) {
for (var response of utils.getKeys(responses)) {
let statusCodeFromRecording = entries[entry]["StatusCode"];
let responseBody = entries[entry]["ResponseBody"];
example['responses'][statusCodeFromRecording] = {};

2
package-lock.json сгенерированный
Просмотреть файл

@ -2329,7 +2329,7 @@
}
},
"sway": {
"version": "github:amarzavery/sway#a76decb5fcafeb49d2fcdbb40f89bc1ade772102",
"version": "github:amarzavery/sway#7004b89c861d57ef543b6cc01afbf6b1c1ade38d",
"requires": {
"debug": "3.1.0",
"faker": "4.1.0",

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

@ -52,7 +52,7 @@
},
"scripts": {
"jshint": "jshint lib --reporter=jslint",
"test": "mocha -t 100000",
"test": "npm -s run-script jshint && mocha -t 100000",
"start": "node ./lib/autorestPlugin/pluginHost.js"
}
}
}