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.
This commit is contained in:
Simon Priet
2021-09-08 14:01:19 +02:00
parent 5fbd7c88fa
commit e69a613a37
5610 changed files with 740417 additions and 3 deletions

View File

@@ -0,0 +1,152 @@
var semver = require('semver'),
FN = 'function',
generateConverter; // constructor
/**
* Prototype interface definition of a converter
*
* @param {Object} model - A manifest that defines the conversion process.
* @param {String} model.input - The input version to convert from.
* @param {String} model.output - The output version to convert to.
* @param {Function} model.convert - A function to convert entire collections.
* @param {Function} model.create - A function to perform creation operations.
* @param {Function} [model.init] - An initializer function to bootstrap the generated converter.
*
* @throws {Error} If model definition does not meet requirements
*/
generateConverter = function (model) {
var error;
// validate the model
if (!model) {
error = 'invalid definition of converter';
}
// ensure that the input version support is a valid semver
else if (!semver.valid(model.input)) {
error = 'input version support for converter is invalid';
}
// ensure that the output version support is a valid semver
else if (!semver.valid(model.output)) {
error = 'output version support for converter is invalid';
}
else if (typeof model.convert !== FN) {
error = 'convert function is not defined';
}
else if (typeof model.convertSingle !== FN) {
error = 'convertSingle function is not defined';
}
if (semver.satisfies(model.input, model.output)) {
error = 'input version ' + model.input + ' matches output version ' + model.output + ' for converter';
}
// If we had encountered any error during validation, we simply exit by executing the callback and forwarding the
// error.
if (error) {
throw new Error(error);
}
return model;
};
module.exports = {
/**
* All converters
*
* @type {object<Converter>}
*
* @note this form of loading is most likely not browser friendly, find browser alternative
*/
converters: {
'converter-v1-to-v2': require('./v1.0.0/converter-v1-to-v2'),
'converter-v1-to-v21': require('./v1.0.0/converter-v1-to-v21'),
'converter-v2-to-v1': require('./v2.0.0/converter-v2-to-v1'),
'converter-v21-to-v1': require('./v2.1.0/converter-v21-to-v1')
},
/**
* Fetches a converter for the given input and output versions
*
* @param {String} inputVersion - The version to convert from.
* @param {String} outputVersion - The version to convert to.
* @returns {Converter} - A converter for the given set of options.
*/
getConverter: function (inputVersion, outputVersion) {
var converter;
inputVersion = semver.clean(inputVersion);
outputVersion = semver.clean(outputVersion);
for (converter in this.converters) {
// eslint-disable-next-line no-prototype-builtins
converter = this.converters.hasOwnProperty(converter) && this.converters[converter];
if (converter && semver.eq(converter.input, inputVersion) && semver.eq(converter.output, outputVersion)) {
return generateConverter(converter);
}
}
},
/**
* Picks the appropriate converter and converts the given collection.
*
* @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 the completion of conversion process.
*/
convert: function (collection, options, callback) {
var chosenConverter = this.getConverter(options.inputVersion, options.outputVersion);
if (!chosenConverter) {
return callback(new Error('no conversion path found'));
}
return chosenConverter.convert(collection, options, callback);
},
/**
* Picks the appropriate converter and converts the given object.
*
* @param {Object} object - A single V1 request or a V2 Item.
* @param {Object} options - The set of options for request conversion.
* @param {Function} callback - The function to be invoked after the completion of conversion process.
*/
convertSingle: function (object, options, callback) {
var chosenConverter = this.getConverter(options.inputVersion, options.outputVersion);
if (!chosenConverter) {
return callback(new Error('no conversion path found'));
}
return chosenConverter.convertSingle(object, options, callback);
},
/**
* Picks the appropriate converter and converts the given object.
*
* @param {Object} object - A single V1 Response or a V2 Response.
* @param {Object} options - The set of options for response conversion.
* @param {Function} callback - The function invoked to mark the completion of the conversion process.
* @returns {*}
*/
convertResponse: function (object, options, callback) {
var chosenConverter = this.getConverter(options.inputVersion, options.outputVersion);
if (!chosenConverter) {
return callback(new Error('no conversion path found'));
}
return chosenConverter.convertResponse(object, options, callback);
},
/**
* Returns a builder, which can be used to convert individual requests, etc.
*
* @param {Object} options - The set of options for builder creation.
* @returns {Function} - The builder for the given set of options.
*/
builder: function (options) {
var chosenConverter = this.getConverter(options.inputVersion, options.outputVersion);
return chosenConverter.create(options);
}
};

View File

@@ -0,0 +1,885 @@
/* eslint-disable object-shorthand */
var _ = require('lodash').noConflict(),
constants = require('../../constants'),
v1Common = require('../../common/v1'),
util = require('../../util'),
headersCommentPrefix = '//',
IS_SUBFOLDER = Symbol('_postman_isSubFolder'),
/**
* A constructor that is capable of being used for one-off conversions of requests, and folders.
*
* @param {Object} options - The set of options for builder construction.
* @class Builders
* @constructor
*/
Builders = function (options) {
this.options = options || {};
},
/**
* Parse formdata & urlencoded request data.
* - filter params missing property `key`
* - handle file type param value
* -cleanup `enabled` & `description`
*
* @param {Object[]} data - Data to parse.
* @param {?Boolean} [retainEmpty] - To retain empty values or not.
* @returns {Object[]} - Parsed data.
*/
parseFormData = function (data, retainEmpty) {
if (!(Array.isArray(data) && data.length)) { return []; }
var formdata = [],
i,
ii,
param;
for (i = 0, ii = data.length; i < ii; i++) {
// clone param to avoid mutating data.
// eslint-disable-next-line prefer-object-spread
param = Object.assign({}, data[i]);
// skip if param is missing property `key`,
// `key` is a required property, value can be null/undefined.
// because in a FormParam property lists, key is used for indexing.
if (_.isNil(param.key)) {
continue;
}
// for file `type`, set `value` to `src`
if (param.type === 'file' && !param.src && param.value) {
param.src = (_.isString(param.value) || _.isArray(param.value)) ? param.value : null;
delete param.value;
}
// `hasOwnProperty` check ensures that it don't delete undefined property: `enabled`
if (Object.prototype.hasOwnProperty.call(param, 'enabled')) {
// set `disabled` flag if `enabled` is false.
param.enabled === false && (param.disabled = true);
delete param.enabled; // cleanup
}
// Prevent empty descriptions from showing up in the converted results. This keeps collections clean.
util.cleanEmptyValue(param, 'description', retainEmpty);
formdata.push(param);
}
return formdata;
};
_.assign(Builders.prototype, {
/**
* Constructs a V2 compatible "info" object from a V1 Postman Collection
*
* @param {Object} collectionV1 - A v1 collection to derive collection metadata from.
* @returns {Object} - The resultant v2 info object.
*/
info: function (collectionV1) {
var info = {
_postman_id: collectionV1.id || util.uid(),
name: collectionV1.name
};
if (collectionV1.description) { info.description = collectionV1.description; }
else if (this.options.retainEmptyValues) { info.description = null; }
info.schema = constants.SCHEMA_V2_URL;
return info;
},
/**
* 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.
*/
variable: function (entity, options) {
return util.handleVars(entity, options);
},
/**
* Constructs a V2 compatible URL object from a V1 request
*
* @param {Object} requestV1 - The source v1 request to extract the URL from.
* @returns {String|Object} - The resultant URL.
*/
url: function (requestV1) {
var queryParams = [],
pathVariables = [],
traversedVars = {},
queryParamAltered,
retainIds = this.options.retainIds,
parsed = util.urlparse(requestV1.url),
retainEmpty = this.options.retainEmptyValues;
// add query params
_.forEach(requestV1.queryParams, function (queryParam) {
(queryParam.enabled === false) && (queryParam.disabled = true);
delete queryParam.enabled;
util.cleanEmptyValue(queryParam, 'description', retainEmpty);
if (_.has(queryParam, 'equals')) {
if (queryParam.equals) {
(queryParam.value === null) && (queryParam.value = '');
}
else {
// = is not appended when the value is null. However,
// non empty value should be preserved
queryParam.value = queryParam.value || null;
}
queryParamAltered = true;
delete queryParam.equals;
}
queryParams.push(queryParam);
});
// only add query params from URL if not given explicitly
if (!_.size(queryParams)) {
// parsed query params are taken from the url, so no descriptions are available from them
queryParams = parsed.query;
}
// Merge path variables
_.forEach(requestV1.pathVariableData, function (pathVariable) {
pathVariable = _.clone(pathVariable);
util.cleanEmptyValue(pathVariable, 'description', retainEmpty);
if (!retainIds && pathVariable.id) {
delete pathVariable.id;
}
pathVariables.push(pathVariable);
traversedVars[pathVariable.key] = true;
});
// pathVariables in v1 are of the form {foo: bar}, so no descriptions can be obtained from them
_.forEach(requestV1.pathVariables, function (value, key) {
!traversedVars[key] && pathVariables.push({
value: value,
key: key
});
});
!_.isEmpty(queryParams) && (parsed.query = queryParams);
!_.isEmpty(pathVariables) && (parsed.variable = pathVariables);
// If the query params have been altered, update the raw stringified URL
queryParamAltered && (parsed.raw = util.urlunparse(parsed));
// return the objectified URL only if query param or path variable descriptions are present, string otherwise
return (parsed.query || parsed.variable) ? parsed : (parsed.raw || requestV1.url);
},
/**
* Extracts the HTTP Method from a V1 request
*
* @param {Object} requestV1 - The v1 request to extract the request method from.
* @returns {String} - The extracted request method.
*/
method: function (requestV1) {
return requestV1.method;
},
/**
* Constructs an array of Key-Values from a raw HTTP Header string.
*
* @param {Object} requestV1 - The v1 request to extract header information from.
* @returns {Object[]} - A list of header definition objects.
*/
header: function (requestV1) {
if (_.isArray(requestV1.headers)) {
return requestV1.headers;
}
var headers = [],
traversed = {},
headerData = requestV1.headerData || [],
retainEmpty = this.options.retainEmptyValues;
_.forEach(headerData, function (header) {
if (_.startsWith(header.key, headersCommentPrefix) || (header.enabled === false)) {
header.disabled = true;
header.key = header.key.replace(headersCommentPrefix, '').trim();
}
// prevent empty header descriptions from showing up in converted results. This keeps the collections clean
util.cleanEmptyValue(header, 'description', retainEmpty);
delete header.enabled;
headers.push(header); // @todo Improve this sequence to account for multi-valued headers
traversed[header.key] = true;
});
// requestV1.headers is a string, so no descriptions can be obtained from it
_.forEach(v1Common.parseHeaders(requestV1.headers), function (header) {
!traversed[header.key] && headers.push(header);
});
return headers;
},
/**
* Constructs a V2 Request compatible "body" object from a V1 Postman request
*
* @param {Object} requestV1 - The v1 request to extract the body from.
* @returns {{mode: *, content: (*|string)}}
*/
body: function (requestV1) {
var modes = {
binary: 'file',
graphql: 'graphql',
params: 'formdata',
raw: 'raw',
urlencoded: 'urlencoded'
},
data = {},
rawModeData,
graphqlModeData,
dataMode = modes[requestV1.dataMode],
retainEmpty = this.options.retainEmptyValues,
bodyOptions = {},
mode,
// flag indicating that all the props which holds request body data
// i.e, data (urlencoded, formdata), rawModeData (raw, file) and graphqlModeData (graphql)
// are empty.
emptyBody = _.isEmpty(requestV1.data) && _.isEmpty(requestV1.rawModeData) &&
_.isEmpty(requestV1.graphqlModeData);
// set body to null if:
// 1. emptyBody is true and dataMode is unset
// 2. dataMode is explicitly set to null
// @note the explicit null check is added to ensure that body is not set
// in case the dataMode was set to null by the app.
if ((!dataMode && emptyBody) || requestV1.dataMode === null) {
return retainEmpty ? null : undefined;
}
// set `rawModeData` if its a string
if (_.isString(requestV1.rawModeData)) {
rawModeData = requestV1.rawModeData;
}
// check if `rawModeData` is an array like: ['rawModeData']
else if (Array.isArray(requestV1.rawModeData) &&
requestV1.rawModeData.length === 1 &&
_.isString(requestV1.rawModeData[0])) {
rawModeData = requestV1.rawModeData[0];
}
// set graphqlModeData if its not empty
if (!_.isEmpty(requestV1.graphqlModeData)) {
graphqlModeData = requestV1.graphqlModeData;
}
// set data.mode.
// if dataMode is not set, infer from data or rawModeData or graphqlModeData
if (dataMode) {
data.mode = dataMode;
}
// at this point we are sure that the body is not empty so let's
// infer the data mode.
// @note its possible that multiple body types are set e.g, both
// rawModeData and graphqlModeData are set. So, the priority will be:
// raw -> formdata -> graphql (aligned with pre-graphql behaviour).
//
// set `formdata` if rawModeData is not set and data is an array
// `data` takes higher precedence over `rawModeData`.
else if (!rawModeData && Array.isArray(requestV1.data || requestV1.rawModeData)) {
data.mode = 'formdata';
}
// set `graphql` if graphqlModeData is set
else if (!rawModeData && graphqlModeData) {
data.mode = 'graphql';
}
// set `raw` mode as default
else {
data.mode = 'raw';
}
if (data.mode === 'raw') {
if (rawModeData) {
data[data.mode] = rawModeData;
}
else if (_.isString(requestV1.data)) {
data[data.mode] = requestV1.data;
}
else {
// empty string instead of retainEmpty check to have parity with other modes.
data[data.mode] = '';
}
}
else if (data.mode === 'graphql') {
data[data.mode] = graphqlModeData;
}
else if (data.mode === 'file') {
// rawModeData can be string or undefined.
data[data.mode] = { src: rawModeData };
}
else {
// parse data for formdata or urlencoded data modes.
// `rawModeData` is checked in case its of type `data`.
data[data.mode] = parseFormData(requestV1.data || requestV1.rawModeData, retainEmpty);
}
if (requestV1.dataOptions) {
// Convert v1 mode to v2 mode
for (mode in modes) {
if (!_.isEmpty(requestV1.dataOptions[mode])) {
bodyOptions[modes[mode]] = requestV1.dataOptions[mode];
}
}
!_.isEmpty(bodyOptions) && (data.options = bodyOptions);
}
if (requestV1.dataDisabled) { data.disabled = true; }
else if (retainEmpty) { data.disabled = false; }
return data;
},
/**
* Constructs a V2 "events" object from a V1 Postman Request
*
* @param {Object} entityV1 - The v1 entity to extract script information from.
* @returns {Object[]|*}
*/
event: function (entityV1) {
if (!entityV1) { return; }
const retainIds = this.options.retainIds;
// if prioritizeV2 is true, events is used as the source of truth
if ((util.notLegacy(entityV1, 'event') || this.options.prioritizeV2) && !_.isEmpty(entityV1.events)) {
// in v1, `events` is regarded as the source of truth if it exists, so handle that first and bail out.
// @todo: Improve this to order prerequest events before test events
_.forEach(entityV1.events, function (event) {
!event.listen && (event.listen = 'test');
// just delete the event.id if retainIds is not set
if (!retainIds && event.id) {
delete event.id;
}
if (event.script) {
// just delete the script.id if retainIds is not set
if (!retainIds && event.script.id) {
delete event.script.id;
}
!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 = [];
// @todo: Extract both flows below into a common method
if (entityV1.tests) {
events.push({
listen: 'test',
script: {
type: 'text/javascript',
exec: _.isString(entityV1.tests) ?
entityV1.tests.split('\n') :
entityV1.tests
}
});
}
if (entityV1.preRequestScript) {
events.push({
listen: 'prerequest',
script: {
type: 'text/javascript',
exec: _.isString(entityV1.preRequestScript) ?
entityV1.preRequestScript.split('\n') :
entityV1.preRequestScript
}
});
}
return events.length ? events : undefined;
},
/**
* A number of auth parameter names have changed from V1 to V2. This function calls the appropriate
* mapper function, and creates the V2 auth parameter object.
*
* @param {Object} entityV1 - The v1 entity to derive auth information from.
* @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 {{type: *}}
*/
auth: function (entityV1, options) {
if (!entityV1) { return; }
if ((util.notLegacy(entityV1, 'auth') || this.options.prioritizeV2) && entityV1.auth) {
return util.authArrayToMap(entityV1, options);
}
if (!entityV1.currentHelper || (entityV1.currentHelper === null) || (entityV1.currentHelper === 'normal')) {
return;
}
var params,
type = v1Common.authMap[entityV1.currentHelper] || entityV1.currentHelper,
auth = {
type: type
};
// Some legacy versions of the App export Helper Attributes as a string.
if (_.isString(entityV1.helperAttributes)) {
try {
entityV1.helperAttributes = JSON.parse(entityV1.helperAttributes);
}
catch (e) {
return;
}
}
if (entityV1.helperAttributes && util.authMappersFromLegacy[entityV1.currentHelper]) {
params = util.authMappersFromLegacy[entityV1.currentHelper](entityV1.helperAttributes);
}
params && (auth[type] = params);
return auth;
},
/**
* Creates a V2 format request from a V1 Postman Collection Request
*
* @param {Object} requestV1 - The v1 request to be transformed.
* @returns {Object} - The converted v2 request.
*/
request: function (requestV1) {
var self = this,
request = {},
retainEmpty = self.options.retainEmptyValues,
units = ['auth', 'method', 'header', 'body', 'url'];
units.forEach(function (unit) {
request[unit] = self[unit](requestV1);
});
if (requestV1.description) { request.description = requestV1.description; }
else if (retainEmpty) { request.description = null; }
return request;
},
/**
* Converts a V1 cookie to a V2 cookie.
*
* @param {Object} cookieV1 - The v1 cookie object to convert.
* @returns {{expires: string, hostOnly: *, httpOnly: *, domain: *, path: *, secure: *, session: *, value: *}}
*/
cookie: function (cookieV1) {
return {
expires: (new Date(cookieV1.expirationDate * 1000)).toString(),
hostOnly: cookieV1.hostOnly,
httpOnly: cookieV1.httpOnly,
domain: cookieV1.domain,
path: cookieV1.path,
secure: cookieV1.secure,
session: cookieV1.session,
value: cookieV1.value,
key: cookieV1.name
};
},
/**
* Gets the saved request for the given response, and handles edge cases between Apps & Sync
*
* Handles a lot of edge cases, so the code is not very clean.
*
* The Flow followed here is:
*
* If responseV1.requestObject is present
* If it is a string
* Try parsing it as JSON
* If parsed,
* return it
* else
* It is a request ID
* If responseV1.request is present
* If it is a string
* Try parsing it as JSON
* If parsed,
* return it
* else
* It is a request ID
* Look up the collection for the request ID and return it, or return undefined.
*
* @param {Object} responseV1 - The v1 response to be converted.
* @returns {Object} - The converted saved request, in v2 format.
*/
savedRequest: function (responseV1) {
var self = this,
associatedRequestId;
if (responseV1.requestObject) {
if (_.isString(responseV1.requestObject)) {
try {
return JSON.parse(responseV1.requestObject);
}
catch (e) {
// if there was an error parsing it as JSON, it's probably an ID, so store it in the ID variable
associatedRequestId = responseV1.requestObject;
}
}
else {
return responseV1.requestObject;
}
}
if (responseV1.request) {
if (_.isString(responseV1.request)) {
try {
return JSON.parse(responseV1.request);
}
catch (e) {
// if there was an error parsing it as JSON, it's probably an ID, so store it in the ID variable
associatedRequestId = responseV1.request;
}
}
else {
return responseV1.request;
}
}
// we have a request ID
return associatedRequestId && _.get(self, ['cache', associatedRequestId]);
},
/**
* Since a V2 response contains the entire associated request that was sent, creating the response means it
* also must use the V1 request.
*
* @param {Object} responseV1 - The response object to convert from v1 to v2.
* @returns {Object} - The v2 response object.
*/
singleResponse: function (responseV1) {
var response = {},
self = this,
originalRequest;
originalRequest = self.savedRequest(responseV1);
// add ids to the v2 result only if both: the id and retainIds are truthy.
// this prevents successive exports to v2 from being overwhelmed by id diffs
self.options.retainIds && (response.id = responseV1.id || util.uid());
response.name = responseV1.name || 'response';
response.originalRequest = originalRequest ? self.request(originalRequest) : undefined;
response.status = responseV1.responseCode && responseV1.responseCode.name || undefined;
response.code = responseV1.responseCode && Number(responseV1.responseCode.code) || undefined;
response._postman_previewlanguage = responseV1.language;
response._postman_previewtype = responseV1.previewType;
response.header = responseV1.headers;
response.cookie = _.map(responseV1.cookies, function (cookie) {
return self.cookie(cookie);
});
response.responseTime = responseV1.time;
response.body = responseV1.text;
return response;
},
/**
* Constructs an array of "sample" responses (compatible with a V2 collection)
* from a Postman Collection V1 Request.
*
* If response ordering via `responses_order` field is present,
* ensure the ordering is respected while constructing responses array.
*
* @param {Object} requestV1 - The v1 request object to extract response information from.
* @returns {Object[]} - The list of v2 response definitions.
*/
response: function (requestV1) {
var self = this,
responses = _.get(requestV1, 'responses', []),
responsesCache = _.keyBy(responses, 'id'),
responses_order = _.get(requestV1, 'responses_order', []);
// If ordering of responses is not available
// Create a default ordering using the `responses` field
if (!(responses_order && responses_order.length)) {
responses_order = _.map(responses, 'id');
}
// Filter out any response id that is not available
responses_order = _.filter(responses_order, function (responseId) {
return _.has(responsesCache, responseId);
});
return _.map(responses_order, function (responseId) {
return self.singleResponse(responsesCache[responseId]);
});
},
/**
* Creates a V2 compatible ``item`` from a V1 Postman Collection Request
*
* @param {Object} requestV1 - Postman collection V1 request.
* @returns {Object} - The converted request object, in v2 format.
*/
singleItem: function (requestV1) {
if (!requestV1) { return; }
var self = this,
retainIds = self.options.retainIds,
units = ['request', 'response'],
variable = self.variable(requestV1, { retainIds: retainIds }),
item = {
name: requestV1.name || '', // Inline building to avoid additional function call
event: self.event(requestV1)
};
retainIds && (item.id = requestV1.id || util.uid());
// add protocolProfileBehavior property from requestV1 to the item
util.addProtocolProfileBehavior(requestV1, item);
units.forEach(function (unit) {
item[unit] = self[unit](requestV1);
});
variable && variable.length && (item.variable = variable);
return item;
},
/**
* Constructs an array of Items & ItemGroups compatible with the V2 format.
*
* @param {Object} collectionV1 - The v1 collection to derive folder information from.
* @returns {Object[]} - The list of item group definitions.
*/
itemGroups: function (collectionV1) {
var self = this,
items = [],
itemGroupCache = {},
retainEmpty = self.options.retainEmptyValues;
// Read all folder data, and prep it so that we can throw subfolders in the right places
itemGroupCache = _.reduce(collectionV1.folders, function (accumulator, folder) {
if (!folder) { return accumulator; }
var retainIds = self.options.retainIds,
auth = self.auth(folder),
event = self.event(folder),
variable = self.variable(folder, { retainIds: retainIds }),
result = {
name: folder.name,
item: []
};
retainIds && (result.id = folder.id || util.uid());
if (folder.description) { result.description = folder.description; }
else if (retainEmpty) { result.description = null; }
(auth || (auth === null)) && (result.auth = auth);
event && (result.event = event);
variable && variable.length && (result.variable = variable);
util.addProtocolProfileBehavior(folder, result);
accumulator[folder.id] = result;
return accumulator;
}, {});
// Populate each ItemGroup with subfolders
_.forEach(collectionV1.folders, function (folderV1) {
if (!folderV1) { return; }
var itemGroup = itemGroupCache[folderV1.id],
hasSubfolders = folderV1.folders_order && folderV1.folders_order.length,
hasRequests = folderV1.order && folderV1.order.length;
// Add subfolders
hasSubfolders && _.forEach(folderV1.folders_order, function (subFolderId) {
if (!itemGroupCache[subFolderId]) {
// todo: figure out what to do when a collection contains a subfolder ID,
// but the subfolder is not actually there.
return;
}
itemGroupCache[subFolderId][IS_SUBFOLDER] = true;
itemGroup.item.push(itemGroupCache[subFolderId]);
});
// Add items
hasRequests && _.forEach(folderV1.order, function (requestId) {
if (!self.cache[requestId]) {
// todo: what do we do here??
return;
}
itemGroup.item.push(self.singleItem(self.cache[requestId]));
});
});
// This compromises some self-healing, which was originally present, but the performance cost of
// doing self-healing the right way is high, so we directly rely on collectionV1.folders_order
// The self-healing way would be to iterate over itemGroupCache directly, but preserving the right order
// becomes a pain in that case.
_.forEach(_.uniq(collectionV1.folders_order || _.map(collectionV1.folders, 'id')), function (folderId) {
var itemGroup = itemGroupCache[folderId];
itemGroup && !_.get(itemGroup, IS_SUBFOLDER) && items.push(itemGroup);
});
// This is useful later
self.itemGroupCache = itemGroupCache;
return _.compact(items);
},
/**
* Creates a V2 compatible array of items from a V1 Postman Collection
*
* @param {Object} collectionV1 - A Postman Collection object in the V1 format.
* @returns {Object[]} - The list of item groups (folders) in v2 format.
*/
item: function (collectionV1) {
var self = this,
requestsCache = _.keyBy(collectionV1.requests, 'id'),
allRequests = _.map(collectionV1.requests, 'id'),
result;
self.cache = requestsCache;
result = self.itemGroups(collectionV1);
_.forEach(_.intersection(collectionV1.order, allRequests), function (requestId) {
var request = self.singleItem(requestsCache[requestId]);
request && (result.push(request));
});
return result;
}
});
module.exports = {
input: '1.0.0',
output: '2.0.0',
Builders: Builders,
/**
* Converts a single V1 request to a V2 item.
*
* @param {Object} request - The v1 request to convert to v2.
* @param {Object} options - The set of options for v1 -> v2 conversion.
* @param {Function} callback - The function to be invoked when the conversion has completed.
*/
convertSingle: function (request, options, callback) {
var builders = new Builders(options),
converted,
err;
try {
converted = builders.singleItem(_.cloneDeep(request));
}
catch (e) {
err = e;
}
if (callback) {
return callback(err, converted);
}
if (err) {
throw err;
}
return converted;
},
/**
* Converts a single V1 Response to a V2 Response.
*
* @param {Object} response - The v1 response to convert to v2.
* @param {Object} options - The set of options for v1 -> v2 conversion.
* @param {Function} callback - The function to be invoked when the conversion has completed.
*/
convertResponse: function (response, options, callback) {
var builders = new Builders(options),
converted,
err;
try {
converted = builders.singleResponse(_.cloneDeep(response));
}
catch (e) {
err = e;
}
if (callback) {
return callback(err, converted);
}
if (err) {
throw err;
}
return converted;
},
/**
* Converts a V1 collection to a V2 collection (performs ID replacement, etc as necessary).
*
* @param {Object} collection - The v1 collection to convert to v2.
* @param {Object} options - The set of options for v1 -> v2 conversion.
* @param {Function} callback - The function to be invoked when the conversion has completed.
*/
convert: function (collection, options, callback) {
collection = _.cloneDeep(collection);
var auth,
event,
variable,
newCollection = {},
units = ['info', 'item'],
builders = new Builders(options),
authOptions = { excludeNoauth: true },
varOpts = options && { fallback: options.env, retainIds: options.retainIds };
try {
units.forEach(function (unit) {
newCollection[unit] = builders[unit](collection);
});
(auth = builders.auth(collection, authOptions)) && (newCollection.auth = auth);
(event = builders.event(collection)) && (newCollection.event = event);
(variable = builders.variable(collection, varOpts)) && (newCollection.variable = variable);
util.addProtocolProfileBehavior(collection, newCollection);
}
catch (e) {
if (callback) {
return callback(e, null);
}
throw e;
}
if (callback) { return callback(null, newCollection); }
return newCollection;
}
};

View File

@@ -0,0 +1,157 @@
/* eslint-disable object-shorthand */
var inherits = require('inherits'),
_ = require('lodash').noConflict(),
url = require('../../url'),
util = require('../../util'),
constants = require('../../constants'),
BaseBuilders = require('./converter-v1-to-v2').Builders,
Builders;
inherits(Builders = function () {
Builders.super_.apply(this, arguments);
}, BaseBuilders);
_.assign(Builders.prototype, {
/**
* Derives v2.1.0 collection info from a v1.0.0 collection object.
*
* @param {Object} collectionV1 - The v1.0.0 collection object to be converted to v2.1.0.
* @return {Object} - The compiled v2.x collection info manifest.
*/
info: function (collectionV1) {
var info = Builders.super_.prototype.info.call(this, collectionV1);
info.schema = constants.SCHEMA_V2_1_0_URL;
return info;
},
/**
* Converts collection request urls from v1.0.0 to v2.1.0
*
* @param {Object} requestV1 - The v1.0.0 request url to be converted to v2.1.0.
* @return {Object} - The objectified v2.1.0 compliant URL.
*/
url: function (requestV1) {
var v21Url = Builders.super_.prototype.url.call(this, requestV1);
return _.isString(v21Url) ? url.parse(v21Url) : v21Url;
},
/**
* A number of auth parameter names have changed from V1 to V2. This function calls the appropriate
* mapper function, and creates the V2 auth parameter object.
*
* @param {Object} entityV1 - A Collection V1 compliant request instance.
* @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 {{type: *}} - The v2.1.0 compliant request object
*/
auth: function (entityV1, options) {
// if the current auth manifest is at a parent level, no further transformation is needed.
// @todo: Possible dead code, prune when confirmed
if (util.notLegacy(entityV1, 'auth') && entityV1.currentHelper) {
return util.sanitizeAuthArray(entityV1, options);
}
var auth = Builders.super_.prototype.auth.call(this, entityV1, options);
return util.authMapToArray({ auth: auth }, options);
}
});
module.exports = {
input: '1.0.0',
output: '2.1.0',
Builders: Builders,
/**
* Converts a single V1 request to a v2.1.0 item.
*
* @param {Object} request - The v1.0.0 request to be converted to a v2.1.0 format.
* @param {Object} options - The set of options for the current conversion sequence.
* @param {?Function} callback - The function invoked to mark the end of the current conversion process.
* @returns {*}
*/
convertSingle: function (request, options, callback) {
var err,
converted,
builders = new Builders(options);
try { converted = builders.singleItem(_.cloneDeep(request)); }
catch (e) { err = e; }
if (callback) { return callback(err, converted); }
if (err) { throw err; }
return converted;
},
/**
* Converts a single V1 Response to a v2.1.0 Response.
*
* @param {Object} response - The V1 compliant response to convert to a v2.1.0 format.
* @param {Object} options - The set of options for the current conversion process.
* @param {?Function} callback - The function invoked to mark the completion of the response conversion.
* @returns {*}
*/
convertResponse: function (response, options, callback) {
var err,
converted,
builders = new Builders(options);
try { converted = builders.singleResponse(_.cloneDeep(response)); }
catch (e) { err = e; }
if (callback) { return callback(err, converted); }
if (err) { throw err; }
return converted;
},
/**
* Converts a V1 collection to a V2 collection (performs ID replacement, etc as necessary).
*
* @param {Object} collection - The V1 collection instance to convert to a v2.1.0 format.
* @param {Object} options - The set of options for the current conversion sequence.
* @param {?Function} callback - The function invoked to mark the completion of the conversion process/
* @returns {*}
*/
convert: function (collection, options, callback) {
collection = _.cloneDeep(collection);
var auth,
event,
variable,
newCollection = {},
units = ['info', 'item'],
builders = new Builders(options),
authOptions = { excludeNoauth: true },
varOpts = options && { fallback: options.env, retainIds: options.retainIds };
try {
units.forEach(function (unit) {
newCollection[unit] = builders[unit](collection);
});
(auth = builders.auth(collection, authOptions)) && (newCollection.auth = auth);
(event = builders.event(collection)) && (newCollection.event = event);
(variable = builders.variable(collection, varOpts)) && (newCollection.variable = variable);
util.addProtocolProfileBehavior(collection, newCollection);
}
catch (e) {
if (callback) { return callback(e); }
throw e;
}
if (callback) { return callback(null, newCollection); }
return newCollection;
}
};

View File

@@ -0,0 +1,797 @@
/* 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;
}
};

View File

@@ -0,0 +1,129 @@
/* eslint-disable object-shorthand */
var _ = require('lodash').noConflict(),
util = require('../../util'),
inherits = require('inherits'),
v2Common = require('../../common/v2'),
BaseBuilders = require('../v2.0.0/converter-v2-to-v1').Builders,
Builders;
inherits(Builders = function () {
Builders.super_.apply(this, arguments);
}, BaseBuilders);
_.assign(Builders.prototype, {
/**
* Converts arrays of v2.1 style auth params to their v1.0.0 equivalent objects.
*
* @param {Object} entity - A v2.1 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 null.
* @returns {Object} - A v1 compliant set of auth helper attributes.
*/
auth: function (entity, options) {
return util.sanitizeAuthArray(entity, options);
}
});
module.exports = {
input: '2.1.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 err,
converted,
clone = _.cloneDeep(request),
builders = new Builders(options);
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 },
units = ['order', 'folders_order', 'folders', 'requests'],
varOpts = options && { fallback: options.env, retainIds: options.retainIds },
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;
}
};