Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow pm.require API to import packages in sandbox #1377

Merged
merged 6 commits into from
Feb 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading