postman-runtime
Version:
Underlying library of executing Postman Collections
178 lines (158 loc) • 8.37 kB
JavaScript
var _ = require('lodash'),
backpack = require('../backpack'),
Run = require('./run'),
extractRunnableItems = require('./extract-runnable-items').extractRunnableItems,
Runner,
defaultTimeouts = {
global: 3 * 60 * 1000, // 3 minutes
request: Infinity,
script: Infinity
};
/**
* @typedef {runCallback}
* @property {Function} [done]
* @property {Function} [error]
* @property {Function} [success]
*/
/**
* @constructor
*
* @param {Object} [options] -
*/
Runner = function PostmanCollectionRunner (options) { // eslint-disable-line func-name-matching
this.options = _.assign({}, options);
};
_.assign(Runner.prototype, {
/**
* Prepares `run` config by combining `runner` config with given run options.
*
* @param {Object} [options] -
* @param {Object} [options.timeout] -
* @param {Object} [options.timeout.global] -
* @param {Object} [options.timeout.request] -
* @param {Object} [options.timeout.script] -
*/
prepareRunConfig (options) {
// combine runner config and make a copy
var runOptions = _.merge(_.omit(options,
['environment', 'globals', 'vaultSecrets', 'data']), this.options.run) || {};
// Ensure we have a default value for max invokable nested requests
!runOptions.maxInvokableNestedRequests && (runOptions.maxInvokableNestedRequests = 10);
// start timeout sanitization
!runOptions.timeout && (runOptions.timeout = {});
_.mergeWith(runOptions.timeout, defaultTimeouts, function (userTimeout, defaultTimeout) {
// non numbers, Infinity and missing values are set to default
if (!_.isFinite(userTimeout)) { return defaultTimeout; }
// 0 and negative numbers are set to Infinity, which only leaves positive numbers
return userTimeout > 0 ? userTimeout : Infinity;
});
return runOptions;
},
/**
* Runs a collection or a folder.
*
* @param {Collection} collection -
* @param {Object} [options] -
* @param {Array.<Item>} options.items -
* @param {Array.<Object>} [options.data] -
* @param {Object} [options.globals] -
* @param {Object} [options.environment] -
* @param {Object} [options.vaultSecrets] - Vault Secrets
* @param {Object} [options.nestedRequest] - State and options used for nested request set by parent request
* @param {Number} [options.nestedRequest.rootCursor] - The cursor of the root request that spun up this
* nested request runner. This is recursively passed down to keep track of which execution started the chain
* and modify cursors for all nested req events for reporters built on top of postman-runtime.
* @param {Number} [options.nestedRequest.rootItem] - The root item that spawned this nested request.
* Used by vault to get consent for root request and determine whether vault access check was performed even once
* throughout the chain. And by request resolver bridge to receive any stored metadata like name/location of
* the request being resolved
* @param {Number} [options.nestedRequest.hasVaultAccess] - Mutated and set by any nested or parent request
* to indicate whether vault access check has been performed.
* @param {Array} [options.nestedRequest.callStack] - The current stack of nested request item ids
* used to enforce max nested depth. Internally set and used.
* @param {Object} [options.requester] - Options specific to the requester
* @param {Function} [options.script.requestResolver] - Resolver that receives an id from
* pm.execution.runRequest and returns the JSON for the request collection.
* Should return a postman-collection compatible collection JSON with `item` containing the request to run,
* `variable` array containing list of request-specific-collection variables and `event` with scripts to execute.
* @param {Number} [options.maxInvokableNestedRequests] - The maximum nested depth
* that a script can invoke via pm.execution.runRequest
* @param {Number} [options.iterationCount] -
* @param {CertificateList} [options.certificates] -
* @param {ProxyConfigList} [options.proxies] -
* @param {Object} [options.entrypoint] -
* @param {String} [options.entrypoint.execute] ID of the item-group to be run.
* Can be Name if `entrypoint.lookupStrategy` is `idOrName`
* @param {String} [options.entrypoint.lookupStrategy=idOrName] strategy to lookup the entrypoint [idOrName, path]
* @param {Array<String>} [options.entrypoint.path] path to lookup
* @param {Object} [options.run] Run-specific options, such as options related to the host
* @param {Function} [options.secretResolver] - Function({ secrets, url }, callback) that resolves secrets.
* Receives: secrets (array of { scopeName, scope, variable, context }), url (request URL without query).
* Callback is (err, result).
* On fatal error: callback(err) — request execution stops.
* On success: callback(null, result) where result is Array<{ resolvedValue?: string,
* error?: Error, allowedInScript?: boolean }>;
* result[i] corresponds to secrets[i].
* resolvedValue: resolved string (undefined if failed/skipped).
* error: Error when resolution failed for particular secret.
* allowedInScript: if true, value is exposed to scripts via pm.environment/pm.variables;
* if false/undefined, masked from scripts. Runtime applies values.
*
* @param {Function} callback -
*/
run (collection, options, callback) {
var self = this,
runOptions = this.prepareRunConfig(options);
callback = backpack.normalise(callback);
!_.isObject(options) && (options = {});
// @todo make the extract runnables interface better defined and documented
// - give the ownership of error to each strategy lookup functions
// - think about moving these codes into an extension command prior to waterfall
// - the third argument in callback that returns control, is ambiguous and can be removed if error is controlled
// by each lookup function.
// - the interface can be further broken down to have the "flattenNode" action be made common and not be
// required to be coded in each lookup strategy
//
// serialise the items into a linear array based on the lookup strategy provided as input
extractRunnableItems(collection, options.entrypoint, function (err, runnableItems, entrypoint) {
if (err || !runnableItems) {
return callback(err || new Error('Error fetching run items'));
}
// Bail out only if: abortOnError is set and the returned entrypoint is invalid
if (options.abortOnError && !entrypoint) {
// eslint-disable-next-line @stylistic/js/max-len
return callback(new Error(`Unable to find a folder or request: ${_.get(options, 'entrypoint.execute')}`));
}
// ensure data is an array
!_.isArray(options.data) && (options.data = [{}]);
// get iterationCount from data if not set
if (!runOptions.iterationCount) {
runOptions.iterationCount = options.data.length;
}
return callback(null, (new Run({
items: runnableItems,
data: options.data,
environment: options.environment,
globals: _.has(options, 'globals') ? options.globals : self.options.globals,
vaultSecrets: options.vaultSecrets,
// Used for nested request executions
nestedRequest: options.nestedRequest,
// @todo Move to item level to support Item and ItemGroup variables
collectionVariables: collection.variables,
localVariables: options.localVariables,
certificates: options.certificates,
proxies: options.proxies,
secretResolver: options.secretResolver
}, runOptions)));
});
}
});
_.assign(Runner, {
/**
* Expose Run instance for testability
*
* @type {Run}
*/
Run
});
module.exports = Runner;