278 lines
12 KiB
JavaScript
278 lines
12 KiB
JavaScript
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);
|
|
}
|
|
}
|
|
};
|