291 lines
10 KiB
JavaScript
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;
|