Simon Priet e69a613a37 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.
2021-09-08 14:01:19 +02:00

164 lines
6.9 KiB
JavaScript

const _ = require('lodash'),
Scope = require('uniscope'),
PostmanEvent = require('postman-collection').Event,
Execution = require('./execution'),
PostmanConsole = require('./console'),
PostmanTimers = require('./timers'),
PostmanAPI = require('./pmapi'),
PostmanCookieStore = require('./cookie-store'),
EXECUTION_RESULT_EVENT_BASE = 'execution.result.',
EXECUTION_REQUEST_EVENT_BASE = 'execution.request.',
EXECUTION_ERROR_EVENT = 'execution.error',
EXECUTION_ERROR_EVENT_BASE = 'execution.error.',
EXECUTION_ABORT_EVENT_BASE = 'execution.abort.',
EXECUTION_RESPONSE_EVENT_BASE = 'execution.response.',
EXECUTION_COOKIES_EVENT_BASE = 'execution.cookies.',
EXECUTION_ASSERTION_EVENT = 'execution.assertion',
EXECUTION_ASSERTION_EVENT_BASE = 'execution.assertion.',
executeContext = require('./execute-context');
module.exports = function (bridge, glob) {
// @note we use a common scope for all executions. this causes issues when scripts are run inside the sandbox
// in parallel, but we still use this way for the legacy "persistent" behaviour needed in environment
const scope = Scope.create({
eval: true,
ignore: ['require'],
block: ['bridge']
});
/**
* @param {String} id
* @param {Event} event
* @param {Object} context
* @param {Object} options
* @param {Boolean=} [options.debug]
* @param {Object=} [options.cursor]
* @param {Number=} [options.timeout]
*
* @note
* options also take in legacy properties: _itemId, _itemName
*/
bridge.on('execute', function (id, event, context, options) {
if (!(id && _.isString(id))) {
return bridge.dispatch('error', new Error('sandbox: execution identifier parameter(s) missing'));
}
!options && (options = {});
!context && (context = {});
event = (new PostmanEvent(event));
const executionEventName = EXECUTION_RESULT_EVENT_BASE + id,
executionRequestEventName = EXECUTION_REQUEST_EVENT_BASE + id,
errorEventName = EXECUTION_ERROR_EVENT_BASE + id,
abortEventName = EXECUTION_ABORT_EVENT_BASE + id,
responseEventName = EXECUTION_RESPONSE_EVENT_BASE + id,
cookiesEventName = EXECUTION_COOKIES_EVENT_BASE + id,
assertionEventName = EXECUTION_ASSERTION_EVENT_BASE + id,
// extract the code from event. The event can be the code itself and we know that if the event is of type
// string.
code = _.isFunction(event.script && event.script.toSource) && event.script.toSource(),
// create the execution object
execution = new Execution(id, event, context, options),
/**
* Dispatch assertions from `pm.test` or legacy `test` API.
*
* @private
* @param {Object[]} assertions -
* @param {String} assertions[].name -
* @param {Number} assertions[].index -
* @param {Object} assertions[].error -
* @param {Boolean} assertions[].async -
* @param {Boolean} assertions[].passed -
* @param {Boolean} assertions[].skipped -
*/
dispatchAssertions = function (assertions) {
// Legacy `test` API accumulates all the assertions and dispatches at once
// whereas, `pm.test` dispatch on every single assertion.
// For compatibility, dispatch the single assertion as an array.
!Array.isArray(assertions) && (assertions = [assertions]);
bridge.dispatch(assertionEventName, options.cursor, assertions);
bridge.dispatch(EXECUTION_ASSERTION_EVENT, options.cursor, assertions);
};
let waiting,
timers;
// create the controlled timers
timers = new PostmanTimers(null, function (err) {
if (err) { // propagate the error out of sandbox
bridge.dispatch(errorEventName, options.cursor, err);
bridge.dispatch(EXECUTION_ERROR_EVENT, options.cursor, err);
}
}, function () {
execution.return.async = true;
}, function (err, dnd) {
// clear timeout tracking timer
waiting && (waiting = clearTimeout(waiting));
// do not allow any more timers
if (timers) {
timers.seal();
timers.clearAll();
}
// remove listener of disconnection event
bridge.off(abortEventName);
bridge.off(responseEventName);
if (err) { // fire extra execution error event
bridge.dispatch(errorEventName, options.cursor, err);
bridge.dispatch(EXECUTION_ERROR_EVENT, options.cursor, err);
}
// @note delete response from the execution object to avoid dispatching
// the large response payload back due to performance reasons.
execution.response && (delete execution.response);
// fire the execution completion event
(dnd !== true) && bridge.dispatch(executionEventName, err || null, execution);
});
// if a timeout is set, we must ensure that all pending timers are cleared and an execution timeout event is
// triggered.
_.isFinite(options.timeout) && (waiting = setTimeout(function () {
timers.terminate(new Error('sandbox: ' +
(execution.return.async ? 'asynchronous' : 'synchronous') + ' script execution timeout'));
}, options.timeout));
// if an abort event is sent, compute cleanup and complete
bridge.on(abortEventName, function () {
timers.terminate(null, true);
});
// handle response event from outside sandbox
bridge.on(responseEventName, function (id, err, res, history) {
timers.clearEvent(id, err, res, history);
});
// handle cookies event from outside sandbox
bridge.on(cookiesEventName, function (id, err, res) {
timers.clearEvent(id, err, res);
});
// send control to the function that executes the context and prepares the scope
executeContext(scope, code, execution,
// if a console is sent, we use it. otherwise this also prevents erroneous referencing to any console
// inside this closure.
(new PostmanConsole(bridge, id, options.cursor, options.debug && glob.console)),
timers,
(
new PostmanAPI(execution, function (request, callback) {
var eventId = timers.setEvent(callback);
bridge.dispatch(executionRequestEventName, options.cursor, id, eventId, request);
}, dispatchAssertions, new PostmanCookieStore(id, bridge, timers))
),
dispatchAssertions);
});
};