wip: code cleanup, added axios shim to quick js vm (#2851)

* wip: code cleanup, added axios, nanoid shims for quickjs vm
* wip: test fn fix
* wip: scrip exec fix
* wip: added node-fetch & uuid shims
This commit is contained in:
lohit 2024-08-19 10:30:19 +05:30 committed by GitHub
parent a42689a717
commit 126c648d7d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 316 additions and 135 deletions

View File

@ -33,7 +33,7 @@
"lodash": "^4.17.21",
"moment": "^2.29.4",
"nanoid": "3.3.4",
"node-fetch": "2.*",
"node-fetch": "^2.7.0",
"node-vault": "^0.10.2",
"quickjs-emscripten": "^0.29.2",
"uuid": "^9.0.0"

View File

@ -96,14 +96,14 @@ class ScriptRuntime {
modules: {},
scriptType: 'jsScript'
});
}
return {
request,
envVariables: cleanJson(envVariables),
runtimeVariables: cleanJson(runtimeVariables),
nextRequestName: bru.nextRequest
};
return {
request,
envVariables: cleanJson(envVariables),
runtimeVariables: cleanJson(runtimeVariables),
nextRequestName: bru.nextRequest
};
}
// default runtime is vm2
const vm = new NodeVM({

View File

@ -8,6 +8,7 @@ const { newQuickJSWASMModule, memoizePromiseFactory } = require('quickjs-emscrip
// execute `npm run build:isolated-vm:inbuilt-modules` if the below file doesn't exist
const getBundledCode = require('../../bundle-browser-rollup');
const addSleepShimToContext = require('./shims/sleep');
let QuickJSSyncContext;
const loader = memoizePromiseFactory(() => newQuickJSWASMModule());
@ -33,17 +34,6 @@ const executeQuickJsVm = ({ script: externalScript, context: externalContext, sc
req && addBrunoRequestShimToContext(vm, req);
res && addBrunoResponseShimToContext(vm, res);
////////////////////////////////////////////////////////////////////////////////
const logHandle = vm.newFunction('log', (...args) => {
const nativeArgs = args.map(vm.dump);
console.log(...nativeArgs);
});
vm.setProp(vm.global, 'log', logHandle);
logHandle.dispose();
////////////////////////////////////////////////////////////////////////////////
const templateLiteralText = `\`${externalScript}\`;`;
const jsExpressionText = `${externalScript};`;
@ -81,15 +71,15 @@ const executeQuickJsVmAsync = async ({
const vm = module.newContext();
const bundledCode = getBundledCode?.toString() || '';
let bundledScript = `
(${bundledCode})()
`;
bundledScript += `
globalThis.require = (module) => {
return globalThis.requireObject[module];
}
`;
vm.evalCode(
`
(${bundledCode})()
globalThis.require = (module) => {
return globalThis.requireObject[module];
}
`
);
const { bru, req, res, test, __brunoTestResults, console: consoleFn } = externalContext;
@ -97,72 +87,13 @@ const executeQuickJsVmAsync = async ({
req && addBrunoRequestShimToContext(vm, req);
res && addBrunoResponseShimToContext(vm, res);
consoleFn && addConsoleShimToContext(vm, consoleFn);
addSleepShimToContext(vm);
// await addLibraryShimsToContext(context);
await addLibraryShimsToContext(vm);
test && __brunoTestResults && addTestShimToContext(vm, __brunoTestResults);
bundledScript += `
globalThis.expect = require('chai').expect;
globalThis.assert = require('chai').assert;
globalThis.__brunoTestResults = {
addResult: globalThis.__bruno__addResult,
getResults: globalThis.__bruno__getResults,
}
globalThis.DummyChaiAssertionError = class DummyChaiAssertionError extends Error {
constructor(message, props, ssf) {
super(message);
this.name = "AssertionError";
Object.assign(this, props);
}
}
globalThis.Test = (__brunoTestResults) => async (description, callback) => {
try {
await callback();
__brunoTestResults.addResult({ description, status: "pass" });
} catch (error) {
if (error instanceof DummyChaiAssertionError) {
const { message, actual, expected } = error;
__brunoTestResults.addResult({
description,
status: "fail",
error: message,
actual,
expected,
});
} else {
globalThis.__bruno__addResult({
description,
status: "fail",
error: error.message || "An unexpected error occurred.",
});
}
}
};
globalThis.test = Test(__brunoTestResults);
`;
////////////////////////////////////////////////////////////////////////////////
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(vm.global, 'sleep', handle));
////////////////////////////////////////////////////////////////////////////////
const script = `
${bundledScript}
(async () => {
const setTimeout = async(fn, timer) => {
v = await sleep(timer);

View File

@ -0,0 +1,72 @@
const axios = require('axios');
const { cleanJson } = require('../../../../utils');
const { marshallToVm } = require('../../utils');
const methods = ['get', 'post', 'put', 'patch', 'delete'];
const addAxiosShimToContext = async (vm) => {
methods?.forEach((method) => {
const axiosHandle = vm.newFunction(method, (...args) => {
const nativeArgs = args.map(vm.dump);
const promise = vm.newPromise();
axios[method](...nativeArgs)
.then((response) => {
const { status, headers, data } = response || {};
promise.resolve(marshallToVm(cleanJson({ status, headers, data }), vm));
})
.catch((err) => {
promise.resolve(
marshallToVm(
cleanJson({
message: err.message
}),
vm
)
);
});
promise.settled.then(vm.runtime.executePendingJobs);
return promise.handle;
});
axiosHandle.consume((handle) => vm.setProp(vm.global, `__bruno__axios__${method}`, handle));
});
const axiosHandle = vm.newFunction('axios', (...args) => {
const nativeArgs = args.map(vm.dump);
const promise = vm.newPromise();
axios(...nativeArgs)
.then((response) => {
const { status, headers, data } = response || {};
promise.resolve(marshallToVm(cleanJson({ status, headers, data }), vm));
})
.catch((err) => {
promise.resolve(
marshallToVm(
cleanJson({
message: err.message
}),
vm
)
);
});
promise.settled.then(vm.runtime.executePendingJobs);
return promise.handle;
});
axiosHandle.consume((handle) => vm.setProp(vm.global, `__bruno__axios`, handle));
vm.evalCode(
`
globalThis.axios = __bruno__axios;
${methods
?.map((method) => {
return `globalThis.axios.${method} = __bruno__axios__${method};`;
})
?.join('\n')}
globalThis.requireObject = {
...globalThis.requireObject,
axios: globalThis.axios,
}
`
);
};
module.exports = addAxiosShimToContext;

View File

@ -1,7 +1,13 @@
const addAxiosShimToContext = require('./axios');
const addNanoidShimToContext = require('./nanoid');
const addNodeFetchShimToContext = require('./node-fetch');
const addUuidShimToContext = require('./uuid');
const addLibraryShimsToContext = async (context) => {
await addNanoidShimToContext(context);
const addLibraryShimsToContext = async (vm) => {
await addNanoidShimToContext(vm);
await addAxiosShimToContext(vm);
await addNodeFetchShimToContext(vm);
await addUuidShimToContext(vm);
};
module.exports = addLibraryShimsToContext;

View File

@ -1,5 +1,24 @@
const { nanoid } = require('nanoid');
const { marshallToVm } = require('../../utils');
const addNanoidShimToContext = async (context) => {};
const addNanoidShimToContext = async (vm) => {
let _nanoid = vm.newFunction('nanoid', function () {
let v = nanoid();
return marshallToVm(v, vm);
});
vm.setProp(vm.global, '__bruno__nanoid', _nanoid);
_nanoid.dispose();
vm.evalCode(
`
globalThis.nanoid = {};
globalThis.nanoid.nanoid = globalThis.__bruno__nanoid;
globalThis.requireObject = {
...globalThis.requireObject,
'nanoid': globalThis.nanoid
}
`
);
};
module.exports = addNanoidShimToContext;

View File

@ -0,0 +1,41 @@
const fetch = require('node-fetch');
const { cleanJson } = require('../../../../utils');
const { marshallToVm } = require('../../utils');
const addNodeFetchShimToContext = async (vm) => {
const nodeFetchHandle = vm.newFunction('node_fetch', (...args) => {
const nativeArgs = args.map(vm.dump);
const promise = vm.newPromise();
fetch(...nativeArgs)
.then(async (response) => {
const { status, headers } = response || {};
const data = await response.json();
promise.resolve(marshallToVm(cleanJson({ status, headers, data }), vm));
})
.catch((err) => {
promise.resolve(
marshallToVm(
cleanJson({
message: err.message
}),
vm
)
);
});
promise.settled.then(vm.runtime.executePendingJobs);
return promise.handle;
});
nodeFetchHandle.consume((handle) => vm.setProp(vm.global, `__bruno__node_fetch`, handle));
vm.evalCode(
`
globalThis.nodeFetch = __bruno__node_fetch;
globalThis.requireObject = {
...globalThis.requireObject,
'node-fetch': globalThis.nodeFetch,
}
`
);
};
module.exports = addNodeFetchShimToContext;

View File

@ -0,0 +1,30 @@
const uuid = require('uuid');
const { marshallToVm } = require('../../utils');
const fns = ['version', 'parse', 'stringify', 'v1', 'v1ToV6', 'v3', 'v4', 'v5', 'v6', 'v6ToV1', 'v7', 'validate'];
const addUuidShimToContext = async (vm) => {
fns.forEach((fn) => {
let fnHandle = vm.newFunction(fn, function (...args) {
const nativeArgs = args.map(vm.dump);
return marshallToVm(uuid[fn](...nativeArgs), vm);
});
vm.setProp(vm.global, `__bruno__uuid__${fn}`, fnHandle);
fnHandle.dispose();
});
vm.evalCode(
`
globalThis.uuid = {};
${['version', 'parse', 'stringify', 'v1', 'v1ToV6', 'v3', 'v4', 'v5', 'v6', 'v6ToV1', 'v7', 'validate']
?.map((fn, idx) => `globalThis.uuid.${fn} = __bruno__uuid__${fn}`)
.join('\n')}
globalThis.requireObject = {
...globalThis.requireObject,
uuid: globalThis.uuid,
}
`
);
};
module.exports = addUuidShimToContext;

View File

@ -0,0 +1,14 @@
const addSleepShimToContext = (vm) => {
const sleepHandle = 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;
});
sleepHandle.consume((handle) => vm.setProp(vm.global, 'sleep', handle));
};
module.exports = addSleepShimToContext;

View File

@ -13,53 +13,51 @@ const addBruShimToContext = (vm, __brunoTestResults) => {
vm.setProp(vm.global, '__bruno__getResults', getResults);
getResults.dispose();
// vm.evalCode(
// `
// globalThis.expect = require('chai').expect;
// globalThis.assert = require('chai').assert;
vm.evalCode(
`
globalThis.expect = require('chai').expect;
globalThis.assert = require('chai').assert;
// globalThis.__brunoTestResults = {
// addResult: globalThis.addResult,
// getResults: globalThis.getResults,
// }
globalThis.__brunoTestResults = {
addResult: globalThis.__bruno__addResult,
getResults: globalThis.__bruno__getResults,
}
// globalThis.DummyChaiAssertionError = class DummyChaiAssertionError extends Error {
// constructor(message, props, ssf) {
// super(message);
// this.name = "AssertionError";
// Object.assign(this, props);
// }
// }
globalThis.DummyChaiAssertionError = class DummyChaiAssertionError extends Error {
constructor(message, props, ssf) {
super(message);
this.name = "AssertionError";
Object.assign(this, props);
}
}
// globalThis.Test = (__brunoTestResults) => async (description, callback) => {
// try {
// await callback();
// __brunoTestResults.addResult({ description, status: "pass" });
// } catch (error) {
// if (error instanceof DummyChaiAssertionError) {
// const { message, actual, expected } = error;
// __brunoTestResults.addResult({
// description,
// status: "fail",
// error: message,
// actual,
// expected,
// });
// } else {
// __brunoTestResults.addResult({
// description,
// status: "fail",
// error: error.message || "An unexpected error occurred.",
// });
// }
// console.log(error);
// }
// };
// let foobar = 'foobar3000';
// log("from test shim");
// globalThis.test = Test(__brunoTestResults);
// `
// );
globalThis.Test = (__brunoTestResults) => async (description, callback) => {
try {
await callback();
__brunoTestResults.addResult({ description, status: "pass" });
} catch (error) {
if (error instanceof DummyChaiAssertionError) {
const { message, actual, expected } = error;
__brunoTestResults.addResult({
description,
status: "fail",
error: message,
actual,
expected,
});
} else {
globalThis.__bruno__addResult({
description,
status: "fail",
error: error.message || "An unexpected error occurred.",
});
}
}
};
globalThis.test = Test(__brunoTestResults);
`
);
};
module.exports = addBruShimToContext;

View File

@ -0,0 +1,17 @@
meta {
name: nanoid
type: http
seq: 1
}
get {
url: {{host}}/ping
body: none
auth: none
}
script:pre-request {
const { nanoid } = require("nanoid");
req.setHeader("transaction-id", nanoid());
}

View File

@ -0,0 +1,36 @@
meta {
name: node-fetch-pre-req-script
type: http
seq: 1
}
get {
url: {{host}}/ping
body: none
auth: none
}
script:pre-request {
const fetch = require("node-fetch");
const url = "https://testbench-sanity.usebruno.com/api/echo/json";
const response = await fetch(url, {
method: 'post',
body: JSON.stringify({hello:'bruno'}),
headers: {'Content-Type': 'application/json'}
});
req.setBody(response.data);
req.setMethod("POST");
req.setUrl(url);
}
tests {
test("req.getBody()", function() {
const data = res.getBody();
expect(data).to.eql({
"hello": "bruno"
});
});
}

View File

@ -0,0 +1,17 @@
meta {
name: uuid
type: http
seq: 1
}
get {
url: {{host}}/ping
body: none
auth: none
}
script:pre-request {
const { v4 } = require("uuid");
req.setHeader("transaction-id", v4());
}