Last active
June 18, 2019 22:32
-
-
Save jvanmetre/4fdfa1ecccc72327efe77177f6ecfb43 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* NRAPITester.js | |
* Copyright (c) 2019 New Relic Inc. All Rights Reserved. | |
* | |
*/ | |
(function (global, factory) { | |
if (typeof define === 'function' && define.amd) { | |
// AMD. Register as an anonymous module. | |
define([], factory); | |
} else if (typeof module !== 'undefined' && module.exports) { | |
// CommonJS. Define export. | |
module.exports = factory(); | |
} else { | |
// Browser globals | |
global.apiTesterFactory = factory(); | |
} | |
}(this, function apiTesterFactory() { | |
function print_usage_and_exit() { | |
var file = process.argv[1].split('/'); | |
var scriptName = file[file.length - 1]; | |
console.log("usage: node " + scriptName + " [-h] [-b base_url] [-s schema_endpoint]\n"); | |
console.log(" b : Base Test URL to prepends to $paths instead of baseTestUrl"); | |
console.log(" s : Schema URL to load schema file from. Non http loads relative to script location"); | |
process.exit(); | |
} | |
function stringIncludes(string, search, start) { | |
if (typeof start !== 'number') { | |
start = 0; | |
} | |
if (start + search.length > string.length) { | |
return false; | |
} else { | |
return string.indexOf(search, start) !== -1; | |
} | |
} | |
//================================ Body Manipulation =====================================// | |
function processBodyForTime(body) { | |
if (null != body && stringIncludes(body, "#")) { | |
var map = JSON.parse(body); | |
for (var key in map) { | |
if (map.hasOwnProperty(key)) { | |
var value = map[key]; | |
if (null == value) { | |
continue; | |
} | |
if (stringIncludes(value, "#NOW_1000#")) { | |
map[key] = Date.now() | |
} else if (stringIncludes(value, "#NOW-30M_1000#")) { | |
map[key] = Date.now() - (30 * 60 * 1000) | |
} | |
} | |
} | |
body = JSON.stringify(map); | |
} | |
return body | |
} | |
function logErrorResult(result) { | |
if (result.hasOwnProperty("errors")) { | |
for (var i = 0, len = result.errors.length; i < len; i++) { | |
var error = result.errors[i]; | |
console.log(error.message + " at data path: " + error.dataPath); | |
} | |
} | |
} | |
function checkArgs(config) { | |
if (process.argv && process.argv.length > 2) { | |
var args = process.argv.slice(2); | |
while (args.length > 0) { | |
if (args[0] === '-h') { | |
print_usage_and_exit(); | |
} else if (args[0] === '-b') { | |
if (args.length < 2) { | |
print_usage_and_exit(); | |
} else { | |
config.baseTestUrl = args[1]; | |
args = args.slice(1); | |
} | |
} else if (args[0] === '-s') { | |
if (args.length < 2) { | |
print_usage_and_exit(); | |
} else { | |
config.schemaUrl = args[1]; | |
args = args.slice(1); | |
} | |
} | |
args = args.slice(1); | |
} | |
} | |
} | |
//================================ Authentication placeholder ============================// | |
function generateAuthentication(config, callback) { | |
if (typeof config.generateAuthentication === 'function') { | |
config.generateAuthentication(callback); | |
} else { | |
callback(); | |
} | |
} | |
function signRequest(config, options) { | |
if (typeof config.signRequest === 'function') { | |
config.signRequest(options); | |
} | |
} | |
var builder = {}; | |
builder.createApiTester = function ($http, req, assert, fs) { | |
function validate(item, err, response, body, done) { | |
var object = JSON.parse(body); | |
console.log("body: " + body); | |
var result = tv4.validateMultiple(object, item.schema); | |
assert.ok(result.valid, logErrorResult(result)); | |
console.log("Finished Checking (%s): %s took: %s sec", item.method, item.url, response.elapsedTime / 1000); | |
done(); | |
} | |
function call_api(item, options, done) { | |
item.numTries = item.numTries - 1; | |
$http(options, function (err, response, body) { | |
console.log("Checking (%s): %s", item.method, item.description, item.url); | |
if (err) throw new Error('Error getting api endpoint: ' + err.message); | |
if (response.statusCode !== 200 && item.numTries > 0) { | |
console.log("Bad status code for " + item.url + " trying again."); | |
call_api(item, options, done); | |
} else { | |
// Validate the response code | |
assert.ok(response.statusCode === 200, 'Expected 200 OK response: ' + JSON.stringify(response)); | |
assert.ok(body.length > 0, 'No data in body: ' + JSON.stringify(response)); | |
// Prints out Example usage | |
// console.log("Example Request: "); | |
// console.log("GET: " + api_endpoint); | |
// console.log("Response:"); | |
// console.log(JSON.stringify(body, null, 2)); | |
validate(item, err, response, body, done); | |
} | |
}); | |
} | |
function checkApiEndpoint(config, item, done) { | |
var options = { | |
//Specify the endpoint URL | |
url: item.url, | |
//Specify the http method | |
method: item.method, | |
//Specify optional headers | |
headers: { | |
'Content-Type': 'application/json' | |
}, | |
time: true | |
}; | |
if (null != item.headers) { | |
var origHeaders = options["headers"]; | |
for (var key in item.headers) { | |
if (item.headers.hasOwnProperty(key)) { | |
origHeaders[key] = item.headers[key]; | |
} | |
} | |
options["headers"] = origHeaders | |
} | |
signRequest(config, options); | |
item.body = processBodyForTime(item.body); | |
if (item.method === "PUT" || item.method === "POST" || item.method === "PATCH") { | |
console.log(JSON.stringify(item.body)); | |
options["body"] = item.body; | |
} | |
// console.log(JSON.stringify(options)) | |
item.numTries = 2; | |
call_api(item, options, done); | |
} | |
var api = {}; | |
//======================== Load TV4 Parser from Github ===========================// | |
api.tv4Parser = function () { | |
var res = req('https://raw.githubusercontent.com/geraintluff/tv4/master/tv4.min.js'); | |
var encoding = res.headers['content-type'].split(' ')[1].split('=')[1]; | |
var tv4_func = new Function(res.data.toString(encoding)); | |
if (typeof module == 'undefined') { | |
module = {} | |
} | |
tv4_func(); //loads the tv4 variable | |
return tv4 | |
}(); | |
//======================== Load BatchFlow from Github ============================// | |
api.batchFlow = function () { | |
var res = req('https://raw.githubusercontent.com/jprichardson/node-batchflow/master/lib/batchflow.js'); | |
var encoding = res.headers['content-type'].split(' ')[1].split('=')[1]; | |
var batchflow_func = new Function('var global = {}; var module = {};' + res.data.toString(encoding) + 'return module.exports;'); | |
return batchflow_func(); //loads the batch variable | |
}(); | |
//================================ Api Endpoint Check =====================================// | |
api.checkSchemas = function (config, schema_array) { | |
generateAuthentication(config, function () { | |
api.batchFlow(schema_array).parallel(1) | |
.each(function (i, api_endpoint, next) { | |
var description = api_endpoint.hasOwnProperty('$description') ? api_endpoint.$description : "api"; | |
console.log(description); | |
if (api_endpoint.hasOwnProperty('$paths')) { | |
api.batchFlow(api_endpoint.$paths).parallel(1) | |
.each(function (j, endpoint, done) { | |
var item = { | |
url: config.baseTestUrl + endpoint, | |
method: api_endpoint.hasOwnProperty('$http_method') ? api_endpoint.$http_method : 'GET', | |
body: api_endpoint.hasOwnProperty('$http_body') ? api_endpoint.$http_body : null, | |
headers: api_endpoint.hasOwnProperty('$headers') ? api_endpoint.$headers : null, | |
schema: api_endpoint.$schema, | |
description: description | |
}; | |
checkApiEndpoint(config, item, done); | |
}).end(next); | |
} else { | |
console.log("Skipping endpoint, no $paths for " + description); | |
next(); | |
} | |
}).error(function (err) { | |
console.error(err); | |
}).end(function () { | |
console.log("End of schemas called"); | |
}); | |
}); | |
}; | |
api.loadHttpAndCheck = function (config) { | |
//Load urls & schema's to test | |
var options = { | |
'url': config.schemaUrl, | |
'headers': { | |
'Accept': 'application/vnd.github.v3.raw', | |
'Authorization': config.schemaAuth | |
} | |
}; | |
$http.get(options, function (err, response, body) { | |
if (err) throw new Error('Error accessing ' + config.schemaUrl + ': ' + err.message); | |
assert.ok(response.statusCode === 200, "response NOT OK: " + JSON.stringify(response)); | |
assert.ok(body.length > 0, 'No Data returned from github api specification'); | |
if (config.schemaUrl.match(/.*\/api\/v3\/gists\/.*/i)) { | |
//GitHub Gist parsing code | |
var object = JSON.parse(body); | |
var files = object.files; | |
var file_names = Object.keys(files); | |
for (var i = 0, len = file_names.length; i < len; i++) { | |
var file = files[file_names[i]]; | |
var api_endpoints = JSON.parse(file.content); | |
assert.ok(Array.isArray(api_endpoints), 'No API endpoints to check'); | |
api.checkSchemas(config, api_endpoints); | |
} | |
} else { | |
//GitHub Repo parsing code | |
var result = JSON.parse(body); | |
assert.ok(Array.isArray(result), 'Unable to read github api specification'); | |
api.checkSchemas(config, result); | |
} | |
}); | |
}; | |
api.readFromFileAndCheck = function (config) { | |
//Relative file parsing code | |
if (fs) { | |
fs.readFile(__dirname + '/' + config.schemaUrl, function (err, data) { | |
assert.ok(err === null, 'Unable to find file'); | |
var result = JSON.parse(data); | |
assert.ok(Array.isArray(result), 'Unable to file api specification'); | |
api.checkSchemas(config, result); | |
}); | |
} else { | |
console.error("Running locally requires passing in the fs module"); | |
} | |
}; | |
api.check = function (config) { | |
checkArgs(config); | |
if (config.testSchema) { | |
api.checkSchemas(config, config.testSchema); | |
} else if (config.schemaUrl.match(/http.*/)) { | |
api.loadHttpAndCheck(config) | |
} else { | |
api.readFromFileAndCheck(config) | |
} | |
}; | |
return api | |
}; | |
return builder; | |
})); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment