Skip to content

Commit

Permalink
Allow pm.require API to import packages in sandbox (#1377)
Browse files Browse the repository at this point in the history
  • Loading branch information
coditva authored Feb 28, 2024
1 parent bb1bf4d commit bca5778
Show file tree
Hide file tree
Showing 6 changed files with 252 additions and 103 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
unreleased:
new features:
- GH-1377 Add feature to resolve packages in scripts using `pm.require`

7.36.3:
date: 2024-02-01
fixed bugs:
Expand Down
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,19 @@ runner.run(collection, {

// Option to set whether to send console logs in serialized format which can be parsed
// using the `teleport-javascript` serialization library.
serializeLogs: false
serializeLogs: false,

// Function to resolve packages that are used in the script.
packageResolver: function ({ packages /* sdk.Script.packages */ }, callback) {
return callback(null, {
pkg1: {
data: 'packagedata'
},
pkg2: {
error: 'Failed to get package'
}
});
}
},

// A ProxyConfigList, from the SDK
Expand Down
214 changes: 121 additions & 93 deletions lib/runner/extensions/event.command.js
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,8 @@ module.exports = {
abortOnFailure = payload.abortOnFailure,
stopOnFailure = payload.stopOnFailure,

packageResolver = _.get(this, 'options.script.packageResolver'),

events;

// @todo: find a better place to code this so that event is not aware of such options
Expand Down Expand Up @@ -442,109 +444,135 @@ module.exports = {
shouldSkipExecution = true;
});

const currentEventItem = event.parent && event.parent();

// 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),

// legacy options
legacy: {
_itemId: item.id,
_itemName: item.name,
_itemPath: item.getPath && item.getPath(),
_eventItemName: currentEventItem && currentEventItem.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);
this.host.removeAllListeners(EXECUTION_SKIP_REQUEST_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;
}
const currentEventItem = event.parent && event.parent(),

executeScript = ({ resolvedPackages }, done) => {
// finally execute the script
this.host.execute(event, {
id: executionId,
// debug: true,

// @todo: Expose this as a property in Collection SDK's Script
timeout: payload.scriptTimeout,
cursor: scriptCursor,
context: _.pick(payload.context, SAFE_CONTEXT_VARIABLES),
resolvedPackages: resolvedPackages,

// legacy options
legacy: {
_itemId: item.id,
_itemName: item.name,
_itemPath: item.getPath && item.getPath(),
_eventItemName: currentEventItem && currentEventItem.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);
this.host.removeAllListeners(EXECUTION_SKIP_REQUEST_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));
// 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; }
// 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;
var contextVariable = payload.context[variable],
mutations = result[variable].mutations;

// bail out if there are no mutations
if (!mutations) {
return;
}
// 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);
// ensure that variable scope is treated accordingly
if (_.isFunction(contextVariable.applyMutation)) {
mutations = new sdk.MutationTracker(result[variable].mutations);

mutations.applyOn(contextVariable);
}
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]);
}
});
// @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 [email protected], 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
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));
};

if (!packageResolver) {
// If packageResolver is not present, make sure that
// resolvedPackages is undefined. This ensures that
// pm.require command will not be present in the sandbox.
return executeScript({}, next);
}

// 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
return packageResolver({ packages: script.packages }, (err, resolvedPackages) => {
if (err) {
return next(err);
}

// 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 [email protected], 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,
script,
result
}, err && { error: err })); // we use assign here to avoid needless error property
}.bind(this));
// Note that we don't check if resolvedPackages is valid
// here but let sandbox handle it.
return executeScript({ resolvedPackages }, next);
});
}.bind(this), function (err, results) {
// trigger the event completion callback
this.triggers[eventName](null, cursor, results, item);
Expand Down
14 changes: 7 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,9 @@
"node-forge": "1.3.1",
"node-oauth1": "1.3.0",
"performance-now": "2.1.0",
"postman-collection": "4.3.0",
"postman-collection": "4.4.0",
"postman-request": "2.88.1-postman.33",
"postman-sandbox": "4.4.0",
"postman-sandbox": "4.5.0",
"postman-url-encoder": "3.0.5",
"serialised-error": "1.1.3",
"strip-json-comments": "3.1.1",
Expand Down
Loading

0 comments on commit bca5778

Please sign in to comment.