diff --git a/.gitignore b/.gitignore index ffc4acb3e9..9ba7a0df0e 100644 --- a/.gitignore +++ b/.gitignore @@ -108,3 +108,4 @@ uri.txt crypt_shared.sh *keytab +driver.bundle.js diff --git a/etc/bundle-driver.mjs b/etc/bundle-driver.mjs new file mode 100755 index 0000000000..e12420ba2a --- /dev/null +++ b/etc/bundle-driver.mjs @@ -0,0 +1,43 @@ +#!/usr/bin/env node +import * as esbuild from 'esbuild'; +import { fileURLToPath } from 'node:url'; +import { isBuiltin } from 'node:module'; +import path from 'node:path'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const rootDir = path.join(__dirname, '..'); + +await esbuild.build({ + entryPoints: [path.join(rootDir, 'src/index.ts')], + bundle: true, + outfile: path.join(rootDir, 'test/tools/runner/driver.bundle.js'), + platform: 'node', + format: 'cjs', + target: 'node20', + external: [ + 'bson', + 'mongodb-connection-string-url', + '@mongodb-js/saslprep', + '@mongodb-js/zstd', + 'mongodb-client-encryption', + 'snappy', + '@napi-rs/snappy*', + 'kerberos', + 'gcp-metadata', + '@aws-sdk/credential-providers' + ], + plugins: [{ + name: 'externalize-node-builtins', + setup(build) { + build.onResolve({ filter: /.*/ }, args => { + if (isBuiltin(args.path)) { + return { path: args.path, external: true }; + } + }); + } + }], + sourcemap: 'inline', + logLevel: 'info' +}); + +console.log('✓ Driver bundle created at test/tools/runner/driver.bundle.js'); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index b1def2b859..79889203ab 100644 --- a/package-lock.json +++ b/package-lock.json @@ -37,6 +37,7 @@ "chai": "^4.4.1", "chai-subset": "^1.6.0", "chalk": "^4.1.2", + "esbuild": "^0.27.2", "eslint": "^9.39.1", "eslint-config-prettier": "^10.1.8", "eslint-plugin-mocha": "^10.4.1", @@ -1142,6 +1143,448 @@ "tslib": "^2.4.0" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.9.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", @@ -4664,6 +5107,48 @@ "dev": true, "license": "MIT" }, + "node_modules/esbuild": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.2", + "@esbuild/android-arm": "0.27.2", + "@esbuild/android-arm64": "0.27.2", + "@esbuild/android-x64": "0.27.2", + "@esbuild/darwin-arm64": "0.27.2", + "@esbuild/darwin-x64": "0.27.2", + "@esbuild/freebsd-arm64": "0.27.2", + "@esbuild/freebsd-x64": "0.27.2", + "@esbuild/linux-arm": "0.27.2", + "@esbuild/linux-arm64": "0.27.2", + "@esbuild/linux-ia32": "0.27.2", + "@esbuild/linux-loong64": "0.27.2", + "@esbuild/linux-mips64el": "0.27.2", + "@esbuild/linux-ppc64": "0.27.2", + "@esbuild/linux-riscv64": "0.27.2", + "@esbuild/linux-s390x": "0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/netbsd-arm64": "0.27.2", + "@esbuild/netbsd-x64": "0.27.2", + "@esbuild/openbsd-arm64": "0.27.2", + "@esbuild/openbsd-x64": "0.27.2", + "@esbuild/openharmony-arm64": "0.27.2", + "@esbuild/sunos-x64": "0.27.2", + "@esbuild/win32-arm64": "0.27.2", + "@esbuild/win32-ia32": "0.27.2", + "@esbuild/win32-x64": "0.27.2" + } + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", diff --git a/package.json b/package.json index 1f97a7595b..846303354f 100644 --- a/package.json +++ b/package.json @@ -85,6 +85,7 @@ "chai": "^4.4.1", "chai-subset": "^1.6.0", "chalk": "^4.1.2", + "esbuild": "^0.27.2", "eslint": "^9.39.1", "eslint-config-prettier": "^10.1.8", "eslint-plugin-mocha": "^10.4.1", @@ -154,6 +155,9 @@ "check:csfle": "nyc mocha --config test/mocha_mongodb.js test/integration/client-side-encryption", "check:snappy": "nyc mocha test/unit/assorted/snappy.test.js", "check:x509": "nyc mocha test/manual/x509_auth.test.ts", + "check:runtime-independence": "ts-node test/tools/runner/vm_runner.ts test/integration/change-streams/change_stream.test.ts", + "bundle:driver": "node etc/bundle-driver.mjs", + "test:bundled": "npm run bundle:driver && npm run check:test", "fix:eslint": "npm run check:eslint -- --fix", "prepare": "node etc/prepare.js", "preview:docs": "ts-node etc/docs/preview.ts", @@ -170,4 +174,4 @@ "moduleResolution": "node" } } -} +} \ No newline at end of file diff --git a/test/integration/crud/crud_api.test.ts b/test/integration/crud/crud_api.test.ts index 6ec29becc4..d6029553c2 100644 --- a/test/integration/crud/crud_api.test.ts +++ b/test/integration/crud/crud_api.test.ts @@ -3,27 +3,37 @@ import { finished } from 'node:stream/promises'; import { expect } from 'chai'; import * as sinon from 'sinon'; -import { +import { loadContextifiedMongoDBModule } from '../../tools/runner/vm_context_helper'; +import { type FailCommandFailPoint } from '../../tools/utils'; +import { assert as test } from '../shared'; + +// Load MongoDB module in VM context +const mongodb = loadContextifiedMongoDBModule(); + +// Extract the exports we need from the contextified module +const { Collection, CommandFailedEvent, - type CommandStartedEvent, CommandSucceededEvent, - type Db, MongoBulkWriteError, - type MongoClient, + MongoClient, MongoServerError, ObjectId, ReturnDocument -} from '../../mongodb'; -import { type FailCommandFailPoint } from '../../tools/utils'; -import { assert as test } from '../shared'; +} = mongodb; + +type MongoClient = typeof mongodb.MongoClient.prototype; +type Db = typeof mongodb.Db.prototype; +type CommandStartedEvent = typeof mongodb.CommandStartedEvent.prototype; const DB_NAME = 'crud_api_tests'; -describe('CRUD API', function () { +describe.only('CRUD API', function () { let client: MongoClient; beforeEach(async function () { + this.configuration.mongodb = mongodb; + client = this.configuration.newClient(); client.s.options.dbName = DB_NAME; // setup the default db @@ -817,6 +827,7 @@ describe('CRUD API', function () { let collection: Collection; beforeEach(async function () { + this.configuration.mongodb = mongodb; client = this.configuration.newClient({ monitorCommands: true }); events = []; client.on('commandStarted', commandStarted => diff --git a/test/mongodb.ts b/test/mongodb.ts index 52b5d1b0f5..dfc50e2d0d 100644 --- a/test/mongodb.ts +++ b/test/mongodb.ts @@ -58,6 +58,9 @@ export * from '../src/constants'; export * from '../src/cursor/abstract_cursor'; export * from '../src/cursor/aggregation_cursor'; export * from '../src/cursor/change_stream_cursor'; +export * from '../src/cursor/find_cursor'; +export * from '../src/cursor/list_collections_cursor'; +export * from '../src/cursor/list_indexes_cursor'; export * from '../src/cursor/client_bulk_write_cursor'; export * from '../src/cursor/explainable_cursor'; export * from '../src/cursor/find_cursor'; @@ -74,6 +77,11 @@ export * from '../src/gridfs/download'; export * from '../src/gridfs/index'; export * from '../src/gridfs/upload'; export * from '../src/mongo_client'; +export * from '../src/mongo_logger'; +export * from '../src/mongo_types'; +export * from '../src/operations/aggregate'; +export * from '../src/operations/client_bulk_write/command_builder'; +export * from '../src/operations/client_bulk_write/common'; export * from '../src/mongo_client_auth_providers'; export * from '../src/mongo_logger'; export * from '../src/mongo_types'; @@ -131,3 +139,6 @@ export * from '../src/timeout'; export * from '../src/transactions'; export * from '../src/utils'; export * from '../src/write_concern'; + +// Must be last for precedence +export * from '../src/index'; diff --git a/test/tools/runner/config.ts b/test/tools/runner/config.ts index a16661f5d6..a04ea89747 100644 --- a/test/tools/runner/config.ts +++ b/test/tools/runner/config.ts @@ -114,20 +114,32 @@ export class TestConfiguration { filters: Record; compressor: CompressorName | null; + // Optional: contextified MongoDB module exports for VM-based testing + private mongodb?: any; + constructor( private uri: string, - private context: Record + private context: Record, + mongodb?: any // Optional contextified mongodb module ) { + this.mongodb = mongodb; + const url = new ConnectionString(uri); const { hosts } = url; - const hostAddresses = hosts.map(HostAddress.fromString); + const hostAddresses = hosts.map( + this.mongodb ? this.mongodb.HostAddress.fromString : HostAddress.fromString + ); this.version = context.version; this.clientSideEncryption = context.clientSideEncryption; this.cryptSharedVersion = context.cryptShared; this.parameters = { ...context.parameters }; this.singleMongosLoadBalancerUri = context.singleMongosLoadBalancerUri; this.multiMongosLoadBalancerUri = context.multiMongosLoadBalancerUri; - this.topologyType = this.isLoadBalanced ? TopologyType.LoadBalanced : context.topologyType; + this.topologyType = this.isLoadBalanced + ? this.mongodb + ? this.mongodb.TopologyType.LoadBalanced + : TopologyType.LoadBalanced + : context.topologyType; this.buildInfo = context.buildInfo; this.serverApi = context.serverApi; this.isSrv = uri.indexOf('mongodb+srv') > -1; @@ -229,6 +241,14 @@ export class TestConfiguration { serverOptions = Object.assign(baseOptions, getEnvironmentalOptions(), serverOptions); + // If using contextified mongodb, inject Node.js runtime adapters + if (this.mongodb) { + serverOptions.runtimeAdapters = { + os: require('os'), + ...serverOptions.runtimeAdapters + }; + } + if (this.loggingEnabled && !Object.hasOwn(serverOptions, 'mongodbLogPath')) { serverOptions = this.setupLogging(serverOptions); } @@ -239,7 +259,8 @@ export class TestConfiguration { throw new Error(`Cannot use options to specify host/port, must be in ${urlOrQueryOptions}`); } - return new MongoClient(urlOrQueryOptions, serverOptions); + const ClientConstructor = this.mongodb ? this.mongodb.MongoClient : MongoClient; + return new ClientConstructor(urlOrQueryOptions, serverOptions); } const queryOptions = urlOrQueryOptions ?? {}; @@ -283,7 +304,10 @@ export class TestConfiguration { delete queryOptions.writeConcern; } - if (this.topologyType === TopologyType.LoadBalanced) { + const LoadBalancedType = this.mongodb + ? this.mongodb.TopologyType.LoadBalanced + : TopologyType.LoadBalanced; + if (this.topologyType === LoadBalancedType) { queryOptions.loadBalanced = true; } @@ -317,7 +341,8 @@ export class TestConfiguration { const connectionString = url.format(urlOptions); - return new MongoClient(connectionString, serverOptions); + const ClientConstructor = this.mongodb ? this.mongodb.MongoClient : MongoClient; + return new ClientConstructor(connectionString, serverOptions); } /** @@ -439,7 +464,8 @@ export class TestConfiguration { } writeConcernMax(): { writeConcern: WriteConcernSettings } { - if (this.topologyType !== TopologyType.Single) { + const SingleType = this.mongodb ? this.mongodb.TopologyType.Single : TopologyType.Single; + if (this.topologyType !== SingleType) { return { writeConcern: { w: 'majority', wtimeoutMS: 30000 } }; } @@ -451,7 +477,7 @@ export class TestConfiguration { } makeAtlasTestConfiguration(): AtlasTestConfiguration { - return new AtlasTestConfiguration(this.uri, this.context); + return new AtlasTestConfiguration(this.uri, this.context, this.mongodb); } loggingEnabled = false; @@ -463,7 +489,8 @@ export class TestConfiguration { testsToEnableLogging = flakyTests; setupLogging(options: MongoClientOptions, id?: string) { - id ??= new ObjectId().toString(); + const ObjectIdConstructor = this.mongodb ? this.mongodb.ObjectId : ObjectId; + id ??= new ObjectIdConstructor().toString(); this.logs = []; const write = log => this.logs.push({ t: log.t, id, ...log }); options.mongodbLogPath = { write }; @@ -478,6 +505,9 @@ export class TestConfiguration { afterEachLogging(ctx: Context) { if (this.loggingEnabled && ctx.currentTest.state === 'failed') { + const LongConstructor = this.mongodb ? this.mongodb.Long : Long; + const DoubleConstructor = this.mongodb ? this.mongodb.Double : Double; + for (const log of this.logs) { console.error( JSON.stringify( @@ -486,12 +516,13 @@ export class TestConfiguration { if (types.isMap(value)) return { Map: Array.from(value.entries()) }; if (types.isSet(value)) return { Set: Array.from(value.values()) }; if (types.isNativeError(value)) return { [value.name]: util.inspect(value) }; - if (typeof value === 'bigint') return { bigint: new Long(value).toExtendedJSON() }; + if (typeof value === 'bigint') + return { bigint: new LongConstructor(value).toExtendedJSON() }; if (typeof value === 'symbol') return `Symbol(${value.description})`; if (typeof value === 'number') { if (Number.isNaN(value) || !Number.isFinite(value) || Object.is(value, -0)) // @ts-expect-error: toExtendedJSON internal on double but not on long - return { number: new Double(value).toExtendedJSON() }; + return { number: new DoubleConstructor(value).toExtendedJSON() }; } if (Buffer.isBuffer(value)) return { [value.constructor.name]: Buffer.prototype.base64Slice.call(value) }; @@ -515,8 +546,10 @@ export class TestConfiguration { */ export class AtlasTestConfiguration extends TestConfiguration { override newClient(): MongoClient { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - return new MongoClient(process.env.MONGODB_URI!); + const ClientConstructor = (this as any).mongodb + ? (this as any).mongodb.MongoClient + : MongoClient; + return new ClientConstructor(process.env.MONGODB_URI!); } override url(): string { @@ -530,8 +563,10 @@ export class AtlasTestConfiguration extends TestConfiguration { */ export class AstrolabeTestConfiguration extends TestConfiguration { override newClient(): MongoClient { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - return new MongoClient(process.env.DRIVERS_ATLAS_TESTING_URI!); + const ClientConstructor = (this as any).mongodb + ? (this as any).mongodb.MongoClient + : MongoClient; + return new ClientConstructor(process.env.DRIVERS_ATLAS_TESTING_URI!); } override url(): string { diff --git a/test/tools/runner/vm_context_helper.ts b/test/tools/runner/vm_context_helper.ts new file mode 100644 index 0000000000..fbb142828f --- /dev/null +++ b/test/tools/runner/vm_context_helper.ts @@ -0,0 +1,129 @@ +/* eslint-disable no-restricted-globals, @typescript-eslint/no-require-imports */ + +import * as fs from 'node:fs'; +import { isBuiltin } from 'node:module'; +import * as path from 'node:path'; +import * as vm from 'node:vm'; + +/** + * Creates a require function that blocks access to specified core modules + */ +function createRestrictedRequire() { + const blockedModules = new Set(['os']); + + return function restrictedRequire(moduleName: string) { + // Block core modules + if (isBuiltin(moduleName) && blockedModules.has(moduleName)) { + throw new Error(`Access to core module '${moduleName}' is restricted in this context`); + } + + return require(moduleName); + } as NodeRequire; +} + +// Create a sandbox context with necessary globals +const sandbox = vm.createContext({ + __proto__: null, + + // Console and timing + console: console, + AbortController: AbortController, + AbortSignal: AbortSignal, + Date: global.Date, + Error: global.Error, + URL: global.URL, + URLSearchParams: global.URLSearchParams, + queueMicrotask: queueMicrotask, + performance: global.performance, + setTimeout: global.setTimeout, + clearTimeout: global.clearTimeout, + setInterval: global.setInterval, + clearInterval: global.clearInterval, + setImmediate: global.setImmediate, + clearImmediate: global.clearImmediate, + + // Process + process: process, + + // Global objects needed for runtime + Buffer: Buffer, + Promise: Promise, + Map: Map, + Set: Set, + WeakMap: WeakMap, + WeakSet: WeakSet, + ArrayBuffer: ArrayBuffer, + SharedArrayBuffer: SharedArrayBuffer, + Atomics: Atomics, + DataView: DataView, + Int8Array: Int8Array, + Uint8Array: Uint8Array, + Uint8ClampedArray: Uint8ClampedArray, + Int16Array: Int16Array, + Uint16Array: Uint16Array, + Int32Array: Int32Array, + Uint32Array: Uint32Array, + Float32Array: Float32Array, + Float64Array: Float64Array, + BigInt64Array: BigInt64Array, + BigUint64Array: BigUint64Array, + + // Other necessary globals + TextEncoder: global.TextEncoder, + TextDecoder: global.TextDecoder, + BigInt: global.BigInt, + Symbol: Symbol, + Proxy: Proxy, + Reflect: Reflect, + Object: Object, + Array: Array, + Function: Function, + String: String, + Number: Number, + Boolean: Boolean, + RegExp: RegExp, + Math: Math, + JSON: JSON, + Intl: global.Intl, + + // Custom require that blocks core modules + require: createRestrictedRequire(), + + // Needed for some modules + global: undefined as any, + globalThis: undefined as any +}); + +// Make global and globalThis point to the sandbox +sandbox.global = sandbox; +sandbox.globalThis = sandbox; + +/** + * Load the bundled MongoDB driver module in a VM context + * This allows us to control the globals that the driver has access to + */ +export function loadContextifiedMongoDBModule() { + const bundlePath = path.join(__dirname, 'driver.bundle.js'); + + if (!fs.existsSync(bundlePath)) { + throw new Error(`Driver bundle not found at ${bundlePath}. Run 'npm run bundle:driver' first.`); + } + + const bundleCode = fs.readFileSync(bundlePath, 'utf8'); + + const exportsContainer = {}; + const moduleContainer = { exports: exportsContainer }; + + // Wrap the bundle in a CommonJS-style wrapper + const wrapper = `(function(exports, module, require) { + ${bundleCode} + })`; + + const script = new vm.Script(wrapper, { filename: bundlePath }); + const fn = script.runInContext(sandbox); + + // Execute the bundle with the restricted require from the sandbox + fn(moduleContainer.exports, moduleContainer, sandbox.require); + + return moduleContainer.exports; +}