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

694 lines
26 KiB
JavaScript

/* eslint-disable object-shorthand */
var _ = require('lodash').noConflict(),
v1Common = require('../common/v1'),
v2Common = require('../common/v2'),
util = require('../util'),
url = require('../url'),
Builders = function (options) {
this.options = options || {};
},
script = function (entityV1, listen, key) {
return {
listen: listen,
script: {
type: 'text/javascript',
exec: _.isString(entityV1[key]) ? entityV1[key].split('\n') : entityV1[key]
}
};
},
authIdMap = {
apikey: 'apikeyAuth',
awsSigV4: 'awsSigV4',
basic: 'basicAuth',
bearer: 'bearerAuth',
digest: 'digestAuth',
hawk: 'hawkAuth',
ntlm: 'ntlmAuth',
oAuth1: 'oAuth1',
oAuth2: 'oAuth2'
},
/**
* Normalizes `description` field of an entity.
* If `description` field is absent, this is a no-op.
* Will mutate the entity.
*
* @param {Object} entity - Wrapper object, possibly containing a description field
* @param {Object} builder - Builder instance that will be called to perform normalization
* @param {Object} utilOptions - Options to be passed to util fn
*/
normalizeDescription = function (entity, builder, utilOptions) {
var retainEmptyValues = _.get(utilOptions, 'retainEmptyValues');
if (_.has(entity, 'description')) {
entity.description = builder.description(entity.description);
}
util.cleanEmptyValue(entity, 'description', retainEmptyValues);
return entity;
};
_.assign(Builders.prototype, {
/**
* Normalizes inherited v1 auth manifests.
*
* @param {Object} entityV1 - A v1 compliant wrapped auth manifest.
* @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 ''.
*
* @returns {Object} - A v1 compliant set of auth helper attributes.
*/
auth: function (entityV1, options) {
if (!entityV1) { return; }
var auth,
params,
mapper,
currentHelper,
helperAttributes,
prioritizeV2 = this.options.prioritizeV2;
// if prioritize v2 is true, use auth as the source of truth
if (util.notLegacy(entityV1, 'auth') || (entityV1.auth && prioritizeV2)) {
return util.sanitizeAuthArray(entityV1, options);
}
if ((entityV1.currentHelper === null) || (entityV1.currentHelper === 'normal')) { return null; }
currentHelper = entityV1.currentHelper;
helperAttributes = entityV1.helperAttributes;
// if noDefaults is false and there is no currentHelper, bail out
if (!(currentHelper || this.options.noDefaults)) { return; }
// if there is a currentHelper without helperAttributes, bail out.
if (currentHelper && !helperAttributes) { return this.options.noDefaults ? undefined : null; }
!currentHelper && (currentHelper = authIdMap[helperAttributes && helperAttributes.id]);
auth = { type: v1Common.authMap[currentHelper] };
mapper = util.authMappersFromLegacy[currentHelper];
// @todo: Change this to support custom auth helpers
mapper && helperAttributes && (params = mapper(helperAttributes)) && (auth[auth.type] = params);
return util.authMapToArray({ auth: auth }, options);
},
/**
* Normalizes v1 collection events.
*
* @param {Object} entityV1 - The v1 entity to be normalized.
* @returns {Array|null} - The normalized events.
*/
events: function (entityV1) {
if (!entityV1) { return; }
if ((util.notLegacy(entityV1, 'event') || this.options.prioritizeV2) && !_.isEmpty(entityV1.events)) {
// @todo: Improve this to order prerequest events before test events
_.forEach(entityV1.events, 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 entityV1.events;
}
var events = [];
entityV1.preRequestScript && events.push(script(entityV1, 'prerequest', 'preRequestScript'));
entityV1.tests && events.push(script(entityV1, 'test', 'tests'));
if (events.length) { return events; }
// retain `null` events
if (entityV1.events === null) { return null; }
},
/**
* 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) {
var self = this,
// Use builder's own options if override is not requested
results = util.handleVars(entity, options || this.options);
// Normalize descriptions that may have been passed in as objects
results = _.map(results, function (item) {
return normalizeDescription(item, self, options || self.options);
});
if (results.length) {
return results;
}
},
/**
* Sanitizes request v1 data.
*
* @param {Object} requestV1 - The wrapper v1 request object around the data list to be sanitized.
* @returns {Object[]} - The normalized list of request body parameters.
*/
data: function (requestV1) {
if (!requestV1) { return; }
var self = this,
mode = requestV1.dataMode,
noDefaults = this.options.noDefaults,
retainEmptyValues = this.options.retainEmptyValues;
if ((!mode || mode === 'binary') && !noDefaults) {
return retainEmptyValues ? [] : undefined;
}
if (!requestV1.data) { return; }
_.isArray(requestV1.data) && _.forEach(requestV1.data, function (datum) {
if (datum.type === 'file' && (_.has(datum, 'value') || !noDefaults)) {
datum.value = (_.isString(datum.value) || _.isArray(datum.value)) ? datum.value : null;
}
normalizeDescription(datum, self, self.options);
});
return requestV1.data;
},
/**
* Normalizes a list of header data from the incoming raw v1 request.
*
* @param {Object} requestV1 - The raw v1 request object.
* @returns {Object[]} - The normalized list of header datum values.
*/
headerData: function (requestV1) {
var self = this,
normalizedHeaderData;
if (!requestV1) { return; }
if (requestV1.headers && _.isEmpty(requestV1.headerData)) {
// this converts a newline concatenated string of headers to an array, so there are no descriptions
return v1Common.parseHeaders(requestV1.headers, true);
}
// however, if non empty headerData already exists, sanitize it.
normalizedHeaderData = _.map(requestV1.headerData, function (entity) {
return normalizeDescription(entity, self, self.options);
});
if (normalizedHeaderData.length) {
return normalizedHeaderData;
}
},
queryParams: function (requestV1) {
if (!requestV1) { return; }
var self = this,
normalizedQueryParams,
urlObj;
if (!requestV1.queryParams) {
return requestV1.url && (urlObj = url.parse(requestV1.url)) && urlObj.query;
}
normalizedQueryParams = _.map(requestV1.queryParams, function (entity) {
return normalizeDescription(entity, self, self.options);
});
if (normalizedQueryParams.length) {
return normalizedQueryParams;
}
},
/**
* 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.retainEmptyValues] - When set to true, empty values are set to null instead of being
* removed.
* @param {?Boolean} [options.retainIds] - When set to true, ids are left as is.
* @returns {Object[]} - The set of sanitized variables.
*/
pathVariableData: function (entity, options) {
var self = this,
results = util.handleVars(entity, options, { isV1: true });
// Normalize descriptions that may have been passed in as objects
results = _.map(results, function (item) {
return normalizeDescription(item, self, self.options);
});
if (results.length) {
return results;
}
},
/**
* Normalizes a potentially raw v1 request object.
*
* @param {Object} requestV1 - The potentially raw v1 request object.
* @param {?String} collectionId - A unique identifier for the v1 collection.
* @param {?Boolean} [skipResponses=false] - When set to true, saved responses will be excluded from the result..
* @returns {Object} - The normalized v1 request object.
*/
request: function (requestV1, collectionId, skipResponses) {
if (!requestV1) { return; }
var map,
auth,
tests,
events,
mapper,
variables,
self = this,
helperAttributes,
preRequestScript,
options = this.options,
noDefaults = options.noDefaults,
retainEmpty = options.retainEmptyValues,
varOpts = { noDefaults: options.noDefaults, retainIds: options.retainIds },
units = ['queryParams', 'pathVariableData', 'headerData', 'data'];
if (!skipResponses) {
units.push('responses');
units.push('responses_order');
}
// if noDefaults is true, do not replace the id
// else
// if id is falsy, replace the id
// if retainIds is false, replace the id
!((options.retainIds && requestV1.id) || options.noDefaults) && (requestV1.id = util.uid());
normalizeDescription(requestV1, self, self.options);
units.forEach(function (unit) {
var result = self[unit](requestV1, self.options);
result && (requestV1[unit] = result);
});
if (requestV1.dataDisabled) { requestV1.dataDisabled = true; }
else if (retainEmpty) { requestV1.dataDisabled = false; }
else { delete requestV1.dataDisabled; }
// remove invalid protocolProfileBehavior property from requestV1
!util.addProtocolProfileBehavior(requestV1) && delete requestV1.protocolProfileBehavior;
collectionId && !noDefaults && (requestV1.collectionId = collectionId);
// normalized v1 requests should not have falsy helperAttributes or currentHelper
if (_.has(requestV1, 'currentHelper')) {
(requestV1.currentHelper === 'normal') && (requestV1.currentHelper = null);
if (!requestV1.currentHelper) {
(requestV1.currentHelper !== null) && (requestV1.currentHelper = null);
// @todo: Should currentHelper be recreated from helperAttributes.id if falsy?
requestV1.helperAttributes = null;
}
}
auth = self.auth(requestV1);
if (auth) {
requestV1.auth = auth;
if (_.has(requestV1, 'helperAttributes') && !requestV1.currentHelper) {
requestV1.currentHelper = authIdMap[auth.type];
}
}
else if (auth === null) { // eslint-disable-line security/detect-possible-timing-attacks
requestV1.auth = requestV1.currentHelper = requestV1.helperAttributes = null;
}
else { delete requestV1.auth; }
events = self.events(requestV1);
if (events || events === null) {
requestV1.events = events;
}
else {
delete requestV1.events;
}
variables = self.variables(requestV1, varOpts);
if (variables) {
requestV1.variables = variables;
}
else {
delete requestV1.variables;
}
if (requestV1.auth && (util.notLegacy(requestV1, 'auth') || options.prioritizeV2)) {
requestV1.currentHelper = v2Common.authMap[requestV1.auth.type];
(requestV1.currentHelper === null) && (requestV1.helperAttributes = null);
mapper = util.authMappersFromCurrent[requestV1.currentHelper];
if (mapper) {
(map = util.authArrayToMap(requestV1)) && (helperAttributes = mapper(map[requestV1.auth.type]));
helperAttributes && (requestV1.helperAttributes = helperAttributes);
}
}
if (requestV1.events && (util.notLegacy(requestV1, 'event') || options.prioritizeV2)) {
tests = preRequestScript = '';
_.forEach(requestV1.events, function (event) {
var exec = event && event.script && event.script.exec;
if (!_.isArray(exec)) { return; }
if (event.listen === 'prerequest') {
preRequestScript += exec.join('\n');
}
else if (event.listen === 'test') {
tests += exec.join('\n');
}
});
requestV1.preRequestScript = preRequestScript ? preRequestScript : null;
requestV1.tests = tests ? tests : null;
}
// prune
['preRequestScript', 'tests'].forEach(function (script) {
if (_.has(requestV1, script) && !requestV1[script] && requestV1[script] !== null) {
delete requestV1[script];
}
});
return requestV1;
},
/**
* Normalizes a potentially raw v1 response object.
*
* @param {Object} responseV1 - The potentially raw v1 response object.
* @returns {Object} - The normalized v1 response object.
*/
response: function (responseV1) {
var self = this;
// if noDefaults is true, do not replace the id
// else
// if id is falsy, replace the id
// if retainIds is false, replace the id
!((self.options.retainIds && responseV1.id) || self.options.noDefaults) && (responseV1.id = util.uid());
// the true in the next line ensures that we don't recursively go on processing responses in a request.
responseV1.request = self.request(responseV1.request, undefined, true);
!responseV1.language && (responseV1.language = 'Text');
!responseV1.previewType && (responseV1.previewType = 'html');
_.isEmpty(responseV1.cookies) && (delete responseV1.cookies);
return responseV1;
},
responses: function (requestV1) {
if (_.isEmpty(requestV1 && requestV1.responses)) { return; }
var self = this;
requestV1.responses.forEach(function (response) {
self.response(response);
});
return requestV1.responses;
},
/**
* Normalizes a request order list.
*
* @param {Object} entityV1 - An object containing a potentially raw list of folder ids.
* @returns {Array} - The normalized list of folder ids.
*/
order: function (entityV1) {
return !this.options.noDefaults && _.compact(entityV1 && entityV1.order);
},
/**
* Normalizes a folder order list.
*
* @param {Object} entityV1 - An object containing a potentially raw list of folder ids.
* @returns {Array} - The normalized list of folder ids.
*/
folders_order: function (entityV1) {
return !this.options.noDefaults && _.compact(entityV1 && entityV1.folders_order);
},
/**
* Normalizes a response order list.
*
* @param {Object} entityV1 - An object containing a potentially raw list of response ids.
* @returns {Array} - The normalized list of response ids.
*/
responses_order: function (entityV1) {
return !this.options.noDefaults && _.compact(entityV1 && entityV1.responses_order);
},
/**
* Normalizes a potentially raw v1 folders list.
*
* @param {Object} collectionV1 - The potentially raw v1 collection object.
* @returns {Object[]} - The normalized v1 collection folders list.
*/
folders: function (collectionV1) {
if (_.isEmpty(collectionV1 && collectionV1.folders)) { return; }
var auth,
events,
variables,
self = this,
order,
foldersOrder,
retainEmpty = self.options.retainEmptyValues,
varOpts = { noDefaults: self.options.noDefaults, retainIds: self.options.retainIds };
_.forEach(collectionV1.folders, function (folder) {
if (!folder) { return; }
// if noDefaults is true, do not replace the id
// else
// if id is falsy, replace the id
// if retainIds is false, replace the id
!((self.options.retainIds && folder.id) || self.options.noDefaults) && (folder.id = util.uid());
folder.description = self.description(folder.description);
util.cleanEmptyValue(folder, 'description', retainEmpty);
// remove invalid protocolProfileBehavior property
!util.addProtocolProfileBehavior(folder) && delete folder.protocolProfileBehavior;
auth = self.auth(folder);
!_.isEmpty((order = self.order(folder))) && (folder.order = order);
!_.isEmpty((foldersOrder = self.folders_order(folder))) && (folder.folders_order = foldersOrder);
(auth || (auth === null)) && (folder.auth = auth);
(events = self.events(folder)) && (folder.events = events);
(variables = self.variables(folder, varOpts)) && (folder.variables = variables);
});
return _.compact(collectionV1.folders);
},
/**
* Normalizes a potentially raw v1 request object.
*
* @param {Object} collectionV1 - The potentially raw v1 collection object.
* @returns {Object[]|*} - The normalized v1 request list.
*/
requests: function (collectionV1) {
if (_.isEmpty(collectionV1 && collectionV1.requests)) { return; }
var self = this;
collectionV1.requests.forEach(function (request) {
self.request(request);
});
return _.compact(collectionV1.requests);
},
/**
* Creates the v1.0.0 compatible description string.
*
* @param {Object} maybeObjectDescription - The description to be converted
*
* @returns {String} - The resultant v1 description.
*/
description: function (maybeObjectDescription) {
var description,
retainEmpty = _.get(this.options, 'retainEmptyValues'),
createDefaultValue = !_.get(this.options, 'noDefaults', false);
if (_.isObject(maybeObjectDescription)) {
description = _.toString(_.get(maybeObjectDescription, 'content'));
}
else {
description = maybeObjectDescription;
}
if (description) {
return description;
}
else if (description === undefined && createDefaultValue) {
return null;
}
else if (_.isEmpty(description) && retainEmpty) {
return null;
}
return undefined;
}
});
module.exports = {
/**
* Normalizes a single v1 request.
*
* @param {Object} request - The v1 request to be normalized.
* @param {Object} options - The set of options for the current normalization.
* @param {?Boolean} [options.mutate=false] - When set to true, normalization is done in place.
* @param {?Boolean} [options.noDefaults=false] - When set to true, sensible defaults are not added.
* @param {?Boolean} [options.prioritizeV2=false] - When set to true, v2 style properties are checked first.
* @param {?Boolean} [options.retainEmptyValues=false] - When set to true, empty values are set to '' instead of
* being removed.
* @param {Function} callback - A function that is invoked when the normalization has completed.
* @returns {*}
*/
normalizeSingle: function (request, options, callback) {
var err,
normalized,
builders = new Builders(options);
// At this stage, mutate will not be passed ordinarily. Hence, the falsy nature of options.mutate can be used
// to selectively clone the request.
options && !options.mutate && (request = _.cloneDeep(request));
try { normalized = builders.request(request); }
catch (e) { err = e; }
if (callback) { return callback(err, normalized); }
if (err) { throw err; }
return normalized;
},
/**
* Normalizes a single v1 response.
*
* @param {Object} response - The v1 request to be normalized.
* @param {Object} options - The set of options for the current normalization.
* @param {?Boolean} [options.mutate=false] - When set to true, normalization is done in place.
* @param {?Boolean} [options.noDefaults=false] - When set to true, sensible defaults are not added.
* @param {?Boolean} [options.prioritizeV2=false] - When set to true, v2 style properties are checked first.
* @param {?Boolean} [options.retainEmptyValues=false] - When set to true, empty values are set to '' instead of
* being removed.
* @param {Function} callback - A function that is invoked when the normalization has completed.
* @returns {*}
*/
normalizeResponse: function (response, options, callback) {
var err,
normalized,
builders = new Builders(options);
// At this stage, mutate will not be passed ordinarily. Hence, the falsy nature of options.mutate can be used
// to selectively clone the response.
options && !options.mutate && (response = _.cloneDeep(response));
try { normalized = builders.response(response); }
catch (e) { err = e; }
if (callback) { return callback(err, normalized); }
if (err) { throw err; }
return normalized;
},
/**
* Converts a V1 collection to a V2 collection (performs ID replacement, etc as necessary).
*
* @param {Object} collection - The v1 collection to be normalized.
* @param {Object} options - The options for the current normalization sequence.
* @param {?Boolean} [options.mutate=false] - When set to true, normalization is done in place.
* @param {?Boolean} [options.noDefaults=false] - When set to true, sensible defaults are not added.
* @param {?Boolean} [options.prioritizeV2=false] - When set to true, v2 style properties are checked first.
* @param {?Boolean} [options.retainEmptyValues=false] - When set to true, empty values are set to '' instead of
* being removed.
* @param {Function} callback - A function invoked to indicate that the normalization has completed.
* @returns {*}
*/
normalize: function (collection, options, callback) {
// At this stage, mutate will not be passed ordinarily. Hence, the falsy nature of options.mutate can be used
// to selectively clone the collection.
options && !options.mutate && (collection = _.cloneDeep(collection));
var auth,
authOptions = { excludeNoauth: true },
builders = new Builders(options),
units = ['events', 'variables', 'order', 'folders_order', 'folders', 'requests'];
// if noDefaults is true, do not replace the id
// else
// if id is falsy, replace the id
// if retainIds is false, replace the id
!((options.retainIds && collection.id) || options.noDefaults) && (collection.id = util.uid());
normalizeDescription(collection, builders, options);
// remove invalid protocolProfileBehavior property
!util.addProtocolProfileBehavior(collection) && delete collection.protocolProfileBehavior;
try {
auth = builders.auth(collection, authOptions);
if (auth || (options.retainEmptyValues && auth === null)) {
collection.auth = auth;
}
else {
delete collection.auth;
}
units.forEach(function (unit) {
var result,
_options;
if (unit === 'variables') {
_options = { retainIds: options.retainIds, noDefaults: options.noDefaults };
}
if (!_.isEmpty(result = builders[unit](collection, _options)) || (unit === 'folders')) {
collection[unit] = result;
}
});
}
catch (e) {
if (callback) { return callback(e, null); }
throw e;
}
if (callback) { return callback(null, collection); }
return collection;
}
};