diff --git a/package-lock.json b/package-lock.json index 6cdca8d2d..8eab4cf11 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32002,9 +32002,9 @@ } }, "node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", "funding": [ "https://github.com/sponsors/broofa", "https://github.com/sponsors/ctavan" @@ -32885,16 +32885,6 @@ "webpack-cli": "^4.9.1" } }, - "packages/bruno-app/node_modules/@babel/compat-data": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.2.tgz", - "integrity": "sha512-TUtMJYRPyUb/9aU8f3K0mjmjf6M9N5Woshn2CS6nqJSeJtTtQcpLUXjGt9vbF8ZGff0El99sWkLgzwW3VXnxZQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, "packages/bruno-app/node_modules/@babel/helper-create-regexp-features-plugin": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.27.1.tgz", @@ -33156,23 +33146,6 @@ "@babel/core": "^7.0.0-0" } }, - "packages/bruno-app/node_modules/@babel/plugin-transform-class-properties": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.27.1.tgz", - "integrity": "sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "packages/bruno-app/node_modules/@babel/plugin-transform-class-static-block": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.27.1.tgz", @@ -33458,23 +33431,6 @@ "@babel/core": "^7.0.0-0" } }, - "packages/bruno-app/node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz", - "integrity": "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-transforms": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "packages/bruno-app/node_modules/@babel/plugin-transform-modules-systemjs": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.27.1.tgz", @@ -34530,16 +34486,6 @@ "typescript": "^5.8.3" } }, - "packages/bruno-common/node_modules/@babel/compat-data": { - "version": "7.26.8", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.8.tgz", - "integrity": "sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, "packages/bruno-common/node_modules/@babel/generator": { "version": "7.27.0", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.0.tgz", @@ -34574,46 +34520,6 @@ "node": ">=6.9.0" } }, - "packages/bruno-common/node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.27.0.tgz", - "integrity": "sha512-vSGCvMecvFCd/BdpGlhpXYNhhC4ccxyvQWpbGL4CWbvfEoLFWUZuSuf7s9Aw70flgQF+6vptvgK2IfOnKlRmBg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-member-expression-to-functions": "^7.25.9", - "@babel/helper-optimise-call-expression": "^7.25.9", - "@babel/helper-replace-supers": "^7.26.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", - "@babel/traverse": "^7.27.0", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "packages/bruno-common/node_modules/@babel/helper-replace-supers": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.26.5.tgz", - "integrity": "sha512-bJ6iIVdYX1YooY2X7w1q6VITt+LnUILtNk7zT78ykuwStx8BauCzxvFqFaHjOpW1bVnSUM1PN1f0p5P21wHxvg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.25.9", - "@babel/helper-optimise-call-expression": "^7.25.9", - "@babel/traverse": "^7.26.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, "packages/bruno-common/node_modules/@babel/parser": { "version": "7.27.0", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz", @@ -34729,26 +34635,6 @@ "@babel/core": "^7.0.0-0" } }, - "packages/bruno-common/node_modules/@babel/plugin-transform-typescript": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.27.0.tgz", - "integrity": "sha512-fRGGjO2UEGPjvEcyAZXRXAS8AfdaQoq7HnxAbJoAoW10B9xOKesmmndJv+Sym2a+9FHWZ9KbyyLCe9s0Sn5jtg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-create-class-features-plugin": "^7.27.0", - "@babel/helper-plugin-utils": "^7.26.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", - "@babel/plugin-syntax-typescript": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "packages/bruno-common/node_modules/@babel/preset-env": { "version": "7.26.9", "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.26.9.tgz", @@ -34833,26 +34719,6 @@ "@babel/core": "^7.0.0-0" } }, - "packages/bruno-common/node_modules/@babel/preset-typescript": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.27.0.tgz", - "integrity": "sha512-vxaPFfJtHhgeOVXRKuHpHPAOgymmy8V8I65T1q53R7GCZlefKeCaTyDs3zOPHTTbmquvNlQYC5klEvWsBAtrBQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.26.5", - "@babel/helper-validator-option": "^7.25.9", - "@babel/plugin-syntax-jsx": "^7.25.9", - "@babel/plugin-transform-modules-commonjs": "^7.26.3", - "@babel/plugin-transform-typescript": "^7.27.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "packages/bruno-common/node_modules/@babel/template": { "version": "7.27.0", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz", @@ -35212,7 +35078,7 @@ "simple-git": "3.32.3", "socks-proxy-agent": "^8.0.2", "tough-cookie": "^6.0.0", - "uuid": "^9.0.0", + "uuid": "^10.0.0", "yup": "^0.32.11" }, "devDependencies": { @@ -35938,23 +35804,6 @@ "react": "^18.2.0" } }, - "packages/bruno-graphql-docs/node_modules/rollup": { - "version": "3.29.5", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.5.tgz", - "integrity": "sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w==", - "dev": true, - "license": "MIT", - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=14.18.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, "packages/bruno-graphql-docs/node_modules/scheduler": { "version": "0.23.2", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", @@ -35990,7 +35839,7 @@ "path": "^0.12.7", "quickjs-emscripten": "^0.29.2", "tv4": "^1.3.0", - "uuid": "^9.0.0", + "uuid": "^10.0.0", "xml-formatter": "^3.5.0", "xml2js": "^0.6.2", "yaml": "^2.3.4" @@ -36104,23 +35953,6 @@ "typescript": "^4.8.4" } }, - "packages/bruno-query/node_modules/rollup": { - "version": "3.29.5", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.5.tgz", - "integrity": "sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w==", - "dev": true, - "license": "MIT", - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=14.18.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, "packages/bruno-requests": { "name": "@usebruno/requests", "version": "0.1.0", diff --git a/packages/bruno-electron/package.json b/packages/bruno-electron/package.json index c4c2dd933..90006c459 100644 --- a/packages/bruno-electron/package.json +++ b/packages/bruno-electron/package.json @@ -74,7 +74,7 @@ "simple-git": "3.32.3", "socks-proxy-agent": "^8.0.2", "tough-cookie": "^6.0.0", - "uuid": "^9.0.0", + "uuid": "^10.0.0", "yup": "^0.32.11" }, "optionalDependencies": { diff --git a/packages/bruno-js/package.json b/packages/bruno-js/package.json index cdd57c7f0..bfc2f7300 100644 --- a/packages/bruno-js/package.json +++ b/packages/bruno-js/package.json @@ -32,7 +32,7 @@ "path": "^0.12.7", "quickjs-emscripten": "^0.29.2", "tv4": "^1.3.0", - "uuid": "^9.0.0", + "uuid": "^10.0.0", "xml-formatter": "^3.5.0", "xml2js": "^0.6.2", "yaml": "^2.3.4" diff --git a/packages/bruno-js/src/sandbox/quickjs/index.js b/packages/bruno-js/src/sandbox/quickjs/index.js index aa419b958..9b3330700 100644 --- a/packages/bruno-js/src/sandbox/quickjs/index.js +++ b/packages/bruno-js/src/sandbox/quickjs/index.js @@ -5,6 +5,7 @@ const addBrunoResponseShimToContext = require('./shims/bruno-response'); const addTestShimToContext = require('./shims/test'); const addLibraryShimsToContext = require('./shims/lib'); const addLocalModuleLoaderShimToContext = require('./shims/local-module'); +const { getRequireCode } = require('./shims/require'); const { newQuickJSWASMModule, memoizePromiseFactory } = require('quickjs-emscripten'); // execute `npm run sandbox:bundle-libraries` if the below file doesn't exist @@ -104,44 +105,11 @@ const executeQuickJsVmAsync = async ({ script: externalScript, context: external await addCryptoUtilsShimToContext(vm); const bundledCode = getBundledCode?.toString() || ''; - const moduleLoaderCode = function () { - return ` - globalThis.require = (mod) => { - let lib = globalThis.requireObject[mod]; - let isModuleAPath = (module) => (module?.startsWith('.') || module?.startsWith?.(bru.cwd())) - if (lib) { - return lib; - } - else if (isModuleAPath(mod)) { - // fetch local module - let localModuleCode = globalThis.__brunoLoadLocalModule(mod); - - // compile local module as iife - (function (){ - const initModuleExportsCode = "const module = { exports: {} };" - const copyModuleExportsCode = "\\n;globalThis.requireObject[mod] = module.exports;"; - const patchedRequire = ${` - "\\n;" + - "let require = (subModule) => isModuleAPath(subModule) ? globalThis.require(path.resolve(bru.cwd(), mod, '..', subModule)) : globalThis.require(subModule)" + - "\\n;" - `} - eval(initModuleExportsCode + patchedRequire + localModuleCode + copyModuleExportsCode); - })(); - - // resolve module - return globalThis.requireObject[mod]; - } - else { - throw new Error("Cannot find module " + mod); - } - } - `; - }; vm.evalCode( ` (${bundledCode})() - ${moduleLoaderCode()} + ${getRequireCode()} ` ); diff --git a/packages/bruno-js/src/sandbox/quickjs/shims/lib/uuid.spec.js b/packages/bruno-js/src/sandbox/quickjs/shims/lib/uuid.spec.js new file mode 100644 index 000000000..62bb34331 --- /dev/null +++ b/packages/bruno-js/src/sandbox/quickjs/shims/lib/uuid.spec.js @@ -0,0 +1,166 @@ +const { describe, it, expect, beforeAll, beforeEach, afterEach, afterAll } = require('@jest/globals'); +const { newQuickJSWASMModule } = require('quickjs-emscripten'); +const { validate: uuidValidate, version: uuidVersion } = require('uuid'); +const addUuidShimToContext = require('./uuid'); +const { addRequireShimToContext } = require('../require'); +const { createEvalHelper } = require('../../utils/test-helpers'); + +describe('uuid shim tests', () => { + let vm, module, evalAndDump; + + beforeAll(async () => { + module = await newQuickJSWASMModule(); + }); + + beforeEach(async () => { + vm = module.newContext(); + evalAndDump = createEvalHelper(vm); + await addUuidShimToContext(vm); + addRequireShimToContext(vm, { enableLocalModules: false }); + }); + + afterEach(() => { + if (vm) { + try { + vm.dispose(); + } catch (err) { + // Ignore disposal errors + } + vm = null; + } + }); + + afterAll(() => { + if (module) { + try { + module.dispose(); + } catch (err) { + // Ignore disposal errors + } + module = null; + } + }); + + /** + * Validates that a string is a valid UUID of the expected version + */ + function expectUuidVersion(uuid, expectedVersion) { + expect(uuidValidate(uuid)).toBe(true); + expect(uuidVersion(uuid)).toBe(expectedVersion); + } + + describe('uuid version functions', () => { + const versionTests = [ + { fn: 'v1', version: 1 }, + { fn: 'v4', version: 4 }, + { fn: 'v6', version: 6, note: 'RFC 9562' }, + { fn: 'v7', version: 7, note: 'RFC 9562' } + ]; + + it.each(versionTests)('$fn should generate valid uuid and be accessible via require', ({ fn, version }) => { + // Test direct access + const directUuid = evalAndDump(`globalThis.uuid.${fn}()`); + expectUuidVersion(directUuid, version); + + // Test require with destructuring + const requireUuid = evalAndDump(`const { ${fn} } = require('uuid'); ${fn}()`); + expectUuidVersion(requireUuid, version); + }); + + it('v7 should be accessible with alias pattern (v7: uuidv7) - issue #7333', () => { + const uuid = evalAndDump(` + const { v7: uuidv7 } = require('uuid'); + uuidv7(); + `); + expectUuidVersion(uuid, 7); + }); + + it('v7 should generate time-ordered uuids', () => { + const [uuid1, uuid2] = evalAndDump(` + const { v7 } = require('uuid'); + [v7(), v7()]; + `); + // v7 UUIDs are lexicographically sortable by design (timestamp in most significant bits) + expect(uuid1 <= uuid2).toBe(true); + }); + }); + + describe('name-based uuid functions (v3, v5)', () => { + const DNS_NAMESPACE = '6ba7b810-9dad-11d1-80b4-00c04fd430c8'; + + it.each([ + { fn: 'v3', version: 3 }, + { fn: 'v5', version: 5 } + ])('$fn should generate valid uuid with namespace', ({ fn, version }) => { + const uuid = evalAndDump(` + const { ${fn} } = require('uuid'); + ${fn}('example.com', '${DNS_NAMESPACE}'); + `); + expectUuidVersion(uuid, version); + }); + }); + + describe('conversion functions', () => { + it('should convert between v1 and v6 using v1ToV6 and v6ToV1', () => { + const [v1Uuid, v6FromV1, v6Uuid, v1FromV6] = evalAndDump(` + const { v1, v6, v1ToV6, v6ToV1 } = require('uuid'); + const v1Uuid = v1(); + const v6Uuid = v6(); + [v1Uuid, v1ToV6(v1Uuid), v6Uuid, v6ToV1(v6Uuid)]; + `); + + expectUuidVersion(v1Uuid, 1); + expectUuidVersion(v6FromV1, 6); + expectUuidVersion(v6Uuid, 6); + expectUuidVersion(v1FromV6, 1); + }); + }); + + describe('utility functions', () => { + it('should validate uuids correctly', () => { + const [isValid, isInvalid] = evalAndDump(` + const { v4, validate } = require('uuid'); + [validate(v4()), validate('not-a-uuid')]; + `); + expect(isValid).toBe(true); + expect(isInvalid).toBe(false); + }); + + it('should detect uuid version correctly', () => { + const [v4Version, v7Version] = evalAndDump(` + const { v4, v7, version } = require('uuid'); + [version(v4()), version(v7())]; + `); + expect(v4Version).toBe(4); + expect(v7Version).toBe(7); + }); + + it('should parse and stringify uuid', () => { + const [original, stringified, isObject, length] = evalAndDump(` + const { v4, parse, stringify } = require('uuid'); + const original = v4(); + const parsed = parse(original); + [original, stringify(parsed), typeof parsed === 'object', Object.keys(parsed).length]; + `); + expect(original).toBe(stringified); + expect(isObject).toBe(true); + expect(length).toBe(16); + }); + }); + + describe('issue #7333 regression test', () => { + it('should work with the exact pattern from the bug report', () => { + const [typeOfFn, id1, id2] = evalAndDump(` + const { v7: uuidv7 } = require('uuid'); + const id1 = uuidv7(); + const id2 = uuidv7(); + [typeof uuidv7, id1, id2]; + `); + + expect(typeOfFn).toBe('function'); + expectUuidVersion(id1, 7); + expectUuidVersion(id2, 7); + expect(id1).not.toBe(id2); + }); + }); +}); diff --git a/packages/bruno-js/src/sandbox/quickjs/shims/require.js b/packages/bruno-js/src/sandbox/quickjs/shims/require.js new file mode 100644 index 000000000..b9ecdd48c --- /dev/null +++ b/packages/bruno-js/src/sandbox/quickjs/shims/require.js @@ -0,0 +1,56 @@ +/** + * Returns JavaScript code that sets up the require() function in the QuickJS VM. + * This module loader looks up modules from globalThis.requireObject and optionally + * supports loading local modules if the necessary context (bru.cwd, __brunoLoadLocalModule) is available. + * + * @param {Object} options + * @param {boolean} options.enableLocalModules - Whether to enable local module loading (requires bru context) + * @returns {string} JavaScript code to eval in the VM + */ +function getRequireCode() { + return ` + globalThis.require = (mod) => { + let lib = globalThis.requireObject[mod]; + let isModuleAPath = (module) => (module?.startsWith('.') || (typeof bru !== 'undefined' && module?.startsWith(bru.cwd()))) + if (lib) { + return lib; + } + else if (isModuleAPath(mod)) { + // fetch local module + let localModuleCode = globalThis.__brunoLoadLocalModule(mod); + + // compile local module as iife + (function (){ + const initModuleExportsCode = "const module = { exports: {} };" + const copyModuleExportsCode = "\\n;globalThis.requireObject[mod] = module.exports;"; + const patchedRequire = ${` + "\\n;" + + "let require = (subModule) => isModuleAPath(subModule) ? globalThis.require(path.resolve(bru.cwd(), mod, '..', subModule)) : globalThis.require(subModule)" + + "\\n;" + `} + eval(initModuleExportsCode + patchedRequire + localModuleCode + copyModuleExportsCode); + })(); + + // resolve module + return globalThis.requireObject[mod]; + } + else { + throw new Error("Cannot find module " + mod); + } + } + `; +} + +/** + * Adds the require() function to a QuickJS VM context + * @param {Object} vm - QuickJS VM context + * @param {Object} options - Options passed to getRequireCode + */ +function addRequireShimToContext(vm) { + vm.evalCode(getRequireCode()); +} + +module.exports = { + getRequireCode, + addRequireShimToContext +}; diff --git a/packages/bruno-js/src/sandbox/quickjs/shims/require.spec.js b/packages/bruno-js/src/sandbox/quickjs/shims/require.spec.js new file mode 100644 index 000000000..9f9ca0f9e --- /dev/null +++ b/packages/bruno-js/src/sandbox/quickjs/shims/require.spec.js @@ -0,0 +1,154 @@ +const { describe, it, expect, beforeAll, beforeEach, afterEach, afterAll } = require('@jest/globals'); +const { newQuickJSWASMModule } = require('quickjs-emscripten'); +const { addRequireShimToContext, getRequireCode } = require('./require'); +const { createEvalHelper } = require('../utils/test-helpers'); + +describe('require shim tests', () => { + let vm, module, evalAndDump; + + beforeAll(async () => { + module = await newQuickJSWASMModule(); + }); + + beforeEach(() => { + vm = module.newContext(); + evalAndDump = createEvalHelper(vm); + // Initialize empty requireObject + vm.evalCode('globalThis.requireObject = {}'); + }); + + afterEach(() => { + if (vm) { + try { + vm.dispose(); + } catch (err) { + // Ignore disposal errors + } + vm = null; + } + }); + + afterAll(() => { + if (module) { + try { + module.dispose(); + } catch (err) { + // Ignore disposal errors + } + module = null; + } + }); + + describe('getRequireCode', () => { + it('should return a string', () => { + expect(typeof getRequireCode()).toBe('string'); + }); + + it('should contain require function definition', () => { + const code = getRequireCode(); + expect(code).toContain('globalThis.require'); + expect(code).toContain('requireObject'); + }); + }); + + describe('addRequireShimToContext', () => { + it('should add require function to the VM context', () => { + addRequireShimToContext(vm); + const typeOfRequire = evalAndDump('typeof globalThis.require'); + expect(typeOfRequire).toBe('function'); + }); + + it('should return module from requireObject', () => { + addRequireShimToContext(vm); + + // Register a mock module + vm.evalCode(` + globalThis.requireObject['test-module'] = { foo: 'bar', answer: 42 }; + `); + + const result = evalAndDump(` + const mod = require('test-module'); + [mod.foo, mod.answer]; + `); + + expect(result).toEqual(['bar', 42]); + }); + + it('should support destructuring from required modules', () => { + addRequireShimToContext(vm, { enableLocalModules: false }); + + vm.evalCode(` + globalThis.requireObject['my-lib'] = { + greet: (name) => 'Hello, ' + name, + VERSION: '1.0.0' + }; + `); + + const [greeting, version] = evalAndDump(` + const { greet, VERSION } = require('my-lib'); + [greet('World'), VERSION]; + `); + + expect(greeting).toBe('Hello, World'); + expect(version).toBe('1.0.0'); + }); + + it('should support aliased destructuring', () => { + addRequireShimToContext(vm); + + vm.evalCode(` + globalThis.requireObject['utils'] = { v1: () => 'version-1' }; + `); + + const result = evalAndDump(` + const { v1: getVersion } = require('utils'); + getVersion(); + `); + + expect(result).toBe('version-1'); + }); + + it('should throw error for unknown modules', () => { + addRequireShimToContext(vm); + + const result = vm.evalCode(` + try { + require('non-existent-module'); + 'no error'; + } catch (e) { + e.message; + } + `); + const handle = vm.unwrapResult(result); + const errorMessage = vm.dump(handle); + handle.dispose(); + + expect(errorMessage).toContain('Cannot find module'); + expect(errorMessage).toContain('non-existent-module'); + }); + + it('should allow requiring the same module multiple times', () => { + addRequireShimToContext(vm); + + vm.evalCode(` + globalThis.requireObject['counter'] = { count: 0 }; + `); + + const result = evalAndDump(` + const mod1 = require('counter'); + const mod2 = require('counter'); + mod1 === mod2; + `); + + expect(result).toBe(true); + }); + }); + + describe('enableLocalModules option', () => { + it('should include local module loading code when enabled', () => { + const code = getRequireCode(); + expect(code).toContain('isModuleAPath'); + expect(code).toContain('__brunoLoadLocalModule'); + }); + }); +}); diff --git a/packages/bruno-js/src/sandbox/quickjs/utils/test-helpers.js b/packages/bruno-js/src/sandbox/quickjs/utils/test-helpers.js new file mode 100644 index 000000000..f9b192ab3 --- /dev/null +++ b/packages/bruno-js/src/sandbox/quickjs/utils/test-helpers.js @@ -0,0 +1,31 @@ +/** + * Evaluates code in a QuickJS VM and returns the dumped result. + * Handles unwrapping and disposing of handles automatically. + * + * @param {Object} vm - QuickJS VM context + * @param {string} code - JavaScript code to evaluate + * @returns {*} The evaluated and dumped result + */ +function evalAndDump(vm, code) { + const result = vm.evalCode(code); + const handle = vm.unwrapResult(result); + const value = vm.dump(handle); + handle.dispose(); + return value; +} + +/** + * Creates a helper function bound to a specific VM instance. + * Useful in beforeEach to create a test-scoped helper. + * + * @param {Object} vm - QuickJS VM context + * @returns {Function} evalAndDump function bound to the VM + */ +function createEvalHelper(vm) { + return (code) => evalAndDump(vm, code); +} + +module.exports = { + evalAndDump, + createEvalHelper +};