Skip to content

Commit

Permalink
Adding semantic and model validator to CI (#910)
Browse files Browse the repository at this point in the history
* added model validation and more refactoring

* Dependency on master of openapi-validation-tools
  • Loading branch information
amarzavery authored and vishrutshah committed Feb 6, 2017
1 parent 3015ce5 commit 2940a1c
Show file tree
Hide file tree
Showing 9 changed files with 374 additions and 257 deletions.
14 changes: 12 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
language: node_js
node_js:
- "4"
- "6"
services:
- docker
env:
Expand All @@ -11,13 +11,21 @@ env:
- MODE=ruby
- MODE=linter PR_ONLY=false
- MODE=linter PR_ONLY=true
- MODE=semantic PR_ONLY=false
- MODE=semantic PR_ONLY=true
- MODE=model PR_ONLY=false
- MODE=model PR_ONLY=true
global:
secure: IwPVExGJQfL6QpTd6Utv3R/qBz7yApgMspzMLSkrq9cMP6t+NHk1vYg0n2evcWaq2rqNZE9xBMfRZi7Yy/RCUT++qALE89xlNvZ/A94PaDUiK3tYYNj1XxsgRauCHshXz6smmLwQMpTKsEl6lyjXeXpwQMDNRehhTu0Hg2tE7ZikfQVb+jjRNcP41RfzzGQyxEhd3fxF2G1G21WXH0cXtZssOd1MYcXhmXk8dR3DyEEMbPzDV1Bk3auXOvbqS79bnZ/Pi4p/7P/aTOm8O2ACKM0XAvww95vAQ0LcPzKnNe3sNjFVAssBiZbIk0Zs30wULBphlTxVoufHKSZuTf+QEBTKpH99v/+SBDDPu9+0q2esq7TKgf8bvzkeXjh54fECdJUqEo9E2gW08+RQxWCqryMJouOPcY2OMs4lZwSMOXQY68a/CYVRWFaFg5s6jntC8sLHtDxV0qem3xyjc+852v8rUfkyvMhOBoZJjWmnqYVqdamHOOfrzjc7AzPUEzSeiN6OPPVli+SzwLHdip0GxdK46pAISCOcbdyYn11VvTIn1QosE66eWhF6SViVH6lNWgSfVTpcQ2zq/qSKh0/zpwn82Ys+wKDOf3EwQAanndgk26npi7Ik4nIuexZ/TE56rQ7qjZqmHoxFz7QMeTZDiVmLFxtR19cTT3GLxDz8nBQ=
matrix:
fast_finish: true
allow_failures:
- env: MODE=linter PR_ONLY=false
- env: MODE=linter PR_ONLY=true
- env: MODE=semantic PR_ONLY=false
- env: MODE=semantic PR_ONLY=true
- env: MODE=model PR_ONLY=false
- env: MODE=model PR_ONLY=true
before_install:
- docker pull lmazuel/swagger-to-sdk
- python -c "import os; print('\n'.join(v for v in os.environ.keys() if v.startswith('TRAVIS')))" > /tmp/env_file
Expand All @@ -38,5 +46,7 @@ script:
- if [[ $MODE == 'python' ]]; then $DOCKER_CMD AutorestCI/azure-sdk-for-python --pr-repo-id Azure/azure-sdk-for-python -o master -v; fi
- if [[ $MODE == 'node' ]]; then $DOCKER_CMD AutorestCI/azure-sdk-for-node --pr-repo-id Azure/azure-sdk-for-node -o master -v; fi
- if [[ $MODE == 'ruby' ]]; then $DOCKER_CMD AutorestCI/azure-sdk-for-ruby --pr-repo-id Azure/azure-sdk-for-ruby -o master -v; fi
- if [[ $MODE == 'syntax' ]]; then npm test -- test/test.js; fi
- if [[ $MODE == 'syntax' ]]; then npm test -- test/syntax.js; fi
- if [[ $MODE == 'linter' ]]; then npm test -- test/linter.js; fi
- if [[ $MODE == 'semantic' ]]; then npm test -- test/semantic.js; fi
- if [[ $MODE == 'model' ]]; then npm test -- test/model.js; fi
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
The MIT License (MIT)

Copyright (c) 2016 Microsoft
Copyright (c) 2017 Microsoft

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
11 changes: 5 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,15 @@
"version": "0.1.0",
"description": "Tests for Azure REST API Specifications",
"license": "MIT",
"dependencies": {
"devDependencies": {
"mocha": "*",
"async": "^2.1.4",
"glob": "^5.0.14",
"json-schema-ref-parser": "^3.1.2",
"lodash": "^3.10.1",
"request": "^2.61.0",
"z-schema": "^3.16.1"
},
"devDependencies": {
"mocha": "*"
"z-schema": "^3.16.1",
"openapi-validation-tools": "azure/openapi-validation-tools#master"
},
"homepage": "https://github.com/azure/azure-rest-api-specs",
"repository": {
Expand All @@ -31,4 +30,4 @@
"scripts": {
"test": "mocha -t 500000"
}
}
}
55 changes: 7 additions & 48 deletions test/linter.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,58 +2,17 @@
// Licensed under the MIT License. See License in the project root for license information.

'use strict';
var glob = require('glob'),
path = require('path'),
_ = require('lodash');

var globPath, swaggers;
var execSync = require('child_process').execSync;
var isWindows = (process.platform.lastIndexOf('win') === 0);
var prOnly = undefined !== process.env['PR_ONLY'] ? process.env['PR_ONLY'] : 'false';
globPath = path.join(__dirname, '../', '/**/swagger/*.json');
swaggers = _(glob.sync(globPath));

/**
* Converts command to OS specific command by prepending `mono` for non-windows prOnlySwaggers
* @returns {string} clr command
*/
function clrCmd(cmd) {
return isWindows ? cmd : ('mono ' + cmd);
};

/**
* Retrieves list of swagger files to be processed for linting
* @returns {Array} list of files to be processed for linting
*/
function getFilesForLinter() {
if (prOnly === 'true') {
// TODO: Currently works for PR into master branch only
var cmd = 'git diff --name-only HEAD $(git merge-base HEAD master)';
let result;
try {
result = execSync(cmd, { encoding: 'utf8' });
console.log(result);
var swaggerFileInPR = result.split('\n').filter(function (item) {
return (item.match(/.*\/swagger\/*/ig) !== null);
});
console.log(`>>>> Number of swaggers found in this PR: ${swaggerFileInPR.length}`);
return swaggerFileInPR;
} catch (err) {
throw err;
}
} else {
// Return all the swagger files for linter processing
return swaggers;
}
}
var _ = require('lodash'),
execSync = require('child_process').execSync,
utils = require('./util/utils');

describe('AutoRest Linter validation:', function () {
var autoRestLocation = './AutoRest.*/tools/AutoRest.exe';
let swaggersToProcess = getFilesForLinter();
let swaggersToProcess = utils.getFilesChangedInPR();
_(swaggersToProcess).each(function (swagger) {
it(swagger + ' should follow linter rules.', function (done) {
var cmd = clrCmd(autoRestLocation + ' -CodeGenerator None -I ' + swagger);
console.log(cmd);
it(swagger + ' should honor linter validation rules.', function (done) {
var cmd = utils.clrCmd(autoRestLocation + ' -CodeGenerator None -I ' + swagger);
console.log(`Executing: ${cmd}`);
let result;
try {
result = execSync(cmd, { encoding: 'utf8' });
Expand Down
19 changes: 19 additions & 0 deletions test/model.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License in the project root for license information.

'use strict';
var _ = require('lodash'),
utils = require('./util/utils'),
oav = require('openapi-validation-tools');

describe('Azure swagger model validation using x-ms-examples and examples in spec', function () {
let swaggersToProcess = utils.getFilesChangedInPR();
_(swaggersToProcess).each(function (swagger) {
it(swagger + ' should have valid examples.', function (done) {
oav.validateExamples(swagger, null, false, 'error').catch(function (err) {
console.log(err);
});
done();
});
}).value();
});
19 changes: 19 additions & 0 deletions test/semantic.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License in the project root for license information.

'use strict';
var _ = require('lodash'),
utils = require('./util/utils'),
oav = require('openapi-validation-tools');

describe('Azure swagger semantic validation:', function () {
let swaggersToProcess = utils.getFilesChangedInPR();
_(swaggersToProcess).each(function (swagger) {
it(swagger + ' should be semantically valid.', function (done) {
oav.validateSpec(swagger, false, 'error').catch(function (err) {
console.log(err);
});
done();
});
}).value();
});
142 changes: 142 additions & 0 deletions test/syntax.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License in the project root for license information.

'use strict';
var assert = require("assert"),
fs = require('fs'),
path = require('path'),
_ = require('lodash'),
async = require('async'),
RefParser = require('json-schema-ref-parser'),
util = require('util'),
utils = require('./util/utils');

var context;


// Useful when debugging a test for a particular swagger.
// Just update the regex. That will return an array of filtered items.
// swaggers = swaggers.filter(function(item) {
// return (item.match(/.*arm-redis.*/ig) !== null);
// });


describe('Azure swagger schema validation:', function () {
before(function (done) {
utils.initializeValidator(function (err, result) {
if (err) {
done(err);
}
context = result;
done();
});
});

_(utils.swaggers).each(function (swagger) {
it(swagger + ' should be a valid Swagger document.', function (done) {
utils.parseJsonFromFile(swagger, function (err, parsedData) {
if (err) { done(err); }
if (parsedData.documents && util.isArray(parsedData.documents)) {
console.log(util.format('Skipping the test for \'%s\' document as it seems to be a composite swagger doc.', swagger));
done();
}
var valid = context.validator.validate(parsedData, context.extensionSwaggerSchema);
if (!valid) {
var error = context.validator.getLastErrors();
throw new Error("Schema validation failed: " + util.inspect(error, { depth: null }));
}
assert(valid === true);
done();
});
});
}).value();

describe('Azure composite swagger schema validation:', function () {
_(utils.compositeSwaggers).each(function (compositeSwagger) {
it('composite: ' + compositeSwagger + ' should be a valid Composite Swagger document.', function (done) {
utils.parseJsonFromFile(compositeSwagger, function (err, parsedData) {
if (err) { done(err); }
var valid = context.validator.validate(parsedData, context.compositeSchema);
if (!valid) {
var error = context.validator.getLastErrors();
throw new Error("Schema validation for Composite Swagger failed: " + util.inspect(error, { depth: null }));
}
assert(valid === true);
var compositeSwaggerDir = path.dirname(compositeSwagger);
var messages = [];
if (parsedData.documents && parsedData.documents.length > 0) {
async.eachSeries(parsedData.documents, function (docUrl, loopCallback) {
//construct the absolue path if the item in the documents array is a relative path
if (!path.isAbsolute(docUrl) && !docUrl.startsWith('http')) {
docUrl = path.join(compositeSwaggerDir, docUrl);
}
//make a request if it is a url
if (docUrl.startsWith('http')) {
request({ url: docUrl, json: true }, function (error, response, responseBody) {
if (error) {
messages.push('An error occurred while accessing the swagger doc ' +
docUrl + ' from the documents list. The error is ' + util.inspect(error, { depth: null }));
}
if (response.statusCode !== 200) {
messages.push('\'' + response.statusCode + '\': \'File Not Found\'- error occurred while accessing the swagger doc ' +
docUrl + ' from the documents list.');
}
loopCallback();
});
} else {
//check whether the file exists
if (!fs.existsSync(docUrl)) {
messages.push('\'File Not Found\': error occurred while accessing the swagger doc ' +
docUrl + ' from the documents list on the host filesystem.');
}
loopCallback();
}
}, function (err) {
if (err) {
throw err;
}
if (messages.length > 0) {
throw new Error(JSON.stringify(messages));
}
done();
});
} else {
done();
}
});
});
}).value();
});

describe('Azure x-ms-example schema validation:', function () {
_(utils.examples).each(function (example) {
it('x-ms-examples: ' + example + ' should be a valid x-ms-example.', function (done) {
utils.parseJsonFromFile(example, function (err, parsedData) {
if (err) { done(err); }
var valid = context.validator.validate(parsedData, context.exampleSchema);
if (!valid) {
var error = context.validator.getLastErrors();
throw new Error("Schema validation failed: " + util.inspect(error, { depth: null }));
}
assert(valid === true);
done();
});
});
}).value();
});
});

describe('External file or url references ("$ref") in a swagger spec:', function () {
_(utils.swaggers).each(function (swagger) {
it(swagger + ' should be completely resolvable.', function (done) {
RefParser.bundle(swagger, function (bundleErr, bundleResult) {
if (bundleErr) {
var msg = swagger + ' has references that cannot be resolved. They are as follows: \n' + util.inspect(bundleErr.message, { depth: null });
console.log(msg);
throw new Error(msg);
}
done();
});
});
}).value();
});
Loading

0 comments on commit 2940a1c

Please sign in to comment.