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

798 lines
27 KiB
JavaScript

/* eslint-disable object-shorthand */
var _ = require('lodash').noConflict(),
util = require('../../util'),
v2Common = require('../../common/v2'),
Builders = function (options) {
this.options = options || {};
};
_.assign(Builders.prototype, {
/**
* Converts v2 style auth manifests into their v1 equivalents.
*
* @param {Object} entityV2 - The v1 auth manifest to be transformed into v1.
* @param {?Object} options - The set of options for the current auth cleansing operation.
* @param {?Boolean} [options.includeNoauth=false] - When set to true, noauth is set to null.
*
* @returns {Object} The transformed auth object.
*/
auth: function (entityV2, options) {
return util.authMapToArray(entityV2, options);
},
/**
* Constructs a V1 "events" object from a V2 Postman entity
*
* @param {Object} entityV2 - The v2 event object to be converted.
* @returns {Object[]} - The resultant v1 script definition.
*/
events: function (entityV2) {
// events is treated as the source of truth in v1, so handle that first and bail out.
var source = entityV2.events || entityV2.event;
if (_.isArray(source)) {
// @todo: Improve this to order prerequest events before test events
_.forEach(source, function (event) {
!event.listen && (event.listen = 'test');
if (event.script) {
!event.script.type && (event.script.type = 'text/javascript');
// @todo: Add support for src
_.isString(event.script.exec) && (event.script.exec = event.script.exec.split('\n'));
}
});
return source;
}
},
/**
* Facilitates sanitized variable transformations across all levels for v1 collection normalization.
*
* @param {Object} entity - The wrapper object containing variable definitions.
* @param {?Object} options - The set of options for the current variable transformation.
* @param {?Object} options.fallback - The set of fallback values to be applied when no variables exist.
* @param {?Boolean} options.noDefaults - When set to true, no defaults are applied.
* @param {?Boolean} options.retainIds - When set to true, ids are left as is.
* @returns {Object[]} - The set of sanitized variables.
*/
variables: function (entity, options) {
return util.handleVars(entity, options);
},
/**
* Extracts all subfolders from a v2.0.0 collection or folder
*
* @param {Object} folderOrCollection - Thw entity to extract child items from.
* @returns {Object[]} - The list of extracted folder items.
*/
extractFolderItems: function (folderOrCollection) {
if (!folderOrCollection) { return; }
var i,
self = this,
folders = [],
items = folderOrCollection.item || folderOrCollection.items;
!_.isArray(items) && (items = [items]);
for (i = 0; i < items.length; i++) {
if (items[i] && (items[i].items || items[i].item)) {
folders.push(items[i]);
folders = [].concat(folders, self.extractFolderItems(items[i]));
}
}
return folders;
},
/**
* Extracts all requests from a v2.0.0 collection or folder
*
* @param {Object} folderOrCollection - The object to extract sub-items from.
* @returns {Object[]} - The list of extracted folder items.
*/
extractItems: function (folderOrCollection) {
if (!folderOrCollection) { return; }
var i,
self = this,
requests = [],
isFolder,
items = folderOrCollection.item || folderOrCollection.items;
!_.isArray(items) && (items = [items]);
for (i = 0; i < items.length; i++) {
isFolder = items[i] && (items[i].items || items[i].item);
if (items[i]) {
isFolder ? (requests = [].concat(requests, self.extractItems(items[i]))) : requests.push(items[i]);
}
}
return requests;
},
/**
* Constructs a monolithic raw HTTP header block from a V2 header array
*
* @param {Object} item - The wrapper object to extract headers from.
* @returns {*|string} - The resultant header string.
*/
headers: function (item) {
if (!(item && item.request)) { return; }
return _.map(item.request.headers || item.request.header, function (header) {
return (header.disabled ? '// ' : '') + header.key + ': ' + header.value;
}).join('\n');
},
/**
* Detects the data mode from a given Postman Collection V2 item
*
* @param {Object} item - .
* @returns {*|number|string}
*/
dataMode: function (item) {
return v2Common.modeMap[_.get(item, 'request.body.mode')];
},
/**
* Returns the appropriate request data based on the data mode.
*
* @param {Object} item - The .
* @returns {Object} - The converted request body.
*/
data: function (item) {
var self = this,
mode = _.get(item, 'request.body.mode'),
retainEmpty = this.options.retainEmptyValues;
if (mode === 'raw' || mode === 'file' || mode === 'graphql' || !mode) {
return retainEmpty ? null : undefined;
}
return _.map(item.request.body[mode], function (elem) {
// Only update the value in v1 if src in v2 is non-empty
if (elem && elem.type === 'file' && _.has(elem, 'src')) {
elem.value = (_.isString(elem.src) || _.isArray(elem.src)) ? elem.src : null;
delete elem.src;
}
// Prevents empty request body descriptions from showing up in the result, keeps collections clean.
elem.description = self.description(elem.description);
_.has(elem, 'disabled') && (elem.enabled = !elem.disabled);
delete elem.disabled;
return elem;
});
},
/**
* In case of raw request bodies, this constructs the proper raw data from a V2 item.
*
* @param {Object} item - The v2 request to derive body information from.
* @returns {String} - The inferred v1 request body mode.
*/
rawModeData: function (item) {
var mode = _.get(item, 'request.body.mode');
if (mode === 'raw') {
return item.request.body.raw;
}
else if (mode === 'file') {
return _.get(item.request.body, 'file.src');
}
return this.options.retainEmptyValues ? null : undefined;
},
/**
* Returns GraphQL data if mode is set to graphql.
*
* @param {Object} item - The v2 request to derive information form.
* @returns {Object} - GraphQL request body.
*/
graphqlModeData: function (item) {
var mode = _.get(item, 'request.body.mode');
if (mode === 'graphql') {
return item.request.body.graphql;
}
return this.options.retainEmptyValues ? null : undefined;
},
/**
* Creates options for body from v2 format.
*
* @param {Object} item - The v2 request to derive information form.
* @returns {Object} - The converted body options.
*/
dataOptions: function (item) {
var options = _.get(item, 'request.body.options'),
bodyOptions = {},
mode;
if (!options) {
return;
}
// Convert v2 mode to v1 mode
for (mode in v2Common.modeMap) {
if (!_.isEmpty(options[mode])) {
bodyOptions[v2Common.modeMap[mode]] = options[mode];
}
}
return _.isEmpty(bodyOptions) ? undefined : bodyOptions;
},
/**
* Creates an object of path-variables and their values from a V2 item
*
* @param {Object} item - The wrapper object containing path variable information.
* @returns {Object} - The resultant hash of path variables.
*/
pathVariables: function (item) {
var variable = _.get(item, 'request.url.variables') || _.get(item, 'request.url.variable');
if (!variable) { return; }
return _.transform(variable, function (accumulator, v) {
accumulator[v.key || v.id] = v.value; // v2.0.0 supports both key and id, v2.1.0 will drop id support
}, {});
},
/**
* Creates a V1 URL from a V2 item
*
* @param {Object} item - The v2 request to extract the URL from.
* @returns {String} - The extracted URL of the request.
*/
url: function (item) {
var url = _.get(item, 'request.url');
if (_.isString(url)) {
return url;
}
if (!url) {
return '';
}
return util.urlunparse(url);
},
/**
* Extracts test from a V2 collection
*
* @param {Object} item - The wrapper object to extract test code from.
* @returns {String} - The resultant test script code.
*/
tests: function (item) {
var allEvents = item.events || item.event,
events;
// Nothing to do if the item has no associated events
if (!allEvents) {
return;
}
events = _.filter(allEvents, { listen: 'test' });
return _.map(events, function (event) {
var tests = _.get(event, 'script.exec');
// @todo: Possible dead code, remove when confirmed
if (_.isArray(tests)) {
tests = tests.join('\n');
}
return tests;
}).join('\n');
},
/**
* Extracts the pre-request script from an Item
*
* @param {Object} item - The wrapper object to extract pre-request code from.
* @returns {String} - The resultant pre-request script code.
*/
preRequestScript: function (item) {
var allEvents = item.events || item.event,
events;
// Nothing to do if the item has no associated events
if (!allEvents) {
return;
}
events = _.filter(allEvents, { listen: 'prerequest' });
return _.map(events, function (event) {
var tests = _.get(event, 'script.exec');
// @todo: Possible dead code, remove when confirmed
if (_.isArray(tests)) {
tests = tests.join('\n');
}
return tests;
}).join('\n');
},
/**
* Converts a V2 cookie to a V1 cookie.
*
* @param {Object} cookieV2 - The v2 cookie object to be converted.
* @returns {{expirationDate: *, hostOnly: *, httpOnly: *,
* domain: (any), path: (any), secure: *, session: *, value: *, name: *}}
*/
cookie: function (cookieV2) {
return {
expirationDate: cookieV2.expires,
hostOnly: cookieV2.hostOnly,
httpOnly: cookieV2.httpOnly,
domain: cookieV2.domain,
path: cookieV2.path,
secure: cookieV2.secure,
session: cookieV2.session,
value: cookieV2.value,
name: cookieV2.key || cookieV2.name
};
},
/**
* Converts a V2 response object to a V1 response
*
* @param {Object} responseV2 - The v2 response to be converted.
* @returns {Object} - The converted v1 response.
*/
response: function (responseV2) {
var self = this,
response = {},
id = responseV2.id || responseV2._postman_id,
originalRequest = responseV2.originalRequest || responseV2.request;
// the true in the next line ensures that we don't recursively go on processing responses in a request.
response.request = originalRequest ? self.request({ request: originalRequest }, undefined, true) : undefined;
// add the requestObject to the response (needed by sync)
try {
response.request && (response.requestObject = JSON.stringify(response.request));
}
catch (e) { /* It's fine, not a fatal error, just move on. */ }
// do not attempt to regenerate response id here when `retainIds` is set to false
// if id is changed here the parent's `responses_order` also needs to be changed
// that can't be done yet
response.id = id || util.uid();
response.name = responseV2.name;
response.status = responseV2.status;
response.responseCode = {
code: responseV2.code,
name: responseV2.status,
// TODO: get a list of descriptions
detail: ''
};
response.language = responseV2._postman_previewlanguage || 'Text';
response.previewType = responseV2._postman_previewtype || 'html';
response.time = responseV2.responseTime;
response.headers = responseV2.headers || responseV2.header;
response.cookies = _.map(responseV2.cookies || responseV2.cookie, self.cookie);
response.text = responseV2.body;
response.rawDataType = 'text';
return response;
},
/**
* Extracts the array of responses from a V2 Item.
*
* @param {Object} item - The v2 item to extract saved responses from.
* @returns {Object[]} - The list of saved response objects for the current request.
*/
responses: function (item) {
var self = this,
allResponses = item.responses || item.response;
if (!allResponses) { return; }
return _.map(allResponses, function (response) {
return self.response(response, item);
});
},
/**
* Creates an ordering field for responses of a V2 Item.
*
* @param {Object} item - The v2 item to extract saved responses from.
* @returns {Object[]} - The order of responses within the V2 Item.
*/
responses_order: function (item) {
var allResponses = item.responses || item.response;
if (!allResponses) {
return [];
}
return _.map(allResponses, 'id');
},
/**
* Converts a V2 request to a V1 request.
*
* @param {Object} item - The v2 item to be converted.
* @param {Object} collectionId - The collection id related to the current conversion routine.
* @param {Boolean} [skipResponses=false] - When set to true, excludes saved responses from the result.
* @returns {{id: *, name: *, description: (*|string|builders.description), url: *, collectionId: *, method: *,
* currentHelper: *, helperAttributes: *}|*}
*/
request: function (item, collectionId, skipResponses) {
if (!item) { return; }
var units = ['headers', 'dataMode', 'data', 'rawModeData', 'graphqlModeData',
'pathVariables', 'tests', 'preRequestScript', 'url', 'dataOptions'],
self = this,
request,
description,
currentHelper,
helperAttributes,
req = item && item.request,
v2Auth = req && req.auth,
auth = self.auth(req),
events = self.events(item),
variables = self.variables(item, { retainIds: self.options.retainIds }),
url = req && req.url,
retainEmpty = self.options.retainEmptyValues,
urlObj = _.isString(url) ? util.urlparse(url) : url;
if (!skipResponses) {
units.push('responses');
units.push('responses_order');
}
if (v2Auth && v2Auth.type) {
// @todo: Add support for custom auth helpers
currentHelper = v2Common.authMap[v2Auth.type];
if (util.authMappersFromCurrent[currentHelper]) {
_.isArray(v2Auth[v2Auth.type]) && (v2Auth = util.authArrayToMap(req));
helperAttributes = util.authMappersFromCurrent[currentHelper](v2Auth[v2Auth.type]);
}
else {
helperAttributes = null;
}
}
else if (v2Auth === null) {
currentHelper = null;
helperAttributes = null;
}
request = {
// do not attempt to regenerate request id here when `retainIds` is set to false
// if id is changed here the parent's `order` also needs to be changed
// that can't be done yet
id: item.id || item._postman_id || util.uid(),
name: item.name,
collectionId: collectionId,
method: item.request ? item.request.method : undefined,
currentHelper: currentHelper,
helperAttributes: helperAttributes
};
// add protocolProfileBehavior property from item to the request
util.addProtocolProfileBehavior(item, request);
// only include the dataDisabled flag if truthy
if (req && req.body && _.has(req.body, 'disabled') && (req.body.disabled || retainEmpty)) {
request.dataDisabled = Boolean(req.body.disabled);
}
description = item.request && self.description(item.request.description);
// Prevent empty request descriptions from showing up in the converted result, keeps collections clean.
if (description) { request.description = description; }
else if (retainEmpty) { request.description = null; }
(auth || (auth === null)) && (request.auth = auth);
events && events.length && (request.events = events);
variables && variables.length && (request.variables = variables);
_.forEach(units, function (unit) {
request[unit] = self[unit](item);
});
// description transformations for v2 to v1
urlObj && (request.pathVariableData = _.map(urlObj.variables || urlObj.variable, function (v) {
var result = { key: v.key || v.id, value: v.value };
// Prevent empty path variable descriptions from showing up in converted results, keeps collections clean.
if (v.description) { result.description = self.description(v.description); }
else if (retainEmpty) { result.description = null; }
return result;
}));
urlObj && (request.queryParams = _.map(urlObj.query, function (queryParam) {
// Prevents empty query param descriptions from showing up in the result, keeps collections clean.
queryParam.description = self.description(queryParam.description);
_.has(queryParam, 'disabled') && (queryParam.enabled = !queryParam.disabled);
delete queryParam.disabled;
return queryParam;
}));
// item truthiness is already validated by this point
request.headerData = _.map(item.request && (item.request.headers || item.request.header), function (header) {
// Prevents empty query param descriptions from showing up in the result, keeps collections clean.
header.description = self.description(header.description);
_.has(header, 'disabled') && (header.enabled = !header.disabled);
delete header.disabled;
return header;
});
return request;
},
/**
* Creates a V1 compatible array of requests from a Postman V2 collection.
*
* @param {Object} collectionV2 - The v2 collection to derive v1 requests from.
* @returns {Object[]} - The list of v1 request objects.
*/
requests: function (collectionV2) {
var self = this,
requests = [],
info = collectionV2 && collectionV2.info,
id = info && (info.id || info._postman_id) || collectionV2.id;
_.forEach(self.extractItems(collectionV2), function (item) {
var requestV1 = self.request(item, id);
requests.push(requestV1);
});
return requests;
},
/**
* Creates a V1 compatible array of solo requestIds from a Postman collection V2
*
* @param {Object} collectionV2 - The v2 collection to be used for request order derivation.
* @returns {Object[]} - The request order for the resultant v1 collection.
*/
order: function (collectionV2) {
var itemArray = collectionV2.items || collectionV2.item,
allItems = _.isArray(itemArray) ? itemArray : [itemArray];
// eslint-disable-next-line lodash/prefer-compact
return _.filter(_.map(allItems, function (item) {
if (!item) { return; }
var isFolder = (item.items || item.item);
if (!isFolder) {
return item.id || item._postman_id;
}
}));
},
/**
* Creates a V1 compatible array of folder orders from a Postman collection V2
*
* @param {Object} folderOrCollection - The object to derive folder order details from.
* @returns {Object[]} - The list of folder ids that indicate the order.
*/
folders_order: function (folderOrCollection) {
var itemArray = folderOrCollection.items || folderOrCollection.item,
allItems = _.isArray(itemArray) ? itemArray : [itemArray];
// eslint-disable-next-line lodash/prefer-compact
return _.filter(_.map(allItems, function (item) {
if (!item) { return; }
var isFolder = (item.items || item.item);
if (isFolder) {
return item.id || item._postman_id;
}
}));
},
/**
* Creates an array of V1 compatible folders from a V2 collection
*
* @param {Object} collectionV2 - The v2 collection to derive folder structure information from.
* @returns {Object[]} - The list of folder definitions.
*/
folders: function (collectionV2) {
var self = this,
retainEmpty = self.options.retainEmptyValues;
return _.map(self.extractFolderItems(collectionV2), function (folder) {
if (!folder) { return; }
var folderItems = folder.items || folder.item,
description = self.description(folder.description),
auth = self.auth(folder),
events = self.events(folder),
variables = self.variables(folder, { retainIds: self.options.retainIds }),
result = {
// do not attempt to regenerate folder id here when `retainIds` is set to false
// if id is changed here the parent's `folder_order` also needs to be changed
// that can't be done yet
id: folder.id || folder._postman_id || util.uid(),
name: folder.name,
// eslint-disable-next-line lodash/prefer-compact
order: _.filter(_.map(folderItems, function (f) {
if (!f) { return; }
var isFolder = (f.items || f.item);
return !isFolder && (f.id || f._postman_id);
})),
folders_order: self.folders_order(folder)
};
((auth && auth.type) || (auth === null)) && (result.auth = auth);
events && events.length && (result.events = events);
variables && variables.length && (result.variables = variables);
util.addProtocolProfileBehavior(folder, result);
// Prevent empty folder descriptions from showing up in the result, keeps collections clean.
if (description) { result.description = description; }
else if (retainEmpty) { result.description = null; }
return result;
});
},
/**
* Creates the v1.0.0 compatible description string from the v2.0.0 description format.
*
* @param {Object} descriptionV2 - The v2 style description to be converted
*
* @returns {String} - The resultant v1 description.
*/
description: function (descriptionV2) {
var description,
retainEmpty = this.options.retainEmptyValues;
description = _.isObject(descriptionV2) ? descriptionV2.content : descriptionV2;
if (description) { return description; }
else if (retainEmpty) { return null; }
}
});
module.exports = {
input: '2.0.0',
output: '1.0.0',
Builders: Builders,
/**
* Converts a single V2 item to a V1 request.
*
* @param {Object} request - The request to be converted.
* @param {Object} options - The set of options for request conversion.
* @param {Function} callback - The function to be invoked after conversion has completed.
*/
convertSingle: function (request, options, callback) {
var builders = new Builders(options),
clone = _.cloneDeep(request),
converted,
err;
try {
clone = v2Common.populateIds(clone);
converted = builders.request(clone);
}
catch (e) {
err = e;
}
if (callback) {
return callback(err, converted);
}
if (err) {
throw err;
}
return converted;
},
/**
* Converts a single V2 item to a V1 request.
*
* @param {Object} response - The response to be converted.
* @param {Object} options - The set of options for request conversion.
* @param {Function} callback - The function to be invoked after conversion has completed.
*/
convertResponse: function (response, options, callback) {
var builders = new Builders(options),
converted,
err;
try {
converted = builders.response(_.cloneDeep(response));
}
catch (e) {
err = e;
}
if (callback) {
return callback(err, converted);
}
if (err) {
throw err;
}
return converted;
},
/**
* Converts a V2 collection to a V1 collection (performs ID replacement, etc as necessary).
*
* @param {Object} collection - The collection to be converted.
* @param {Object} options - The set of options for request conversion.
* @param {Function} callback - The function to be invoked after conversion has completed.
*/
convert: function (collection, options, callback) {
collection = _.cloneDeep(collection);
var auth,
events,
variables,
builders = new Builders(options),
authOptions = { excludeNoauth: true },
varOpts = options && { fallback: options.env, retainIds: options.retainIds },
units = ['order', 'folders_order', 'folders', 'requests'],
id = _.get(collection, 'info._postman_id') || _.get(collection, 'info.id'),
info = collection && collection.info,
newCollection = {
id: id && options && options.retainIds ? id : util.uid(),
name: info && info.name
};
// ensure that each item has an ID
collection = v2Common.populateIds(collection);
try {
// eslint-disable-next-line max-len
newCollection.description = builders.description(info && info.description);
(auth = builders.auth(collection, authOptions)) && (newCollection.auth = auth);
(events = builders.events(collection)) && (newCollection.events = events);
(variables = builders.variables(collection, varOpts)) && (newCollection.variables = variables);
util.addProtocolProfileBehavior(collection, newCollection);
units.forEach(function (unit) {
newCollection[unit] = builders[unit](collection);
});
}
catch (e) {
if (callback) {
return callback(e, null);
}
throw e;
}
if (callback) {
return callback(null, newCollection);
}
return newCollection;
}
};