From 7418b738fa0c51719d9be2ed820493100335c75f Mon Sep 17 00:00:00 2001 From: Vishrut Shah Date: Mon, 10 Apr 2017 22:32:48 -0700 Subject: [PATCH 01/10] First version of cache builder and lookup logic for live validation --- lib/liveValidator.js | 188 +++++++++++++++++++++++++++++++++++++++++++ lib/util/utils.js | 56 +++++++++++++ package.json | 3 +- 3 files changed, 246 insertions(+), 1 deletion(-) create mode 100644 lib/liveValidator.js diff --git a/lib/liveValidator.js b/lib/liveValidator.js new file mode 100644 index 00000000..6a8393c7 --- /dev/null +++ b/lib/liveValidator.js @@ -0,0 +1,188 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +'use strict'; + +var util = require('util'), + path = require('path'), + _ = require('lodash'), + glob = require('glob'), + SpecValidator = require('./specValidator'), + log = require('./util/logging'), + utils = require('./util/utils'), + url = require("url"); + +/* + * @class + * Live Validator for Azure swagger APIs. + */ +class LiveValidator { + /** + * Constructs LiveValidator based on provided options. + * + * @param {object} options The configuration options. + * + * @param {string} [options.url] The url of the github repository. Defaults to "https://github.com/Azure/azure-rest-api-specs.git". + * + * @param {string} [options.directory] The directory where to clone github repository. Defaults to "repo/.". + * + * @returns {object} CacheBuilder Returns the configured CacheBuilder object. + */ + constructor(options) { + this.cache = {}; + this.options = options; + if (!this.options.url) { + this.options.url = "https://github.com/Azure/azure-rest-api-specs.git"; + } + if (!this.options.directory) { + this.options.directory = "repo/."; + } + } + + /* + * Initializes the Live Validator. + */ + initialize() { + let self = this; + + // Clone github repository + // utils.gitClone(options.url, options.directory); + + // Construct array of swagger paths to be used for building a cache + this.swaggerPaths = glob.sync(path.join(options.directory, '/**/swagger/*.json')); + + // Used for quick debugging + // this.swaggerPaths = ["/Users/vishrut/repos/azure-rest-api-specs/arm-authorization/2015-07-01/swagger/authorization.json"]; + + // Create array of promise factories that builds up cache + // Structure of the cache is + // { + // "api-version": { + // "provider1": { + // "GetMethod": [ + // "operation1", + // "operation2", + // ], + // "PutMethod": [ + // "operation1", + // "operation2", + // ], + // ... + // }, + // ... + // }, + // ... + // } + let promiseFactories = self.swaggerPaths.map((swaggerPath) => { + return () => { + log.info(`Building cache from "${swaggerPath}" file.`); + let validator = new SpecValidator(swaggerPath); + return validator.initialize().then(function (api) { + let operations = api.getOperations(); + let apiVersion = api.info.version; + + operations.forEach(function (operation) { + let httpMethod = operation.method.toLocaleLowerCase(); + let provider = utils.getProvider(operation.pathObject.path); + if (provider) { + provider = provider.toLocaleLowerCase(); + } else { + log.warn(`Unable to find provider for path : ${operation.pathObject.path}`); + return; + } + + // Get providers for given api version or initialize it + let providers = self.cache[apiVersion] || {}; + // Get methods for given provider or initialize it + let allMethods = providers[provider] || {}; + // Get specific http methods array for given verb or initialize it + let httpMethods = allMethods[httpMethod] || []; + + // Builds the cache + httpMethods.push(operation); + allMethods[httpMethod] = httpMethods; + providers[provider] = allMethods; + self.cache[apiVersion] = providers; + }); + return Promise.resolve(self.cache); + }).catch(function (err) { + log.warn(`Unable to initialize "${swaggerPath}" file.`); + log.warn(err); + return Promise.reject(err); + }); + } + }); + + utils.executePromisesSequentially(promiseFactories).then(() => { + log.info("Cache initialization complete."); + }); + } + + /** + * Gets list of potential sway operations objects for given url and method. + * + * @param {string} requestUrl The url for which to find potential operations. + * + * @param {string} requestMethod The http verb for the method to be used for lookup. + * + * @returns {Array} List of potential objects matching the url and method. + */ + getPotentialOperations(requestUrl, requestMethod) { + if (_.isEmpty(this.cache)) { + let msg = `Please call "liveValidator.initialize()" before calling this method, so that cache is populated.`; + throw new Error(msg); + } + + if (requestUrl === null || requestUrl === undefined || typeof requestUrl.valueOf() !== 'string' || + !requestUrl.trim().length) { + throw new Error('requestUrl is a required parameter of type string and it cannot be an empty string.'); + } + + if (requestMethod === null || requestMethod === undefined || typeof requestMethod.valueOf() !== 'string' || + !requestMethod.trim().length) { + throw new Error('requestMethod is a required parameter of type string and it cannot be an empty string.'); + } + + let self = this; + let potentialOperations = []; + let parsedUrl = url.parse(requestUrl, true); + let path = parsedUrl.pathname; // TODO: Do we need to take care of encoded url before matching? + if (path === null || path === undefined) { + log.warn(`Could not find path parameter from requestUrl: ${requestUrl}.`); + return potentialOperations; + } + + let apiVersion = parsedUrl.query['api-version']; // TODO: Do we always have api-version in lower case? + if (apiVersion === null || apiVersion === undefined) { + log.warn(`Could not find api-version query parameter from requestUrl: ${requestUrl}.`); + return potentialOperations; + } + apiVersion = apiVersion.toLocaleLowerCase(); + + let provider = utils.getProvider(path); + if (provider === null || provider === undefined || !provider.trim().length) { + log.warn(`Could not find provider namespace from requestUrl: ${requestUrl}.`); + return potentialOperations; + } + provider = provider.toLocaleLowerCase(); + requestMethod = requestMethod.toLocaleLowerCase(); + + if (self.cache[apiVersion] === undefined || + self.cache[apiVersion][provider] === undefined || + self.cache[apiVersion][provider][requestMethod] === undefined) { + log.warn(`Could not find cache entry for requestUrl: ${requestUrl} and method: ${requestMethod}.`); + return potentialOperations; + } + + let operations = self.cache[apiVersion][provider][requestMethod]; + + potentialOperations = operations.filter(function(operation) { + let pathMatch = operation.pathObject.regexp.exec(path); + return pathMatch === null ? false : true; + }); + + return potentialOperations; + } +} + +module.exports = LiveValidator; diff --git a/lib/util/utils.js b/lib/util/utils.js index 547ca131..dc59b4e9 100644 --- a/lib/util/utils.js +++ b/lib/util/utils.js @@ -3,6 +3,8 @@ 'use strict'; var fs = require('fs'), + sys = require('sys'), + execSync = require('child_process').execSync, util = require('util'), path = require('path'), jsonPointer = require('json-pointer'), @@ -363,6 +365,60 @@ exports.removeObject = function removeObject(doc, ptr) { }; /** +/* + * Removes the location pointed by the json pointer in the given doc. + * @param {object} path The path of the operation. + * Example "/subscriptions/{subscriptionId}/resourcegroups/{resourceGroupName}/providers/{resourceProviderNamespace}/ + * {parentResourcePath}/{resourceType}/{resourceName}/providers/Microsoft.Authorization/roleAssignments" + * will return "Microsoft.Authorization". + */ +exports.getProvider = function getProvider(path) { + if (path === null || path === undefined || typeof path.valueOf() !== 'string' || !path.trim().length) { + throw new Error('path is a required parameter of type string and it cannot be an empty string.'); + } + let providerRegEx = new RegExp('\/providers\/(\:?[^{\/]+)', 'i'); + let result; + + let pathMatch = providerRegEx.exec(path); + + if (pathMatch) { + result = pathMatch[1]; + } + + return result; +}; + +/* + * Clones a github repository in the given directory. + * @param {string} github url to be cloned. + * Example "https://github.com/Azure/azure-rest-api-specs.git" or + * "git@github.com:Azure/azure-rest-api-specs.git". + * + * @param {string} path where to clone the repository. + */ +exports.gitClone = function gitClone(url, directory) { + if (url === null || url === undefined || typeof url.valueOf() !== 'string' || !url.trim().length) { + throw new Error('url is a required parameter of type string and it cannot be an empty string.'); + } + + if (directory === null || directory === undefined || typeof directory.valueOf() !== 'string' || !directory.trim().length) { + throw new Error('directory is a required parameter of type string and it cannot be an empty string.'); + } + + if (fs.existsSync(directory) && fs.lstatSync(directory).isDirectory()) { + throw new Error('directory already exists. Please delete it first and then try again.'); + } else { + fs.mkdirSync(directory); + } + + try { + let cmd = `git clone ${url} ${directory}`; + let result = execSync(cmd, { encoding: 'utf8' }); + } catch (err) { + throw new Error(`An error occurred while cloning git repository: ${util.inspect(err, { depth: null })}.`); + } +}; +/* * Finds the first content-type that contains "/json". Only supported Content-Types are * "text/json" & "application/json" so we perform first best match that contains '/json' * diff --git a/package.json b/package.json index b388fb69..e77785d4 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,8 @@ "request": "^2.79.0", "sway": "amarzavery/sway#validation", "winston": "^2.3.0", - "yargs": "^6.6.0" + "yargs": "^6.6.0", + "glob": "^5.0.14" }, "homepage": "https://github.com/azure/openapi-validaton-tools", "repository": { From f779562c361bff739f2dbc9471034d1da3e52f03 Mon Sep 17 00:00:00 2001 From: Vishrut Shah Date: Mon, 10 Apr 2017 22:35:35 -0700 Subject: [PATCH 02/10] rebased master onto init-cache --- lib/util/utils.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/util/utils.js b/lib/util/utils.js index dc59b4e9..d2c4c01b 100644 --- a/lib/util/utils.js +++ b/lib/util/utils.js @@ -418,6 +418,7 @@ exports.gitClone = function gitClone(url, directory) { throw new Error(`An error occurred while cloning git repository: ${util.inspect(err, { depth: null })}.`); } }; + /* * Finds the first content-type that contains "/json". Only supported Content-Types are * "text/json" & "application/json" so we perform first best match that contains '/json' From 270d615061e95a9dba30e207c85f74871ca22b43 Mon Sep 17 00:00:00 2001 From: Vishrut Shah Date: Tue, 11 Apr 2017 12:08:12 -0700 Subject: [PATCH 03/10] Minor log updates and fixing self --- .vscode/launch.json | 11 +++++++++++ lib/liveValidator.js | 11 ++++++++--- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 38dde347..5c9d8535 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -28,6 +28,17 @@ ], "env": {} }, + { + "type": "node", + "request": "launch", + "name": "Live Validator", + "program": "${workspaceRoot}/lib/liveValidator.js", + "cwd": "${workspaceRoot}", + "args": [ + + ], + "env": {} + }, { "type": "node", "request": "attach", diff --git a/lib/liveValidator.js b/lib/liveValidator.js index 6a8393c7..3b74f972 100644 --- a/lib/liveValidator.js +++ b/lib/liveValidator.js @@ -46,10 +46,10 @@ class LiveValidator { let self = this; // Clone github repository - // utils.gitClone(options.url, options.directory); + utils.gitClone(self.options.url, self.options.directory); // Construct array of swagger paths to be used for building a cache - this.swaggerPaths = glob.sync(path.join(options.directory, '/**/swagger/*.json')); + this.swaggerPaths = glob.sync(path.join(self.options.directory, '/**/swagger/*.json')); // Used for quick debugging // this.swaggerPaths = ["/Users/vishrut/repos/azure-rest-api-specs/arm-authorization/2015-07-01/swagger/authorization.json"]; @@ -80,6 +80,7 @@ class LiveValidator { return validator.initialize().then(function (api) { let operations = api.getOperations(); let apiVersion = api.info.version; + log.info(`api-version: ${apiVersion}`); operations.forEach(function (operation) { let httpMethod = operation.method.toLocaleLowerCase(); @@ -176,7 +177,7 @@ class LiveValidator { let operations = self.cache[apiVersion][provider][requestMethod]; - potentialOperations = operations.filter(function(operation) { + potentialOperations = operations.filter(function (operation) { let pathMatch = operation.pathObject.regexp.exec(path); return pathMatch === null ? false : true; }); @@ -186,3 +187,7 @@ class LiveValidator { } module.exports = LiveValidator; + +// Used for testing +// var validator = new LiveValidator({}); +// validator.initialize(); \ No newline at end of file From df2e34f4994e972d7071ccaf028f7d1a7f4974bd Mon Sep 17 00:00:00 2001 From: Vishrut Shah Date: Wed, 12 Apr 2017 20:54:39 -0700 Subject: [PATCH 04/10] Adding mocha tests and moving test file to test folder --- .vscode/launch.json | 14 +- lib/liveValidator.js | 160 +- lib/util/constants.js | 4 + lib/util/utils.js | 17 +- package.json | 7 + test/liveValidatorTests.js | 176 ++ .../2015-10-01/swagger/media.json | 0 .../2016-09-01/swagger/resources.json | 2693 +++++++++++++++++ .../2015-02-28/swagger/search.json | 0 .../2015-08-19/swagger/search.json | 0 .../2015-05-01-preview/swagger/storage.json | 0 .../2015-06-15/swagger/storage.json | 0 .../storageAccountCheckNameAvailability.json | 0 .../examples/storageAccountCreate.json | 0 .../examples/storageAccountDelete.json | 0 .../examples/storageAccountGetProperties.json | 0 .../examples/storageAccountList.json | 0 .../examples/storageAccountListByRG.json | 0 .../examples/storageAccountListKeys.json | 0 .../examples/storageAccountListUsages.json | 0 .../storageAccountRegenerateKeys.json | 0 .../examples/storageAccountUpdate.json | 0 .../2016-01-01/swagger/storage.json | 0 test/utilsTests.js | 40 + 24 files changed, 3059 insertions(+), 52 deletions(-) create mode 100644 test/liveValidatorTests.js rename {arm-mediaservices => test/swaggers/arm-mediaservices}/2015-10-01/swagger/media.json (100%) create mode 100644 test/swaggers/arm-resources/2016-09-01/swagger/resources.json rename {arm-search => test/swaggers/arm-search}/2015-02-28/swagger/search.json (100%) rename {arm-search => test/swaggers/arm-search}/2015-08-19/swagger/search.json (100%) rename {arm-storage => test/swaggers/arm-storage}/2015-05-01-preview/swagger/storage.json (100%) rename {arm-storage => test/swaggers/arm-storage}/2015-06-15/swagger/storage.json (100%) rename {arm-storage => test/swaggers/arm-storage}/2016-01-01/examples/storageAccountCheckNameAvailability.json (100%) rename {arm-storage => test/swaggers/arm-storage}/2016-01-01/examples/storageAccountCreate.json (100%) rename {arm-storage => test/swaggers/arm-storage}/2016-01-01/examples/storageAccountDelete.json (100%) rename {arm-storage => test/swaggers/arm-storage}/2016-01-01/examples/storageAccountGetProperties.json (100%) rename {arm-storage => test/swaggers/arm-storage}/2016-01-01/examples/storageAccountList.json (100%) rename {arm-storage => test/swaggers/arm-storage}/2016-01-01/examples/storageAccountListByRG.json (100%) rename {arm-storage => test/swaggers/arm-storage}/2016-01-01/examples/storageAccountListKeys.json (100%) rename {arm-storage => test/swaggers/arm-storage}/2016-01-01/examples/storageAccountListUsages.json (100%) rename {arm-storage => test/swaggers/arm-storage}/2016-01-01/examples/storageAccountRegenerateKeys.json (100%) rename {arm-storage => test/swaggers/arm-storage}/2016-01-01/examples/storageAccountUpdate.json (100%) rename {arm-storage => test/swaggers/arm-storage}/2016-01-01/swagger/storage.json (100%) create mode 100644 test/utilsTests.js diff --git a/.vscode/launch.json b/.vscode/launch.json index 5c9d8535..c8d0a750 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -35,7 +35,19 @@ "program": "${workspaceRoot}/lib/liveValidator.js", "cwd": "${workspaceRoot}", "args": [ - + "${workspaceRoot}/test/utilsTests.js" + ], + "env": {} + }, + { + "type": "node", + "request": "launch", + "name": "Mocha Tests", + "program": "${workspaceRoot}/node_modules/mocha/bin/mocha", + "cwd": "${workspaceRoot}", + "stopOnEntry": false, + "args": [ + "test/utilsTests.js" ], "env": {} }, diff --git a/lib/liveValidator.js b/lib/liveValidator.js index 3b74f972..7be7328b 100644 --- a/lib/liveValidator.js +++ b/lib/liveValidator.js @@ -8,6 +8,7 @@ var util = require('util'), _ = require('lodash'), glob = require('glob'), SpecValidator = require('./specValidator'), + Constants = require('./util/constants'), log = require('./util/logging'), utils = require('./util/utils'), url = require("url"); @@ -22,21 +23,59 @@ class LiveValidator { * * @param {object} options The configuration options. * - * @param {string} [options.url] The url of the github repository. Defaults to "https://github.com/Azure/azure-rest-api-specs.git". + * @param {array} [options.swaggerPaths] Array of swagger paths to be used for initializing Live Validator. * - * @param {string} [options.directory] The directory where to clone github repository. Defaults to "repo/.". + * @param {string} [options.git.url] The url of the github repository. Defaults to "https://github.com/Azure/azure-rest-api-specs.git". + * + * @param {string} [options.git.shouldClone] Specifies whether to clone the repository or not. Defaults to false. + * + * @param {string} [options.directory] The directory where to clone github repository or from where to find swaggers. Defaults to "./repo". * * @returns {object} CacheBuilder Returns the configured CacheBuilder object. */ constructor(options) { - this.cache = {}; this.options = options; - if (!this.options.url) { - this.options.url = "https://github.com/Azure/azure-rest-api-specs.git"; + + if (this.options === null || this.options === undefined) { + this.options = {}; + } + if (typeof this.options !== 'object') { + throw new Error('options must be of type object'); + } + if (this.options.swaggerPaths === null || this.options.swaggerPaths === undefined) { + this.options.swaggerPaths = []; + } + if (!Array.isArray(this.options.swaggerPaths.valueOf())) { + throw new Error(`options.swaggerPaths must be of type array instead of type "${this.options.swaggerPaths.valueOf()}".`); + } + if (this.options.git === null || this.options.git === undefined) { + this.options.git = { + "url": "https://github.com/Azure/azure-rest-api-specs.git", + "shouldClone": false + }; + } + if (typeof this.options.git.valueOf() !== 'object') { + throw new Error('options.git must be of type object.'); + } + if (this.options.git.url === null || this.options.git.url === undefined) { + this.options.git.url = "https://github.com/Azure/azure-rest-api-specs.git"; + } + if (typeof this.options.git.url.valueOf() !== 'string') { + throw new Error('options.git.url must be of type string.'); + } + if (this.options.git.shouldClone === null || this.options.git.shouldClone === undefined) { + this.options.git.shouldClone = false; } - if (!this.options.directory) { - this.options.directory = "repo/."; + if (typeof this.options.git.shouldClone.valueOf() !== 'boolean') { + throw new Error('options.git.shouldClone must be of type boolean.'); } + if (this.options.directory === null || this.options.directory === undefined) { + this.options.directory = "./repo"; + } + if (typeof this.options.directory.valueOf() !== 'string') { + throw new Error('options.directory must be of type string.'); + } + this.cache = {}; } /* @@ -45,25 +84,31 @@ class LiveValidator { initialize() { let self = this; - // Clone github repository - utils.gitClone(self.options.url, self.options.directory); + // Clone github repository if required + if (self.options.git.shouldClone) { + utils.gitClone(self.options.git.url, self.options.directory); + } // Construct array of swagger paths to be used for building a cache - this.swaggerPaths = glob.sync(path.join(self.options.directory, '/**/swagger/*.json')); - - // Used for quick debugging - // this.swaggerPaths = ["/Users/vishrut/repos/azure-rest-api-specs/arm-authorization/2015-07-01/swagger/authorization.json"]; - + let swaggerPaths; + if (self.options.swaggerPaths.length !== 0) { + swaggerPaths = self.options.swaggerPaths; + log.debug(`Using user provided swagger paths. Total paths: ${swaggerPaths.length}`); + } else { + swaggerPaths = glob.sync(path.join(self.options.directory, '/**/swagger/*.json')); + log.debug(`Using swaggers found from ${self.options.directory} provided swagger paths. Total paths: ${swaggerPaths.length}`); + } + // console.log(swaggerPaths); // Create array of promise factories that builds up cache // Structure of the cache is // { - // "api-version": { - // "provider1": { - // "GetMethod": [ + // "provider1": { + // "api-version1": { + // "get": [ // "operation1", // "operation2", // ], - // "PutMethod": [ + // "put": [ // "operation1", // "operation2", // ], @@ -73,48 +118,60 @@ class LiveValidator { // }, // ... // } - let promiseFactories = self.swaggerPaths.map((swaggerPath) => { + let promiseFactories = swaggerPaths.map((swaggerPath) => { return () => { - log.info(`Building cache from "${swaggerPath}" file.`); + log.info(`Building cache from: "${swaggerPath}"`); let validator = new SpecValidator(swaggerPath); return validator.initialize().then(function (api) { let operations = api.getOperations(); - let apiVersion = api.info.version; - log.info(`api-version: ${apiVersion}`); + let apiVersion = api.info.version.toLowerCase(); operations.forEach(function (operation) { - let httpMethod = operation.method.toLocaleLowerCase(); + let httpMethod = operation.method.toLowerCase(); let provider = utils.getProvider(operation.pathObject.path); - if (provider) { - provider = provider.toLocaleLowerCase(); - } else { - log.warn(`Unable to find provider for path : ${operation.pathObject.path}`); - return; + log.debug(`${apiVersion}, ${operation.operationId}, ${operation.pathObject.path}, ${httpMethod}`); + + if (!provider) { + let title = api.info.title; + + // Whitelist lookups: Look up knownTitleToResourceProviders + if (title && Constants.knownTitleToResourceProviders[title]) { + provider = Constants.knownTitleToResourceProviders[title]; + } + // Put the operation into 'Microsoft.Unknown' RPs + else { + provider = Constants.unknownResourceProvider; + } + log.warn(`Unable to find provider for path : "${operation.pathObject.path}". Bucketizing into provider: "${provider}"`); } + provider = provider.toLowerCase(); - // Get providers for given api version or initialize it - let providers = self.cache[apiVersion] || {}; - // Get methods for given provider or initialize it - let allMethods = providers[provider] || {}; + // TODO Look for operation those have api-version inside enums before + // using apiVersion directly + + // Get all api-version for given provider or initialize it + let apiVersions = self.cache[provider] || {}; + // Get methods for given apiVersion or initialize it + let allMethods = apiVersions[apiVersion] || {}; // Get specific http methods array for given verb or initialize it let httpMethods = allMethods[httpMethod] || []; // Builds the cache httpMethods.push(operation); allMethods[httpMethod] = httpMethods; - providers[provider] = allMethods; - self.cache[apiVersion] = providers; + apiVersions[apiVersion] = allMethods; + self.cache[provider] = apiVersions; }); + return Promise.resolve(self.cache); }).catch(function (err) { - log.warn(`Unable to initialize "${swaggerPath}" file.`); - log.warn(err); + log.warn(`Unable to initialize "${swaggerPath}" file from SpecValidator. Error: ${err}`); return Promise.reject(err); }); } }); - utils.executePromisesSequentially(promiseFactories).then(() => { + return utils.executePromisesSequentially(promiseFactories).then(() => { log.info("Cache initialization complete."); }); } @@ -147,26 +204,39 @@ class LiveValidator { let self = this; let potentialOperations = []; let parsedUrl = url.parse(requestUrl, true); - let path = parsedUrl.pathname; // TODO: Do we need to take care of encoded url before matching? + let path = parsedUrl.pathname; if (path === null || path === undefined) { log.warn(`Could not find path parameter from requestUrl: ${requestUrl}.`); return potentialOperations; } - let apiVersion = parsedUrl.query['api-version']; // TODO: Do we always have api-version in lower case? + // TODO: Do we always have api-version in lower case? + // Lower the keys and then find api-version + let apiVersion = parsedUrl.query['api-version']; + + // 1. First find based on providers + // 1.1 Find based on api-version + // 1.1.1 Else we don't recognize this operation -- log + // 1.2 Find based on method + // 1.2.1 Else we don't recognize this operation -- log + // 2 Didn't find provider at all - Microsoft.unknown + // 2.1 Search for api-version + // 2.1.1 Else look for "http method" all operations + // 2.2 Search for method + // 2.2.1 Else we don't recognize this operation -- log if (apiVersion === null || apiVersion === undefined) { log.warn(`Could not find api-version query parameter from requestUrl: ${requestUrl}.`); return potentialOperations; } - apiVersion = apiVersion.toLocaleLowerCase(); + apiVersion = apiVersion.toLowerCase(); let provider = utils.getProvider(path); if (provider === null || provider === undefined || !provider.trim().length) { log.warn(`Could not find provider namespace from requestUrl: ${requestUrl}.`); return potentialOperations; } - provider = provider.toLocaleLowerCase(); - requestMethod = requestMethod.toLocaleLowerCase(); + provider = provider.toLowerCase(); + requestMethod = requestMethod.toLowerCase(); if (self.cache[apiVersion] === undefined || self.cache[apiVersion][provider] === undefined || @@ -189,5 +259,7 @@ class LiveValidator { module.exports = LiveValidator; // Used for testing -// var validator = new LiveValidator({}); -// validator.initialize(); \ No newline at end of file +// var validator = new LiveValidator({ "directory": "/Users/vishrut/repos/openapi-validation-tools/test/swaggers/arm-resources" }); +// validator.initialize().then(function () { +// console.log(validator.cache); +// }) diff --git a/lib/util/constants.js b/lib/util/constants.js index bb2a830e..0fe8d773 100644 --- a/lib/util/constants.js +++ b/lib/util/constants.js @@ -40,6 +40,10 @@ var Constants = { AzureSubscriptionId: 'AZURE_SUBSCRIPTION_ID', AzureLocation: 'AZURE_LOCATION', AzureResourcegroup: 'AZURE_RESOURCE_GROUP' + }, + unknownResourceProvider: 'Microsoft.Unknown', + knownTitleToResourceProviders: { + 'ResourceManagementClient': 'Microsoft.Resources' } }; diff --git a/lib/util/utils.js b/lib/util/utils.js index d2c4c01b..a5a7fa3d 100644 --- a/lib/util/utils.js +++ b/lib/util/utils.js @@ -366,22 +366,25 @@ exports.removeObject = function removeObject(doc, ptr) { /** /* - * Removes the location pointed by the json pointer in the given doc. - * @param {object} path The path of the operation. + * Gets provider namespace from the given path. In case of multiple, last one will be returned. + * @param {string} path The path of the operation. * Example "/subscriptions/{subscriptionId}/resourcegroups/{resourceGroupName}/providers/{resourceProviderNamespace}/ * {parentResourcePath}/{resourceType}/{resourceName}/providers/Microsoft.Authorization/roleAssignments" * will return "Microsoft.Authorization". + * + * @returns {string} result - provider namespace from the given path. */ exports.getProvider = function getProvider(path) { if (path === null || path === undefined || typeof path.valueOf() !== 'string' || !path.trim().length) { throw new Error('path is a required parameter of type string and it cannot be an empty string.'); } - let providerRegEx = new RegExp('\/providers\/(\:?[^{\/]+)', 'i'); - let result; - let pathMatch = providerRegEx.exec(path); + let providerRegEx = new RegExp('/providers/(\:?[^{/]+)', 'gi'); + let result; + let pathMatch; - if (pathMatch) { + // Loop over the paths to find the last matched provider namespace + while ((pathMatch = providerRegEx.exec(path)) != null) { result = pathMatch[1]; } @@ -406,7 +409,7 @@ exports.gitClone = function gitClone(url, directory) { } if (fs.existsSync(directory) && fs.lstatSync(directory).isDirectory()) { - throw new Error('directory already exists. Please delete it first and then try again.'); + throw new Error(`directory "${directory}" already exists. Please delete it first and then try again.`); } else { fs.mkdirSync(directory); } diff --git a/package.json b/package.json index e77785d4..6570c3e9 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,13 @@ "yargs": "^6.6.0", "glob": "^5.0.14" }, + "devDependencies": { + "jshint": "2.9.4", + "mocha": "^3.2.0", + "should": "5.2.0", + "@types/mocha": "^2.2.40", + "@types/should": "^8.1.30" + }, "homepage": "https://github.com/azure/openapi-validaton-tools", "repository": { "type": "git", diff --git a/test/liveValidatorTests.js b/test/liveValidatorTests.js new file mode 100644 index 00000000..cdd9a120 --- /dev/null +++ b/test/liveValidatorTests.js @@ -0,0 +1,176 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +var assert = require('assert'); +var LiveValidator = require('../lib/liveValidator.js'); + +describe('Live Validator', function () { + describe('Initialization', function () { + it('should initialize with defaults', function () { + let options = { + "swaggerPaths": [], + "git": { + "url": "https://github.com/Azure/azure-rest-api-specs.git", + "shouldClone": false + }, + "directory": "./repo" + }; + let validator = new LiveValidator(); + assert.deepEqual(validator.cache, {}); + assert.deepEqual(validator.options, options); + }); + it('should initialize with user provided swaggerPaths', function () { + let swaggerPaths = ["swaggerPath1", "swaggerPath2"]; + let options = { + "swaggerPaths": swaggerPaths, + "git": { + "url": "https://github.com/Azure/azure-rest-api-specs.git", + "shouldClone": false + }, + "directory": "./repo" + }; + let validator = new LiveValidator({ "swaggerPaths": swaggerPaths }); + assert.deepEqual(validator.cache, {}); + assert.deepEqual(validator.options, options); + }); + it('should initialize with user provided swaggerPaths & directory', function () { + let swaggerPaths = ["swaggerPath1", "swaggerPath2"]; + let directory = "/Users/username/repos/" + let options = { + "swaggerPaths": swaggerPaths, + "git": { + "url": "https://github.com/Azure/azure-rest-api-specs.git", + "shouldClone": false + }, + "directory": directory + }; + let validator = new LiveValidator({ "swaggerPaths": swaggerPaths, "directory": directory }); + assert.deepEqual(validator.cache, {}); + assert.deepEqual(validator.options, options); + }); + it('should initialize with user provided partial git configuration', function () { + let swaggerPaths = ["swaggerPath1", "swaggerPath2"]; + let directory = "/Users/username/repos/" + let git = { + "url": "https://github.com/vishrutshah/azure-rest-api-specs.git" + } + let options = { + "swaggerPaths": swaggerPaths, + "git": { + "url": git.url, + "shouldClone": false + }, + "directory": directory + }; + let validator = new LiveValidator({ "swaggerPaths": swaggerPaths, "directory": directory, "git": git }); + assert.deepEqual(validator.cache, {}); + assert.deepEqual(validator.options, options); + }); + it('should initialize with user provided full git configuration', function () { + let swaggerPaths = ["swaggerPath1", "swaggerPath2"]; + let directory = "/Users/username/repos/" + let git = { + "url": "https://github.com/vishrutshah/azure-rest-api-specs.git", + "shouldClone": true + } + let options = { + "swaggerPaths": swaggerPaths, + "git": git, + "directory": directory + }; + let validator = new LiveValidator({ "swaggerPaths": swaggerPaths, "directory": directory, "git": git }); + assert.deepEqual(validator.cache, {}); + assert.deepEqual(validator.options, options); + }); + it('should throw on invalid options types', function () { + assert.throws(() => { + new LiveValidator('string'); + }, /must be of type object/); + assert.throws(() => { + new LiveValidator({ "swaggerPaths": "should be array" }); + }, /must be of type array/); + assert.throws(() => { + new LiveValidator({ "git": 1 }); + }, /must be of type object/); + assert.throws(() => { + new LiveValidator({ "git": { "url": [] } }); + }, /must be of type string/); + assert.throws(() => { + new LiveValidator({ "git": { "url": "url", "shouldClone": "no" } }); + }, /must be of type boolean/); + }); + }); + describe('Initialize cache', function () { + it('should initialize for arm-mediaservices', function (done) { + let expectedProvider = 'microsoft.media'; + let expectedApiVersion = '2015-10-01'; + let options = { + "directory": "./test/swaggers/arm-mediaservices" + }; + let validator = new LiveValidator(options); + validator.initialize().then(function () { + assert.equal(true, expectedProvider in validator.cache); + assert.equal(1, Object.keys(validator.cache).length); + assert.equal(true, expectedApiVersion in (validator.cache[expectedProvider])); + assert.equal(1, Object.keys(validator.cache[expectedProvider]).length); + assert.equal(2, validator.cache[expectedProvider][expectedApiVersion]['get'].length); + assert.equal(1, validator.cache[expectedProvider][expectedApiVersion]['put'].length); + assert.equal(1, validator.cache[expectedProvider][expectedApiVersion]['patch'].length); + assert.equal(1, validator.cache[expectedProvider][expectedApiVersion]['delete'].length); + assert.equal(4, validator.cache[expectedProvider][expectedApiVersion]['post'].length); + done(); + }).catch((err) => { + assert.ifError(err); + done(); + }).catch(done); + }); + it('should initialize for arm-resources', function (done) { + let expectedProvider = 'microsoft.resources'; + let expectedApiVersion = '2016-09-01'; + let options = { + "directory": "./test/swaggers/arm-resources" + }; + let validator = new LiveValidator(options); + validator.initialize().then(function () { + assert.equal(true, expectedProvider in validator.cache); + assert.equal(1, Object.keys(validator.cache).length); + assert.equal(true, expectedApiVersion in (validator.cache[expectedProvider])); + assert.equal(1, Object.keys(validator.cache[expectedProvider]).length); + assert.equal(13, validator.cache[expectedProvider][expectedApiVersion]['get'].length); + assert.equal(6, validator.cache[expectedProvider][expectedApiVersion]['put'].length); + assert.equal(1, validator.cache[expectedProvider][expectedApiVersion]['patch'].length); + assert.equal(6, validator.cache[expectedProvider][expectedApiVersion]['delete'].length); + assert.equal(7, validator.cache[expectedProvider][expectedApiVersion]['post'].length); + assert.equal(4, validator.cache[expectedProvider][expectedApiVersion]['head'].length); + done(); + }).catch((err) => { + assert.ifError(err); + done(); + }).catch(done); + }); + it.only('should initialize for all swaggers', function (done) { + let expectedProvider = ''; + let expectedApiVersion = ''; + let options = { + "directory": "./test/swaggers" + }; + let validator = new LiveValidator(options); + validator.initialize().then(function () { + assert.equal(4, Object.keys(validator.cache).length); + assert.equal(13, validator.cache['microsoft.resources']['2016-09-01']['get'].length); + assert.equal(4, validator.cache['microsoft.resources']['2016-09-01']['head'].length); + assert.equal(1, validator.cache['microsoft.media']['2015-10-01']['patch'].length); + assert.equal(4, validator.cache['microsoft.media']['2015-10-01']['post'].length); + assert.equal(2, validator.cache['microsoft.search']['2015-02-28']['get'].length); + assert.equal(3, validator.cache['microsoft.search']['2015-08-19']['get'].length); + assert.equal(1, validator.cache['microsoft.storage']['2015-05-01-preview']['patch'].length); + assert.equal(4, validator.cache['microsoft.storage']['2015-06-15']['get'].length); + assert.equal(3, validator.cache['microsoft.storage']['2016-01-01']['post'].length); + done(); + }).catch((err) => { + assert.ifError(err); + done(); + }).catch(done); + }); + }); +}); \ No newline at end of file diff --git a/arm-mediaservices/2015-10-01/swagger/media.json b/test/swaggers/arm-mediaservices/2015-10-01/swagger/media.json similarity index 100% rename from arm-mediaservices/2015-10-01/swagger/media.json rename to test/swaggers/arm-mediaservices/2015-10-01/swagger/media.json diff --git a/test/swaggers/arm-resources/2016-09-01/swagger/resources.json b/test/swaggers/arm-resources/2016-09-01/swagger/resources.json new file mode 100644 index 00000000..b2194982 --- /dev/null +++ b/test/swaggers/arm-resources/2016-09-01/swagger/resources.json @@ -0,0 +1,2693 @@ +{ + "swagger": "2.0", + "info": { + "title": "ResourceManagementClient", + "version": "2016-09-01", + "description": "Provides operations for working with resources and resource groups." + }, + "host": "management.azure.com", + "schemes": [ + "https" + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "security": [ + { + "azure_auth": [ + "user_impersonation" + ] + } + ], + "securityDefinitions": { + "azure_auth": { + "type": "oauth2", + "authorizationUrl": "https://login.microsoftonline.com/common/oauth2/authorize", + "flow": "implicit", + "description": "Azure Active Directory OAuth2 Flow", + "scopes": { + "user_impersonation": "impersonate your user account" + } + } + }, + "paths": { + "/subscriptions/{subscriptionId}/resourcegroups/{resourceGroupName}/providers/Microsoft.Resources/deployments/{deploymentName}": { + "delete": { + "tags": [ + "Deployments" + ], + "operationId": "Deployments_Delete", + "summary": "Deletes a deployment from the deployment history.", + "description": "A template deployment that is currently running cannot be deleted. Deleting a template deployment removes the associated deployment operations. Deleting a template deployment does not affect the state of the resource group. This is an asynchronous operation that returns a status of 202 until the template deployment is successfully deleted. The Location response header contains the URI that is used to obtain the status of the process. While the process is running, a call to the URI in the Location header returns a status of 202. When the process finishes, the URI in the Location header returns a status of 204 on success. If the asynchronous request failed, the URI in the Location header returns an error-level status code.", + "parameters": [ + { + "name": "resourceGroupName", + "in": "path", + "required": true, + "type": "string", + "description": "The name of the resource group with the deployment to delete. The name is case insensitive.", + "pattern": "^[-\\w\\._\\(\\)]+$", + "minLength": 1, + "maxLength": 90 + }, + { + "name": "deploymentName", + "in": "path", + "required": true, + "type": "string", + "description": "The name of the deployment to delete.", + "pattern": "^[-\\w\\._\\(\\)]+$", + "minLength": 1, + "maxLength": 64 + }, + { + "$ref": "#/parameters/ApiVersionParameter" + }, + { + "$ref": "#/parameters/SubscriptionIdParameter" + } + ], + "responses": { + "202": { + "description": "Accepted - Returns this status until the asynchronous operation has completed." + }, + "204": { + "description": "No Content" + } + }, + "x-ms-long-running-operation": true + }, + "head": { + "tags": [ + "Deployments" + ], + "operationId": "Deployments_CheckExistence", + "description": "Checks whether the deployment exists.", + "parameters": [ + { + "name": "resourceGroupName", + "in": "path", + "required": true, + "type": "string", + "description": "The name of the resource group with the deployment to check. The name is case insensitive.", + "pattern": "^[-\\w\\._\\(\\)]+$", + "minLength": 1, + "maxLength": 90 + }, + { + "name": "deploymentName", + "in": "path", + "required": true, + "type": "string", + "description": "The name of the deployment to check.", + "pattern": "^[-\\w\\._\\(\\)]+$", + "minLength": 1, + "maxLength": 64 + }, + { + "$ref": "#/parameters/ApiVersionParameter" + }, + { + "$ref": "#/parameters/SubscriptionIdParameter" + } + ], + "responses": { + "204": { + "description": "No Content" + }, + "404": { + "description": "Not Found" + } + } + }, + "put": { + "tags": [ + "Deployments" + ], + "operationId": "Deployments_CreateOrUpdate", + "summary": "Deploys resources to a resource group.", + "description": "You can provide the template and parameters directly in the request or link to JSON files.", + "parameters": [ + { + "name": "resourceGroupName", + "in": "path", + "required": true, + "type": "string", + "description": "The name of the resource group to deploy the resources to. The name is case insensitive. The resource group must already exist.", + "pattern": "^[-\\w\\._\\(\\)]+$", + "minLength": 1, + "maxLength": 90 + }, + { + "name": "deploymentName", + "in": "path", + "required": true, + "type": "string", + "description": "The name of the deployment.", + "pattern": "^[-\\w\\._\\(\\)]+$", + "minLength": 1, + "maxLength": 64 + }, + { + "name": "parameters", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/Deployment" + }, + "description": "Additional parameters supplied to the operation." + }, + { + "$ref": "#/parameters/ApiVersionParameter" + }, + { + "$ref": "#/parameters/SubscriptionIdParameter" + } + ], + "responses": { + "200": { + "description": "OK - Returns information about the deployment, including provisioning status.", + "schema": { + "$ref": "#/definitions/DeploymentExtended" + } + }, + "201": { + "description": "Created - Returns information about the deployment, including provisioning status.", + "schema": { + "$ref": "#/definitions/DeploymentExtended" + } + } + }, + "x-ms-long-running-operation": true + }, + "get": { + "tags": [ + "Deployments" + ], + "operationId": "Deployments_Get", + "description": "Gets a deployment.", + "parameters": [ + { + "name": "resourceGroupName", + "in": "path", + "required": true, + "type": "string", + "description": "The name of the resource group. The name is case insensitive.", + "pattern": "^[-\\w\\._\\(\\)]+$", + "minLength": 1, + "maxLength": 90 + }, + { + "name": "deploymentName", + "in": "path", + "required": true, + "type": "string", + "description": "The name of the deployment to get.", + "pattern": "^[-\\w\\._\\(\\)]+$", + "minLength": 1, + "maxLength": 64 + }, + { + "$ref": "#/parameters/ApiVersionParameter" + }, + { + "$ref": "#/parameters/SubscriptionIdParameter" + } + ], + "responses": { + "200": { + "description": "OK - Returns information about the deployment, including provisioning status.", + "schema": { + "$ref": "#/definitions/DeploymentExtended" + } + } + } + } + }, + "/subscriptions/{subscriptionId}/resourcegroups/{resourceGroupName}/providers/Microsoft.Resources/deployments/{deploymentName}/cancel": { + "post": { + "tags": [ + "Deployments" + ], + "operationId": "Deployments_Cancel", + "summary": "Cancels a currently running template deployment.", + "description": "You can cancel a deployment only if the provisioningState is Accepted or Running. After the deployment is canceled, the provisioningState is set to Canceled. Canceling a template deployment stops the currently running template deployment and leaves the resource group partially deployed.", + "parameters": [ + { + "name": "resourceGroupName", + "in": "path", + "required": true, + "type": "string", + "description": "The name of the resource group. The name is case insensitive.", + "pattern": "^[-\\w\\._\\(\\)]+$", + "minLength": 1, + "maxLength": 90 + }, + { + "name": "deploymentName", + "in": "path", + "required": true, + "type": "string", + "description": "The name of the deployment to cancel.", + "pattern": "^[-\\w\\._\\(\\)]+$", + "minLength": 1, + "maxLength": 64 + }, + { + "$ref": "#/parameters/ApiVersionParameter" + }, + { + "$ref": "#/parameters/SubscriptionIdParameter" + } + ], + "responses": { + "204": { + "description": "No Content" + } + } + } + }, + "/subscriptions/{subscriptionId}/resourcegroups/{resourceGroupName}/providers/Microsoft.Resources/deployments/{deploymentName}/validate": { + "post": { + "tags": [ + "Deployments" + ], + "operationId": "Deployments_Validate", + "description": "Validates whether the specified template is syntactically correct and will be accepted by Azure Resource Manager..", + "parameters": [ + { + "name": "resourceGroupName", + "in": "path", + "required": true, + "type": "string", + "description": "The name of the resource group the template will be deployed to. The name is case insensitive.", + "pattern": "^[-\\w\\._\\(\\)]+$", + "minLength": 1, + "maxLength": 90 + }, + { + "name": "deploymentName", + "in": "path", + "required": true, + "type": "string", + "description": "The name of the deployment.", + "pattern": "^[-\\w\\._\\(\\)]+$", + "minLength": 1, + "maxLength": 64 + }, + { + "name": "parameters", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/Deployment" + }, + "description": "Parameters to validate." + }, + { + "$ref": "#/parameters/ApiVersionParameter" + }, + { + "$ref": "#/parameters/SubscriptionIdParameter" + } + ], + "responses": { + "200": { + "description": "OK - Returns the validation result.", + "schema": { + "$ref": "#/definitions/DeploymentValidateResult" + } + }, + "400": { + "description": "Returns the validation result.", + "schema": { + "$ref": "#/definitions/DeploymentValidateResult" + } + } + } + } + }, + "/subscriptions/{subscriptionId}/resourcegroups/{resourceGroupName}/providers/Microsoft.Resources/deployments/{deploymentName}/exportTemplate": { + "post": { + "tags": [ + "Deployments" + ], + "operationId": "Deployments_ExportTemplate", + "description": "Exports the template used for specified deployment.", + "parameters": [ + { + "name": "resourceGroupName", + "in": "path", + "required": true, + "type": "string", + "description": "The name of the resource group. The name is case insensitive.", + "pattern": "^[-\\w\\._\\(\\)]+$", + "minLength": 1, + "maxLength": 90 + }, + { + "name": "deploymentName", + "in": "path", + "required": true, + "type": "string", + "description": "The name of the deployment from which to get the template.", + "pattern": "^[-\\w\\._\\(\\)]+$", + "minLength": 1, + "maxLength": 64 + }, + { + "$ref": "#/parameters/ApiVersionParameter" + }, + { + "$ref": "#/parameters/SubscriptionIdParameter" + } + ], + "responses": { + "200": { + "description": "OK - Returns the template.", + "schema": { + "$ref": "#/definitions/DeploymentExportResult" + } + } + } + } + }, + "/subscriptions/{subscriptionId}/resourcegroups/{resourceGroupName}/providers/Microsoft.Resources/deployments/": { + "get": { + "tags": [ + "Deployments" + ], + "operationId": "Deployments_List", + "description": "Get all the deployments for a resource group.", + "parameters": [ + { + "name": "resourceGroupName", + "in": "path", + "required": true, + "type": "string", + "description": "The name of the resource group with the deployments to get. The name is case insensitive.", + "pattern": "^[-\\w\\._\\(\\)]+$", + "minLength": 1, + "maxLength": 90 + }, + { + "name": "$filter", + "in": "query", + "required": false, + "type": "string", + "description": "The filter to apply on the operation. For example, you can use $filter=provisioningState eq '{state}'." + }, + { + "name": "$top", + "in": "query", + "required": false, + "type": "integer", + "format": "int32", + "description": "The number of results to get. If null is passed, returns all deployments." + }, + { + "$ref": "#/parameters/ApiVersionParameter" + }, + { + "$ref": "#/parameters/SubscriptionIdParameter" + } + ], + "responses": { + "200": { + "description": "OK - Returns an array of deployments.", + "schema": { + "$ref": "#/definitions/DeploymentListResult" + } + } + }, + "x-ms-pageable": { + "nextLinkName": "nextLink" + }, + "x-ms-odata": "#/definitions/DeploymentExtendedFilter" + } + }, + "/subscriptions/{subscriptionId}/providers/{resourceProviderNamespace}/unregister": { + "post": { + "tags": [ + "Providers" + ], + "operationId": "Providers_Unregister", + "description": "Unregisters a subscription from a resource provider.", + "parameters": [ + { + "name": "resourceProviderNamespace", + "in": "path", + "required": true, + "type": "string", + "description": "The namespace of the resource provider to unregister." + }, + { + "$ref": "#/parameters/ApiVersionParameter" + }, + { + "$ref": "#/parameters/SubscriptionIdParameter" + } + ], + "responses": { + "200": { + "description": "OK - Returns informatin about the resource provider.", + "schema": { + "$ref": "#/definitions/Provider" + } + } + } + } + }, + "/subscriptions/{subscriptionId}/providers/{resourceProviderNamespace}/register": { + "post": { + "tags": [ + "Providers" + ], + "operationId": "Providers_Register", + "description": "Registers a subscription with a resource provider.", + "parameters": [ + { + "name": "resourceProviderNamespace", + "in": "path", + "required": true, + "type": "string", + "description": "The namespace of the resource provider to register." + }, + { + "$ref": "#/parameters/ApiVersionParameter" + }, + { + "$ref": "#/parameters/SubscriptionIdParameter" + } + ], + "responses": { + "200": { + "description": "OK - Returns information about the resource provider.", + "schema": { + "$ref": "#/definitions/Provider" + } + } + } + } + }, + "/subscriptions/{subscriptionId}/providers": { + "get": { + "tags": [ + "Providers" + ], + "operationId": "Providers_List", + "description": "Gets all resource providers for a subscription.", + "parameters": [ + { + "name": "$top", + "in": "query", + "required": false, + "type": "integer", + "format": "int32", + "description": "The number of results to return. If null is passed returns all deployments." + }, + { + "name": "$expand", + "in": "query", + "required": false, + "type": "string", + "description": "The properties to include in the results. For example, use &$expand=metadata in the query string to retrieve resource provider metadata. To include property aliases in response, use $expand=resourceTypes/aliases." + }, + { + "$ref": "#/parameters/ApiVersionParameter" + }, + { + "$ref": "#/parameters/SubscriptionIdParameter" + } + ], + "responses": { + "200": { + "description": "OK - Returns an array of resource providers.", + "schema": { + "$ref": "#/definitions/ProviderListResult" + } + } + }, + "x-ms-pageable": { + "nextLinkName": "nextLink" + } + } + }, + "/subscriptions/{subscriptionId}/providers/{resourceProviderNamespace}": { + "get": { + "tags": [ + "Providers" + ], + "operationId": "Providers_Get", + "description": "Gets the specified resource provider.", + "parameters": [ + { + "name": "$expand", + "in": "query", + "required": false, + "type": "string", + "description": "The $expand query parameter. For example, to include property aliases in response, use $expand=resourceTypes/aliases." + }, + { + "name": "resourceProviderNamespace", + "in": "path", + "required": true, + "type": "string", + "description": "The namespace of the resource provider." + }, + { + "$ref": "#/parameters/ApiVersionParameter" + }, + { + "$ref": "#/parameters/SubscriptionIdParameter" + } + ], + "responses": { + "200": { + "description": "OK - Returns information about the resource provider.", + "schema": { + "$ref": "#/definitions/Provider" + } + } + } + } + }, + "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/resources": { + "get": { + "tags": [ + "ResourceGroups" + ], + "operationId": "ResourceGroups_ListResources", + "description": "Get all the resources for a resource group.", + "parameters": [ + { + "name": "resourceGroupName", + "in": "path", + "required": true, + "type": "string", + "description": "The resource group with the resources to get.", + "pattern": "^[-\\w\\._\\(\\)]+$", + "minLength": 1, + "maxLength": 90 + }, + { + "name": "$filter", + "in": "query", + "required": false, + "type": "string", + "description": "The filter to apply on the operation." + }, + { + "name": "$expand", + "in": "query", + "required": false, + "type": "string", + "description": "The $expand query parameter" + }, + { + "name": "$top", + "in": "query", + "required": false, + "type": "integer", + "format": "int32", + "description": "The number of results to return. If null is passed, returns all resources." + }, + { + "$ref": "#/parameters/ApiVersionParameter" + }, + { + "$ref": "#/parameters/SubscriptionIdParameter" + } + ], + "responses": { + "200": { + "description": "OK - Returns an array of resources", + "schema": { + "$ref": "#/definitions/ResourceListResult" + } + } + }, + "x-ms-pageable": { + "nextLinkName": "nextLink" + }, + "x-ms-odata": "#/definitions/GenericResourceFilter" + } + }, + "/subscriptions/{subscriptionId}/resourcegroups/{resourceGroupName}": { + "head": { + "tags": [ + "ResourceGroups" + ], + "operationId": "ResourceGroups_CheckExistence", + "description": "Checks whether a resource group exists.", + "parameters": [ + { + "name": "resourceGroupName", + "in": "path", + "required": true, + "type": "string", + "description": "The name of the resource group to check. The name is case insensitive.", + "pattern": "^[-\\w\\._\\(\\)]+$", + "minLength": 1, + "maxLength": 90 + }, + { + "$ref": "#/parameters/ApiVersionParameter" + }, + { + "$ref": "#/parameters/SubscriptionIdParameter" + } + ], + "responses": { + "204": { + "description": "No Content" + }, + "404": { + "description": "Not Found" + } + } + }, + "put": { + "tags": [ + "ResourceGroups" + ], + "operationId": "ResourceGroups_CreateOrUpdate", + "description": "Creates a resource group.", + "parameters": [ + { + "name": "resourceGroupName", + "in": "path", + "required": true, + "type": "string", + "description": "The name of the resource group to create or update.", + "pattern": "^[-\\w\\._\\(\\)]+$", + "minLength": 1, + "maxLength": 90 + }, + { + "name": "parameters", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/ResourceGroup" + }, + "description": "Parameters supplied to the create or update a resource group." + }, + { + "$ref": "#/parameters/ApiVersionParameter" + }, + { + "$ref": "#/parameters/SubscriptionIdParameter" + } + ], + "responses": { + "201": { + "description": "Created - Returns information about the new resource group.", + "schema": { + "$ref": "#/definitions/ResourceGroup" + } + }, + "200": { + "description": "OK - Returns information about the new resource group.", + "schema": { + "$ref": "#/definitions/ResourceGroup" + } + } + } + }, + "delete": { + "tags": [ + "ResourceGroups" + ], + "operationId": "ResourceGroups_Delete", + "summary": "Deletes a resource group.", + "description": "When you delete a resource group, all of its resources are also deleted. Deleting a resource group deletes all of its template deployments and currently stored operations.", + "parameters": [ + { + "name": "resourceGroupName", + "in": "path", + "required": true, + "type": "string", + "description": "The name of the resource group to delete. The name is case insensitive.", + "pattern": "^[-\\w\\._\\(\\)]+$", + "minLength": 1, + "maxLength": 90 + }, + { + "$ref": "#/parameters/ApiVersionParameter" + }, + { + "$ref": "#/parameters/SubscriptionIdParameter" + } + ], + "responses": { + "202": { + "description": "Accepted" + }, + "200": { + "description": "OK" + } + }, + "x-ms-long-running-operation": true + }, + "get": { + "tags": [ + "ResourceGroups" + ], + "operationId": "ResourceGroups_Get", + "description": "Gets a resource group.", + "parameters": [ + { + "name": "resourceGroupName", + "in": "path", + "required": true, + "type": "string", + "description": "The name of the resource group to get. The name is case insensitive.", + "pattern": "^[-\\w\\._\\(\\)]+$", + "minLength": 1, + "maxLength": 90 + }, + { + "$ref": "#/parameters/ApiVersionParameter" + }, + { + "$ref": "#/parameters/SubscriptionIdParameter" + } + ], + "responses": { + "200": { + "description": "OK - Returns information about the resource group.", + "schema": { + "$ref": "#/definitions/ResourceGroup" + } + } + } + }, + "patch": { + "tags": [ + "ResourceGroups" + ], + "operationId": "ResourceGroups_Patch", + "summary": "Updates a resource group.", + "description": "Resource groups can be updated through a simple PATCH operation to a group address. The format of the request is the same as that for creating a resource group. If a field is unspecified, the current value is retained.", + "parameters": [ + { + "name": "resourceGroupName", + "in": "path", + "required": true, + "type": "string", + "description": "The name of the resource group to update. The name is case insensitive.", + "pattern": "^[-\\w\\._\\(\\)]+$", + "minLength": 1, + "maxLength": 90 + }, + { + "name": "parameters", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/ResourceGroup" + }, + "description": "Parameters supplied to update a resource group." + }, + { + "$ref": "#/parameters/ApiVersionParameter" + }, + { + "$ref": "#/parameters/SubscriptionIdParameter" + } + ], + "responses": { + "200": { + "description": "OK - Returns information about the resource group.", + "schema": { + "$ref": "#/definitions/ResourceGroup" + } + } + } + } + }, + "/subscriptions/{subscriptionId}/resourcegroups/{resourceGroupName}/exportTemplate": { + "post": { + "tags": [ + "ResourceGroups" + ], + "operationId": "ResourceGroups_ExportTemplate", + "description": "Captures the specified resource group as a template.", + "parameters": [ + { + "name": "resourceGroupName", + "in": "path", + "required": true, + "type": "string", + "description": "The name of the resource group to export as a template.", + "pattern": "^[-\\w\\._\\(\\)]+$", + "minLength": 1, + "maxLength": 90 + }, + { + "name": "parameters", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/ExportTemplateRequest" + }, + "description": "Parameters for exporting the template." + }, + { + "$ref": "#/parameters/ApiVersionParameter" + }, + { + "$ref": "#/parameters/SubscriptionIdParameter" + } + ], + "responses": { + "200": { + "description": "OK - Returns the result of the export.", + "schema": { + "$ref": "#/definitions/ResourceGroupExportResult" + } + } + } + } + }, + "/subscriptions/{subscriptionId}/resourcegroups": { + "get": { + "tags": [ + "ResourceGroups" + ], + "operationId": "ResourceGroups_List", + "description": "Gets all the resource groups for a subscription.", + "parameters": [ + { + "name": "$filter", + "in": "query", + "required": false, + "type": "string", + "description": "The filter to apply on the operation." + }, + { + "name": "$top", + "in": "query", + "required": false, + "type": "integer", + "format": "int32", + "description": "The number of results to return. If null is passed, returns all resource groups." + }, + { + "$ref": "#/parameters/ApiVersionParameter" + }, + { + "$ref": "#/parameters/SubscriptionIdParameter" + } + ], + "responses": { + "200": { + "description": "OK - Returns an array of resource groups.", + "schema": { + "$ref": "#/definitions/ResourceGroupListResult" + } + } + }, + "x-ms-pageable": { + "nextLinkName": "nextLink" + }, + "x-ms-odata": "#/definitions/ResourceGroupFilter" + } + }, + "/subscriptions/{subscriptionId}/resourceGroups/{sourceResourceGroupName}/moveResources": { + "post": { + "tags": [ + "Resources" + ], + "operationId": "Resources_MoveResources", + "summary": "Moves resources from one resource group to another resource group.", + "description": "The resources to move must be in the same source resource group. The target resource group may be in a different subscription. When moving resources, both the source group and the target group are locked for the duration of the operation. Write and delete operations are blocked on the groups until the move completes. ", + "parameters": [ + { + "name": "sourceResourceGroupName", + "in": "path", + "required": true, + "type": "string", + "description": "The name of the resource group containing the rsources to move.", + "pattern": "^[-\\w\\._\\(\\)]+$", + "minLength": 1, + "maxLength": 90 + }, + { + "name": "parameters", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/ResourcesMoveInfo" + }, + "description": "Parameters for moving resources." + }, + { + "$ref": "#/parameters/ApiVersionParameter" + }, + { + "$ref": "#/parameters/SubscriptionIdParameter" + } + ], + "responses": { + "202": { + "description": "Accepted" + }, + "204": { + "description": "No Content" + } + }, + "x-ms-long-running-operation": true + } + }, + "/subscriptions/{subscriptionId}/resources": { + "get": { + "tags": [ + "Resources" + ], + "operationId": "Resources_List", + "description": "Get all the resources in a subscription.", + "parameters": [ + { + "name": "$filter", + "in": "query", + "required": false, + "type": "string", + "description": "The filter to apply on the operation." + }, + { + "name": "$expand", + "in": "query", + "required": false, + "type": "string", + "description": "The $expand query parameter." + }, + { + "name": "$top", + "in": "query", + "required": false, + "type": "integer", + "format": "int32", + "description": "The number of results to return. If null is passed, returns all resource groups." + }, + { + "$ref": "#/parameters/ApiVersionParameter" + }, + { + "$ref": "#/parameters/SubscriptionIdParameter" + } + ], + "responses": { + "200": { + "description": "OK - Returns an array of resources.", + "schema": { + "$ref": "#/definitions/ResourceListResult" + } + } + }, + "x-ms-pageable": { + "nextLinkName": "nextLink" + }, + "x-ms-odata": "#/definitions/GenericResourceFilter" + } + }, + "/subscriptions/{subscriptionId}/resourcegroups/{resourceGroupName}/providers/{resourceProviderNamespace}/{parentResourcePath}/{resourceType}/{resourceName}": { + "head": { + "tags": [ + "Resources" + ], + "operationId": "Resources_CheckExistence", + "description": "Checks whether a resource exists.", + "parameters": [ + { + "name": "resourceGroupName", + "in": "path", + "required": true, + "type": "string", + "description": "The name of the resource group containing the resource to check. The name is case insensitive.", + "pattern": "^[-\\w\\._\\(\\)]+$", + "minLength": 1, + "maxLength": 90 + }, + { + "name": "resourceProviderNamespace", + "in": "path", + "required": true, + "type": "string", + "description": "The resource provider of the resource to check." + }, + { + "name": "parentResourcePath", + "in": "path", + "required": true, + "type": "string", + "description": "The parent resource identity.", + "x-ms-skip-url-encoding": true + }, + { + "name": "resourceType", + "in": "path", + "required": true, + "type": "string", + "description": "The resource type.", + "x-ms-skip-url-encoding": true + }, + { + "name": "resourceName", + "in": "path", + "required": true, + "type": "string", + "description": "The name of the resource to check whether it exists." + }, + { + "name": "api-version", + "in": "query", + "required": true, + "type": "string", + "description": "The API version to use for the operation." + }, + { + "$ref": "#/parameters/SubscriptionIdParameter" + } + ], + "responses": { + "204": { + "description": "No Content" + }, + "404": { + "description": "Not Found" + } + } + }, + "delete": { + "tags": [ + "Resources" + ], + "operationId": "Resources_Delete", + "description": "Deletes a resource.", + "parameters": [ + { + "name": "resourceGroupName", + "in": "path", + "required": true, + "type": "string", + "description": "The name of the resource group that contains the resource to delete. The name is case insensitive.", + "pattern": "^[-\\w\\._\\(\\)]+$", + "minLength": 1, + "maxLength": 90 + }, + { + "name": "resourceProviderNamespace", + "in": "path", + "required": true, + "type": "string", + "description": "The namespace of the resource provider." + }, + { + "name": "parentResourcePath", + "in": "path", + "required": true, + "type": "string", + "description": "The parent resource identity.", + "x-ms-skip-url-encoding": true + }, + { + "name": "resourceType", + "in": "path", + "required": true, + "type": "string", + "description": "The resource type.", + "x-ms-skip-url-encoding": true + }, + { + "name": "resourceName", + "in": "path", + "required": true, + "type": "string", + "description": "The name of the resource to delete." + }, + { + "name": "api-version", + "in": "query", + "required": true, + "type": "string", + "description": "The API version to use for the operation." + }, + { + "$ref": "#/parameters/SubscriptionIdParameter" + } + ], + "responses": { + "200": { + "description": "OK" + }, + "204": { + "description": "No Content" + }, + "202": { + "description": "Accepted" + } + }, + "x-ms-long-running-operation": true + }, + "put": { + "tags": [ + "Resources" + ], + "operationId": "Resources_CreateOrUpdate", + "description": "Creates a resource.", + "parameters": [ + { + "name": "resourceGroupName", + "in": "path", + "required": true, + "type": "string", + "description": "The name of the resource group for the resource. The name is case insensitive.", + "pattern": "^[-\\w\\._\\(\\)]+$", + "minLength": 1, + "maxLength": 90 + }, + { + "name": "resourceProviderNamespace", + "in": "path", + "required": true, + "type": "string", + "description": "The namespace of the resource provider." + }, + { + "name": "parentResourcePath", + "in": "path", + "required": true, + "type": "string", + "description": "The parent resource identity.", + "x-ms-skip-url-encoding": true + }, + { + "name": "resourceType", + "in": "path", + "required": true, + "type": "string", + "description": "The resource type of the resource to create.", + "x-ms-skip-url-encoding": true + }, + { + "name": "resourceName", + "in": "path", + "required": true, + "type": "string", + "description": "The name of the resource to create." + }, + { + "name": "api-version", + "in": "query", + "required": true, + "type": "string", + "description": "The API version to use for the operation." + }, + { + "name": "parameters", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/GenericResource" + }, + "description": "Parameters for creating or updating the resource." + }, + { + "$ref": "#/parameters/SubscriptionIdParameter" + } + ], + "responses": { + "201": { + "description": "Created - Returns information about the resource.", + "schema": { + "$ref": "#/definitions/GenericResource" + } + }, + "200": { + "description": "OK - Returns information about the resource.", + "schema": { + "$ref": "#/definitions/GenericResource" + } + }, + "202": { + "description": "Accepted" + } + }, + "x-ms-long-running-operation": true + }, + "get": { + "tags": [ + "Resources" + ], + "operationId": "Resources_Get", + "description": "Gets a resource.", + "parameters": [ + { + "name": "resourceGroupName", + "in": "path", + "required": true, + "type": "string", + "description": "The name of the resource group containing the resource to get. The name is case insensitive.", + "pattern": "^[-\\w\\._\\(\\)]+$", + "minLength": 1, + "maxLength": 90 + }, + { + "name": "resourceProviderNamespace", + "in": "path", + "required": true, + "type": "string", + "description": "The namespace of the resource provider." + }, + { + "name": "parentResourcePath", + "in": "path", + "required": true, + "type": "string", + "description": "The parent resource identity.", + "x-ms-skip-url-encoding": true + }, + { + "name": "resourceType", + "in": "path", + "required": true, + "type": "string", + "description": "The resource type of the resource.", + "x-ms-skip-url-encoding": true + }, + { + "name": "resourceName", + "in": "path", + "required": true, + "type": "string", + "description": "The name of the resource to get." + }, + { + "name": "api-version", + "in": "query", + "required": true, + "type": "string", + "description": "The API version to use for the operation." + }, + { + "$ref": "#/parameters/SubscriptionIdParameter" + } + ], + "responses": { + "200": { + "description": "OK - Returns information about the resource.", + "schema": { + "$ref": "#/definitions/GenericResource" + } + } + } + } + }, + "/{resourceId}": { + "head": { + "tags": [ + "Resources" + ], + "operationId": "Resources_CheckExistenceById", + "description": "Checks by ID whether a resource exists.", + "parameters": [ + { + "name": "resourceId", + "in": "path", + "required": true, + "type": "string", + "description": "The fully qualified ID of the resource, including the resource name and resource type. Use the format, /subscriptions/{guid}/resourceGroups/{resource-group-name}/{resource-provider-namespace}/{resource-type}/{resource-name}", + "x-ms-skip-url-encoding": true + }, + { + "name": "api-version", + "in": "query", + "required": true, + "type": "string", + "description": "The API version to use for the operation." + } + ], + "responses": { + "204": { + "description": "No Content" + }, + "404": { + "description": "Not Found" + } + } + }, + "delete": { + "tags": [ + "Resources" + ], + "operationId": "Resources_DeleteById", + "description": "Deletes a resource by ID.", + "parameters": [ + { + "name": "resourceId", + "in": "path", + "required": true, + "type": "string", + "description": "The fully qualified ID of the resource, including the resource name and resource type. Use the format, /subscriptions/{guid}/resourceGroups/{resource-group-name}/{resource-provider-namespace}/{resource-type}/{resource-name}", + "x-ms-skip-url-encoding": true + }, + { + "name": "api-version", + "in": "query", + "required": true, + "type": "string", + "description": "The API version to use for the operation." + } + ], + "responses": { + "200": { + "description": "OK" + }, + "204": { + "description": "No Content" + }, + "202": { + "description": "Accepted" + } + }, + "x-ms-long-running-operation": true + }, + "put": { + "tags": [ + "Resources" + ], + "operationId": "Resources_CreateOrUpdateById", + "description": "Create a resource by ID.", + "parameters": [ + { + "name": "resourceId", + "in": "path", + "required": true, + "type": "string", + "description": "The fully qualified ID of the resource, including the resource name and resource type. Use the format, /subscriptions/{guid}/resourceGroups/{resource-group-name}/{resource-provider-namespace}/{resource-type}/{resource-name}", + "x-ms-skip-url-encoding": true + }, + { + "name": "api-version", + "in": "query", + "required": true, + "type": "string", + "description": "The API version to use for the operation." + }, + { + "name": "parameters", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/GenericResource" + }, + "description": "Create or update resource parameters." + } + ], + "responses": { + "201": { + "description": "Created - Returns information about the resource.", + "schema": { + "$ref": "#/definitions/GenericResource" + } + }, + "200": { + "description": "OK - Returns information about the resource.", + "schema": { + "$ref": "#/definitions/GenericResource" + } + }, + "202": { + "description": "Accepted" + } + }, + "x-ms-long-running-operation": true + }, + "get": { + "tags": [ + "Resources" + ], + "operationId": "Resources_GetById", + "description": "Gets a resource by ID.", + "parameters": [ + { + "name": "resourceId", + "in": "path", + "required": true, + "type": "string", + "description": "The fully qualified ID of the resource, including the resource name and resource type. Use the format, /subscriptions/{guid}/resourceGroups/{resource-group-name}/{resource-provider-namespace}/{resource-type}/{resource-name}", + "x-ms-skip-url-encoding": true + }, + { + "name": "api-version", + "in": "query", + "required": true, + "type": "string", + "description": "The API version to use for the operation." + } + ], + "responses": { + "200": { + "description": "OK - Returns information about the resource.", + "schema": { + "$ref": "#/definitions/GenericResource" + } + } + } + } + }, + "/subscriptions/{subscriptionId}/tagNames/{tagName}/tagValues/{tagValue}": { + "delete": { + "tags": [ + "Tags" + ], + "operationId": "Tags_DeleteValue", + "description": "Deletes a tag value.", + "parameters": [ + { + "name": "tagName", + "in": "path", + "required": true, + "type": "string", + "description": "The name of the tag." + }, + { + "name": "tagValue", + "in": "path", + "required": true, + "type": "string", + "description": "The value of the tag to delete." + }, + { + "$ref": "#/parameters/ApiVersionParameter" + }, + { + "$ref": "#/parameters/SubscriptionIdParameter" + } + ], + "responses": { + "200": { + "description": "OK" + }, + "204": { + "description": "No Content" + } + } + }, + "put": { + "tags": [ + "Tags" + ], + "operationId": "Tags_CreateOrUpdateValue", + "description": "Creates a tag value. The name of the tag must already exist.", + "parameters": [ + { + "name": "tagName", + "in": "path", + "required": true, + "type": "string", + "description": "The name of the tag." + }, + { + "name": "tagValue", + "in": "path", + "required": true, + "type": "string", + "description": "The value of the tag to create." + }, + { + "$ref": "#/parameters/ApiVersionParameter" + }, + { + "$ref": "#/parameters/SubscriptionIdParameter" + } + ], + "responses": { + "200": { + "description": "OK - Returns information about the tag value.", + "schema": { + "$ref": "#/definitions/TagValue" + } + }, + "201": { + "description": "Created - Returns information about the tag value.", + "schema": { + "$ref": "#/definitions/TagValue" + } + } + } + } + }, + "/subscriptions/{subscriptionId}/tagNames/{tagName}": { + "put": { + "tags": [ + "Tags" + ], + "operationId": "Tags_CreateOrUpdate", + "summary": "Creates a tag in the subscription.", + "description": "The tag name can have a maximum of 512 characters and is case insensitive. Tag names created by Azure have prefixes of microsoft, azure, or windows. You cannot create tags with one of these prefixes.", + "parameters": [ + { + "name": "tagName", + "in": "path", + "required": true, + "type": "string", + "description": "The name of the tag to create." + }, + { + "$ref": "#/parameters/ApiVersionParameter" + }, + { + "$ref": "#/parameters/SubscriptionIdParameter" + } + ], + "responses": { + "200": { + "description": "OK - Returns information about the tag.", + "schema": { + "$ref": "#/definitions/TagDetails" + } + }, + "201": { + "description": "Created - Returns information about the tag.", + "schema": { + "$ref": "#/definitions/TagDetails" + } + } + } + }, + "delete": { + "tags": [ + "Tags" + ], + "operationId": "Tags_Delete", + "summary": "Deletes a tag from the subscription.", + "description": "You must remove all values from a resource tag before you can delete it.", + "parameters": [ + { + "name": "tagName", + "in": "path", + "required": true, + "type": "string", + "description": "The name of the tag." + }, + { + "$ref": "#/parameters/ApiVersionParameter" + }, + { + "$ref": "#/parameters/SubscriptionIdParameter" + } + ], + "responses": { + "200": { + "description": "OK" + }, + "204": { + "description": "No Content" + } + } + } + }, + "/subscriptions/{subscriptionId}/tagNames": { + "get": { + "tags": [ + "Tags" + ], + "operationId": "Tags_List", + "description": "Gets the names and values of all resource tags that are defined in a subscription.", + "parameters": [ + { + "$ref": "#/parameters/ApiVersionParameter" + }, + { + "$ref": "#/parameters/SubscriptionIdParameter" + } + ], + "responses": { + "200": { + "description": "OK - Returns an array of tag names and values.", + "schema": { + "$ref": "#/definitions/TagsListResult" + } + } + }, + "x-ms-pageable": { + "nextLinkName": "nextLink" + } + } + }, + "/subscriptions/{subscriptionId}/resourcegroups/{resourceGroupName}/deployments/{deploymentName}/operations/{operationId}": { + "get": { + "tags": [ + "DeploymentOperations" + ], + "operationId": "DeploymentOperations_Get", + "description": "Gets a deployments operation.", + "parameters": [ + { + "name": "resourceGroupName", + "in": "path", + "required": true, + "type": "string", + "description": "The name of the resource group. The name is case insensitive.", + "pattern": "^[-\\w\\._\\(\\)]+$", + "minLength": 1, + "maxLength": 90 + }, + { + "name": "deploymentName", + "in": "path", + "required": true, + "type": "string", + "description": "The name of the deployment.", + "pattern": "^[-\\w\\._\\(\\)]+$", + "minLength": 1, + "maxLength": 64 + }, + { + "name": "operationId", + "in": "path", + "required": true, + "type": "string", + "description": "The ID of the operation to get." + }, + { + "$ref": "#/parameters/ApiVersionParameter" + }, + { + "$ref": "#/parameters/SubscriptionIdParameter" + } + ], + "responses": { + "200": { + "description": "OK - Returns information about the deployment operation.", + "schema": { + "$ref": "#/definitions/DeploymentOperation" + } + } + } + } + }, + "/subscriptions/{subscriptionId}/resourcegroups/{resourceGroupName}/deployments/{deploymentName}/operations": { + "get": { + "tags": [ + "DeploymentOperations" + ], + "operationId": "DeploymentOperations_List", + "description": "Gets all deployments operations for a deployment.", + "parameters": [ + { + "name": "resourceGroupName", + "in": "path", + "required": true, + "type": "string", + "description": "The name of the resource group. The name is case insensitive.", + "pattern": "^[-\\w\\._\\(\\)]+$", + "minLength": 1, + "maxLength": 90 + }, + { + "name": "deploymentName", + "in": "path", + "required": true, + "type": "string", + "description": "The name of the deployment with the operation to get.", + "pattern": "^[-\\w\\._\\(\\)]+$", + "minLength": 1, + "maxLength": 64 + }, + { + "name": "$top", + "in": "query", + "required": false, + "type": "integer", + "format": "int32", + "description": "The number of results to return." + }, + { + "$ref": "#/parameters/ApiVersionParameter" + }, + { + "$ref": "#/parameters/SubscriptionIdParameter" + } + ], + "responses": { + "200": { + "description": "OK - Return an array of deployment operations.", + "schema": { + "$ref": "#/definitions/DeploymentOperationsListResult" + } + } + }, + "x-ms-pageable": { + "nextLinkName": "nextLink" + } + } + } + }, + "definitions": { + "DeploymentExtendedFilter": { + "properties": { + "provisioningState": { + "type": "string", + "description": "The provisioning state." + } + }, + "description": "Deployment filter." + }, + "GenericResourceFilter": { + "properties": { + "resourceType": { + "type": "string", + "description": "The resource type." + }, + "tagname": { + "type": "string", + "description": "The tag name." + }, + "tagvalue": { + "type": "string", + "description": "The tag value." + } + }, + "description": "Resource filter." + }, + "ResourceGroupFilter": { + "properties": { + "tagName": { + "type": "string", + "description": "The tag name." + }, + "tagValue": { + "type": "string", + "description": "The tag value." + } + }, + "description": "Resource group filter." + }, + "TemplateLink": { + "properties": { + "uri": { + "type": "string", + "description": "The URI of the template to deploy." + }, + "contentVersion": { + "type": "string", + "description": "If included, must match the ContentVersion in the template." + } + }, + "required": [ + "uri" + ], + "description": "Entity representing the reference to the template." + }, + "ParametersLink": { + "properties": { + "uri": { + "type": "string", + "description": "The URI of the parameters file." + }, + "contentVersion": { + "type": "string", + "description": "If included, must match the ContentVersion in the template." + } + }, + "required": [ + "uri" + ], + "description": "Entity representing the reference to the deployment paramaters." + }, + "DeploymentProperties": { + "properties": { + "template": { + "type": "object", + "description": "The template content. You use this element when you want to pass the template syntax directly in the request rather than link to an existing template. It can be a JObject or well-formed JSON string. Use either the templateLink property or the template property, but not both." + }, + "templateLink": { + "$ref": "#/definitions/TemplateLink", + "description": "The URI of the template. Use either the templateLink property or the template property, but not both." + }, + "parameters": { + "type": "object", + "description": "Name and value pairs that define the deployment parameters for the template. You use this element when you want to provide the parameter values directly in the request rather than link to an existing parameter file. Use either the parametersLink property or the parameters property, but not both. It can be a JObject or a well formed JSON string." + }, + "parametersLink": { + "$ref": "#/definitions/ParametersLink", + "description": "The URI of parameters file. You use this element to link to an existing parameters file. Use either the parametersLink property or the parameters property, but not both." + }, + "mode": { + "type": "string", + "description": "The mode that is used to deploy resources. This value can be either Incremental or Complete. In Incremental mode, resources are deployed without deleting existing resources that are not included in the template. In Complete mode, resources are deployed and existing resources in the resource group that are not included in the template are deleted. Be careful when using Complete mode as you may unintentionally delete resources.", + "enum": [ + "Incremental", + "Complete" + ], + "x-ms-enum": { + "name": "DeploymentMode", + "modelAsString": false + } + }, + "debugSetting": { + "$ref": "#/definitions/DebugSetting", + "description": "The debug setting of the deployment." + } + }, + "required": [ + "mode" + ], + "description": "Deployment properties." + }, + "DebugSetting": { + "properties": { + "detailLevel": { + "type": "string", + "description": "Specifies the type of information to log for debugging. The permitted values are none, requestContent, responseContent, or both requestContent and responseContent separated by a comma. The default is none. When setting this value, carefully consider the type of information you are passing in during deployment. By logging information about the request or response, you could potentially expose sensitive data that is retrieved through the deployment operations." + } + } + }, + "Deployment": { + "properties": { + "properties": { + "$ref": "#/definitions/DeploymentProperties", + "description": "The deployment properties." + } + }, + "required": [ + "properties" + ], + "description": "Deployment operation parameters." + }, + "DeploymentExportResult": { + "properties": { + "template": { + "type": "object", + "description": "The template content." + } + }, + "description": "The deployment export result. " + }, + "ResourceManagementErrorWithDetails": { + "properties": { + "code": { + "readOnly": true, + "type": "string", + "description": "The error code returned when exporting the template." + }, + "message": { + "readOnly": true, + "type": "string", + "description": "The error message describing the export error." + }, + "target": { + "readOnly": true, + "type": "string", + "description": "The target of the error." + }, + "details": { + "readOnly": true, + "type": "array", + "items": { + "$ref": "#/definitions/ResourceManagementErrorWithDetails" + }, + "description": "Validation error." + } + } + }, + "AliasPathType": { + "properties": { + "path": { + "type": "string", + "description": "The path of an alias." + }, + "apiVersions": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The API versions." + } + }, + "description": "The type of the paths for alias. " + }, + "AliasType": { + "properties": { + "name": { + "type": "string", + "description": "The alias name." + }, + "paths": { + "type": "array", + "items": { + "$ref": "#/definitions/AliasPathType" + }, + "description": "The paths for an alias." + } + }, + "description": "The alias type. " + }, + "ProviderResourceType": { + "properties": { + "resourceType": { + "type": "string", + "description": "The resource type." + }, + "locations": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The collection of locations where this resource type can be created." + }, + "aliases": { + "type": "array", + "items": { + "$ref": "#/definitions/AliasType" + }, + "description": "The aliases that are supported by this resource type." + }, + "apiVersions": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The API version." + }, + "properties": { + "type": "object", + "additionalProperties": { + "type": "string", + "description": "The additional properties. " + }, + "description": "The properties." + } + }, + "description": "Resource type managed by the resource provider." + }, + "Provider": { + "properties": { + "id": { + "readOnly": true, + "type": "string", + "description": "The provider ID." + }, + "namespace": { + "type": "string", + "description": "The namespace of the resource provider." + }, + "registrationState": { + "readOnly": true, + "type": "string", + "description": "The registration state of the provider." + }, + "resourceTypes": { + "readOnly": true, + "type": "array", + "items": { + "$ref": "#/definitions/ProviderResourceType" + }, + "description": "The collection of provider resource types." + } + }, + "description": "Resource provider information." + }, + "BasicDependency": { + "properties": { + "id": { + "type": "string", + "description": "The ID of the dependency." + }, + "resourceType": { + "type": "string", + "description": "The dependency resource type." + }, + "resourceName": { + "type": "string", + "description": "The dependency resource name." + } + }, + "description": "Deployment dependency information." + }, + "Dependency": { + "properties": { + "dependsOn": { + "type": "array", + "items": { + "$ref": "#/definitions/BasicDependency" + }, + "description": "The list of dependencies." + }, + "id": { + "type": "string", + "description": "The ID of the dependency." + }, + "resourceType": { + "type": "string", + "description": "The dependency resource type." + }, + "resourceName": { + "type": "string", + "description": "The dependency resource name." + } + }, + "description": "Deployment dependency information." + }, + "DeploymentPropertiesExtended": { + "properties": { + "provisioningState": { + "readOnly": true, + "type": "string", + "description": "The state of the provisioning." + }, + "correlationId": { + "readOnly": true, + "type": "string", + "description": "The correlation ID of the deployment." + }, + "timestamp": { + "readOnly": true, + "type": "string", + "format": "date-time", + "description": "The timestamp of the template deployment." + }, + "outputs": { + "type": "object", + "description": "Key/value pairs that represent deploymentoutput." + }, + "providers": { + "type": "array", + "items": { + "$ref": "#/definitions/Provider" + }, + "description": "The list of resource providers needed for the deployment." + }, + "dependencies": { + "type": "array", + "items": { + "$ref": "#/definitions/Dependency" + }, + "description": "The list of deployment dependencies." + }, + "template": { + "type": "object", + "description": "The template content. Use only one of Template or TemplateLink." + }, + "templateLink": { + "$ref": "#/definitions/TemplateLink", + "description": "The URI referencing the template. Use only one of Template or TemplateLink." + }, + "parameters": { + "type": "object", + "description": "Deployment parameters. Use only one of Parameters or ParametersLink." + }, + "parametersLink": { + "$ref": "#/definitions/ParametersLink", + "description": "The URI referencing the parameters. Use only one of Parameters or ParametersLink." + }, + "mode": { + "type": "string", + "description": "The deployment mode. Possible values are Incremental and Complete.", + "enum": [ + "Incremental", + "Complete" + ], + "x-ms-enum": { + "name": "DeploymentMode", + "modelAsString": false + } + }, + "debugSetting": { + "$ref": "#/definitions/DebugSetting", + "description": "The debug setting of the deployment." + } + }, + "description": "Deployment properties with additional details." + }, + "DeploymentValidateResult": { + "properties": { + "error": { + "$ref": "#/definitions/ResourceManagementErrorWithDetails", + "description": "Validation error." + }, + "properties": { + "$ref": "#/definitions/DeploymentPropertiesExtended", + "description": "The template deployment properties." + } + }, + "description": "Information from validate template deployment response." + }, + "DeploymentExtended": { + "properties": { + "id": { + "type": "string", + "description": "The ID of the deployment." + }, + "name": { + "type": "string", + "description": "The name of the deployment." + }, + "properties": { + "$ref": "#/definitions/DeploymentPropertiesExtended", + "description": "Deployment properties." + } + }, + "required": [ + "name" + ], + "description": "Deployment information." + }, + "DeploymentListResult": { + "properties": { + "value": { + "type": "array", + "items": { + "$ref": "#/definitions/DeploymentExtended" + }, + "description": "An array of deployments." + }, + "nextLink": { + "readOnly": true, + "type": "string", + "description": "The URL to use for getting the next set of results." + } + }, + "description": "List of deployments." + }, + "ProviderListResult": { + "properties": { + "value": { + "type": "array", + "items": { + "$ref": "#/definitions/Provider" + }, + "description": "An array of resource providers." + }, + "nextLink": { + "readOnly": true, + "type": "string", + "description": "The URL to use for getting the next set of results." + } + }, + "description": "List of resource providers." + }, + "GenericResource": { + "properties": { + "plan": { + "$ref": "#/definitions/Plan", + "description": "The plan of the resource." + }, + "properties": { + "type": "object", + "description": "The resource properties." + }, + "kind": { + "type": "string", + "description": "The kind of the resource.", + "pattern": "^[-\\w\\._,\\(\\)]+$" + }, + "managedBy": { + "type": "string", + "description": "ID of the resource that manages this resource." + }, + "sku": { + "$ref": "#/definitions/Sku", + "description": "The SKU of the resource." + }, + "identity": { + "$ref": "#/definitions/Identity", + "description": "The identity of the resource." + } + }, + "allOf": [ + { + "$ref": "#/definitions/Resource" + } + ], + "description": "Resource information." + }, + "Plan": { + "properties": { + "name": { + "type": "string", + "description": "The plan ID." + }, + "publisher": { + "type": "string", + "description": "The publisher ID." + }, + "product": { + "type": "string", + "description": "The offer ID." + }, + "promotionCode": { + "type": "string", + "description": "The promotion code." + } + }, + "description": "Plan for the resource." + }, + "Sku": { + "properties": { + "name": { + "type": "string", + "description": "The SKU name." + }, + "tier": { + "type": "string", + "description": "The SKU tier." + }, + "size": { + "type": "string", + "description": "The SKU size." + }, + "family": { + "type": "string", + "description": "The SKU family." + }, + "model": { + "type": "string", + "description": "The SKU model." + }, + "capacity": { + "type": "integer", + "format": "int32", + "description": "The SKU capacity." + } + }, + "description": "SKU for the resource." + }, + "Identity": { + "properties": { + "principalId": { + "readOnly": true, + "type": "string", + "description": "The principal ID of resource identity." + }, + "tenantId": { + "readOnly": true, + "type": "string", + "description": "The tenant ID of resource." + }, + "type": { + "type": "string", + "description": "The identity type.", + "enum": [ + "SystemAssigned" + ], + "x-ms-enum": { + "name": "ResourceIdentityType", + "modelAsString": false + } + } + }, + "description": "Identity for the resource." + }, + "ResourceListResult": { + "properties": { + "value": { + "type": "array", + "items": { + "$ref": "#/definitions/GenericResource" + }, + "description": "An array of resources." + }, + "nextLink": { + "readOnly": true, + "type": "string", + "description": "The URL to use for getting the next set of results." + } + }, + "description": "List of resource groups." + }, + "ResourceGroup": { + "properties": { + "id": { + "readOnly": true, + "type": "string", + "description": "The ID of the resource group." + }, + "name": { + "type": "string", + "description": "The name of the resource group." + }, + "properties": { + "$ref": "#/definitions/ResourceGroupProperties" + }, + "location": { + "type": "string", + "description": "The location of the resource group. It cannot be changed after the resource group has been created. It muct be one of the supported Azure locations." + }, + "managedBy": { + "type": "string", + "description": "The ID of the resource that manages this resource group." + }, + "tags": { + "type": "object", + "additionalProperties": { + "type": "string", + "description": "The additional properties. " + }, + "description": "The tags attached to the resource group." + } + }, + "required": [ + "location" + ], + "description": "Resource group information." + }, + "ResourceGroupProperties": { + "properties": { + "provisioningState": { + "readOnly": true, + "type": "string", + "description": "The provisioning state. " + } + }, + "description": "The resource group properties." + }, + "ResourceGroupListResult": { + "properties": { + "value": { + "type": "array", + "items": { + "$ref": "#/definitions/ResourceGroup" + }, + "description": "An array of resource groups." + }, + "nextLink": { + "readOnly": true, + "type": "string", + "description": "The URL to use for getting the next set of results." + } + }, + "description": "List of resource groups." + }, + "ResourcesMoveInfo": { + "properties": { + "resources": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The IDs of the resources." + }, + "targetResourceGroup": { + "type": "string", + "description": "The target resource group." + } + }, + "description": "Parameters of move resources." + }, + "ExportTemplateRequest": { + "properties": { + "resources": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The IDs of the resources. The only supported string currently is '*' (all resources). Future updates will support exporting specific resources." + }, + "options": { + "type": "string", + "description": "The export template options. Supported values include 'IncludeParameterDefaultValue', 'IncludeComments' or 'IncludeParameterDefaultValue, IncludeComments" + } + }, + "description": "Export resource group template request parameters." + }, + "TagCount": { + "properties": { + "type": { + "type": "string", + "description": "Type of count." + }, + "value": { + "type": "integer", + "description": "Value of count." + } + }, + "description": "Tag count." + }, + "TagValue": { + "properties": { + "id": { + "type": "string", + "description": "The tag ID." + }, + "tagValue": { + "type": "string", + "description": "The tag value." + }, + "count": { + "$ref": "#/definitions/TagCount", + "description": "The tag value count." + } + }, + "description": "Tag information." + }, + "TagDetails": { + "properties": { + "id": { + "type": "string", + "description": "The tag ID." + }, + "tagName": { + "type": "string", + "description": "The tag name." + }, + "count": { + "$ref": "#/definitions/TagCount", + "description": "The total number of resources that use the resource tag. When a tag is initially created and has no associated resources, the value is 0." + }, + "values": { + "type": "array", + "items": { + "$ref": "#/definitions/TagValue" + }, + "description": "The list of tag values." + } + }, + "description": "Tag details." + }, + "TagsListResult": { + "properties": { + "value": { + "type": "array", + "items": { + "$ref": "#/definitions/TagDetails" + }, + "description": "An array of tags." + }, + "nextLink": { + "readOnly": true, + "type": "string", + "description": "The URL to use for getting the next set of results." + } + }, + "description": "List of subscription tags." + }, + "TargetResource": { + "properties": { + "id": { + "type": "string", + "description": "The ID of the resource." + }, + "resourceName": { + "type": "string", + "description": "The name of the resource." + }, + "resourceType": { + "type": "string", + "description": "The type of the resource." + } + }, + "description": "Target resource." + }, + "HttpMessage": { + "properties": { + "content": { + "type": "object", + "description": "HTTP message content." + } + } + }, + "DeploymentOperationProperties": { + "properties": { + "provisioningState": { + "readOnly": true, + "type": "string", + "description": "The state of the provisioning." + }, + "timestamp": { + "readOnly": true, + "type": "string", + "format": "date-time", + "description": "The date and time of the operation." + }, + "serviceRequestId": { + "readOnly": true, + "type": "string", + "description": "Deployment operation service request id." + }, + "statusCode": { + "readOnly": true, + "type": "string", + "description": "Operation status code." + }, + "statusMessage": { + "readOnly": true, + "type": "object", + "description": "Operation status message." + }, + "targetResource": { + "readOnly": true, + "$ref": "#/definitions/TargetResource", + "description": "The target resource." + }, + "request": { + "readOnly": true, + "$ref": "#/definitions/HttpMessage", + "description": "The HTTP request message." + }, + "response": { + "readOnly": true, + "$ref": "#/definitions/HttpMessage", + "description": "The HTTP response message." + } + }, + "description": "Deployment operation properties." + }, + "DeploymentOperation": { + "properties": { + "id": { + "readOnly": true, + "type": "string", + "description": "Full deployment operation ID." + }, + "operationId": { + "readOnly": true, + "type": "string", + "description": "Deployment operation ID." + }, + "properties": { + "$ref": "#/definitions/DeploymentOperationProperties", + "description": "Deployment properties." + } + }, + "description": "Deployment operation information." + }, + "DeploymentOperationsListResult": { + "properties": { + "value": { + "type": "array", + "items": { + "$ref": "#/definitions/DeploymentOperation" + }, + "description": "An array of deployment operations." + }, + "nextLink": { + "readOnly": true, + "type": "string", + "description": "The URL to use for getting the next set of results." + } + }, + "description": "List of deployment operations." + }, + "ResourceProviderOperationDisplayProperties": { + "properties": { + "publisher": { + "type": "string", + "description": "Operation description." + }, + "provider": { + "type": "string", + "description": "Operation provider." + }, + "resource": { + "type": "string", + "description": "Operation resource." + }, + "operation": { + "type": "string", + "description": "Operation." + }, + "description": { + "type": "string", + "description": "Operation description." + } + }, + "description": "Resource provider operation's display properties." + }, + "Resource": { + "properties": { + "id": { + "readOnly": true, + "type": "string", + "description": "Resource ID" + }, + "name": { + "readOnly": true, + "type": "string", + "description": "Resource name" + }, + "type": { + "readOnly": true, + "type": "string", + "description": "Resource type" + }, + "location": { + "type": "string", + "description": "Resource location" + }, + "tags": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Resource tags" + } + }, + "x-ms-azure-resource": true + }, + "SubResource": { + "properties": { + "id": { + "type": "string", + "description": "Resource ID" + } + }, + "x-ms-azure-resource": true + }, + "ResourceGroupExportResult": { + "properties": { + "template": { + "type": "object", + "description": "The template content." + }, + "error": { + "$ref": "#/definitions/ResourceManagementErrorWithDetails", + "description": "The error." + } + } + } + }, + "parameters": { + "SubscriptionIdParameter": { + "name": "subscriptionId", + "in": "path", + "required": true, + "type": "string", + "description": "The ID of the target subscription." + }, + "ApiVersionParameter": { + "name": "api-version", + "in": "query", + "required": true, + "type": "string", + "description": "The API version to use for this operation." + } + } +} \ No newline at end of file diff --git a/arm-search/2015-02-28/swagger/search.json b/test/swaggers/arm-search/2015-02-28/swagger/search.json similarity index 100% rename from arm-search/2015-02-28/swagger/search.json rename to test/swaggers/arm-search/2015-02-28/swagger/search.json diff --git a/arm-search/2015-08-19/swagger/search.json b/test/swaggers/arm-search/2015-08-19/swagger/search.json similarity index 100% rename from arm-search/2015-08-19/swagger/search.json rename to test/swaggers/arm-search/2015-08-19/swagger/search.json diff --git a/arm-storage/2015-05-01-preview/swagger/storage.json b/test/swaggers/arm-storage/2015-05-01-preview/swagger/storage.json similarity index 100% rename from arm-storage/2015-05-01-preview/swagger/storage.json rename to test/swaggers/arm-storage/2015-05-01-preview/swagger/storage.json diff --git a/arm-storage/2015-06-15/swagger/storage.json b/test/swaggers/arm-storage/2015-06-15/swagger/storage.json similarity index 100% rename from arm-storage/2015-06-15/swagger/storage.json rename to test/swaggers/arm-storage/2015-06-15/swagger/storage.json diff --git a/arm-storage/2016-01-01/examples/storageAccountCheckNameAvailability.json b/test/swaggers/arm-storage/2016-01-01/examples/storageAccountCheckNameAvailability.json similarity index 100% rename from arm-storage/2016-01-01/examples/storageAccountCheckNameAvailability.json rename to test/swaggers/arm-storage/2016-01-01/examples/storageAccountCheckNameAvailability.json diff --git a/arm-storage/2016-01-01/examples/storageAccountCreate.json b/test/swaggers/arm-storage/2016-01-01/examples/storageAccountCreate.json similarity index 100% rename from arm-storage/2016-01-01/examples/storageAccountCreate.json rename to test/swaggers/arm-storage/2016-01-01/examples/storageAccountCreate.json diff --git a/arm-storage/2016-01-01/examples/storageAccountDelete.json b/test/swaggers/arm-storage/2016-01-01/examples/storageAccountDelete.json similarity index 100% rename from arm-storage/2016-01-01/examples/storageAccountDelete.json rename to test/swaggers/arm-storage/2016-01-01/examples/storageAccountDelete.json diff --git a/arm-storage/2016-01-01/examples/storageAccountGetProperties.json b/test/swaggers/arm-storage/2016-01-01/examples/storageAccountGetProperties.json similarity index 100% rename from arm-storage/2016-01-01/examples/storageAccountGetProperties.json rename to test/swaggers/arm-storage/2016-01-01/examples/storageAccountGetProperties.json diff --git a/arm-storage/2016-01-01/examples/storageAccountList.json b/test/swaggers/arm-storage/2016-01-01/examples/storageAccountList.json similarity index 100% rename from arm-storage/2016-01-01/examples/storageAccountList.json rename to test/swaggers/arm-storage/2016-01-01/examples/storageAccountList.json diff --git a/arm-storage/2016-01-01/examples/storageAccountListByRG.json b/test/swaggers/arm-storage/2016-01-01/examples/storageAccountListByRG.json similarity index 100% rename from arm-storage/2016-01-01/examples/storageAccountListByRG.json rename to test/swaggers/arm-storage/2016-01-01/examples/storageAccountListByRG.json diff --git a/arm-storage/2016-01-01/examples/storageAccountListKeys.json b/test/swaggers/arm-storage/2016-01-01/examples/storageAccountListKeys.json similarity index 100% rename from arm-storage/2016-01-01/examples/storageAccountListKeys.json rename to test/swaggers/arm-storage/2016-01-01/examples/storageAccountListKeys.json diff --git a/arm-storage/2016-01-01/examples/storageAccountListUsages.json b/test/swaggers/arm-storage/2016-01-01/examples/storageAccountListUsages.json similarity index 100% rename from arm-storage/2016-01-01/examples/storageAccountListUsages.json rename to test/swaggers/arm-storage/2016-01-01/examples/storageAccountListUsages.json diff --git a/arm-storage/2016-01-01/examples/storageAccountRegenerateKeys.json b/test/swaggers/arm-storage/2016-01-01/examples/storageAccountRegenerateKeys.json similarity index 100% rename from arm-storage/2016-01-01/examples/storageAccountRegenerateKeys.json rename to test/swaggers/arm-storage/2016-01-01/examples/storageAccountRegenerateKeys.json diff --git a/arm-storage/2016-01-01/examples/storageAccountUpdate.json b/test/swaggers/arm-storage/2016-01-01/examples/storageAccountUpdate.json similarity index 100% rename from arm-storage/2016-01-01/examples/storageAccountUpdate.json rename to test/swaggers/arm-storage/2016-01-01/examples/storageAccountUpdate.json diff --git a/arm-storage/2016-01-01/swagger/storage.json b/test/swaggers/arm-storage/2016-01-01/swagger/storage.json similarity index 100% rename from arm-storage/2016-01-01/swagger/storage.json rename to test/swaggers/arm-storage/2016-01-01/swagger/storage.json diff --git a/test/utilsTests.js b/test/utilsTests.js new file mode 100644 index 00000000..ca3260ed --- /dev/null +++ b/test/utilsTests.js @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +var assert = require('assert'); +var utils = require('../lib/util/utils.js'); + +describe('Utility functions', function () { + describe('Get Provider', function () { + it('should throw on empty', function () { + assert.throws(() => { + utils.getProvider(''); + }) + }); + it('should throw null', function () { + assert.throws(() => { + utils.getProvider(null); + }) + }); + it('should throw undefined', function () { + assert.throws(() => { + utils.getProvider(); + }) + }); + it('should return Microsoft.Resources', function () { + let path = "/subscriptions/{subscriptionId}/resourcegroups/{resourceGroupName}/providers/Microsoft.Resources/{parentResourcePath}/{resourceType}/{resourceName}" + let provider = utils.getProvider(path); + assert.equal(provider, 'Microsoft.Resources'); + }) + it('should return undefined', function () { + let path = "/subscriptions/{subscriptionId}/resourcegroups/{resourceGroupName}/providers/" + let provider = utils.getProvider(path); + assert.equal(provider, undefined); + }) + it('should return Microsoft.Authorization', function () { + let path = "/subscriptions/{subscriptionId}/resourcegroups/{resourceGroupName}/providers/Microsoft.Resources/{parentResourcePath}/{resourceType}/{resourceName}/providers/Microsoft.Authorization/roleAssignments" + let provider = utils.getProvider(path); + assert.equal(provider, 'Microsoft.Authorization'); + }) + }); +}); \ No newline at end of file From fc152d5cd63b0283db0efebef0f166328656591a Mon Sep 17 00:00:00 2001 From: Vishrut Shah Date: Wed, 12 Apr 2017 20:58:35 -0700 Subject: [PATCH 05/10] Adding travis file to repo --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..122a98a2 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,4 @@ +language: node_js +sudo: false +node_js: + - "6" From e805cd5f49c749812a49544ab44cb26007add33e Mon Sep 17 00:00:00 2001 From: Vishrut Shah Date: Wed, 12 Apr 2017 21:07:58 -0700 Subject: [PATCH 06/10] Adding script to run tests --- package.json | 5 ++++- test/liveValidatorTests.js | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 6570c3e9..0898b2b6 100644 --- a/package.json +++ b/package.json @@ -37,5 +37,8 @@ "url": "http://github.com/azure/openapi-validaton-tools/issues" }, "main": "./index.js", - "bin": { "oav": "./cli.js" } + "bin": { "oav": "./cli.js" }, + "scripts": { + "test": "./node_modules/mocha/bin/mocha test/*" + } } diff --git a/test/liveValidatorTests.js b/test/liveValidatorTests.js index cdd9a120..a71eb3d1 100644 --- a/test/liveValidatorTests.js +++ b/test/liveValidatorTests.js @@ -148,7 +148,7 @@ describe('Live Validator', function () { done(); }).catch(done); }); - it.only('should initialize for all swaggers', function (done) { + it('should initialize for all swaggers', function (done) { let expectedProvider = ''; let expectedApiVersion = ''; let options = { From e0ab4551ff6c6f5aa73c6c66e119abfdaa0b364b Mon Sep 17 00:00:00 2001 From: Vishrut Shah Date: Wed, 12 Apr 2017 23:42:48 -0700 Subject: [PATCH 07/10] Updating logic for search and adding tests --- lib/liveValidator.js | 92 ++++++++++++++++++++++++-------------- test/liveValidatorTests.js | 33 +++++++++++++- 2 files changed, 89 insertions(+), 36 deletions(-) diff --git a/lib/liveValidator.js b/lib/liveValidator.js index 7be7328b..364c783d 100644 --- a/lib/liveValidator.js +++ b/lib/liveValidator.js @@ -205,50 +205,74 @@ class LiveValidator { let potentialOperations = []; let parsedUrl = url.parse(requestUrl, true); let path = parsedUrl.pathname; + requestMethod = requestMethod.toLowerCase(); if (path === null || path === undefined) { log.warn(`Could not find path parameter from requestUrl: ${requestUrl}.`); return potentialOperations; } - // TODO: Do we always have api-version in lower case? - // Lower the keys and then find api-version - let apiVersion = parsedUrl.query['api-version']; + // Lower all the keys of query parameters before searching for `api-version` + var queryObject = _.transform(parsedUrl.query, function (result, value, key) { + result[key.toLowerCase()] = value; + }); + let apiVersion = queryObject['api-version']; + let provider = utils.getProvider(path); - // 1. First find based on providers - // 1.1 Find based on api-version - // 1.1.1 Else we don't recognize this operation -- log - // 1.2 Find based on method - // 1.2.1 Else we don't recognize this operation -- log - // 2 Didn't find provider at all - Microsoft.unknown - // 2.1 Search for api-version - // 2.1.1 Else look for "http method" all operations - // 2.2 Search for method - // 2.2.1 Else we don't recognize this operation -- log - if (apiVersion === null || apiVersion === undefined) { - log.warn(`Could not find api-version query parameter from requestUrl: ${requestUrl}.`); - return potentialOperations; - } - apiVersion = apiVersion.toLowerCase(); + // Provider would be provider found from the path or Microsoft.Unknown + provider = provider || Constants.unknownResourceProvider; + provider = provider.toLowerCase(); - let provider = utils.getProvider(path); - if (provider === null || provider === undefined || !provider.trim().length) { - log.warn(`Could not find provider namespace from requestUrl: ${requestUrl}.`); - return potentialOperations; + // Search using provider + let allApiVersions = self.cache[provider]; + if (allApiVersions) { + // Search using api-version found in the requestUrl + if (apiVersion) { + let allMethods = allApiVersions[apiVersion]; + if (allMethods) { + let methodsWithHttpVerb = allMethods[requestMethod]; + // Search using requestMethod provided by user + if (methodsWithHttpVerb) { + // Find the best match using regex on path + potentialOperations = self.getPotentialOperationsHelper(path, methodsWithHttpVerb); + } else { + log.warn(`Could not find any methods with verb "${requestMethod}" for api-version "${apiVersion}" and provider "${provider}" in the cache.`); + } + } else { + log.warn(`Could not find api-version "${apiVersion}" for provider "${provider}" in the cache.`); + } + } else { + log.warn(`Could not find api-version in requestUrl "${requestUrl}".`); + } + } else { + // provider does not exist in cache + log.warn(`Could not find provider "${provider}" in the cache.`); } - provider = provider.toLowerCase(); - requestMethod = requestMethod.toLowerCase(); - if (self.cache[apiVersion] === undefined || - self.cache[apiVersion][provider] === undefined || - self.cache[apiVersion][provider][requestMethod] === undefined) { - log.warn(`Could not find cache entry for requestUrl: ${requestUrl} and method: ${requestMethod}.`); - return potentialOperations; + return potentialOperations; + } + + /** + * Gets list of potential sway operations objects for given path and method. + * + * @param {string} requestPath The path of the url for which to find potential operations. + * + * @param {Array} operations The list of operations to search. + * + * @returns {Array} List of potential objects matching the requestPath. + */ + getPotentialOperationsHelper(requestPath, operations) { + if (requestPath === null || requestPath === undefined || typeof requestPath.valueOf() !== 'string' || + !requestPath.trim().length) { + throw new Error('requestPath is a required parameter of type string and it cannot be an empty string.'); } - let operations = self.cache[apiVersion][provider][requestMethod]; + if (operations === null || operations === undefined || !Array.isArray(operations) || + !operations.length) { + throw new Error('operations is a required parameter of type array and it cannot be an empty array.'); + } - potentialOperations = operations.filter(function (operation) { - let pathMatch = operation.pathObject.regexp.exec(path); + let potentialOperations = operations.filter(function (operation) { + let pathMatch = operation.pathObject.regexp.exec(requestPath); return pathMatch === null ? false : true; }); @@ -259,7 +283,7 @@ class LiveValidator { module.exports = LiveValidator; // Used for testing -// var validator = new LiveValidator({ "directory": "/Users/vishrut/repos/openapi-validation-tools/test/swaggers/arm-resources" }); +// var validator = new LiveValidator({ "directory": "./test/swaggers/arm-storage" }); // validator.initialize().then(function () { -// console.log(validator.cache); +// validator.getPotentialOperations("https://management.azure.com/subscriptions/subscriptionId/providers/Microsoft.Storage/storageAccounts/accname?api-version=2015-06-15", 'delete'); // }) diff --git a/test/liveValidatorTests.js b/test/liveValidatorTests.js index a71eb3d1..53c54674 100644 --- a/test/liveValidatorTests.js +++ b/test/liveValidatorTests.js @@ -149,8 +149,6 @@ describe('Live Validator', function () { }).catch(done); }); it('should initialize for all swaggers', function (done) { - let expectedProvider = ''; - let expectedApiVersion = ''; let options = { "directory": "./test/swaggers" }; @@ -173,4 +171,35 @@ describe('Live Validator', function () { }).catch(done); }); }); + describe('Initialize cache and search', function () { + it('should return one matched operation for arm-storage', function (done) { + let options = { + "directory": "./test/swaggers/arm-storage" + }; + let listRequestUrl = "https://management.azure.com/subscriptions/subscriptionId/providers/Microsoft.Storage/storageAccounts?api-version=2015-06-15"; + let postRequestUrl = "https://management.azure.com/subscriptions/subscriptionId/providers/Microsoft.Storage/checkNameAvailability?api-version=2015-06-15"; + let deleteRequestUrl = "https://management.azure.com/subscriptions/subscriptionId/resourceGroups/myRG/providers/Microsoft.Storage/storageAccounts/accname?api-version=2015-06-15"; + let validator = new LiveValidator(options); + validator.initialize().then(function () { + // Operations to match is StorageAccounts_List + let operations = validator.getPotentialOperations(listRequestUrl, 'Get'); + assert.equal(1, operations.length); + assert.equal("/subscriptions/{subscriptionId}/providers/Microsoft.Storage/storageAccounts", operations[0].pathObject.path); + + // Operations to match is StorageAccounts_CheckNameAvailability + operations = validator.getPotentialOperations(postRequestUrl, 'PoSt'); + assert.equal(1, operations.length); + assert.equal("/subscriptions/{subscriptionId}/providers/Microsoft.Storage/checkNameAvailability", operations[0].pathObject.path); + + // Operations to match is StorageAccounts_Delete + operations = validator.getPotentialOperations(deleteRequestUrl, 'delete'); + assert.equal(1, operations.length); + assert.equal("/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Storage/storageAccounts/{accountName}", operations[0].pathObject.path); + done(); + }).catch((err) => { + assert.ifError(err); + done(); + }).catch(done); + }); + }); }); \ No newline at end of file From 3ed38a10a7125e5e6def071b1bcfab0a4094a8e5 Mon Sep 17 00:00:00 2001 From: Vishrut Shah Date: Wed, 12 Apr 2017 23:49:14 -0700 Subject: [PATCH 08/10] Adding setting for vscode for code formatting --- .vscode/settings.json | 13 ++ test/liveValidatorTests.js | 384 ++++++++++++++++++------------------- test/utilsTests.js | 62 +++--- 3 files changed, 236 insertions(+), 223 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..394ba197 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,13 @@ +{ + "files.exclude": { + "**/.git": true, + "**/.svn": true, + "**/.DS_Store": true + }, + "[javascript]": { + "editor.formatOnSave": true, + "editor.formatOnPaste": true, + "editor.tabSize": 2, + "editor.detectIndentation": false + } +} \ No newline at end of file diff --git a/test/liveValidatorTests.js b/test/liveValidatorTests.js index 53c54674..5ddad556 100644 --- a/test/liveValidatorTests.js +++ b/test/liveValidatorTests.js @@ -5,201 +5,201 @@ var assert = require('assert'); var LiveValidator = require('../lib/liveValidator.js'); describe('Live Validator', function () { - describe('Initialization', function () { - it('should initialize with defaults', function () { - let options = { - "swaggerPaths": [], - "git": { - "url": "https://github.com/Azure/azure-rest-api-specs.git", - "shouldClone": false - }, - "directory": "./repo" - }; - let validator = new LiveValidator(); - assert.deepEqual(validator.cache, {}); - assert.deepEqual(validator.options, options); - }); - it('should initialize with user provided swaggerPaths', function () { - let swaggerPaths = ["swaggerPath1", "swaggerPath2"]; - let options = { - "swaggerPaths": swaggerPaths, - "git": { - "url": "https://github.com/Azure/azure-rest-api-specs.git", - "shouldClone": false - }, - "directory": "./repo" - }; - let validator = new LiveValidator({ "swaggerPaths": swaggerPaths }); - assert.deepEqual(validator.cache, {}); - assert.deepEqual(validator.options, options); - }); - it('should initialize with user provided swaggerPaths & directory', function () { - let swaggerPaths = ["swaggerPath1", "swaggerPath2"]; - let directory = "/Users/username/repos/" - let options = { - "swaggerPaths": swaggerPaths, - "git": { - "url": "https://github.com/Azure/azure-rest-api-specs.git", - "shouldClone": false - }, - "directory": directory - }; - let validator = new LiveValidator({ "swaggerPaths": swaggerPaths, "directory": directory }); - assert.deepEqual(validator.cache, {}); - assert.deepEqual(validator.options, options); - }); - it('should initialize with user provided partial git configuration', function () { - let swaggerPaths = ["swaggerPath1", "swaggerPath2"]; - let directory = "/Users/username/repos/" - let git = { - "url": "https://github.com/vishrutshah/azure-rest-api-specs.git" - } - let options = { - "swaggerPaths": swaggerPaths, - "git": { - "url": git.url, - "shouldClone": false - }, - "directory": directory - }; - let validator = new LiveValidator({ "swaggerPaths": swaggerPaths, "directory": directory, "git": git }); - assert.deepEqual(validator.cache, {}); - assert.deepEqual(validator.options, options); - }); - it('should initialize with user provided full git configuration', function () { - let swaggerPaths = ["swaggerPath1", "swaggerPath2"]; - let directory = "/Users/username/repos/" - let git = { - "url": "https://github.com/vishrutshah/azure-rest-api-specs.git", - "shouldClone": true - } - let options = { - "swaggerPaths": swaggerPaths, - "git": git, - "directory": directory - }; - let validator = new LiveValidator({ "swaggerPaths": swaggerPaths, "directory": directory, "git": git }); - assert.deepEqual(validator.cache, {}); - assert.deepEqual(validator.options, options); - }); - it('should throw on invalid options types', function () { - assert.throws(() => { - new LiveValidator('string'); - }, /must be of type object/); - assert.throws(() => { - new LiveValidator({ "swaggerPaths": "should be array" }); - }, /must be of type array/); - assert.throws(() => { - new LiveValidator({ "git": 1 }); - }, /must be of type object/); - assert.throws(() => { - new LiveValidator({ "git": { "url": [] } }); - }, /must be of type string/); - assert.throws(() => { - new LiveValidator({ "git": { "url": "url", "shouldClone": "no" } }); - }, /must be of type boolean/); - }); + describe('Initialization', function () { + it('should initialize with defaults', function () { + let options = { + "swaggerPaths": [], + "git": { + "url": "https://github.com/Azure/azure-rest-api-specs.git", + "shouldClone": false + }, + "directory": "./repo" + }; + let validator = new LiveValidator(); + assert.deepEqual(validator.cache, {}); + assert.deepEqual(validator.options, options); }); - describe('Initialize cache', function () { - it('should initialize for arm-mediaservices', function (done) { - let expectedProvider = 'microsoft.media'; - let expectedApiVersion = '2015-10-01'; - let options = { - "directory": "./test/swaggers/arm-mediaservices" - }; - let validator = new LiveValidator(options); - validator.initialize().then(function () { - assert.equal(true, expectedProvider in validator.cache); - assert.equal(1, Object.keys(validator.cache).length); - assert.equal(true, expectedApiVersion in (validator.cache[expectedProvider])); - assert.equal(1, Object.keys(validator.cache[expectedProvider]).length); - assert.equal(2, validator.cache[expectedProvider][expectedApiVersion]['get'].length); - assert.equal(1, validator.cache[expectedProvider][expectedApiVersion]['put'].length); - assert.equal(1, validator.cache[expectedProvider][expectedApiVersion]['patch'].length); - assert.equal(1, validator.cache[expectedProvider][expectedApiVersion]['delete'].length); - assert.equal(4, validator.cache[expectedProvider][expectedApiVersion]['post'].length); - done(); - }).catch((err) => { - assert.ifError(err); - done(); - }).catch(done); - }); - it('should initialize for arm-resources', function (done) { - let expectedProvider = 'microsoft.resources'; - let expectedApiVersion = '2016-09-01'; - let options = { - "directory": "./test/swaggers/arm-resources" - }; - let validator = new LiveValidator(options); - validator.initialize().then(function () { - assert.equal(true, expectedProvider in validator.cache); - assert.equal(1, Object.keys(validator.cache).length); - assert.equal(true, expectedApiVersion in (validator.cache[expectedProvider])); - assert.equal(1, Object.keys(validator.cache[expectedProvider]).length); - assert.equal(13, validator.cache[expectedProvider][expectedApiVersion]['get'].length); - assert.equal(6, validator.cache[expectedProvider][expectedApiVersion]['put'].length); - assert.equal(1, validator.cache[expectedProvider][expectedApiVersion]['patch'].length); - assert.equal(6, validator.cache[expectedProvider][expectedApiVersion]['delete'].length); - assert.equal(7, validator.cache[expectedProvider][expectedApiVersion]['post'].length); - assert.equal(4, validator.cache[expectedProvider][expectedApiVersion]['head'].length); - done(); - }).catch((err) => { - assert.ifError(err); - done(); - }).catch(done); - }); - it('should initialize for all swaggers', function (done) { - let options = { - "directory": "./test/swaggers" - }; - let validator = new LiveValidator(options); - validator.initialize().then(function () { - assert.equal(4, Object.keys(validator.cache).length); - assert.equal(13, validator.cache['microsoft.resources']['2016-09-01']['get'].length); - assert.equal(4, validator.cache['microsoft.resources']['2016-09-01']['head'].length); - assert.equal(1, validator.cache['microsoft.media']['2015-10-01']['patch'].length); - assert.equal(4, validator.cache['microsoft.media']['2015-10-01']['post'].length); - assert.equal(2, validator.cache['microsoft.search']['2015-02-28']['get'].length); - assert.equal(3, validator.cache['microsoft.search']['2015-08-19']['get'].length); - assert.equal(1, validator.cache['microsoft.storage']['2015-05-01-preview']['patch'].length); - assert.equal(4, validator.cache['microsoft.storage']['2015-06-15']['get'].length); - assert.equal(3, validator.cache['microsoft.storage']['2016-01-01']['post'].length); - done(); - }).catch((err) => { - assert.ifError(err); - done(); - }).catch(done); - }); + it('should initialize with user provided swaggerPaths', function () { + let swaggerPaths = ["swaggerPath1", "swaggerPath2"]; + let options = { + "swaggerPaths": swaggerPaths, + "git": { + "url": "https://github.com/Azure/azure-rest-api-specs.git", + "shouldClone": false + }, + "directory": "./repo" + }; + let validator = new LiveValidator({ "swaggerPaths": swaggerPaths }); + assert.deepEqual(validator.cache, {}); + assert.deepEqual(validator.options, options); }); - describe('Initialize cache and search', function () { - it('should return one matched operation for arm-storage', function (done) { - let options = { - "directory": "./test/swaggers/arm-storage" - }; - let listRequestUrl = "https://management.azure.com/subscriptions/subscriptionId/providers/Microsoft.Storage/storageAccounts?api-version=2015-06-15"; - let postRequestUrl = "https://management.azure.com/subscriptions/subscriptionId/providers/Microsoft.Storage/checkNameAvailability?api-version=2015-06-15"; - let deleteRequestUrl = "https://management.azure.com/subscriptions/subscriptionId/resourceGroups/myRG/providers/Microsoft.Storage/storageAccounts/accname?api-version=2015-06-15"; - let validator = new LiveValidator(options); - validator.initialize().then(function () { - // Operations to match is StorageAccounts_List - let operations = validator.getPotentialOperations(listRequestUrl, 'Get'); - assert.equal(1, operations.length); - assert.equal("/subscriptions/{subscriptionId}/providers/Microsoft.Storage/storageAccounts", operations[0].pathObject.path); + it('should initialize with user provided swaggerPaths & directory', function () { + let swaggerPaths = ["swaggerPath1", "swaggerPath2"]; + let directory = "/Users/username/repos/" + let options = { + "swaggerPaths": swaggerPaths, + "git": { + "url": "https://github.com/Azure/azure-rest-api-specs.git", + "shouldClone": false + }, + "directory": directory + }; + let validator = new LiveValidator({ "swaggerPaths": swaggerPaths, "directory": directory }); + assert.deepEqual(validator.cache, {}); + assert.deepEqual(validator.options, options); + }); + it('should initialize with user provided partial git configuration', function () { + let swaggerPaths = ["swaggerPath1", "swaggerPath2"]; + let directory = "/Users/username/repos/" + let git = { + "url": "https://github.com/vishrutshah/azure-rest-api-specs.git" + } + let options = { + "swaggerPaths": swaggerPaths, + "git": { + "url": git.url, + "shouldClone": false + }, + "directory": directory + }; + let validator = new LiveValidator({ "swaggerPaths": swaggerPaths, "directory": directory, "git": git }); + assert.deepEqual(validator.cache, {}); + assert.deepEqual(validator.options, options); + }); + it('should initialize with user provided full git configuration', function () { + let swaggerPaths = ["swaggerPath1", "swaggerPath2"]; + let directory = "/Users/username/repos/" + let git = { + "url": "https://github.com/vishrutshah/azure-rest-api-specs.git", + "shouldClone": true + } + let options = { + "swaggerPaths": swaggerPaths, + "git": git, + "directory": directory + }; + let validator = new LiveValidator({ "swaggerPaths": swaggerPaths, "directory": directory, "git": git }); + assert.deepEqual(validator.cache, {}); + assert.deepEqual(validator.options, options); + }); + it('should throw on invalid options types', function () { + assert.throws(() => { + new LiveValidator('string'); + }, /must be of type object/); + assert.throws(() => { + new LiveValidator({ "swaggerPaths": "should be array" }); + }, /must be of type array/); + assert.throws(() => { + new LiveValidator({ "git": 1 }); + }, /must be of type object/); + assert.throws(() => { + new LiveValidator({ "git": { "url": [] } }); + }, /must be of type string/); + assert.throws(() => { + new LiveValidator({ "git": { "url": "url", "shouldClone": "no" } }); + }, /must be of type boolean/); + }); + }); + describe('Initialize cache', function () { + it('should initialize for arm-mediaservices', function (done) { + let expectedProvider = 'microsoft.media'; + let expectedApiVersion = '2015-10-01'; + let options = { + "directory": "./test/swaggers/arm-mediaservices" + }; + let validator = new LiveValidator(options); + validator.initialize().then(function () { + assert.equal(true, expectedProvider in validator.cache); + assert.equal(1, Object.keys(validator.cache).length); + assert.equal(true, expectedApiVersion in (validator.cache[expectedProvider])); + assert.equal(1, Object.keys(validator.cache[expectedProvider]).length); + assert.equal(2, validator.cache[expectedProvider][expectedApiVersion]['get'].length); + assert.equal(1, validator.cache[expectedProvider][expectedApiVersion]['put'].length); + assert.equal(1, validator.cache[expectedProvider][expectedApiVersion]['patch'].length); + assert.equal(1, validator.cache[expectedProvider][expectedApiVersion]['delete'].length); + assert.equal(4, validator.cache[expectedProvider][expectedApiVersion]['post'].length); + done(); + }).catch((err) => { + assert.ifError(err); + done(); + }).catch(done); + }); + it('should initialize for arm-resources', function (done) { + let expectedProvider = 'microsoft.resources'; + let expectedApiVersion = '2016-09-01'; + let options = { + "directory": "./test/swaggers/arm-resources" + }; + let validator = new LiveValidator(options); + validator.initialize().then(function () { + assert.equal(true, expectedProvider in validator.cache); + assert.equal(1, Object.keys(validator.cache).length); + assert.equal(true, expectedApiVersion in (validator.cache[expectedProvider])); + assert.equal(1, Object.keys(validator.cache[expectedProvider]).length); + assert.equal(13, validator.cache[expectedProvider][expectedApiVersion]['get'].length); + assert.equal(6, validator.cache[expectedProvider][expectedApiVersion]['put'].length); + assert.equal(1, validator.cache[expectedProvider][expectedApiVersion]['patch'].length); + assert.equal(6, validator.cache[expectedProvider][expectedApiVersion]['delete'].length); + assert.equal(7, validator.cache[expectedProvider][expectedApiVersion]['post'].length); + assert.equal(4, validator.cache[expectedProvider][expectedApiVersion]['head'].length); + done(); + }).catch((err) => { + assert.ifError(err); + done(); + }).catch(done); + }); + it('should initialize for all swaggers', function (done) { + let options = { + "directory": "./test/swaggers" + }; + let validator = new LiveValidator(options); + validator.initialize().then(function () { + assert.equal(4, Object.keys(validator.cache).length); + assert.equal(13, validator.cache['microsoft.resources']['2016-09-01']['get'].length); + assert.equal(4, validator.cache['microsoft.resources']['2016-09-01']['head'].length); + assert.equal(1, validator.cache['microsoft.media']['2015-10-01']['patch'].length); + assert.equal(4, validator.cache['microsoft.media']['2015-10-01']['post'].length); + assert.equal(2, validator.cache['microsoft.search']['2015-02-28']['get'].length); + assert.equal(3, validator.cache['microsoft.search']['2015-08-19']['get'].length); + assert.equal(1, validator.cache['microsoft.storage']['2015-05-01-preview']['patch'].length); + assert.equal(4, validator.cache['microsoft.storage']['2015-06-15']['get'].length); + assert.equal(3, validator.cache['microsoft.storage']['2016-01-01']['post'].length); + done(); + }).catch((err) => { + assert.ifError(err); + done(); + }).catch(done); + }); + }); + describe('Initialize cache and search', function () { + it('should return one matched operation for arm-storage', function (done) { + let options = { + "directory": "./test/swaggers/arm-storage" + }; + let listRequestUrl = "https://management.azure.com/subscriptions/subscriptionId/providers/Microsoft.Storage/storageAccounts?api-version=2015-06-15"; + let postRequestUrl = "https://management.azure.com/subscriptions/subscriptionId/providers/Microsoft.Storage/checkNameAvailability?api-version=2015-06-15"; + let deleteRequestUrl = "https://management.azure.com/subscriptions/subscriptionId/resourceGroups/myRG/providers/Microsoft.Storage/storageAccounts/accname?api-version=2015-06-15"; + let validator = new LiveValidator(options); + validator.initialize().then(function () { + // Operations to match is StorageAccounts_List + let operations = validator.getPotentialOperations(listRequestUrl, 'Get'); + assert.equal(1, operations.length); + assert.equal("/subscriptions/{subscriptionId}/providers/Microsoft.Storage/storageAccounts", operations[0].pathObject.path); - // Operations to match is StorageAccounts_CheckNameAvailability - operations = validator.getPotentialOperations(postRequestUrl, 'PoSt'); - assert.equal(1, operations.length); - assert.equal("/subscriptions/{subscriptionId}/providers/Microsoft.Storage/checkNameAvailability", operations[0].pathObject.path); + // Operations to match is StorageAccounts_CheckNameAvailability + operations = validator.getPotentialOperations(postRequestUrl, 'PoSt'); + assert.equal(1, operations.length); + assert.equal("/subscriptions/{subscriptionId}/providers/Microsoft.Storage/checkNameAvailability", operations[0].pathObject.path); - // Operations to match is StorageAccounts_Delete - operations = validator.getPotentialOperations(deleteRequestUrl, 'delete'); - assert.equal(1, operations.length); - assert.equal("/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Storage/storageAccounts/{accountName}", operations[0].pathObject.path); - done(); - }).catch((err) => { - assert.ifError(err); - done(); - }).catch(done); - }); + // Operations to match is StorageAccounts_Delete + operations = validator.getPotentialOperations(deleteRequestUrl, 'delete'); + assert.equal(1, operations.length); + assert.equal("/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Storage/storageAccounts/{accountName}", operations[0].pathObject.path); + done(); + }).catch((err) => { + assert.ifError(err); + done(); + }).catch(done); }); + }); }); \ No newline at end of file diff --git a/test/utilsTests.js b/test/utilsTests.js index ca3260ed..c24c6090 100644 --- a/test/utilsTests.js +++ b/test/utilsTests.js @@ -5,36 +5,36 @@ var assert = require('assert'); var utils = require('../lib/util/utils.js'); describe('Utility functions', function () { - describe('Get Provider', function () { - it('should throw on empty', function () { - assert.throws(() => { - utils.getProvider(''); - }) - }); - it('should throw null', function () { - assert.throws(() => { - utils.getProvider(null); - }) - }); - it('should throw undefined', function () { - assert.throws(() => { - utils.getProvider(); - }) - }); - it('should return Microsoft.Resources', function () { - let path = "/subscriptions/{subscriptionId}/resourcegroups/{resourceGroupName}/providers/Microsoft.Resources/{parentResourcePath}/{resourceType}/{resourceName}" - let provider = utils.getProvider(path); - assert.equal(provider, 'Microsoft.Resources'); - }) - it('should return undefined', function () { - let path = "/subscriptions/{subscriptionId}/resourcegroups/{resourceGroupName}/providers/" - let provider = utils.getProvider(path); - assert.equal(provider, undefined); - }) - it('should return Microsoft.Authorization', function () { - let path = "/subscriptions/{subscriptionId}/resourcegroups/{resourceGroupName}/providers/Microsoft.Resources/{parentResourcePath}/{resourceType}/{resourceName}/providers/Microsoft.Authorization/roleAssignments" - let provider = utils.getProvider(path); - assert.equal(provider, 'Microsoft.Authorization'); - }) + describe('Get Provider', function () { + it('should throw on empty', function () { + assert.throws(() => { + utils.getProvider(''); + }) }); + it('should throw null', function () { + assert.throws(() => { + utils.getProvider(null); + }) + }); + it('should throw undefined', function () { + assert.throws(() => { + utils.getProvider(); + }) + }); + it('should return Microsoft.Resources', function () { + let path = "/subscriptions/{subscriptionId}/resourcegroups/{resourceGroupName}/providers/Microsoft.Resources/{parentResourcePath}/{resourceType}/{resourceName}" + let provider = utils.getProvider(path); + assert.equal(provider, 'Microsoft.Resources'); + }) + it('should return undefined', function () { + let path = "/subscriptions/{subscriptionId}/resourcegroups/{resourceGroupName}/providers/" + let provider = utils.getProvider(path); + assert.equal(provider, undefined); + }) + it('should return Microsoft.Authorization', function () { + let path = "/subscriptions/{subscriptionId}/resourcegroups/{resourceGroupName}/providers/Microsoft.Resources/{parentResourcePath}/{resourceType}/{resourceName}/providers/Microsoft.Authorization/roleAssignments" + let provider = utils.getProvider(path); + assert.equal(provider, 'Microsoft.Authorization'); + }) + }); }); \ No newline at end of file From 7705d8fdae82ecd9a2577afa3464babf54cfdd1d Mon Sep 17 00:00:00 2001 From: Vishrut Shah Date: Thu, 13 Apr 2017 15:14:58 -0700 Subject: [PATCH 09/10] Adding logic to fall back on searching operation into Microsoft.Unknown --- .vscode/launch.json | 3 - lib/liveValidator.js | 120 +++++++++++++++++++++++-------------- lib/util/constants.js | 3 +- lib/util/utils.js | 4 +- test/liveValidatorTests.js | 50 +++++++++------- test/utilsTests.js | 2 +- 6 files changed, 110 insertions(+), 72 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index c8d0a750..4ec07356 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -34,9 +34,6 @@ "name": "Live Validator", "program": "${workspaceRoot}/lib/liveValidator.js", "cwd": "${workspaceRoot}", - "args": [ - "${workspaceRoot}/test/utilsTests.js" - ], "env": {} }, { diff --git a/lib/liveValidator.js b/lib/liveValidator.js index 364c783d..8de4ba76 100644 --- a/lib/liveValidator.js +++ b/lib/liveValidator.js @@ -5,6 +5,7 @@ var util = require('util'), path = require('path'), + os = require('os'), _ = require('lodash'), glob = require('glob'), SpecValidator = require('./specValidator'), @@ -29,7 +30,7 @@ class LiveValidator { * * @param {string} [options.git.shouldClone] Specifies whether to clone the repository or not. Defaults to false. * - * @param {string} [options.directory] The directory where to clone github repository or from where to find swaggers. Defaults to "./repo". + * @param {string} [options.directory] The directory where to clone github repository or from where to find swaggers. Defaults to "repo" under user directory. * * @returns {object} CacheBuilder Returns the configured CacheBuilder object. */ @@ -40,13 +41,13 @@ class LiveValidator { this.options = {}; } if (typeof this.options !== 'object') { - throw new Error('options must be of type object'); + throw new Error('options must be of type "object".'); } if (this.options.swaggerPaths === null || this.options.swaggerPaths === undefined) { this.options.swaggerPaths = []; } - if (!Array.isArray(this.options.swaggerPaths.valueOf())) { - throw new Error(`options.swaggerPaths must be of type array instead of type "${this.options.swaggerPaths.valueOf()}".`); + if (!Array.isArray(this.options.swaggerPaths)) { + throw new Error(`options.swaggerPaths must be of type "array" instead of type "${typeof this.options.swaggerPaths}".`); } if (this.options.git === null || this.options.git === undefined) { this.options.git = { @@ -54,26 +55,26 @@ class LiveValidator { "shouldClone": false }; } - if (typeof this.options.git.valueOf() !== 'object') { - throw new Error('options.git must be of type object.'); + if (typeof this.options.git !== 'object') { + throw new Error('options.git must be of type "object".'); } if (this.options.git.url === null || this.options.git.url === undefined) { this.options.git.url = "https://github.com/Azure/azure-rest-api-specs.git"; } if (typeof this.options.git.url.valueOf() !== 'string') { - throw new Error('options.git.url must be of type string.'); + throw new Error('options.git.url must be of type "string".'); } if (this.options.git.shouldClone === null || this.options.git.shouldClone === undefined) { this.options.git.shouldClone = false; } - if (typeof this.options.git.shouldClone.valueOf() !== 'boolean') { - throw new Error('options.git.shouldClone must be of type boolean.'); + if (typeof this.options.git.shouldClone !== 'boolean') { + throw new Error('options.git.shouldClone must be of type "boolean".'); } if (this.options.directory === null || this.options.directory === undefined) { - this.options.directory = "./repo"; + this.options.directory = path.resolve(os.homedir(), 'repo'); } if (typeof this.options.directory.valueOf() !== 'string') { - throw new Error('options.directory must be of type string.'); + throw new Error('options.directory must be of type "string".'); } this.cache = {}; } @@ -96,7 +97,7 @@ class LiveValidator { log.debug(`Using user provided swagger paths. Total paths: ${swaggerPaths.length}`); } else { swaggerPaths = glob.sync(path.join(self.options.directory, '/**/swagger/*.json')); - log.debug(`Using swaggers found from ${self.options.directory} provided swagger paths. Total paths: ${swaggerPaths.length}`); + log.debug(`Using swaggers found from "${self.options.directory}" provided swagger paths. Total paths: ${swaggerPaths.length}`); } // console.log(swaggerPaths); // Create array of promise factories that builds up cache @@ -116,17 +117,24 @@ class LiveValidator { // }, // ... // }, + // "microsoft.unknown": { + // "unknown-api-version": { + // "post": [ + // "operation1" + // ] + // } + // } // ... // } let promiseFactories = swaggerPaths.map((swaggerPath) => { return () => { log.info(`Building cache from: "${swaggerPath}"`); let validator = new SpecValidator(swaggerPath); - return validator.initialize().then(function (api) { + return validator.initialize().then((api) => { let operations = api.getOperations(); let apiVersion = api.info.version.toLowerCase(); - operations.forEach(function (operation) { + operations.forEach((operation) => { let httpMethod = operation.method.toLowerCase(); let provider = utils.getProvider(operation.pathObject.path); log.debug(`${apiVersion}, ${operation.operationId}, ${operation.pathObject.path}, ${httpMethod}`); @@ -135,30 +143,28 @@ class LiveValidator { let title = api.info.title; // Whitelist lookups: Look up knownTitleToResourceProviders + // Putting the provider namespace onto operation for future use if (title && Constants.knownTitleToResourceProviders[title]) { - provider = Constants.knownTitleToResourceProviders[title]; + operation.provider = Constants.knownTitleToResourceProviders[title]; } + // Put the operation into 'Microsoft.Unknown' RPs - else { - provider = Constants.unknownResourceProvider; - } + provider = Constants.unknownResourceProvider; + apiVersion = Constants.unknownApiVersion; log.warn(`Unable to find provider for path : "${operation.pathObject.path}". Bucketizing into provider: "${provider}"`); } provider = provider.toLowerCase(); - // TODO Look for operation those have api-version inside enums before - // using apiVersion directly - // Get all api-version for given provider or initialize it let apiVersions = self.cache[provider] || {}; // Get methods for given apiVersion or initialize it let allMethods = apiVersions[apiVersion] || {}; // Get specific http methods array for given verb or initialize it - let httpMethods = allMethods[httpMethod] || []; + let operationsForHttpMethod = allMethods[httpMethod] || []; // Builds the cache - httpMethods.push(operation); - allMethods[httpMethod] = httpMethods; + operationsForHttpMethod.push(operation); + allMethods[httpMethod] = operationsForHttpMethod; apiVersions[apiVersion] = allMethods; self.cache[provider] = apiVersions; }); @@ -177,13 +183,13 @@ class LiveValidator { } /** - * Gets list of potential sway operations objects for given url and method. + * Gets list of potential operations objects for given url and method. * * @param {string} requestUrl The url for which to find potential operations. * * @param {string} requestMethod The http verb for the method to be used for lookup. * - * @returns {Array} List of potential objects matching the url and method. + * @returns {Array} List of potential operations matching the url and method. */ getPotentialOperations(requestUrl, requestMethod) { if (_.isEmpty(this.cache)) { @@ -193,12 +199,12 @@ class LiveValidator { if (requestUrl === null || requestUrl === undefined || typeof requestUrl.valueOf() !== 'string' || !requestUrl.trim().length) { - throw new Error('requestUrl is a required parameter of type string and it cannot be an empty string.'); + throw new Error('requestUrl is a required parameter of type "string" and it cannot be an empty string.'); } if (requestMethod === null || requestMethod === undefined || typeof requestMethod.valueOf() !== 'string' || !requestMethod.trim().length) { - throw new Error('requestMethod is a required parameter of type string and it cannot be an empty string.'); + throw new Error('requestMethod is a required parameter of type "string" and it cannot be an empty string.'); } let self = this; @@ -207,8 +213,7 @@ class LiveValidator { let path = parsedUrl.pathname; requestMethod = requestMethod.toLowerCase(); if (path === null || path === undefined) { - log.warn(`Could not find path parameter from requestUrl: ${requestUrl}.`); - return potentialOperations; + throw new Error(`Could not find path from requestUrl: "${requestUrl}".`); } // Lower all the keys of query parameters before searching for `api-version` @@ -220,6 +225,9 @@ class LiveValidator { // Provider would be provider found from the path or Microsoft.Unknown provider = provider || Constants.unknownResourceProvider; + if (provider === Constants.unknownResourceProvider) { + apiVersion = Constants.unknownApiVersion; + } provider = provider.toLowerCase(); // Search using provider @@ -229,61 +237,83 @@ class LiveValidator { if (apiVersion) { let allMethods = allApiVersions[apiVersion]; if (allMethods) { - let methodsWithHttpVerb = allMethods[requestMethod]; + let operationsForHttpMethod = allMethods[requestMethod]; // Search using requestMethod provided by user - if (methodsWithHttpVerb) { + if (operationsForHttpMethod) { // Find the best match using regex on path - potentialOperations = self.getPotentialOperationsHelper(path, methodsWithHttpVerb); + potentialOperations = self.getPotentialOperationsHelper(path, requestMethod, operationsForHttpMethod); } else { log.warn(`Could not find any methods with verb "${requestMethod}" for api-version "${apiVersion}" and provider "${provider}" in the cache.`); } } else { - log.warn(`Could not find api-version "${apiVersion}" for provider "${provider}" in the cache.`); + log.warn(`Could not find exact api-version "${apiVersion}" for provider "${provider}" in the cache but we'll search in the Microsoft.Unknown.`); + potentialOperations = self.getPotentialOperationsHelper(path, requestMethod, []); } } else { log.warn(`Could not find api-version in requestUrl "${requestUrl}".`); } } else { // provider does not exist in cache - log.warn(`Could not find provider "${provider}" in the cache.`); + log.warn(`Could not find provider "${provider}" in the cache but we'll search in the Microsoft.Unknown.`); + potentialOperations = self.getPotentialOperationsHelper(path, requestMethod, []); } return potentialOperations; } /** - * Gets list of potential sway operations objects for given path and method. + * Gets list of potential operations objects for given path and method. * * @param {string} requestPath The path of the url for which to find potential operations. * - * @param {Array} operations The list of operations to search. + * @param {string} requestMethod The http verb for the method to be used for lookup. + * + * @param {Array} operations The list of operations to search. * - * @returns {Array} List of potential objects matching the requestPath. + * @returns {Array} List of potential operations matching the requestPath. */ - getPotentialOperationsHelper(requestPath, operations) { + getPotentialOperationsHelper(requestPath, requestMethod, operations) { if (requestPath === null || requestPath === undefined || typeof requestPath.valueOf() !== 'string' || !requestPath.trim().length) { throw new Error('requestPath is a required parameter of type string and it cannot be an empty string.'); } + if (requestMethod === null || requestMethod === undefined || typeof requestMethod.valueOf() !== 'string' || + !requestMethod.trim().length) { + throw new Error('requestMethod is a required parameter of type "string" and it cannot be an empty string.'); + } + if (operations === null || operations === undefined || !Array.isArray(operations) || !operations.length) { throw new Error('operations is a required parameter of type array and it cannot be an empty array.'); } - let potentialOperations = operations.filter(function (operation) { + let potentialOperations = []; + potentialOperations = operations.filter((operation) => { let pathMatch = operation.pathObject.regexp.exec(requestPath); return pathMatch === null ? false : true; }); + // If we do not find any match then we'll look into Microsoft.Unknown -> unknown-api-version + // for given requestMethod as the fall back option + if (!potentialOperations.length) { + if (self.cache[Constants.unknownResourceProvider] && + self.cache[Constants.unknownResourceProvider][Constants.unknownApiVersion]) { + operations = self.cache[Constants.unknownResourceProvider][Constants.unknownApiVersion][requestMethod]; + potentialOperations = operations.filter((operation) => { + let pathMatch = operation.pathObject.regexp.exec(requestPath); + return pathMatch === null ? false : true; + }); + } + } + return potentialOperations; } } module.exports = LiveValidator; -// Used for testing -// var validator = new LiveValidator({ "directory": "./test/swaggers/arm-storage" }); -// validator.initialize().then(function () { -// validator.getPotentialOperations("https://management.azure.com/subscriptions/subscriptionId/providers/Microsoft.Storage/storageAccounts/accname?api-version=2015-06-15", 'delete'); -// }) +let validator = new LiveValidator({ "git": { "shouldClone": true } }); +validator.initialize().then(() => { + console.log("Hello"); +}) \ No newline at end of file diff --git a/lib/util/constants.js b/lib/util/constants.js index 0fe8d773..f82bf5d3 100644 --- a/lib/util/constants.js +++ b/lib/util/constants.js @@ -41,7 +41,8 @@ var Constants = { AzureLocation: 'AZURE_LOCATION', AzureResourcegroup: 'AZURE_RESOURCE_GROUP' }, - unknownResourceProvider: 'Microsoft.Unknown', + unknownResourceProvider: 'microsoft.unknown', + unknownApiVersion: 'unknown-api-version', knownTitleToResourceProviders: { 'ResourceManagementClient': 'Microsoft.Resources' } diff --git a/lib/util/utils.js b/lib/util/utils.js index a5a7fa3d..223ae54c 100644 --- a/lib/util/utils.js +++ b/lib/util/utils.js @@ -393,8 +393,8 @@ exports.getProvider = function getProvider(path) { /* * Clones a github repository in the given directory. - * @param {string} github url to be cloned. - * Example "https://github.com/Azure/azure-rest-api-specs.git" or + * @param {string} github url to be cloned. + * Example "https://github.com/Azure/azure-rest-api-specs.git" or * "git@github.com:Azure/azure-rest-api-specs.git". * * @param {string} path where to clone the repository. diff --git a/test/liveValidatorTests.js b/test/liveValidatorTests.js index 5ddad556..fecf773d 100644 --- a/test/liveValidatorTests.js +++ b/test/liveValidatorTests.js @@ -1,8 +1,11 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. -var assert = require('assert'); -var LiveValidator = require('../lib/liveValidator.js'); +var assert = require('assert'), + path = require('path'), + os = require('os'), + LiveValidator = require('../lib/liveValidator.js'), + Constants = require('../lib/util/constants'); describe('Live Validator', function () { describe('Initialization', function () { @@ -13,7 +16,7 @@ describe('Live Validator', function () { "url": "https://github.com/Azure/azure-rest-api-specs.git", "shouldClone": false }, - "directory": "./repo" + "directory": path.resolve(os.homedir(), 'repo') }; let validator = new LiveValidator(); assert.deepEqual(validator.cache, {}); @@ -27,7 +30,7 @@ describe('Live Validator', function () { "url": "https://github.com/Azure/azure-rest-api-specs.git", "shouldClone": false }, - "directory": "./repo" + "directory": path.resolve(os.homedir(), 'repo') }; let validator = new LiveValidator({ "swaggerPaths": swaggerPaths }); assert.deepEqual(validator.cache, {}); @@ -85,19 +88,19 @@ describe('Live Validator', function () { it('should throw on invalid options types', function () { assert.throws(() => { new LiveValidator('string'); - }, /must be of type object/); + }, /must be of type "object"/); assert.throws(() => { new LiveValidator({ "swaggerPaths": "should be array" }); - }, /must be of type array/); + }, /must be of type "array"/); assert.throws(() => { new LiveValidator({ "git": 1 }); - }, /must be of type object/); + }, /must be of type "object"/); assert.throws(() => { new LiveValidator({ "git": { "url": [] } }); - }, /must be of type string/); + }, /must be of type "string"/); assert.throws(() => { new LiveValidator({ "git": { "url": "url", "shouldClone": "no" } }); - }, /must be of type boolean/); + }, /must be of type "boolean"/); }); }); describe('Initialize cache', function () { @@ -133,15 +136,22 @@ describe('Live Validator', function () { let validator = new LiveValidator(options); validator.initialize().then(function () { assert.equal(true, expectedProvider in validator.cache); - assert.equal(1, Object.keys(validator.cache).length); + assert.equal(2, Object.keys(validator.cache).length); assert.equal(true, expectedApiVersion in (validator.cache[expectedProvider])); assert.equal(1, Object.keys(validator.cache[expectedProvider]).length); - assert.equal(13, validator.cache[expectedProvider][expectedApiVersion]['get'].length); - assert.equal(6, validator.cache[expectedProvider][expectedApiVersion]['put'].length); - assert.equal(1, validator.cache[expectedProvider][expectedApiVersion]['patch'].length); - assert.equal(6, validator.cache[expectedProvider][expectedApiVersion]['delete'].length); - assert.equal(7, validator.cache[expectedProvider][expectedApiVersion]['post'].length); - assert.equal(4, validator.cache[expectedProvider][expectedApiVersion]['head'].length); + // 'microsoft.resources' -> '2016-09-01' + assert.equal(2, validator.cache[expectedProvider][expectedApiVersion]['get'].length); + assert.equal(1, validator.cache[expectedProvider][expectedApiVersion]['delete'].length); + assert.equal(3, validator.cache[expectedProvider][expectedApiVersion]['post'].length); + assert.equal(1, validator.cache[expectedProvider][expectedApiVersion]['head'].length); + assert.equal(1, validator.cache[expectedProvider][expectedApiVersion]['put'].length); + // 'microsoft.unknown' -> 'unknown-api-version' + assert.equal(4, validator.cache[Constants.unknownResourceProvider][Constants.unknownApiVersion]['post'].length); + assert.equal(11, validator.cache[Constants.unknownResourceProvider][Constants.unknownApiVersion]['get'].length); + assert.equal(3, validator.cache[Constants.unknownResourceProvider][Constants.unknownApiVersion]['head'].length); + assert.equal(5, validator.cache[Constants.unknownResourceProvider][Constants.unknownApiVersion]['put'].length); + assert.equal(5, validator.cache[Constants.unknownResourceProvider][Constants.unknownApiVersion]['delete'].length); + assert.equal(1, validator.cache[Constants.unknownResourceProvider][Constants.unknownApiVersion]['patch'].length); done(); }).catch((err) => { assert.ifError(err); @@ -154,9 +164,9 @@ describe('Live Validator', function () { }; let validator = new LiveValidator(options); validator.initialize().then(function () { - assert.equal(4, Object.keys(validator.cache).length); - assert.equal(13, validator.cache['microsoft.resources']['2016-09-01']['get'].length); - assert.equal(4, validator.cache['microsoft.resources']['2016-09-01']['head'].length); + assert.equal(5, Object.keys(validator.cache).length); + assert.equal(2, validator.cache['microsoft.resources']['2016-09-01']['get'].length); + assert.equal(1, validator.cache['microsoft.resources']['2016-09-01']['head'].length); assert.equal(1, validator.cache['microsoft.media']['2015-10-01']['patch'].length); assert.equal(4, validator.cache['microsoft.media']['2015-10-01']['post'].length); assert.equal(2, validator.cache['microsoft.search']['2015-02-28']['get'].length); @@ -202,4 +212,4 @@ describe('Live Validator', function () { }).catch(done); }); }); -}); \ No newline at end of file +}); diff --git a/test/utilsTests.js b/test/utilsTests.js index c24c6090..5ba67df2 100644 --- a/test/utilsTests.js +++ b/test/utilsTests.js @@ -37,4 +37,4 @@ describe('Utility functions', function () { assert.equal(provider, 'Microsoft.Authorization'); }) }); -}); \ No newline at end of file +}); From 97ab24e7403650299129d903d0ed275992f517a0 Mon Sep 17 00:00:00 2001 From: Vishrut Shah Date: Thu, 13 Apr 2017 15:19:19 -0700 Subject: [PATCH 10/10] Removing test code from liveValidator --- lib/liveValidator.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/liveValidator.js b/lib/liveValidator.js index 8de4ba76..7106d75f 100644 --- a/lib/liveValidator.js +++ b/lib/liveValidator.js @@ -312,8 +312,3 @@ class LiveValidator { } module.exports = LiveValidator; - -let validator = new LiveValidator({ "git": { "shouldClone": true } }); -validator.initialize().then(() => { - console.log("Hello"); -}) \ No newline at end of file