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:
105
node_modules/postman-runtime/lib/runner/extensions/control.command.js
generated
vendored
Normal file
105
node_modules/postman-runtime/lib/runner/extensions/control.command.js
generated
vendored
Normal file
@@ -0,0 +1,105 @@
|
||||
var _ = require('lodash'),
|
||||
util = require('../util'),
|
||||
backpack = require('../../backpack');
|
||||
|
||||
module.exports = {
|
||||
/**
|
||||
* All the events that this extension triggers
|
||||
* @type {Array}
|
||||
*/
|
||||
triggers: ['pause', 'resume', 'abort'],
|
||||
|
||||
prototype: /** @lends Run.prototype */ {
|
||||
/**
|
||||
* Pause a run
|
||||
*
|
||||
* @param {Function} callback
|
||||
*/
|
||||
pause: function (callback) {
|
||||
callback = backpack.ensure(callback, this);
|
||||
|
||||
if (this.paused) { return callback && callback(new Error('run: already paused')); }
|
||||
|
||||
// schedule the pause command as an interrupt and flag that the run is pausing
|
||||
this.paused = true;
|
||||
this.interrupt('pause', null, callback);
|
||||
},
|
||||
|
||||
/**
|
||||
* Resume a paused a run
|
||||
*
|
||||
* @param {Function} callback
|
||||
*/
|
||||
resume: function (callback) {
|
||||
callback = backpack.ensure(callback, this);
|
||||
|
||||
if (!this.paused) { return callback && callback(new Error('run: not paused')); }
|
||||
|
||||
// set flag that it is no longer paused and fire the stored callback for the command when it was paused
|
||||
this.paused = false;
|
||||
setTimeout(function () {
|
||||
this.__resume();
|
||||
delete this.__resume;
|
||||
this.triggers.resume(null, this.state.cursor.current());
|
||||
}.bind(this), 0);
|
||||
|
||||
callback && callback();
|
||||
},
|
||||
|
||||
/**
|
||||
* Aborts a run
|
||||
*
|
||||
* @param {boolean} [summarise=true]
|
||||
* @param {function} callback
|
||||
*/
|
||||
abort: function (summarise, callback) {
|
||||
if (_.isFunction(summarise) && !callback) {
|
||||
callback = summarise;
|
||||
summarise = true;
|
||||
}
|
||||
|
||||
this.interrupt('abort', {
|
||||
summarise: summarise
|
||||
}, callback);
|
||||
|
||||
_.isFunction(this.__resume) && this.resume();
|
||||
}
|
||||
},
|
||||
|
||||
process: /** @lends Run.commands */ {
|
||||
pause: function (userback, payload, next) {
|
||||
// trigger the secondary callbacks
|
||||
this.triggers.pause(null, this.state.cursor.current());
|
||||
|
||||
// tuck away the command completion callback in the run object so that it can be used during resume
|
||||
this.__resume = next;
|
||||
|
||||
// execute the userback sent as part of the command and do so in a try block to ensure it does not hamper
|
||||
// the process tick
|
||||
var error = util.safeCall(userback, this);
|
||||
|
||||
// if there is an error executing the userback, then and only then raise the error (which stops the run)
|
||||
if (error) {
|
||||
return next(error);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {Function} userback
|
||||
* @param {Object} payload
|
||||
* @param {Boolean} payload.summarise
|
||||
* @param {Function} next
|
||||
*/
|
||||
abort: function (userback, payload, next) {
|
||||
// clear instruction pool and as such there will be nothing next to execute
|
||||
this.pool.clear();
|
||||
this.triggers.abort(null, this.state.cursor.current());
|
||||
|
||||
// execute the userback sent as part of the command and do so in a try block to ensure it does not hamper
|
||||
// the process tick
|
||||
backpack.ensure(userback, this) && userback();
|
||||
|
||||
next(null);
|
||||
}
|
||||
}
|
||||
};
|
62
node_modules/postman-runtime/lib/runner/extensions/delay.command.js
generated
vendored
Normal file
62
node_modules/postman-runtime/lib/runner/extensions/delay.command.js
generated
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
var _ = require('lodash');
|
||||
|
||||
module.exports = {
|
||||
init: function (done) {
|
||||
done();
|
||||
},
|
||||
|
||||
triggers: ['waitStateChange'],
|
||||
|
||||
prototype: {
|
||||
/**
|
||||
* @param {Function} fn - function to execute
|
||||
* @param {Object} options
|
||||
* @param {String} options.source
|
||||
* @param {Number} options.time
|
||||
* @param {Object} options.cursor
|
||||
* @param {Function} next
|
||||
* @private
|
||||
*/
|
||||
queueDelay: function (fn, options, next) {
|
||||
var time = _.isFinite(options.time) ? parseInt(options.time, 10) : 0;
|
||||
|
||||
// if the time is a valid and finite time, we queue the delay command
|
||||
if (time > 0) {
|
||||
this.queue('delay', {
|
||||
cursor: options.cursor,
|
||||
source: options.source,
|
||||
time: time
|
||||
}).done(fn);
|
||||
}
|
||||
// otherwise, we do not delay and simply execute the function that was supposed to be called post delay
|
||||
else {
|
||||
fn();
|
||||
}
|
||||
|
||||
next();
|
||||
}
|
||||
},
|
||||
|
||||
process: {
|
||||
/**
|
||||
* @param {Object} payload
|
||||
* @param {Number} payload.time
|
||||
* @param {Object} payload.cursor
|
||||
* @param {String} payload.source
|
||||
* @param {Function} next
|
||||
*/
|
||||
delay: function (payload, next) {
|
||||
var cursor = payload.cursor || this.state.cursor.current();
|
||||
|
||||
this.waiting = true; // set flag
|
||||
// trigger the waiting stae change event
|
||||
this.triggers.waitStateChange(null, cursor, true, payload.time, payload.source);
|
||||
|
||||
setTimeout((function () {
|
||||
this.waiting = false; // unset flag
|
||||
this.triggers.waitStateChange(null, cursor, false, payload.time, payload.source);
|
||||
next();
|
||||
}).bind(this), payload.time || 0);
|
||||
}
|
||||
}
|
||||
};
|
530
node_modules/postman-runtime/lib/runner/extensions/event.command.js
generated
vendored
Normal file
530
node_modules/postman-runtime/lib/runner/extensions/event.command.js
generated
vendored
Normal file
@@ -0,0 +1,530 @@
|
||||
var _ = require('lodash'),
|
||||
uuid = require('uuid'),
|
||||
async = require('async'),
|
||||
|
||||
util = require('../util'),
|
||||
sdk = require('postman-collection'),
|
||||
sandbox = require('postman-sandbox'),
|
||||
serialisedError = require('serialised-error'),
|
||||
ToughCookie = require('tough-cookie').Cookie,
|
||||
|
||||
createItemContext = require('../create-item-context'),
|
||||
|
||||
ASSERTION_FAILURE = 'AssertionFailure',
|
||||
SAFE_CONTEXT_VARIABLES = ['_variables', 'environment', 'globals', 'collectionVariables', 'cookies', 'data',
|
||||
'request', 'response'],
|
||||
|
||||
EXECUTION_REQUEST_EVENT_BASE = 'execution.request.',
|
||||
EXECUTION_RESPONSE_EVENT_BASE = 'execution.response.',
|
||||
EXECUTION_ASSERTION_EVENT_BASE = 'execution.assertion.',
|
||||
EXECUTION_ERROR_EVENT_BASE = 'execution.error.',
|
||||
EXECUTION_COOKIES_EVENT_BASE = 'execution.cookies.',
|
||||
|
||||
COOKIES_EVENT_STORE_ACTION = 'store',
|
||||
COOKIE_STORE_PUT_METHOD = 'putCookie',
|
||||
COOKIE_STORE_UPDATE_METHOD = 'updateCookie',
|
||||
|
||||
FILE = 'file',
|
||||
|
||||
REQUEST_BODY_MODE_FILE = 'file',
|
||||
REQUEST_BODY_MODE_FORMDATA = 'formdata',
|
||||
|
||||
getCookieDomain, // fn
|
||||
postProcessContext, // fn
|
||||
sanitizeFiles; // fn
|
||||
|
||||
postProcessContext = function (execution, failures) { // function determines whether the event needs to abort
|
||||
var error;
|
||||
|
||||
if (failures && failures.length) {
|
||||
error = new Error(failures.join(', '));
|
||||
error.name = ASSERTION_FAILURE;
|
||||
}
|
||||
|
||||
return error ? serialisedError(error, true) : undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes files in Request body if any.
|
||||
*
|
||||
* @private
|
||||
*
|
||||
* @param {Request~definition} request Request JSON representation to be sanitized
|
||||
* @param {Function} callback function invoked with error, request and sanitisedFiles.
|
||||
* sanitisedFiles is the list of files removed from request.
|
||||
*
|
||||
* @note this function mutates the request
|
||||
* @todo remove files path from request.certificate
|
||||
*/
|
||||
sanitizeFiles = function (request, callback) {
|
||||
if (!request) {
|
||||
return callback(new Error('Could not complete pm.sendRequest. Request is empty.'));
|
||||
}
|
||||
|
||||
var sanitisedFiles = [];
|
||||
|
||||
// do nothing if request body is empty
|
||||
if (!request.body) {
|
||||
// send request as such
|
||||
return callback(null, request, sanitisedFiles);
|
||||
}
|
||||
|
||||
// in case of request body mode is file, we strip it out
|
||||
if (request.body.mode === REQUEST_BODY_MODE_FILE) {
|
||||
sanitisedFiles.push(_.get(request, 'body.file.src'));
|
||||
request.body = null; // mutate the request for body
|
||||
}
|
||||
|
||||
// if body is form-data then we deep dive into the data items and remove the entries that have file data
|
||||
else if (request.body.mode === REQUEST_BODY_MODE_FORMDATA) {
|
||||
// eslint-disable-next-line lodash/prefer-immutable-method
|
||||
_.remove(request.body.formdata, function (param) {
|
||||
// blank param and non-file param is removed
|
||||
if (!param || param.type !== FILE) { return false; }
|
||||
|
||||
// at this point the param needs to be removed
|
||||
sanitisedFiles.push(param.src);
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
return callback(null, request, sanitisedFiles);
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetch domain name from CookieStore event arguments.
|
||||
*
|
||||
* @private
|
||||
* @param {String} fnName - CookieStore method name
|
||||
* @param {Array} args - CookieStore method arguments
|
||||
* @returns {String|Undefined} - Domain name
|
||||
*/
|
||||
getCookieDomain = function (fnName, args) {
|
||||
if (!(fnName && args)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var domain;
|
||||
|
||||
switch (fnName) {
|
||||
case 'findCookie':
|
||||
case 'findCookies':
|
||||
case 'removeCookie':
|
||||
case 'removeCookies':
|
||||
domain = args[0];
|
||||
break;
|
||||
case 'putCookie':
|
||||
case 'updateCookie':
|
||||
domain = args[0] && args[0].domain;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
return domain;
|
||||
};
|
||||
|
||||
/**
|
||||
* Script execution extension of the runner.
|
||||
* This module exposes processors for executing scripts before and after requests. Essentially, the processors are
|
||||
* itself not aware of other processors and simply allow running of a script and then queue a procesor as defined in
|
||||
* payload.
|
||||
*
|
||||
* Adds options
|
||||
* - stopOnScriptError:Boolean [false]
|
||||
* - host:Object [undefined]
|
||||
*/
|
||||
module.exports = {
|
||||
init: function (done) {
|
||||
var run = this;
|
||||
|
||||
// if this run object already has a host, we do not need to create one.
|
||||
if (run.host) {
|
||||
return done();
|
||||
}
|
||||
|
||||
// @todo - remove this when chrome app and electron host creation is offloaded to runner
|
||||
// @todo - can this be removed now in runtime v4?
|
||||
if (run.options.host && run.options.host.external === true) {
|
||||
run.host = run.options.host.instance;
|
||||
|
||||
return done();
|
||||
}
|
||||
|
||||
sandbox.createContext(_.merge({
|
||||
timeout: _(run.options.timeout).pick(['script', 'global']).values().min()
|
||||
// debug: true
|
||||
}, run.options.host), function (err, context) {
|
||||
if (err) { return done(err); }
|
||||
// store the host in run object for future use and move on
|
||||
run.host = context;
|
||||
|
||||
context.on('console', function () {
|
||||
run.triggers.console.apply(run.triggers, arguments);
|
||||
});
|
||||
|
||||
context.on('error', function () {
|
||||
run.triggers.error.apply(run.triggers, arguments);
|
||||
});
|
||||
|
||||
context.on('execution.error', function () {
|
||||
run.triggers.exception.apply(run.triggers, arguments);
|
||||
});
|
||||
|
||||
context.on('execution.assertion', function () {
|
||||
run.triggers.assertion.apply(run.triggers, arguments);
|
||||
});
|
||||
|
||||
done();
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* This lists the name of the events that the script processors are likely to trigger
|
||||
*
|
||||
* @type {Array}
|
||||
*/
|
||||
triggers: ['beforeScript', 'script', 'assertion', 'exception', 'console'],
|
||||
|
||||
process: {
|
||||
/**
|
||||
* This processors job is to do the following:
|
||||
* - trigger event by its name
|
||||
* - execute all scripts that the event listens to and return execution results
|
||||
*
|
||||
* @param {Object} payload
|
||||
* @param {String} payload.name
|
||||
* @param {Item} payload.item
|
||||
* @param {Object} [payload.context]
|
||||
* @param {Cursor} [payload.coords]
|
||||
* @param {Number} [payload.scriptTimeout] - The millisecond timeout for the current running script.
|
||||
* @param {Array.<String>} [payload.trackContext]
|
||||
* @param {Boolean} [payload.stopOnScriptError] - if set to true, then a synchronous error encountered during
|
||||
* execution of a script will stop executing any further scripts
|
||||
* @param {Boolean} [payload.abortOnFailure]
|
||||
* @param {Boolean} [payload.stopOnFailure]
|
||||
* @param {Function} next
|
||||
*
|
||||
* @note - in order to raise trigger for the entire event, ensure your extension has registered the triggers
|
||||
*/
|
||||
event: function (payload, next) {
|
||||
var item = payload.item,
|
||||
eventName = payload.name,
|
||||
cursor = payload.coords,
|
||||
// the payload can have a list of variables to track from the context post execution, ensure that
|
||||
// those are accurately set
|
||||
track = _.isArray(payload.trackContext) && _.isObject(payload.context) &&
|
||||
// ensure that only those variables that are defined in the context are synced
|
||||
payload.trackContext.filter(function (variable) {
|
||||
return _.isObject(payload.context[variable]);
|
||||
}),
|
||||
stopOnScriptError = (_.has(payload, 'stopOnScriptError') ? payload.stopOnScriptError :
|
||||
this.options.stopOnScriptError),
|
||||
abortOnError = (_.has(payload, 'abortOnError') ? payload.abortOnError : this.options.abortOnError),
|
||||
|
||||
// @todo: find a better home for this option processing
|
||||
abortOnFailure = payload.abortOnFailure,
|
||||
stopOnFailure = payload.stopOnFailure,
|
||||
|
||||
events;
|
||||
|
||||
// @todo: find a better place to code this so that event is not aware of such options
|
||||
if (abortOnFailure) {
|
||||
abortOnError = true;
|
||||
}
|
||||
|
||||
// validate the payload
|
||||
if (!eventName) {
|
||||
return next(new Error('runner.extension~events: event payload is missing the event name.'));
|
||||
}
|
||||
if (!item) {
|
||||
return next(new Error('runner.extension~events: event payload is missing the triggered item.'));
|
||||
}
|
||||
|
||||
// get the list of events to be executed
|
||||
// includes events in parent as well
|
||||
events = item.events.listeners(eventName, {excludeDisabled: true});
|
||||
|
||||
// call the "before" event trigger by its event name.
|
||||
// at this point, the one who queued this event, must ensure that the trigger for it is defined in its
|
||||
// 'trigger' interface
|
||||
this.triggers[_.camelCase('before-' + eventName)](null, cursor, events, item);
|
||||
|
||||
// with all the event listeners in place, we now iterate on them and execute its scripts. post execution,
|
||||
// we accumulate the results in order to be passed on to the event callback trigger.
|
||||
async.mapSeries(events, function (event, next) {
|
||||
// in case the event has no script we bail out early
|
||||
if (!event.script) {
|
||||
return next(null, {event: event});
|
||||
}
|
||||
|
||||
// get access to the script from the event.
|
||||
var script = event.script,
|
||||
executionId = uuid(),
|
||||
assertionFailed = [],
|
||||
asyncScriptError,
|
||||
|
||||
// create copy of cursor so we don't leak script ids outside `event.command`
|
||||
// and across scripts
|
||||
scriptCursor = _.clone(cursor);
|
||||
|
||||
// store the execution id in script
|
||||
script._lastExecutionId = executionId; // please don't use it anywhere else!
|
||||
|
||||
// if we can find an id on script or event we add them to the cursor
|
||||
// so logs and errors can be traced back to the script they came from
|
||||
event.id && (scriptCursor.eventId = event.id);
|
||||
event.script.id && (scriptCursor.scriptId = event.script.id);
|
||||
|
||||
// trigger the "beforeScript" callback
|
||||
this.triggers.beforeScript(null, scriptCursor, script, event, item);
|
||||
|
||||
// add event listener to trap all assertion events, but only if needed. to avoid needlessly accumulate
|
||||
// stuff in memory.
|
||||
(abortOnFailure || stopOnFailure) &&
|
||||
this.host.on(EXECUTION_ASSERTION_EVENT_BASE + executionId, function (scriptCursor, assertions) {
|
||||
_.forEach(assertions, function (assertion) {
|
||||
assertion && !assertion.passed && assertionFailed.push(assertion.name);
|
||||
});
|
||||
});
|
||||
|
||||
// To store error event, but only if needed. Because error in callback of host.execute()
|
||||
// don't show execution errors for async scripts
|
||||
(abortOnError || stopOnScriptError) &&
|
||||
// only store first async error in case of multiple errors
|
||||
this.host.once(EXECUTION_ERROR_EVENT_BASE + executionId, function (scriptCursor, error) {
|
||||
if (error && !(error instanceof Error)) {
|
||||
error = new Error(error.message || error);
|
||||
}
|
||||
|
||||
asyncScriptError = error;
|
||||
|
||||
// @todo: Figure out a way to abort the script execution here as soon as we get an error.
|
||||
// We can send `execution.abort.` event to sandbox for this, but currently it silently
|
||||
// terminates the script execution without triggering the callback.
|
||||
});
|
||||
|
||||
this.host.on(EXECUTION_COOKIES_EVENT_BASE + executionId,
|
||||
function (eventId, action, fnName, args) {
|
||||
// only store action is supported, might need to support
|
||||
// more cookie actions in next 2 years ¯\_(ツ)_/¯
|
||||
if (action !== COOKIES_EVENT_STORE_ACTION) { return; }
|
||||
|
||||
var self = this,
|
||||
dispatchEvent = EXECUTION_COOKIES_EVENT_BASE + executionId,
|
||||
cookieJar = _.get(self, 'requester.options.cookieJar'),
|
||||
cookieStore = cookieJar && cookieJar.store,
|
||||
cookieDomain;
|
||||
|
||||
if (!cookieStore) {
|
||||
return self.host.dispatch(dispatchEvent, eventId, 'CookieStore: no store found');
|
||||
}
|
||||
|
||||
if (typeof cookieStore[fnName] !== 'function') {
|
||||
return self.host.dispatch(dispatchEvent, eventId,
|
||||
`CookieStore: invalid method name '${fnName}'`);
|
||||
}
|
||||
|
||||
!Array.isArray(args) && (args = []);
|
||||
|
||||
// set expected args length to make sure callback is always called
|
||||
args.length = cookieStore[fnName].length - 1;
|
||||
|
||||
// there's no way cookie store can identify the difference
|
||||
// between regular and programmatic access. So, for now
|
||||
// we check for programmatic access using the cookieJar
|
||||
// helper method and emit the default empty value for that
|
||||
// method.
|
||||
// @note we don't emit access denied error here because
|
||||
// that might blocks users use-case while accessing
|
||||
// cookies for a sub-domain.
|
||||
cookieDomain = getCookieDomain(fnName, args);
|
||||
if (cookieJar && typeof cookieJar.allowProgrammaticAccess === 'function' &&
|
||||
!cookieJar.allowProgrammaticAccess(cookieDomain)) {
|
||||
return self.host.dispatch(dispatchEvent, eventId,
|
||||
`CookieStore: programmatic access to "${cookieDomain}" is denied`);
|
||||
}
|
||||
|
||||
// serialize cookie object
|
||||
if (fnName === COOKIE_STORE_PUT_METHOD && args[0]) {
|
||||
args[0] = ToughCookie.fromJSON(args[0]);
|
||||
}
|
||||
|
||||
if (fnName === COOKIE_STORE_UPDATE_METHOD && args[0] && args[1]) {
|
||||
args[0] = ToughCookie.fromJSON(args[0]);
|
||||
args[1] = ToughCookie.fromJSON(args[1]);
|
||||
}
|
||||
|
||||
// add store method's callback argument
|
||||
args.push(function (err, res) {
|
||||
// serialize error message
|
||||
if (err && err instanceof Error) {
|
||||
err = err.message || String(err);
|
||||
}
|
||||
|
||||
self.host.dispatch(dispatchEvent, eventId, err, res);
|
||||
});
|
||||
|
||||
try {
|
||||
cookieStore[fnName].apply(cookieStore, args);
|
||||
}
|
||||
catch (error) {
|
||||
self.host.dispatch(dispatchEvent, eventId,
|
||||
`runtime~CookieStore: error executing "${fnName}"`);
|
||||
}
|
||||
}.bind(this));
|
||||
|
||||
this.host.on(EXECUTION_REQUEST_EVENT_BASE + executionId,
|
||||
function (scriptCursor, id, requestId, request) {
|
||||
// remove files in request body if any
|
||||
sanitizeFiles(request, function (err, request, sanitisedFiles) {
|
||||
if (err) {
|
||||
return this.host.dispatch(EXECUTION_RESPONSE_EVENT_BASE + id, requestId, err);
|
||||
}
|
||||
|
||||
var nextPayload;
|
||||
|
||||
// if request is sanitized send a warning
|
||||
if (!_.isEmpty(sanitisedFiles)) {
|
||||
this.triggers.console(scriptCursor, 'warn',
|
||||
'uploading files from scripts is not allowed');
|
||||
}
|
||||
|
||||
nextPayload = {
|
||||
item: new sdk.Item({request: request}),
|
||||
coords: scriptCursor,
|
||||
// @todo - get script type from the sandbox
|
||||
source: 'script',
|
||||
// abortOnError makes sure request command bubbles errors
|
||||
// so we can pass it on to the callback
|
||||
abortOnError: true
|
||||
};
|
||||
|
||||
// create context for executing this request
|
||||
nextPayload.context = createItemContext(nextPayload);
|
||||
|
||||
this.immediate('httprequest', nextPayload).done(function (result) {
|
||||
this.host.dispatch(
|
||||
EXECUTION_RESPONSE_EVENT_BASE + id,
|
||||
requestId,
|
||||
null,
|
||||
result && result.response,
|
||||
|
||||
// @todo get cookies from result.history or pass PostmanHistory
|
||||
// instance once it is fully supported
|
||||
result && {cookies: result.cookies}
|
||||
);
|
||||
}).catch(function (err) {
|
||||
this.host.dispatch(EXECUTION_RESPONSE_EVENT_BASE + id, requestId, err);
|
||||
});
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
|
||||
// finally execute the script
|
||||
this.host.execute(event, {
|
||||
id: executionId,
|
||||
// debug: true,
|
||||
timeout: payload.scriptTimeout, // @todo: Expose this as a property in Collection SDK's Script
|
||||
cursor: scriptCursor,
|
||||
context: _.pick(payload.context, SAFE_CONTEXT_VARIABLES),
|
||||
serializeLogs: _.get(this, 'options.script.serializeLogs'),
|
||||
|
||||
// legacy options
|
||||
legacy: {
|
||||
_itemId: item.id,
|
||||
_itemName: item.name
|
||||
}
|
||||
}, function (err, result) {
|
||||
this.host.removeAllListeners(EXECUTION_REQUEST_EVENT_BASE + executionId);
|
||||
this.host.removeAllListeners(EXECUTION_ASSERTION_EVENT_BASE + executionId);
|
||||
this.host.removeAllListeners(EXECUTION_RESPONSE_EVENT_BASE + executionId);
|
||||
this.host.removeAllListeners(EXECUTION_COOKIES_EVENT_BASE + executionId);
|
||||
this.host.removeAllListeners(EXECUTION_ERROR_EVENT_BASE + executionId);
|
||||
|
||||
// Handle async errors as well.
|
||||
// If there was an error running the script itself, that takes precedence
|
||||
if (!err && asyncScriptError) {
|
||||
err = asyncScriptError;
|
||||
}
|
||||
|
||||
// electron IPC does not bubble errors to the browser process, so we serialize it here.
|
||||
err && (err = serialisedError(err, true));
|
||||
|
||||
// if it is defined that certain variables are to be synced back to result, we do the same
|
||||
track && result && track.forEach(function (variable) {
|
||||
if (!(_.isObject(result[variable]) && payload.context[variable])) { return; }
|
||||
|
||||
var contextVariable = payload.context[variable],
|
||||
mutations = result[variable].mutations;
|
||||
|
||||
// bail out if there are no mutations
|
||||
if (!mutations) {
|
||||
return;
|
||||
}
|
||||
|
||||
// ensure that variable scope is treated accordingly
|
||||
if (_.isFunction(contextVariable.applyMutation)) {
|
||||
mutations = new sdk.MutationTracker(result[variable].mutations);
|
||||
|
||||
mutations.applyOn(contextVariable);
|
||||
}
|
||||
|
||||
// @todo: unify the non variable scope flows and consume diff always
|
||||
// and drop sending the full variable scope from sandbox
|
||||
else {
|
||||
util.syncObject(contextVariable, result[variable]);
|
||||
}
|
||||
});
|
||||
|
||||
// Get the failures. If there was an error running the script itself, that takes precedence
|
||||
if (!err && (abortOnFailure || stopOnFailure)) {
|
||||
err = postProcessContext(result, assertionFailed); // also use async assertions
|
||||
}
|
||||
|
||||
// Ensure that we have SDK instances, not serialized plain objects.
|
||||
// @todo - should this be handled by the sandbox?
|
||||
result && result._variables && (result._variables = new sdk.VariableScope(result._variables));
|
||||
result && result.environment && (result.environment = new sdk.VariableScope(result.environment));
|
||||
result && result.globals && (result.globals = new sdk.VariableScope(result.globals));
|
||||
result && result.collectionVariables &&
|
||||
(result.collectionVariables = new sdk.VariableScope(result.collectionVariables));
|
||||
result && result.request && (result.request = new sdk.Request(result.request));
|
||||
|
||||
// @note Since postman-sandbox@3.5.2, response object is not included in the execution result.
|
||||
// Refer: https://github.com/postmanlabs/postman-sandbox/pull/512
|
||||
// Adding back here to avoid breaking change in `script` callback.
|
||||
// @todo revisit script callback args in runtime v8.
|
||||
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
|
||||
next(((stopOnScriptError || abortOnError || stopOnFailure) && err) ? err : null, _.assign({
|
||||
event: event,
|
||||
script: script,
|
||||
result: result
|
||||
}, err && {error: err})); // we use assign here to avoid needless error property
|
||||
}.bind(this));
|
||||
}.bind(this), function (err, results) {
|
||||
// trigger the event completion callback
|
||||
this.triggers[eventName](null, cursor, results, item);
|
||||
next((abortOnError && err) ? err : null, results, err);
|
||||
}.bind(this));
|
||||
}
|
||||
}
|
||||
};
|
211
node_modules/postman-runtime/lib/runner/extensions/http-request.command.js
generated
vendored
Normal file
211
node_modules/postman-runtime/lib/runner/extensions/http-request.command.js
generated
vendored
Normal file
@@ -0,0 +1,211 @@
|
||||
var _ = require('lodash'),
|
||||
async = require('async'),
|
||||
uuid = require('uuid'),
|
||||
|
||||
// These are functions which a request passes through _before_ being sent. They take care of stuff such as
|
||||
// variable resolution, loading of files, etc.
|
||||
prehelpers = require('../request-helpers-presend'),
|
||||
|
||||
// Similarly, these run after the request, and have the power to dictate whether a request should be re-queued
|
||||
posthelpers = require('../request-helpers-postsend'),
|
||||
|
||||
ReplayController = require('../replay-controller'),
|
||||
RequesterPool = require('../../requester').RequesterPool,
|
||||
|
||||
RESPONSE_START_EVENT_BASE = 'response.start.',
|
||||
RESPONSE_END_EVENT_BASE = 'response.end.';
|
||||
|
||||
module.exports = {
|
||||
init: function (done) {
|
||||
// Request timeouts are applied by the requester, so add them to requester options (if any).
|
||||
|
||||
// create a requester pool
|
||||
this.requester = new RequesterPool(this.options, done);
|
||||
},
|
||||
|
||||
// the http trigger is actually directly triggered by the requester
|
||||
// todo - figure out whether we should trigger it from here rather than the requester.
|
||||
triggers: ['beforeRequest', 'request', 'responseStart', 'io'],
|
||||
|
||||
process: {
|
||||
/**
|
||||
* @param {Object} payload
|
||||
* @param {Item} payload.item
|
||||
* @param {Object} payload.data
|
||||
* @param {Object} payload.context
|
||||
* @param {VariableScope} payload.globals
|
||||
* @param {VariableScope} payload.environment
|
||||
* @param {Cursor} payload.coords
|
||||
* @param {Boolean} payload.abortOnError
|
||||
* @param {String} payload.source
|
||||
* @param {Function} next
|
||||
*
|
||||
* @todo validate payload
|
||||
*/
|
||||
httprequest: function (payload, next) {
|
||||
var abortOnError = _.has(payload, 'abortOnError') ? payload.abortOnError : this.options.abortOnError,
|
||||
self = this,
|
||||
context;
|
||||
|
||||
context = payload.context;
|
||||
|
||||
// generates a unique id for each http request
|
||||
// a collection request can have multiple http requests
|
||||
_.set(context, 'coords.httpRequestId', payload.httpRequestId || uuid());
|
||||
|
||||
// Run the helper functions
|
||||
async.applyEachSeries(prehelpers, context, self, function (err) {
|
||||
var xhr,
|
||||
aborted,
|
||||
item = context.item,
|
||||
beforeRequest,
|
||||
afterRequest,
|
||||
safeNext;
|
||||
|
||||
// finish up current command
|
||||
safeNext = function (error, finalPayload) {
|
||||
// the error is passed twice to allow control between aborting the error vs just
|
||||
// bubbling it up
|
||||
return next((error && abortOnError) ? error : null, finalPayload, error);
|
||||
};
|
||||
|
||||
// Helper function which calls the beforeRequest trigger ()
|
||||
beforeRequest = function (err) {
|
||||
self.triggers.beforeRequest(err, context.coords, item.request, payload.item, {
|
||||
httpRequestId: context.coords && context.coords.httpRequestId,
|
||||
abort: function () {
|
||||
!aborted && xhr && xhr.abort();
|
||||
aborted = true;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Helper function to call the afterRequest trigger.
|
||||
afterRequest = function (err, response, request, cookies, history) {
|
||||
self.triggers.request(err, context.coords, response, request, payload.item, cookies, history);
|
||||
};
|
||||
|
||||
// Ensure that this is called.
|
||||
beforeRequest(null);
|
||||
|
||||
if (err) {
|
||||
// Since we encountered an error before even attempting to send the request, we bubble it up
|
||||
// here.
|
||||
afterRequest(err, undefined, item.request);
|
||||
|
||||
return safeNext(
|
||||
err,
|
||||
{request: item.request, coords: context.coords, item: context.originalItem}
|
||||
);
|
||||
}
|
||||
|
||||
if (aborted) {
|
||||
return next(new Error('runtime: request aborted'));
|
||||
}
|
||||
|
||||
self.requester.create({
|
||||
type: 'http',
|
||||
source: payload.source,
|
||||
cursor: context.coords
|
||||
}, function (err, requester) {
|
||||
if (err) { return next(err); } // this should never happen
|
||||
|
||||
var requestId = uuid(),
|
||||
replayOptions;
|
||||
|
||||
// eslint-disable-next-line max-len
|
||||
requester.on(RESPONSE_START_EVENT_BASE + requestId, function (err, response, request, cookies, history) {
|
||||
// we could have also added the response to the set of responses in the cloned item,
|
||||
// but then, we would have to iterate over all of them, which seems unnecessary
|
||||
context.response = response;
|
||||
|
||||
// run the post request helpers, which need to use the response, assigned above
|
||||
async.applyEachSeries(posthelpers, context, self, function (error, options) {
|
||||
if (error) {
|
||||
return;
|
||||
}
|
||||
|
||||
// find the first helper that requested a replay
|
||||
replayOptions = _.find(options, {replay: true});
|
||||
|
||||
// bail out if we know that request will be replayed.
|
||||
if (replayOptions) {
|
||||
return;
|
||||
}
|
||||
|
||||
// bail out if its a pm.sendRequest
|
||||
// @todo find a better way of identifying scripts
|
||||
// @note don't use source='script'. Script requests
|
||||
// can trigger `*.auth` source requests as well.
|
||||
if (context.coords && context.coords.scriptId) {
|
||||
return;
|
||||
}
|
||||
|
||||
// trigger responseStart only for collection request.
|
||||
// if there are replays, this will be triggered for the last request in the replay chain.
|
||||
self.triggers.responseStart(err, context.coords, response, request, payload.item, cookies,
|
||||
history);
|
||||
});
|
||||
});
|
||||
|
||||
requester.on(RESPONSE_END_EVENT_BASE + requestId, self.triggers.io.bind(self.triggers));
|
||||
|
||||
// eslint-disable-next-line max-len
|
||||
xhr = requester.request(requestId, item.request, context.protocolProfileBehavior, function (err, res, req, cookies, history) {
|
||||
err = err || null;
|
||||
|
||||
var nextPayload = {
|
||||
response: res,
|
||||
request: req,
|
||||
item: context.originalItem,
|
||||
cookies: cookies,
|
||||
coords: context.coords,
|
||||
history: history
|
||||
},
|
||||
replayController;
|
||||
|
||||
// trigger the request event.
|
||||
// @note - we give the _original_ item in this trigger, so someone can do reference
|
||||
// checking. Not sure if we should do that or not, but that's how it is.
|
||||
// Don't break it.
|
||||
afterRequest(err, res, req, cookies, history);
|
||||
|
||||
// Dispose off the requester, we don't need it anymore.
|
||||
requester.dispose();
|
||||
|
||||
// do not process replays if there was an error
|
||||
if (err) {
|
||||
return safeNext(err, nextPayload);
|
||||
}
|
||||
|
||||
// request replay logic
|
||||
if (replayOptions) {
|
||||
// prepare for replay
|
||||
replayController = new ReplayController(context.replayState, self);
|
||||
|
||||
// replay controller invokes callback no. 1 when replaying the request
|
||||
// invokes callback no. 2 when replay count has exceeded maximum limit
|
||||
// @note: errors in replayed requests are passed to callback no. 1
|
||||
return replayController.requestReplay(context,
|
||||
context.item,
|
||||
{source: replayOptions.helper},
|
||||
// new payload with response from replay is sent to `next`
|
||||
function (err, payloadFromReplay) { safeNext(err, payloadFromReplay); },
|
||||
// replay was stopped, move on with older payload
|
||||
function (err) {
|
||||
// warn users that maximum retries have exceeded
|
||||
// but don't bubble up the error with the request
|
||||
self.triggers.console(context.coords, 'warn', (err.message || err));
|
||||
safeNext(null, nextPayload);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// finish up for any other request
|
||||
return safeNext(err, nextPayload);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
277
node_modules/postman-runtime/lib/runner/extensions/item.command.js
generated
vendored
Normal file
277
node_modules/postman-runtime/lib/runner/extensions/item.command.js
generated
vendored
Normal file
@@ -0,0 +1,277 @@
|
||||
var _ = require('lodash'),
|
||||
uuid = require('uuid'),
|
||||
Response = require('postman-collection').Response,
|
||||
visualizer = require('../../visualizer'),
|
||||
|
||||
/**
|
||||
* List of request properties which can be mutated via pre-request
|
||||
*
|
||||
* @private
|
||||
* @const
|
||||
* @type {String[]}
|
||||
*/
|
||||
ALLOWED_REQUEST_MUTATIONS = ['url', 'method', 'headers', 'body'],
|
||||
|
||||
extractVisualizerData,
|
||||
getResponseJSON;
|
||||
|
||||
/**
|
||||
* Returns visualizer data from the latest execution result.
|
||||
*
|
||||
* @param {Array} prereqExecutions - pre-script executions results
|
||||
* @param {Array} testExecutions - test-script executions results
|
||||
* @returns {Object|undefined} - visualizer data
|
||||
*/
|
||||
extractVisualizerData = function (prereqExecutions, testExecutions) {
|
||||
var visualizerData,
|
||||
i;
|
||||
|
||||
if (_.isArray(testExecutions)) {
|
||||
// loop through the test executions in reverse order to return data from latest execution
|
||||
for (i = testExecutions.length - 1; i >= 0; i--) {
|
||||
visualizerData = _.get(testExecutions[i], 'result.return.visualizer');
|
||||
|
||||
if (visualizerData) {
|
||||
return visualizerData;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_.isArray(prereqExecutions)) {
|
||||
// extract visualizer data from pre-request script results if it is not found earlier
|
||||
for (i = prereqExecutions.length - 1; i >= 0; i--) {
|
||||
visualizerData = _.get(prereqExecutions[i], 'result.return.visualizer');
|
||||
|
||||
if (visualizerData) {
|
||||
return visualizerData;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert response into a JSON serializable object.
|
||||
* The stream property is converted to base64 string for performance reasons.
|
||||
*
|
||||
* @param {Object} response - SDK Response instance
|
||||
* @returns {Object}
|
||||
*/
|
||||
getResponseJSON = function (response) {
|
||||
if (!Response.isResponse(response)) {
|
||||
return;
|
||||
}
|
||||
|
||||
return {
|
||||
id: response.id,
|
||||
code: response.code,
|
||||
status: response.status,
|
||||
header: response.headers && response.headers.toJSON(),
|
||||
stream: response.stream && {
|
||||
type: 'Base64',
|
||||
data: response.stream.toString('base64')
|
||||
},
|
||||
responseTime: response.responseTime
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Add options
|
||||
* stopOnError:Boolean
|
||||
* @type {Object}
|
||||
*/
|
||||
module.exports = {
|
||||
init: function (done) {
|
||||
// @todo - code item global timeout and delay here
|
||||
done();
|
||||
},
|
||||
|
||||
triggers: ['beforeItem', 'item', 'beforePrerequest', 'prerequest', 'beforeTest', 'test'],
|
||||
|
||||
process: {
|
||||
/**
|
||||
* @param {Function=} callback
|
||||
* @param {Object} payload
|
||||
* @param {Function} next
|
||||
* @todo validate payload
|
||||
*/
|
||||
item: function (callback, payload, next) {
|
||||
// adjust for polymorphic instructions
|
||||
if (!next && _.isFunction(payload) && !_.isFunction(callback)) {
|
||||
next = payload;
|
||||
payload = callback;
|
||||
callback = null;
|
||||
}
|
||||
|
||||
var item = payload.item,
|
||||
originalRequest = item.request.clone(),
|
||||
coords = payload.coords,
|
||||
data = _.isObject(payload.data) ? payload.data : {},
|
||||
environment = _.isObject(payload.environment) ? payload.environment : {},
|
||||
globals = _.isObject(payload.globals) ? payload.globals : {},
|
||||
collectionVariables = _.isObject(payload.collectionVariables) ? payload.collectionVariables : {},
|
||||
_variables = _.isObject(payload._variables) ? payload._variables : {},
|
||||
stopOnError = _.has(payload, 'stopOnError') ? payload.stopOnError : this.options.stopOnError,
|
||||
|
||||
// @todo: this is mostly coded in event extension and we are
|
||||
// still not sure whether that is the right place for it to be.
|
||||
abortOnFailure = this.options.abortOnFailure,
|
||||
stopOnFailure = this.options.stopOnFailure,
|
||||
delay = _.get(this.options, 'delay.item'),
|
||||
|
||||
ctxTemplate;
|
||||
|
||||
// validate minimum parameters required for the command to work
|
||||
if (!(item && coords)) {
|
||||
return next(new Error('runtime: item execution is missing required parameters'));
|
||||
}
|
||||
|
||||
// store a common uuid in the coords
|
||||
coords.ref = uuid.v4();
|
||||
|
||||
// here we code to queue prerequest script, then make a request and then execute test script
|
||||
this.triggers.beforeItem(null, coords, item);
|
||||
|
||||
this.queueDelay(function () {
|
||||
// create the context object for scripts to run
|
||||
ctxTemplate = {
|
||||
collectionVariables: collectionVariables,
|
||||
_variables: _variables,
|
||||
globals: globals,
|
||||
environment: environment,
|
||||
data: data,
|
||||
request: item.request
|
||||
};
|
||||
|
||||
// @todo make it less nested by coding Instruction.thenQueue
|
||||
this.queue('event', {
|
||||
name: 'prerequest',
|
||||
item: item,
|
||||
coords: coords,
|
||||
context: ctxTemplate,
|
||||
trackContext: ['globals', 'environment', 'collectionVariables'],
|
||||
stopOnScriptError: stopOnError,
|
||||
stopOnFailure: stopOnFailure
|
||||
}).done(function (prereqExecutions, prereqExecutionError) {
|
||||
// if stop on error is marked and script executions had an error,
|
||||
// do not proceed with more commands, instead we bail out
|
||||
if ((stopOnError || stopOnFailure) && prereqExecutionError) {
|
||||
this.triggers.item(null, coords, item); // @todo - should this trigger receive error?
|
||||
|
||||
return callback && callback.call(this, prereqExecutionError, {
|
||||
prerequest: prereqExecutions
|
||||
});
|
||||
}
|
||||
|
||||
// update allowed request mutation properties with the mutated context
|
||||
// @note from this point forward, make sure this mutated
|
||||
// request instance is used for upcoming commands.
|
||||
ALLOWED_REQUEST_MUTATIONS.forEach(function (property) {
|
||||
if (_.has(ctxTemplate, ['request', property])) {
|
||||
item.request[property] = ctxTemplate.request[property];
|
||||
}
|
||||
|
||||
// update property's parent reference
|
||||
if (item.request[property] && typeof item.request[property].setParent === 'function') {
|
||||
item.request[property].setParent(item.request);
|
||||
}
|
||||
});
|
||||
|
||||
this.queue('request', {
|
||||
item: item,
|
||||
globals: ctxTemplate.globals,
|
||||
environment: ctxTemplate.environment,
|
||||
collectionVariables: ctxTemplate.collectionVariables,
|
||||
_variables: ctxTemplate._variables,
|
||||
data: ctxTemplate.data,
|
||||
coords: coords,
|
||||
source: 'collection'
|
||||
}).done(function (result, requestError) {
|
||||
!result && (result = {});
|
||||
|
||||
var request = result.request,
|
||||
response = result.response,
|
||||
cookies = result.cookies;
|
||||
|
||||
if ((stopOnError || stopOnFailure) && requestError) {
|
||||
this.triggers.item(null, coords, item); // @todo - should this trigger receive error?
|
||||
|
||||
return callback && callback.call(this, requestError, {
|
||||
request: request
|
||||
});
|
||||
}
|
||||
|
||||
// also the test object requires the updated request object (since auth helpers may modify it)
|
||||
request && (ctxTemplate.request = request);
|
||||
|
||||
// @note convert response instance to plain object.
|
||||
// we want to avoid calling Response.toJSON() which triggers toJSON on Response.stream buffer.
|
||||
// Because that increases the size of stringified object by 3 times.
|
||||
// Also, that increases the total number of tokens (buffer.data) whereas Buffer.toString
|
||||
// generates a single string that is easier to stringify and sent over the UVM bridge.
|
||||
response && (ctxTemplate.response = getResponseJSON(response));
|
||||
|
||||
// set cookies for this transaction
|
||||
cookies && (ctxTemplate.cookies = cookies);
|
||||
|
||||
// the context template also has a test object to store assertions
|
||||
ctxTemplate.tests = {}; // @todo remove
|
||||
|
||||
this.queue('event', {
|
||||
name: 'test',
|
||||
item: item,
|
||||
coords: coords,
|
||||
context: ctxTemplate,
|
||||
trackContext: ['tests', 'globals', 'environment', 'collectionVariables'],
|
||||
stopOnScriptError: stopOnError,
|
||||
abortOnFailure: abortOnFailure,
|
||||
stopOnFailure: stopOnFailure
|
||||
}).done(function (testExecutions, testExecutionError) {
|
||||
var visualizerData = extractVisualizerData(prereqExecutions, testExecutions),
|
||||
visualizerResult;
|
||||
|
||||
if (visualizerData) {
|
||||
visualizer.processTemplate(visualizerData.template,
|
||||
visualizerData.data,
|
||||
visualizerData.options,
|
||||
function (err, processedTemplate) {
|
||||
visualizerResult = {
|
||||
// bubble up the errors while processing template through visualizer result
|
||||
error: err,
|
||||
|
||||
// add processed template and data to visualizer result
|
||||
processedTemplate: processedTemplate,
|
||||
data: visualizerData.data
|
||||
};
|
||||
|
||||
// trigger an event saying that item has been processed
|
||||
this.triggers.item(null, coords, item, visualizerResult);
|
||||
}.bind(this));
|
||||
}
|
||||
else {
|
||||
// trigger an event saying that item has been processed
|
||||
// @todo - should this trigger receive error?
|
||||
this.triggers.item(null, coords, item, null);
|
||||
}
|
||||
|
||||
// reset mutated request with original request instance
|
||||
// @note request mutations are not persisted across iterations
|
||||
item.request = originalRequest;
|
||||
|
||||
callback && callback.call(this, ((stopOnError || stopOnFailure) && testExecutionError) ?
|
||||
testExecutionError : null, {
|
||||
prerequest: prereqExecutions,
|
||||
request: request,
|
||||
response: response,
|
||||
test: testExecutions
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}.bind(this), {
|
||||
time: delay,
|
||||
source: 'item',
|
||||
cursor: coords
|
||||
}, next);
|
||||
}
|
||||
}
|
||||
};
|
100
node_modules/postman-runtime/lib/runner/extensions/request.command.js
generated
vendored
Normal file
100
node_modules/postman-runtime/lib/runner/extensions/request.command.js
generated
vendored
Normal file
@@ -0,0 +1,100 @@
|
||||
var _ = require('lodash'),
|
||||
sdk = require('postman-collection'),
|
||||
|
||||
createItemContext = require('../create-item-context'),
|
||||
|
||||
/**
|
||||
* Resolve variables in item and auth in context.
|
||||
*
|
||||
* @param {ItemContext} context
|
||||
* @param {Item} [context.item]
|
||||
* @param {RequestAuth} [context.auth]
|
||||
* @param {Object} payload
|
||||
* @param {VariableScope} payload._variables
|
||||
* @param {Object} payload.data
|
||||
* @param {VariableScope} payload.environment
|
||||
* @param {VariableScope} payload.collectionVariables
|
||||
* @param {VariableScope} payload.globals
|
||||
*/
|
||||
resolveVariables = function (context, payload) {
|
||||
if (!(context.item && context.item.request)) { return; }
|
||||
|
||||
// @todo - resolve variables in a more graceful way
|
||||
var variableDefinitions = [
|
||||
// extract the variable list from variable scopes
|
||||
// @note: this is the order of precedence for variable resolution - don't change it
|
||||
payload._variables.values,
|
||||
payload.data,
|
||||
payload.environment.values,
|
||||
payload.collectionVariables.values,
|
||||
payload.globals.values
|
||||
],
|
||||
urlString = context.item.request.url.toString(),
|
||||
item,
|
||||
auth;
|
||||
|
||||
// @todo - no need to sync variables when SDK starts supporting resolution from scope directly
|
||||
// @todo - avoid resolving the entire item as this unnecessarily resolves URL
|
||||
item = context.item = new sdk.Item(context.item.toObjectResolved(null,
|
||||
variableDefinitions, {ignoreOwnVariables: true}));
|
||||
|
||||
auth = context.auth;
|
||||
|
||||
// resolve variables in URL string
|
||||
if (urlString) {
|
||||
// @note this adds support resolving nested variables as URL parser doesn't support them well.
|
||||
urlString = sdk.Property.replaceSubstitutions(urlString, variableDefinitions);
|
||||
|
||||
// Re-parse the URL from the resolved string
|
||||
item.request.url = new sdk.Url(urlString);
|
||||
}
|
||||
|
||||
// resolve variables in auth
|
||||
auth && (context.auth = new sdk.RequestAuth(auth.toObjectResolved(null,
|
||||
variableDefinitions, {ignoreOwnVariables: true})));
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
init: function (done) {
|
||||
done();
|
||||
},
|
||||
|
||||
triggers: ['response'],
|
||||
|
||||
process: {
|
||||
request: function (payload, next) {
|
||||
var abortOnError = _.has(payload, 'abortOnError') ? payload.abortOnError : this.options.abortOnError,
|
||||
|
||||
// helper function to trigger `response` callback anc complete the command
|
||||
complete = function (err, nextPayload) {
|
||||
// nextPayload will be empty for unhandled errors
|
||||
// trigger `response` callback
|
||||
// nextPayload.response will be empty for error flows
|
||||
// the `item` argument is resolved and mutated here
|
||||
nextPayload && this.triggers.response(err, nextPayload.coords, nextPayload.response,
|
||||
nextPayload.request, nextPayload.item, nextPayload.cookies, nextPayload.history);
|
||||
|
||||
// the error is passed twice to allow control between aborting the error vs just
|
||||
// bubbling it up
|
||||
return next(err && abortOnError ? err : null, nextPayload, err);
|
||||
}.bind(this),
|
||||
context = createItemContext(payload);
|
||||
|
||||
// resolve variables in item and auth
|
||||
resolveVariables(context, payload);
|
||||
|
||||
// add context for use, after resolution
|
||||
payload.context = context;
|
||||
|
||||
// we do not queue `httprequest` instruction here,
|
||||
// queueing will unblock the item command to prepare for the next `event` instruction
|
||||
// at this moment request is not fulfilled, and we want to block it
|
||||
this.immediate('httprequest', payload)
|
||||
.done(function (nextPayload, err) {
|
||||
// change signature to error first
|
||||
complete(err, nextPayload);
|
||||
})
|
||||
.catch(complete);
|
||||
}
|
||||
}
|
||||
};
|
239
node_modules/postman-runtime/lib/runner/extensions/waterfall.command.js
generated
vendored
Normal file
239
node_modules/postman-runtime/lib/runner/extensions/waterfall.command.js
generated
vendored
Normal file
@@ -0,0 +1,239 @@
|
||||
var _ = require('lodash'),
|
||||
Cursor = require('../cursor'),
|
||||
VariableScope = require('postman-collection').VariableScope,
|
||||
|
||||
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];
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds options
|
||||
* disableSNR:Boolean
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
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.collectionVariables = VariableScope.isVariableScope(state.collectionVariables) ?
|
||||
state.collectionVariables : new VariableScope(state.collectionVariables);
|
||||
state._variables = new VariableScope();
|
||||
|
||||
// ensure that the items and iteration data set is in place
|
||||
!_.isArray(state.items) && (state.items = []);
|
||||
!_.isArray(state.data) && (state.data = []);
|
||||
!_.isObject(state.data[0]) && (state.data[0] = {});
|
||||
|
||||
// if the location in state is already normalised then go ahead and queue iteration, else normalise the
|
||||
// location
|
||||
state.cursor = Cursor.box(state.cursor, { // we pass bounds to ensure there is no stale state
|
||||
cycles: this.options.iterationCount,
|
||||
length: state.items.length
|
||||
});
|
||||
this.waterfall = state.cursor; // copy the location object to instance for quick access
|
||||
|
||||
// queue the iteration command on start
|
||||
this.queue('waterfall', {
|
||||
coords: this.waterfall.current(),
|
||||
static: true,
|
||||
start: true
|
||||
});
|
||||
|
||||
// clear the variable that is supposed to store item name and id lookup hash for easy setNextRequest
|
||||
this.snrHash = null; // we populate it in the first SNR call
|
||||
|
||||
done();
|
||||
},
|
||||
|
||||
triggers: ['beforeIteration', 'iteration'],
|
||||
|
||||
process: {
|
||||
/**
|
||||
* This processor simply queues scripts and requests in a linear chain.
|
||||
*
|
||||
* @param {Object} payload
|
||||
* @param {Object} payload.coords
|
||||
* @param {Boolean} [payload.static=false]
|
||||
* @param {Function} next
|
||||
*/
|
||||
waterfall: function (payload, next) {
|
||||
// we procure the coordinates that we have to pick item and data from. the data is
|
||||
var coords = payload.static ? payload.coords : this.waterfall.whatnext(payload.coords),
|
||||
item = this.state.items[coords.position],
|
||||
delay;
|
||||
|
||||
// if there is nothing to process, we bail out from here, even before we enter the iteration cycle
|
||||
if (coords.empty) {
|
||||
return next();
|
||||
}
|
||||
|
||||
if (payload.stopRunNow) {
|
||||
this.triggers.iteration(null, payload.coords);
|
||||
|
||||
return next();
|
||||
}
|
||||
|
||||
// if it is a beginning of a run, we need to raise events for iteration start
|
||||
if (payload.start) {
|
||||
this.triggers.beforeIteration(null, coords);
|
||||
}
|
||||
|
||||
// if this is a new iteration, we close the previous one and start new
|
||||
if (coords.cr) {
|
||||
// getting the iteration delay here ensures that delay is only called between two iterations
|
||||
delay = _.get(this.options, 'delay.iteration', 0);
|
||||
|
||||
this.triggers.iteration(null, payload.coords);
|
||||
this.triggers.beforeIteration(null, coords);
|
||||
}
|
||||
|
||||
// if this is end of waterfall, it is an end of iteration and also end of run
|
||||
if (coords.eof) {
|
||||
this.triggers.iteration(null, coords);
|
||||
|
||||
return next();
|
||||
}
|
||||
|
||||
this.queueDelay(function () {
|
||||
this.queue('item', {
|
||||
item: item,
|
||||
coords: coords,
|
||||
data: getIterationData(this.state.data, coords.iteration),
|
||||
environment: this.state.environment,
|
||||
globals: this.state.globals,
|
||||
collectionVariables: this.state.collectionVariables,
|
||||
_variables: this.state._variables
|
||||
}, function (executionError, executions) {
|
||||
var snr = {},
|
||||
nextCoords,
|
||||
seekingToStart,
|
||||
stopRunNow,
|
||||
|
||||
stopOnFailure = this.options.stopOnFailure;
|
||||
|
||||
if (!executionError) {
|
||||
// extract set next request
|
||||
snr = extractSNR(executions.prerequest);
|
||||
snr = extractSNR(executions.test, snr);
|
||||
}
|
||||
|
||||
if (!this.options.disableSNR && snr.defined) {
|
||||
// prepare the snr lookup hash if it is not already provided
|
||||
// @todo - figure out a way to reset this post run complete
|
||||
!this.snrHash && (this.snrHash = prepareLookupHash(this.state.items));
|
||||
|
||||
// if it is null, we do not proceed further and move on
|
||||
// see if a request is found in the hash and then reset the coords position to the lookup
|
||||
// value.
|
||||
(snr.value !== null) && (snr.position = // eslint-disable-next-line no-nested-ternary
|
||||
this.snrHash[_.has(this.snrHash.ids, snr.value) ? 'ids' :
|
||||
(_.has(this.snrHash.names, snr.value) ? 'names' : 'obj')][snr.value]);
|
||||
|
||||
snr.valid = _.isNumber(snr.position);
|
||||
}
|
||||
|
||||
nextCoords = _.clone(coords);
|
||||
|
||||
if (snr.valid) {
|
||||
// if the position was detected, we set the position to the one previous to the desired location
|
||||
// this ensures that the next call to .whatnext() will return the desired position.
|
||||
nextCoords.position = snr.position - 1;
|
||||
}
|
||||
else {
|
||||
// if snr was requested, but not valid, we stop this iteration.
|
||||
// stopping an iteration is equivalent to seeking the last position of the current
|
||||
// iteration, so that the next call to .whatnext() will automatically move to the next
|
||||
// iteration.
|
||||
(snr.defined || executionError) && (nextCoords.position = nextCoords.length - 1);
|
||||
|
||||
// If we need to stop on a run, we set the stop flag to true.
|
||||
(stopOnFailure && executionError) && (stopRunNow = true);
|
||||
}
|
||||
|
||||
// @todo - do this in unhacky way
|
||||
if (nextCoords.position === -1) {
|
||||
nextCoords.position = 0;
|
||||
seekingToStart = true;
|
||||
}
|
||||
|
||||
this.waterfall.seek(nextCoords.position, nextCoords.iteration, function (err, chngd, coords) {
|
||||
// this condition should never arise, so better throw error when this happens
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
this.queue('waterfall', {
|
||||
coords: coords,
|
||||
static: seekingToStart,
|
||||
stopRunNow: stopRunNow
|
||||
});
|
||||
}, this);
|
||||
});
|
||||
}.bind(this), {
|
||||
time: delay,
|
||||
source: 'iteration',
|
||||
cursor: coords
|
||||
}, next);
|
||||
}
|
||||
}
|
||||
};
|
Reference in New Issue
Block a user