Skip to content

Instantly share code, notes, and snippets.

@jvanmetre jvanmetre/NRApiTester.js
Last active Jun 18, 2019

Embed
What would you like to do?
/**
* 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
You can’t perform that action at this time.