Simon Priet e69a613a37 feat: Created a mini nodeJS server with NewMan for testing without PostMan GUI.
This will mimic a run in a CD/CI environment or docker container.
2021-09-08 14:01:19 +02:00

291 lines
10 KiB
JavaScript

var fs = require('fs'),
{ URL } = require('url'),
_ = require('lodash'),
chardet = require('chardet'),
filesize = require('filesize'),
prettyms = require('pretty-ms'),
liquidJSON = require('liquid-json'),
request = require('postman-request'),
util,
version = require('../package.json').version,
SEP = ' / ',
/**
* The auxiliary character used to prettify file sizes from raw byte counts.
*
* @type {Object}
*/
FILESIZE_OPTIONS = { spacer: '' },
/**
* Maps the charset returned by chardet to node buffer ones
*
* @constant
* @type {Object}
*/
CHARDET_BUFF_MAP = {
ASCII: 'ascii',
'UTF-8': 'utf8',
'UTF-16LE': 'utf16le',
'ISO-8859-1': 'latin1'
},
POSTMAN_API_HOST = 'api.getpostman.com',
POSTMAN_API_URL = 'https://' + POSTMAN_API_HOST,
/**
* Map of resource type and its equivalent API pathname.
*
* @type {Object}
*/
POSTMAN_API_PATH_MAP = {
collection: 'collections',
environment: 'environments'
},
API_KEY_HEADER = 'X-Api-Key',
USER_AGENT_VALUE = 'Newman/' + version,
// Matches valid Postman UID, case insensitive.
// Same used for validation on the Postman API side.
UID_REGEX = /^[0-9A-Z]+-[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}$/i;
util = {
/**
* The raw newman version, taken from package.json in the root directory
*
* @type {String}
*/
version: version,
/**
* The user agent that this newman identifies as.
*
* @type {String}
*/
userAgent: USER_AGENT_VALUE,
/**
* A utility helper method that prettifies and returns raw millisecond counts.
*
* @param {Number} ms - The raw millisecond count, usually from response times.
* @returns {String} - The prettified time, scaled to units of time, depending on the input value.
*/
prettyms: function (ms) {
if (ms < 1) {
return `${parseInt(ms * 1000, 10)}µs`;
}
return (ms < 1998) ? `${parseInt(ms, 10)}ms` : prettyms(ms || 0);
},
/**
* Returns the time object with all values in largest time unit possible as strings.
*
* @param {Object} obj - {event1: time1, event2: time2, ...} (time in milliseconds)
* @returns {Object} - {event1: time1, event2: time2, ...} (time in string with appropriate unit)
*/
beautifyTime: function (obj) {
return _.forEach(obj, (value, key) => {
// convert only non-zero values
value && (obj[key] = this.prettyms(value));
});
},
/**
* A utility helper method to prettify byte counts into human readable strings.
*
* @param {Number} bytes - The raw byte count, usually from computed response sizes.
* @returns {String} - The prettified size, suffixed with scaled units, depending on the actual value provided.
*/
filesize: function (bytes) {
return filesize(bytes || 0, FILESIZE_OPTIONS);
},
/**
* Resolves the fully qualified name for the provided item
*
* @param {PostmanItem|PostmanItemGroup} item The item for which to resolve the full name
* @param {?String} [separator=SEP] The separator symbol to join path name entries with
* @returns {String} The full name of the provided item, including prepended parent item names
* @private
*/
getFullName: function (item, separator) {
if (_.isEmpty(item) || !_.isFunction(item.parent) || !_.isFunction(item.forEachParent)) { return; }
var chain = [];
item.forEachParent(function (parent) { chain.unshift(parent.name || parent.id); });
item.parent() && chain.push(item.name || item.id); // Add the current item only if it is not the collection
return chain.join(_.isString(separator) ? separator : SEP);
},
/**
* Given a buffer, it tries to match relevant encoding of the buffer.
*
* @param {Buffer} buff - Buffer for which encoding needs to be determined
* @returns {String|undefined} - Detected encoding of the given buffer
*/
detectEncoding: function (buff) {
return CHARDET_BUFF_MAP[chardet.detect(buff)];
},
/**
* Loads JSON data from the given location.
*
* @param {String} type - The type of data to load.
* @param {String} location - Can be an HTTP URL, a local file path or an UID.
* @param {Object=} options - A set of options for JSON data loading.
* @param {Object} options.postmanApiKey - API Key used to load the resources via UID from the Postman API.
* @param {Function} callback - The function whose invocation marks the end of the JSON fetch routine.
* @returns {*}
*/
fetchJson: function (type, location, options, callback) {
!callback && _.isFunction(options) && (callback = options, options = {});
var postmanApiKey = _.get(options, 'postmanApiKey'),
headers = { 'User-Agent': USER_AGENT_VALUE };
// build API URL if `location` is a valid UID and api key is provided.
// Fetch from file in case a file with valid UID name is present.
if (!fs.existsSync(location) && POSTMAN_API_PATH_MAP[type] && postmanApiKey && UID_REGEX.test(location)) {
location = `${POSTMAN_API_URL}/${POSTMAN_API_PATH_MAP[type]}/${location}`;
headers[API_KEY_HEADER] = postmanApiKey;
}
return (/^https?:\/\/.*/).test(location) ?
// Load from URL
request.get({
url: location,
json: true,
headers: headers,
// Temporary fix to fetch the collection from https URL on Node v12
// @todo find the root cause in postman-request
// Refer: https://github.com/postmanlabs/newman/issues/1991
agentOptions: {
keepAlive: true
}
}, (err, response, body) => {
if (err) {
return callback(_.set(err, 'help', `unable to fetch data from url "${location}"`));
}
try {
_.isString(body) && (body = liquidJSON.parse(body.trim()));
}
catch (e) {
return callback(_.set(e, 'help', `the url "${location}" did not provide valid JSON data`));
}
var error,
urlObj,
resource = 'resource';
if (response.statusCode !== 200) {
urlObj = new URL(location);
(urlObj.hostname === POSTMAN_API_HOST) &&
(resource = _(urlObj.pathname).split('/').get(1).slice(0, -1) || resource);
error = new Error(_.get(body, 'error.message',
`Error fetching ${resource}, the provided URL returned status code: ${response.statusCode}`));
return callback(_.assign(error, {
name: _.get(body, 'error.name', _.capitalize(resource) + 'FetchError'),
help: `Error fetching the ${resource} from the provided URL. Ensure that the URL is valid.`
}));
}
return callback(null, body);
}) :
fs.readFile(location, function (err, value) {
if (err) {
return callback(_.set(err, 'help', `unable to read data from file "${location}"`));
}
try {
value = liquidJSON.parse(value.toString(util.detectEncoding(value)).trim());
}
catch (e) {
return callback(_.set(e, 'help', `the file at "${location}" does not contain valid JSON data`));
}
return callback(null, value);
});
},
/**
* Loads raw data from a location, useful for working with non JSON data such as CSV files.
*
* @param {String} location - The relative path / URL to the raw data file.
* @param {Object=} options - A set of load options for the raw data file.
* @param {Function} callback - The callback function whose invocation marks the end of the fetch routine.
* @returns {*}
*/
fetch: function (location, options, callback) {
!callback && _.isFunction(options) && (callback = options, options = {});
return (/^https?:\/\/.*/).test(location) ?
// Load from URL
request.get({ url: location }, (err, response, body) => {
if (err) {
return callback(err);
}
return callback(null, body);
}) :
fs.readFile(String(location), function (err, value) {
if (err) {
return callback(err);
}
return callback(null, value.toString(util.detectEncoding(value)));
});
},
/**
* Checks whether the given object is a v1 collection
*
* Reference: https://github.com/postmanlabs/postman-collection-transformer/blob/v2.6.2/lib/index.js#L44
*
* @param {Object} object - The Object to check for v1 collection compliance.
* @returns {Boolean} - A boolean result indicating whether or not the passed object was a v1 collection.
*/
isV1Collection: function (object) {
return Boolean(object && object.name && object.order && object.requests);
},
/**
* Helper function to test if a given string is an integer.
* Reference: [node-csv-parse]: https://github.com/adaltas/node-csv-parse/blob/v2.5.0/lib/index.js#L207
*
* @param {String} value - The string to test for.
* @returns {Boolean}
*/
isInt: function (value) {
return (/^(-|\+)?([1-9]+[0-9]*)$/).test(value);
},
/**
* Helper function to test if a given string is a float.
* Reference: [node-csv-parse]: https://github.com/adaltas/node-csv-parse/blob/v2.5.0/lib/index.js#L210
*
* @param {String} value - The string to test for.
* @returns {Boolean}
*/
isFloat: function (value) {
return (value - parseFloat(value) + 1) >= 0;
}
};
module.exports = util;