Files
bruno/packages/bruno-js/src/sandbox/quickjs/shims/bru.js
Pragadesh-45 b0d0e4aabc Feat: Support multipart/mixed (#7155)
* feat(): support multipart mixed

fix: support vars interpolation on mixed multi-part

Update packages/bruno-electron/src/ipc/network/interpolate-vars.js

Co-authored-by: Timon <39559178+Its-treason@users.noreply.github.com>

refactor: use startsWith

feat: best effort for other multipart/* contentypes

* feat: enhance variable interpolation for multipart requests

- Updated `interpolateVars` function to support interpolation in multipart/form-data and multipart/mixed requests.
- Added handling for empty multipart arrays and parts with missing or undefined values.
- Improved type checks for content types to ensure proper interpolation behavior.

Includes new tests to validate the interpolation functionality for multipart requests.

* fix: normalize error handling in sendRequest and improve test reliability

---------

Co-authored-by: Alfonso Presa <alfonso-presa@users.noreply.github.com>
2026-02-26 17:43:37 +05:30

538 lines
19 KiB
JavaScript

const { cleanJson, cleanCircularJson } = require('../../../utils');
const { marshallToVm } = require('../utils');
const addBruShimToContext = (vm, bru) => {
const bruObject = vm.newObject();
const bruRunnerObject = vm.newObject();
let cwd = vm.newFunction('cwd', function () {
return marshallToVm(bru.cwd(), vm);
});
vm.setProp(bruObject, 'cwd', cwd);
cwd.dispose();
let getEnvName = vm.newFunction('getEnvName', function () {
return marshallToVm(bru.getEnvName(), vm);
});
vm.setProp(bruObject, 'getEnvName', getEnvName);
getEnvName.dispose();
let getCollectionName = vm.newFunction('getCollectionName', function () {
return marshallToVm(bru.getCollectionName(), vm);
});
vm.setProp(bruObject, 'getCollectionName', getCollectionName);
getCollectionName.dispose();
let isSafeMode = vm.newFunction('isSafeMode', function () {
return marshallToVm(bru.isSafeMode(), vm);
});
vm.setProp(bruObject, 'isSafeMode', isSafeMode);
isSafeMode.dispose();
let getProcessEnv = vm.newFunction('getProcessEnv', function (key) {
return marshallToVm(bru.getProcessEnv(vm.dump(key)), vm);
});
vm.setProp(bruObject, 'getProcessEnv', getProcessEnv);
getProcessEnv.dispose();
let interpolate = vm.newFunction('interpolate', function (str) {
return marshallToVm(bru.interpolate(vm.dump(str)), vm);
});
vm.setProp(bruObject, 'interpolate', interpolate);
interpolate.dispose();
let hasEnvVar = vm.newFunction('hasEnvVar', function (key) {
return marshallToVm(bru.hasEnvVar(vm.dump(key)), vm);
});
vm.setProp(bruObject, 'hasEnvVar', hasEnvVar);
hasEnvVar.dispose();
let getEnvVar = vm.newFunction('getEnvVar', function (key) {
return marshallToVm(bru.getEnvVar(vm.dump(key)), vm);
});
vm.setProp(bruObject, 'getEnvVar', getEnvVar);
getEnvVar.dispose();
let setEnvVar = vm.newFunction('setEnvVar', function (key, value, options = {}) {
bru.setEnvVar(vm.dump(key), vm.dump(value), vm.dump(options));
});
vm.setProp(bruObject, 'setEnvVar', setEnvVar);
setEnvVar.dispose();
let deleteEnvVar = vm.newFunction('deleteEnvVar', function (key) {
bru.deleteEnvVar(vm.dump(key));
});
vm.setProp(bruObject, 'deleteEnvVar', deleteEnvVar);
deleteEnvVar.dispose();
let getAllEnvVars = vm.newFunction('getAllEnvVars', function () {
return marshallToVm(bru.getAllEnvVars(), vm);
});
vm.setProp(bruObject, 'getAllEnvVars', getAllEnvVars);
getAllEnvVars.dispose();
let deleteAllEnvVars = vm.newFunction('deleteAllEnvVars', function () {
bru.deleteAllEnvVars();
});
vm.setProp(bruObject, 'deleteAllEnvVars', deleteAllEnvVars);
deleteAllEnvVars.dispose();
let getGlobalEnvVar = vm.newFunction('getGlobalEnvVar', function (key) {
return marshallToVm(bru.getGlobalEnvVar(vm.dump(key)), vm);
});
vm.setProp(bruObject, 'getGlobalEnvVar', getGlobalEnvVar);
getGlobalEnvVar.dispose();
let getOauth2CredentialVar = vm.newFunction('getOauth2CredentialVar', function (key) {
return marshallToVm(bru.getOauth2CredentialVar(vm.dump(key)), vm);
});
vm.setProp(bruObject, 'getOauth2CredentialVar', getOauth2CredentialVar);
getOauth2CredentialVar.dispose();
let resetOauth2Credential = vm.newFunction('resetOauth2Credential', function (credentialId) {
bru.resetOauth2Credential(vm.dump(credentialId));
});
vm.setProp(bruObject, 'resetOauth2Credential', resetOauth2Credential);
resetOauth2Credential.dispose();
let setGlobalEnvVar = vm.newFunction('setGlobalEnvVar', function (key, value) {
bru.setGlobalEnvVar(vm.dump(key), vm.dump(value));
});
vm.setProp(bruObject, 'setGlobalEnvVar', setGlobalEnvVar);
setGlobalEnvVar.dispose();
let deleteGlobalEnvVar = vm.newFunction('deleteGlobalEnvVar', function (key) {
bru.deleteGlobalEnvVar(vm.dump(key));
});
vm.setProp(bruObject, 'deleteGlobalEnvVar', deleteGlobalEnvVar);
deleteGlobalEnvVar.dispose();
let getAllGlobalEnvVars = vm.newFunction('getAllGlobalEnvVars', function () {
return marshallToVm(bru.getAllGlobalEnvVars(), vm);
});
vm.setProp(bruObject, 'getAllGlobalEnvVars', getAllGlobalEnvVars);
getAllGlobalEnvVars.dispose();
let deleteAllGlobalEnvVars = vm.newFunction('deleteAllGlobalEnvVars', function () {
bru.deleteAllGlobalEnvVars();
});
vm.setProp(bruObject, 'deleteAllGlobalEnvVars', deleteAllGlobalEnvVars);
deleteAllGlobalEnvVars.dispose();
let hasVar = vm.newFunction('hasVar', function (key) {
return marshallToVm(bru.hasVar(vm.dump(key)), vm);
});
vm.setProp(bruObject, 'hasVar', hasVar);
hasVar.dispose();
let getVar = vm.newFunction('getVar', function (key) {
return marshallToVm(bru.getVar(vm.dump(key)), vm);
});
vm.setProp(bruObject, 'getVar', getVar);
getVar.dispose();
let setVar = vm.newFunction('setVar', function (key, value) {
bru.setVar(vm.dump(key), vm.dump(value));
});
vm.setProp(bruObject, 'setVar', setVar);
setVar.dispose();
let deleteVar = vm.newFunction('deleteVar', function (key) {
bru.deleteVar(vm.dump(key));
});
vm.setProp(bruObject, 'deleteVar', deleteVar);
deleteVar.dispose();
let deleteAllVars = vm.newFunction('deleteAllVars', function () {
bru.deleteAllVars();
});
vm.setProp(bruObject, 'deleteAllVars', deleteAllVars);
deleteAllVars.dispose();
let getAllVars = vm.newFunction('getAllVars', function () {
return marshallToVm(bru.getAllVars(), vm);
});
vm.setProp(bruObject, 'getAllVars', getAllVars);
getAllVars.dispose();
let setNextRequest = vm.newFunction('setNextRequest', function (nextRequest) {
bru.setNextRequest(vm.dump(nextRequest));
});
vm.setProp(bruObject, 'setNextRequest', setNextRequest);
setNextRequest.dispose();
let runnerSkipRequest = vm.newFunction('skipRequest', function () {
bru?.runner?.skipRequest();
});
vm.setProp(bruRunnerObject, 'skipRequest', runnerSkipRequest);
runnerSkipRequest.dispose();
let runnerStopExecution = vm.newFunction('stopExecution', function () {
bru?.runner?.stopExecution();
});
vm.setProp(bruRunnerObject, 'stopExecution', runnerStopExecution);
runnerStopExecution.dispose();
let runnerSetNextRequest = vm.newFunction('setNextRequest', function (nextRequest) {
bru?.runner?.setNextRequest(vm.dump(nextRequest));
});
vm.setProp(bruRunnerObject, 'setNextRequest', runnerSetNextRequest);
runnerSetNextRequest.dispose();
let visualize = vm.newFunction('visualize', function (htmlString) {
bru.visualize(vm.dump(htmlString));
});
vm.setProp(bruObject, 'visualize', visualize);
visualize.dispose();
let getSecretVar = vm.newFunction('getSecretVar', function (key) {
return marshallToVm(bru.getSecretVar(vm.dump(key)), vm);
});
vm.setProp(bruObject, 'getSecretVar', getSecretVar);
getSecretVar.dispose();
let getRequestVar = vm.newFunction('getRequestVar', function (key) {
return marshallToVm(bru.getRequestVar(vm.dump(key)), vm);
});
vm.setProp(bruObject, 'getRequestVar', getRequestVar);
getRequestVar.dispose();
let getFolderVar = vm.newFunction('getFolderVar', function (key) {
return marshallToVm(bru.getFolderVar(vm.dump(key)), vm);
});
vm.setProp(bruObject, 'getFolderVar', getFolderVar);
getFolderVar.dispose();
let getCollectionVar = vm.newFunction('getCollectionVar', function (key) {
return marshallToVm(bru.getCollectionVar(vm.dump(key)), vm);
});
vm.setProp(bruObject, 'getCollectionVar', getCollectionVar);
getCollectionVar.dispose();
let setCollectionVar = vm.newFunction('setCollectionVar', function (key, value) {
bru.setCollectionVar(vm.dump(key), vm.dump(value));
});
vm.setProp(bruObject, 'setCollectionVar', setCollectionVar);
setCollectionVar.dispose();
let hasCollectionVar = vm.newFunction('hasCollectionVar', function (key) {
return marshallToVm(bru.hasCollectionVar(vm.dump(key)), vm);
});
vm.setProp(bruObject, 'hasCollectionVar', hasCollectionVar);
hasCollectionVar.dispose();
let deleteCollectionVar = vm.newFunction('deleteCollectionVar', function (key) {
bru.deleteCollectionVar(vm.dump(key));
});
vm.setProp(bruObject, 'deleteCollectionVar', deleteCollectionVar);
deleteCollectionVar.dispose();
let deleteAllCollectionVars = vm.newFunction('deleteAllCollectionVars', function () {
bru.deleteAllCollectionVars();
});
vm.setProp(bruObject, 'deleteAllCollectionVars', deleteAllCollectionVars);
deleteAllCollectionVars.dispose();
let getAllCollectionVars = vm.newFunction('getAllCollectionVars', function () {
return marshallToVm(bru.getAllCollectionVars(), vm);
});
vm.setProp(bruObject, 'getAllCollectionVars', getAllCollectionVars);
getAllCollectionVars.dispose();
let getTestResults = vm.newFunction('getTestResults', () => {
const promise = vm.newPromise();
bru
.getTestResults()
.then((results) => {
promise.resolve(marshallToVm(cleanJson(results), vm));
})
.catch((err) => {
promise.resolve(
marshallToVm(
cleanJson({
message: err.message
}),
vm
)
);
});
promise.settled.then(vm.runtime.executePendingJobs);
return promise.handle;
});
getTestResults.consume((handle) => vm.setProp(bruObject, 'getTestResults', handle));
let getAssertionResults = vm.newFunction('getAssertionResults', () => {
const promise = vm.newPromise();
bru
.getAssertionResults()
.then((results) => {
promise.resolve(marshallToVm(cleanJson(results), vm));
})
.catch((err) => {
promise.resolve(
marshallToVm(
cleanJson({
message: err.message
}),
vm
)
);
});
promise.settled.then(vm.runtime.executePendingJobs);
return promise.handle;
});
getAssertionResults.consume((handle) => vm.setProp(bruObject, 'getAssertionResults', handle));
let runRequestHandle = vm.newFunction('runRequest', (args) => {
const promise = vm.newPromise();
bru
.runRequest(vm.dump(args))
.then((response) => {
promise.resolve(marshallToVm(cleanCircularJson(response), vm));
})
.catch((err) => {
promise.resolve(
marshallToVm(
cleanJson({
message: err.message
}),
vm
)
);
});
promise.settled.then(vm.runtime.executePendingJobs);
return promise.handle;
});
runRequestHandle.consume((handle) => vm.setProp(bruObject, 'runRequest', handle));
let sendRequestHandle = vm.newFunction('_sendRequest', (args) => {
const promise = vm.newPromise();
bru
.sendRequest(vm.dump(args))
.then((response) => {
promise.resolve(marshallToVm(cleanCircularJson(response), vm));
})
.catch((err) => {
promise.reject(
marshallToVm(
cleanJson(err),
vm
)
);
});
promise.settled.then(vm.runtime.executePendingJobs);
return promise.handle;
});
sendRequestHandle.consume((handle) => vm.setProp(bruObject, '_sendRequest', handle));
const sleep = vm.newFunction('sleep', (timer) => {
const t = vm.getString(timer);
const promise = vm.newPromise();
setTimeout(() => {
promise.resolve(vm.newString('slept'));
}, t);
promise.settled.then(vm.runtime.executePendingJobs);
return promise.handle;
});
sleep.consume((handle) => vm.setProp(bruObject, 'sleep', handle));
let bruCookiesObject = vm.newObject();
const _jarFn = vm.newFunction('_jar', () => {
const nativeJar = bru.cookies.jar();
const jarObj = vm.newObject();
const _getCookieFn = vm.newFunction('_getCookie', (url, cookieName) => {
const promise = vm.newPromise();
nativeJar.getCookie(vm.dump(url), vm.dump(cookieName), (err, cookie) => {
if (err) {
promise.reject(marshallToVm(cleanJson(err), vm));
} else {
promise.resolve(marshallToVm(cleanCircularJson(cookie), vm));
}
});
promise.settled.then(vm.runtime.executePendingJobs);
return promise.handle;
});
_getCookieFn.consume((handle) => vm.setProp(jarObj, '_getCookie', handle));
const _getCookiesFn = vm.newFunction('_getCookies', (url) => {
const promise = vm.newPromise();
nativeJar.getCookies(vm.dump(url), (err, cookies) => {
if (err) {
promise.reject(marshallToVm(cleanJson(err), vm));
} else {
promise.resolve(marshallToVm(cleanCircularJson(cookies), vm));
}
});
promise.settled.then(vm.runtime.executePendingJobs);
return promise.handle;
});
_getCookiesFn.consume((handle) => vm.setProp(jarObj, '_getCookies', handle));
const _setCookieFn = vm.newFunction('_setCookie', (url, nameOrCookieObj, value) => {
const promise = vm.newPromise();
const dumpedUrl = vm.dump(url);
const dumpedNameOrObj = vm.dump(nameOrCookieObj);
// Check if the second argument is an object (cookie object case)
if (typeof dumpedNameOrObj === 'object' && dumpedNameOrObj !== null) {
// Cookie object case: setCookie(url, cookieObject, callback)
nativeJar.setCookie(dumpedUrl, dumpedNameOrObj, (err) => {
if (err) {
promise.reject(marshallToVm(cleanJson(err), vm));
} else {
promise.resolve(vm.undefined);
}
});
} else {
// Name/value case: setCookie(url, name, value, callback)
const dumpedValue = value ? vm.dump(value) : '';
nativeJar.setCookie(dumpedUrl, dumpedNameOrObj, dumpedValue, (err) => {
if (err) {
promise.reject(marshallToVm(cleanJson(err), vm));
} else {
promise.resolve(vm.undefined);
}
});
}
promise.settled.then(vm.runtime.executePendingJobs);
return promise.handle;
});
_setCookieFn.consume((handle) => vm.setProp(jarObj, '_setCookie', handle));
const _setCookiesFn = vm.newFunction('_setCookies', (url, cookiesArray) => {
const promise = vm.newPromise();
nativeJar.setCookies(vm.dump(url), vm.dump(cookiesArray), (err) => {
if (err) {
promise.reject(marshallToVm(cleanJson(err), vm));
} else {
promise.resolve(vm.undefined);
}
});
promise.settled.then(vm.runtime.executePendingJobs);
return promise.handle;
});
_setCookiesFn.consume((handle) => vm.setProp(jarObj, '_setCookies', handle));
const _clearFn = vm.newFunction('_clear', () => {
const promise = vm.newPromise();
nativeJar.clear((err) => {
if (err) {
promise.reject(marshallToVm(cleanJson(err), vm));
} else {
promise.resolve(vm.undefined);
}
});
promise.settled.then(vm.runtime.executePendingJobs);
return promise.handle;
});
_clearFn.consume((handle) => vm.setProp(jarObj, '_clear', handle));
const _deleteCookiesFn = vm.newFunction('_deleteCookies', (url) => {
const promise = vm.newPromise();
nativeJar.deleteCookies(vm.dump(url), (err) => {
if (err) {
promise.reject(marshallToVm(cleanJson(err), vm));
} else {
promise.resolve(vm.undefined);
}
});
promise.settled.then(vm.runtime.executePendingJobs);
return promise.handle;
});
_deleteCookiesFn.consume((handle) => vm.setProp(jarObj, '_deleteCookies', handle));
const _deleteCookieFn = vm.newFunction('_deleteCookie', (url, cookieName) => {
const promise = vm.newPromise();
nativeJar.deleteCookie(vm.dump(url), vm.dump(cookieName), (err) => {
if (err) {
promise.reject(marshallToVm(cleanJson(err), vm));
} else {
promise.resolve(vm.undefined);
}
});
promise.settled.then(vm.runtime.executePendingJobs);
return promise.handle;
});
_deleteCookieFn.consume((handle) => vm.setProp(jarObj, '_deleteCookie', handle));
return jarObj;
});
_jarFn.consume((handle) => vm.setProp(bruCookiesObject, '_jar', handle));
vm.setProp(bruObject, 'cookies', bruCookiesObject);
bruCookiesObject.dispose();
vm.setProp(bruObject, 'runner', bruRunnerObject);
vm.setProp(vm.global, 'bru', bruObject);
bruObject.dispose();
vm.evalCode(`
// sendRequest with callback: normalize error.status (axios uses error.response.status) so
// tests like expect(error.status).to.eql(404) pass in safe sandbox; return response after
// success callback for consistent promise resolution.
globalThis.bru.sendRequest = async (requestConfig, callback) => {
if (!callback) return await globalThis.bru._sendRequest(requestConfig);
try {
const response = await globalThis.bru._sendRequest(requestConfig);
try {
await callback(null, response);
return response;
}
catch(error) {
return Promise.reject(error);
}
}
catch(error) {
const errObj = JSON.parse(JSON.stringify(error));
if (errObj && errObj.response && typeof errObj.response.status === 'number') errObj.status = errObj.response.status;
try {
await callback(errObj, null);
}
catch(err) {
return Promise.reject(err);
}
}
};
globalThis.bru.cookies.jar = () => {
const _jar = globalThis.bru.cookies._jar();
const callWithCallback = async (promiseFn, callback) => {
if (!callback) return await promiseFn();
try {
const result = await promiseFn();
try { await callback(null, result); } catch(cbErr) { return Promise.reject(cbErr); }
} catch(err) {
try { await callback(err, null); } catch(cbErr) { return Promise.reject(cbErr); }
}
};
return {
getCookie: (url, name, cb) => callWithCallback(() => _jar._getCookie(url, name), cb),
getCookies: (url, cb) => callWithCallback(() => _jar._getCookies(url), cb),
setCookie: (url, nameOrCookieObj, valueOrCallback, maybeCallback) => {
if (typeof nameOrCookieObj === 'object' && nameOrCookieObj !== null) {
const callback = typeof valueOrCallback === 'function' ? valueOrCallback : undefined;
return callWithCallback(() => _jar._setCookie(url, nameOrCookieObj), callback);
} else {
const value = typeof valueOrCallback === 'string' ? valueOrCallback : '';
const callback = typeof maybeCallback === 'function' ? maybeCallback :
(typeof valueOrCallback === 'function' ? valueOrCallback : undefined);
return callWithCallback(() => _jar._setCookie(url, nameOrCookieObj, value), callback);
}
},
setCookies: (url, cookiesArray, cb) => callWithCallback(() => _jar._setCookies(url, cookiesArray), cb),
clear: (cb) => callWithCallback(() => _jar._clear(), cb),
deleteCookies: (url, cb) => callWithCallback(() => _jar._deleteCookies(url), cb),
deleteCookie: (url, name, cb) => callWithCallback(() => _jar._deleteCookie(url, name), cb)
};
};
`);
};
module.exports = addBruShimToContext;