diff --git a/lib/index.js b/lib/index.js index d57eb7503..9e300eee6 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,5 +1,9 @@ module.exports = { Runner: require('./runner'), Requester: require('./requester').Requester, - version: require('./version') + Cursor: require('./runner/cursor'), + version: require('./version'), + utils: require('./runner/util'), + Instruction: require('./runner/instruction'), + backpack: require('./backpack') }; diff --git a/lib/runner/extensions/event.command.js b/lib/runner/extensions/event.command.js index 5ccd36279..c198c5b52 100644 --- a/lib/runner/extensions/event.command.js +++ b/lib/runner/extensions/event.command.js @@ -582,28 +582,25 @@ module.exports = { result && payload.context && payload.context.response && (result.response = new sdk.Response(payload.context.response)); - // persist the pm.variables for the next script - result && result._variables && - (payload.context._variables = new sdk.VariableScope(result._variables)); - - // persist the pm.variables for the next request - result && result._variables && - (this.state._variables = new sdk.VariableScope(result._variables)); - - // persist the mutated request in payload context, - // @note this will be used for the next prerequest script or - // upcoming commands(request, httprequest). - result && result.request && (payload.context.request = result.request); - - // now that this script is done executing, we trigger the event and move to the next script - this.triggers.script(err || null, scriptCursor, result, script, event, item); - - // move to next script and pass on the results for accumulation - done(((stopOnScriptError || abortOnError || stopOnFailure) && err) ? err : null, _.assign({ - event, - script, - result - }, err && { error: err })); // we use assign here to avoid needless error property + // by persisting in an instruction, we allow extensions to define + // how exactly they want to do it. + this.immediate('persist', { + coords: cursor, + result: result, + payload: payload + }).done(function () { + // now that this script is done executing, + // we trigger the event and move to the next script + this.triggers.script(err || null, scriptCursor, result, script, event, item); + + // move to next script and pass on the results for accumulation + done(((stopOnScriptError || abortOnError || stopOnFailure) && err) ? err : null, + _.assign({ + event, + script, + result + }, err && { error: err })); // we use assign here to avoid needless error property + }.bind(this)); }.bind(this)); }; diff --git a/lib/runner/extensions/persist.command.js b/lib/runner/extensions/persist.command.js new file mode 100644 index 000000000..13dad4f03 --- /dev/null +++ b/lib/runner/extensions/persist.command.js @@ -0,0 +1,22 @@ +var sdk = require('postman-collection'); + +module.exports = { + process: { + persist ({ result, payload }, next) { + console.log('persist command'); + // persist the pm.variables for the next script + result && result._variables && + (payload.context._variables = new sdk.VariableScope(result._variables)); + + // persist the pm.variables for the next request + result && result._variables && + (this.state._variables = new sdk.VariableScope(result._variables)); + + // persist the mutated request in payload context, + // @note this will be used for the next prerequest script or + // upcoming commands(request, httprequest). + result && result.request && (payload.context.request = result.request); + next(); + } + } +}; diff --git a/lib/runner/extensions/waterfall.command.js b/lib/runner/extensions/waterfall.command.js index c75f1831d..7beaa5068 100644 --- a/lib/runner/extensions/waterfall.command.js +++ b/lib/runner/extensions/waterfall.command.js @@ -1,65 +1,7 @@ var _ = require('lodash'), Cursor = require('../cursor'), - VariableScope = require('postman-collection').VariableScope, - { prepareVaultVariableScope } = require('../util'), - - prepareLookupHash, - extractSNR, - getIterationData; - -/** - * Returns a hash of IDs and Names of items in an array - * - * @param {Array} items - - * @returns {Object} - */ -prepareLookupHash = function (items) { - var hash = { - ids: {}, - names: {}, - obj: {} - }; - - _.forEach(items, function (item, index) { - if (item) { - item.id && (hash.ids[item.id] = index); - item.name && (hash.names[item.name] = index); - } - }); - - return hash; -}; - -extractSNR = function (executions, previous) { - var snr = previous || {}; - - _.isArray(executions) && executions.forEach(function (execution) { - _.has(_.get(execution, 'result.return'), 'nextRequest') && ( - (snr.defined = true), - (snr.value = execution.result.return.nextRequest) - ); - }); - - return snr; -}; - -/** - * Returns the data for the given iteration - * - * @function getIterationData - * @param {Array} data - The data array containing all iterations' data - * @param {Number} iteration - The iteration to get data for - * @return {Any} - The data for the iteration - */ -getIterationData = function (data, iteration) { - // if iteration has a corresponding data element use that - if (iteration < data.length) { - return data[iteration]; - } - - // otherwise use the last data element - return data[data.length - 1]; -}; + { prepareVaultVariableScope, prepareVariablesScope, prepareLookupHash, + extractSNR, getIterationData } = require('../util'); /** * Adds options @@ -71,19 +13,8 @@ module.exports = { init: function (done) { var state = this.state; - // ensure that the environment, globals and collectionVariables are in VariableScope instance format - state.environment = VariableScope.isVariableScope(state.environment) ? state.environment : - new VariableScope(state.environment); - state.globals = VariableScope.isVariableScope(state.globals) ? state.globals : - new VariableScope(state.globals); - state.vaultSecrets = VariableScope.isVariableScope(state.vaultSecrets) ? state.vaultSecrets : - new VariableScope(state.vaultSecrets); - state.collectionVariables = VariableScope.isVariableScope(state.collectionVariables) ? - state.collectionVariables : new VariableScope(state.collectionVariables); - state._variables = VariableScope.isVariableScope(state.localVariables) ? - state.localVariables : new VariableScope(state.localVariables); - - // prepare the vault variable scope + // prepare the vault variable scope and other variables + prepareVariablesScope(state); prepareVaultVariableScope(state.vaultSecrets); // ensure that the items and iteration data set is in place diff --git a/lib/runner/index.js b/lib/runner/index.js index 09459f366..0f89c9045 100644 --- a/lib/runner/index.js +++ b/lib/runner/index.js @@ -11,6 +11,13 @@ var _ = require('lodash'), script: Infinity }; + +function applyRunExtensions (extensions) { + if (extensions && !_.isEmpty(extensions)) { + Run.applyExtensions(extensions); + } +} + /** * @typedef {runCallback} * @property {Function} [done] @@ -25,6 +32,7 @@ var _ = require('lodash'), */ Runner = function PostmanCollectionRunner (options) { // eslint-disable-line func-name-matching this.options = _.assign({}, options); + applyRunExtensions(this.options.extensions); }; _.assign(Runner.prototype, { @@ -82,6 +90,7 @@ _.assign(Runner.prototype, { var self = this, runOptions = this.prepareRunConfig(options); + applyRunExtensions(this.options.extensions); callback = backpack.normalise(callback); !_.isObject(options) && (options = {}); diff --git a/lib/runner/run.js b/lib/runner/run.js index f867250b0..f8f945626 100644 --- a/lib/runner/run.js +++ b/lib/runner/run.js @@ -94,6 +94,7 @@ _.assign(Run.prototype, { * @param {Function|Object} callback - */ start (callback) { + this.commands = Run.commands; // @todo add `when` parameter to backpack.normalise callback = backpack.normalise(callback, Object.keys(Run.triggers)); @@ -111,7 +112,7 @@ _.assign(Run.prototype, { } // invoke all the initialiser functions one after another and if it has any error then abort with callback. - async.series(_.map(Run.initialisers, function (initializer) { + async.series(_.map(Array.from(Run.initialisers.values()), function (initializer) { return initializer.bind(this); }.bind(this)), function (err) { if (err) { return callback(err); } @@ -195,7 +196,7 @@ _.assign(Run, { * * @type {Array} */ - initialisers: [] + initialisers: new Map() }); // commands are loaded by flattening the modules in the `./commands` directory @@ -206,8 +207,9 @@ Run.commands = _.transform({ 'request.command': require('./extensions/request.command'), 'waterfall.command': require('./extensions/waterfall.command'), 'item.command': require('./extensions/item.command'), - 'delay.command': require('./extensions/delay.command') -}, function (all, extension) { + 'delay.command': require('./extensions/delay.command'), + 'persist.command': require('./extensions/persist.command') +}, function (all, extension, extensionName) { // extract the prototype from the command interface if (_.has(extension, 'prototype')) { _.forOwn(extension.prototype, function (value, prop) { @@ -238,7 +240,31 @@ Run.commands = _.transform({ } // add the initialisation functions - _.has(extension, 'init') && _.isFunction(extension.init) && Run.initialisers.push(extension.init); + _.has(extension, 'init') && _.isFunction(extension.init) && Run.initialisers.set(extensionName, extension.init); }); +Run.applyExtensions = function (extensions) { + _.forEach(extensions, (extension, extensionName) => { + if (_.has(extension, 'prototype')) { + _.forOwn(extension.prototype, (value, prop) => { + Run.prototype[prop] = value; + }); + } + if (_.has(extension, 'triggers') && _.isArray(extension.triggers)) { + _.forEach(extension.triggers, function (name) { + name && (Run.triggers[name] = true); + }); + } + if (_.has(extension, 'process')) { + _.forOwn(extension.process, function (command, name) { + if (!_.isFunction(command)) { return; } + Run.commands[name] = command; + }); + } + if (_.has(extension, 'init') && _.isFunction(extension.init)) { + Run.initialisers.set(extensionName, extension.init); + } + }); +}; + module.exports = Run; diff --git a/lib/runner/util.js b/lib/runner/util.js index 3d65fcd8e..a759b22b9 100644 --- a/lib/runner/util.js +++ b/lib/runner/util.js @@ -1,4 +1,5 @@ var { Url, UrlMatchPatternList, VariableList } = require('postman-collection'), + VariableScope = require('postman-collection').VariableScope, sdk = require('postman-collection'), _ = require('lodash'), @@ -238,6 +239,86 @@ module.exports = { scope.__vaultVariableScope = true; }, + /** + * ensure that the environment, globals and collectionVariables are in VariableScope instance format + * @param {*} state application state object. + */ + prepareVariablesScope (state) { + state.environment = VariableScope.isVariableScope(state.environment) ? state.environment : + new VariableScope(state.environment); + state.globals = VariableScope.isVariableScope(state.globals) ? state.globals : + new VariableScope(state.globals); + state.vaultSecrets = VariableScope.isVariableScope(state.vaultSecrets) ? state.vaultSecrets : + new VariableScope(state.vaultSecrets); + state.collectionVariables = VariableScope.isVariableScope(state.collectionVariables) ? + state.collectionVariables : new VariableScope(state.collectionVariables); + state._variables = VariableScope.isVariableScope(state.localVariables) ? + state.localVariables : new VariableScope(state.localVariables); + }, + + /** + * Returns a hash of IDs and Names of items in an array + * + * @param {Array} items - + * @returns {Object} + */ + prepareLookupHash (items) { + var hash = { + ids: {}, + names: {}, + obj: {} + }; + + _.forEach(items, function (item, index) { + if (item) { + item.id && (hash.ids[item.id] = index); + item.name && (hash.names[item.name] = index); + } + }); + + return hash; + }, + + + /** + * Extract set next request from the execution. + * + * @function getIterationData + * @param {Array} executions - The prerequests or the tests of an item's execution. + * @param {Object} previous - If extracting the tests request then prerequest's snr. + * @return {Any} - The Set Next Request + */ + extractSNR (executions, previous) { + var snr = previous || {}; + + _.isArray(executions) && executions.forEach(function (execution) { + _.has(_.get(execution, 'result.return'), 'nextRequest') && ( + (snr.defined = true), + (snr.value = execution.result.return.nextRequest) + ); + }); + + return snr; + }, + + /** + * Returns the data for the given iteration + * + * @function getIterationData + * @param {Array} data - The data array containing all iterations' data + * @param {Number} iteration - The iteration to get data for + * @return {Any} - The data for the iteration + */ + getIterationData (data, iteration) { + // if iteration has a corresponding data element use that + if (iteration < data.length) { + return data[iteration]; + } + + // otherwise use the last data element + return data[data.length - 1]; + }, + /** * Resolve variables in item and auth in context. *