240 lines
9.0 KiB
JavaScript
240 lines
9.0 KiB
JavaScript
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);
|
|
}
|
|
}
|
|
};
|