diff --git a/etc/bundle-driver.mjs b/etc/bundle-driver.mjs index f9c4c5dd1b9..17cdb1e70f2 100755 --- a/etc/bundle-driver.mjs +++ b/etc/bundle-driver.mjs @@ -17,15 +17,14 @@ await esbuild.build({ entryPoints: [path.join(rootDir, 'test/mongodb_all.ts')], bundle: true, outfile: outputBundleFile, - platform: 'node', + platform: 'browser', format: 'cjs', - target: 'node20', + target: 'chrome112', external: [ '@aws-sdk/credential-providers', '@mongodb-js/saslprep', '@mongodb-js/zstd', '@napi-rs/snappy*', - 'bson', 'gcp-metadata', 'kerberos', 'mongodb-client-encryption', diff --git a/src/bson.ts b/src/bson.ts index 99ea9d5bd85..623913156b7 100644 --- a/src/bson.ts +++ b/src/bson.ts @@ -55,6 +55,12 @@ export const readInt32LE = (buffer: Uint8Array, offset: number): number => { return NumberUtils.getInt32LE(buffer, offset); }; +// readUint8, reads a single unsigned byte from buffer at given offset +export const readUint8 = (buffer: Uint8Array, offset: number): number => { + validateBufferInputs(buffer, offset, 1); + return buffer[offset]; +}; + export const setUint32LE = (destination: Uint8Array, offset: number, value: number): 4 => { destination[offset] = value; value >>>= 8; diff --git a/src/cmap/auth/scram.ts b/src/cmap/auth/scram.ts index 842edd46e0c..675571ee1a8 100644 --- a/src/cmap/auth/scram.ts +++ b/src/cmap/auth/scram.ts @@ -166,11 +166,10 @@ async function continueScramConversation( const clientKey = await HMAC(cryptoMethod, saltedPassword, 'Client Key'); const serverKey = await HMAC(cryptoMethod, saltedPassword, 'Server Key'); const storedKey = await H(cryptoMethod, clientKey); - const authMessage = [ - clientFirstMessageBare(username, nonce), - payload.toString('utf8'), - withoutProof - ].join(','); + const firstMessageBytes = clientFirstMessageBare(username, nonce); + const firstMessage = ByteUtils.toUTF8(firstMessageBytes, 0, firstMessageBytes.length, false); + const payloadString = ByteUtils.toUTF8(payload.buffer, 0, payload.position, false); + const authMessage = [firstMessage, payloadString, withoutProof].join(','); const clientSignature = await HMAC(cryptoMethod, storedKey, authMessage); const clientProof = `p=${xor(clientKey, clientSignature)}`; @@ -205,7 +204,7 @@ async function continueScramConversation( } function parsePayload(payload: Binary) { - const payloadStr = payload.toString('utf8'); + const payloadStr = ByteUtils.toUTF8(payload.buffer, 0, payload.position, false); const dict: Document = {}; const parts = payloadStr.split(','); for (let i = 0; i < parts.length; i++) { diff --git a/test/integration/change-streams/change_stream.test.ts b/test/integration/change-streams/change_stream.test.ts index ae79cb5f3f1..bdcdfa6b829 100644 --- a/test/integration/change-streams/change_stream.test.ts +++ b/test/integration/change-streams/change_stream.test.ts @@ -21,11 +21,12 @@ import { type MongoClient, MongoServerError, ReadPreference, - type ResumeToken + type ResumeToken, + runNodelessTests } from '../../mongodb'; import * as mock from '../../tools/mongodb-mock/index'; import { TestBuilder, UnifiedTestSuiteBuilder } from '../../tools/unified_suite_builder'; -import { type FailCommandFailPoint, sleep } from '../../tools/utils'; +import { ensureTypeByName, type FailCommandFailPoint, sleep } from '../../tools/utils'; import { delay, filterForCommands } from '../shared'; const initIteratorMode = async (cs: ChangeStream) => { @@ -1826,7 +1827,11 @@ describe('Change Streams', function () { expect(result).to.exist; const change = await willBeChange; - expect(change).to.have.nested.property('fullDocument.a').that.is.instanceOf(Long); + if (runNodelessTests) { + ensureTypeByName(change?.fullDocument?.a, '_Long'); + } else { + expect(change).to.have.nested.property('fullDocument.a').that.is.instanceOf(Long); + } } }); }); diff --git a/test/integration/client-side-encryption/driver.test.ts b/test/integration/client-side-encryption/driver.test.ts index 65592e9f371..98a828aeb86 100644 --- a/test/integration/client-side-encryption/driver.test.ts +++ b/test/integration/client-side-encryption/driver.test.ts @@ -725,14 +725,14 @@ describe('CSOT', function () { 'test.test': { bsonType: 'object', encryptMetadata: { - keyId: [new UUID(dataKey)] + keyId: [new UUID(dataKey.toHexString(true))] }, properties: { a: { encrypt: { bsonType: 'int', algorithm: 'AEAD_AES_256_CBC_HMAC_SHA_512-Random', - keyId: [new UUID(dataKey)] + keyId: [new UUID(dataKey.toHexString(true))] } } } diff --git a/test/integration/crud/insert.test.ts b/test/integration/crud/insert.test.ts index b649d6a5b5c..100fac93084 100644 --- a/test/integration/crud/insert.test.ts +++ b/test/integration/crud/insert.test.ts @@ -1,5 +1,6 @@ import * as Script from 'node:vm'; +import { ByteUtils } from 'bson'; import { expect } from 'chai'; import * as process from 'process'; import { satisfies } from 'semver'; @@ -180,7 +181,7 @@ describe('crud - insert', function () { test.equal(date.toString(), doc.date.toString()); test.equal(date.getTime(), doc.date.getTime()); test.equal(motherOfAllDocuments.oid.toHexString(), doc.oid.toHexString()); - test.equal(motherOfAllDocuments.binary.toString('hex'), doc.binary.value().toString('hex')); + test.equal(motherOfAllDocuments.binary.toString('hex'), ByteUtils.toHex(doc.binary.value())); test.equal(motherOfAllDocuments.int, doc.int); test.equal(motherOfAllDocuments.long, doc.long); @@ -281,8 +282,8 @@ describe('crud - insert', function () { test.equal(date.getTime(), doc.date.getTime()); test.equal(motherOfAllDocuments.oid.toHexString(), doc.oid.toHexString()); test.equal( - motherOfAllDocuments.binary.value().toString('hex'), - doc.binary.value().toString('hex') + ByteUtils.toHex(motherOfAllDocuments.binary.value()), + ByteUtils.toHex(doc.binary.value()) ); test.equal(motherOfAllDocuments.int, doc.int); diff --git a/test/integration/node-specific/bson-options/promote_buffers.test.ts b/test/integration/node-specific/bson-options/promote_buffers.test.ts index e71311c9dae..f3db2de75cc 100644 --- a/test/integration/node-specific/bson-options/promote_buffers.test.ts +++ b/test/integration/node-specific/bson-options/promote_buffers.test.ts @@ -1,8 +1,9 @@ import { expect } from 'chai'; +import { runOnlyInNodeMetadata } from '../../../tools/utils'; import { setupDatabase } from '../../shared'; -describe('Promote Buffers', function () { +describe('Promote Buffers', runOnlyInNodeMetadata, function () { before(function () { return setupDatabase(this.configuration); }); diff --git a/test/integration/node-specific/bson-options/raw.test.ts b/test/integration/node-specific/bson-options/raw.test.ts index 455344f9a92..37f590e41a0 100644 --- a/test/integration/node-specific/bson-options/raw.test.ts +++ b/test/integration/node-specific/bson-options/raw.test.ts @@ -1,8 +1,9 @@ import { expect } from 'chai'; import { type Collection, type MongoClient, ObjectId } from '../../../mongodb'; +import { runOnlyInNodeMetadata } from '../../../tools/utils'; -describe('raw bson support', () => { +describe('raw bson support', runOnlyInNodeMetadata, () => { describe('raw', () => { describe('option inheritance', () => { // define client and option for tests to use diff --git a/test/integration/node-specific/bson-options/utf8_validation.test.ts b/test/integration/node-specific/bson-options/utf8_validation.test.ts index e9920834bd0..fa0cc7a6aaf 100644 --- a/test/integration/node-specific/bson-options/utf8_validation.test.ts +++ b/test/integration/node-specific/bson-options/utf8_validation.test.ts @@ -11,7 +11,9 @@ import { MongoServerError, OpMsgResponse } from '../../../mongodb'; -describe('class MongoDBResponse', () => { +import { runOnlyInNodeMetadata } from '../../../tools/utils'; + +describe('class MongoDBResponse', runOnlyInNodeMetadata, () => { let client; afterEach(async () => { @@ -72,7 +74,7 @@ describe('class MongoDBResponse', () => { ); }); -describe('parsing of utf8-invalid documents with cursors', function () { +describe('parsing of utf8-invalid documents with cursors', runOnlyInNodeMetadata, function () { let client: MongoClient; let collection: Collection; const compressionPredicate = () => diff --git a/test/mongodb_bundled.ts b/test/mongodb_bundled.ts index 0667751a780..307a9e015c2 100644 --- a/test/mongodb_bundled.ts +++ b/test/mongodb_bundled.ts @@ -109,6 +109,7 @@ export const { decompress, decorateWithExplain, DEFAULT_ALLOWED_HOSTS, + DEFAULT_KEEP_ALIVE_INITIAL_DELAY_MS, DEFAULT_MAX_DOCUMENT_LENGTH, DEFAULT_PK_FACTORY, DeleteManyOperation, @@ -176,6 +177,7 @@ export const { ListIndexesOperation, Long, makeClientMetadata, + makeSocket, MAX_SUPPORTED_SERVER_VERSION, MAX_SUPPORTED_WIRE_VERSION, MaxKey, @@ -421,6 +423,7 @@ export type { import type { AbstractCursor as _AbstractCursor, AuthMechanism as _AuthMechanism, + Binary as _Binary, ChangeStream as _ChangeStream, ClientEncryption as _ClientEncryption, ClientSession as _ClientSession, @@ -482,6 +485,7 @@ import type { // To re-sort: Highlight the list and use VSCode's "Sort Lines Ascending" command export type AbstractCursor = _AbstractCursor; export type AuthMechanism = _AuthMechanism; +export type Binary = _Binary; export type ChangeStream = _ChangeStream; export type ClientEncryption = _ClientEncryption; export type ClientSession = _ClientSession; diff --git a/test/tools/runner/metadata_ui.js b/test/tools/runner/metadata_ui.js index 4c945d56c1a..2063cabadf7 100644 --- a/test/tools/runner/metadata_ui.js +++ b/test/tools/runner/metadata_ui.js @@ -102,7 +102,8 @@ module.exports = Mocha.interfaces.metadata_ui = function (suite) { newSuite.parent._onlySuites = newSuite.parent._onlySuites.concat(newSuite); mocha.options.hasOnly = true; } - newSuite.metadata = testData.metadata || {}; + // Inherit parent metadata if metadata is not explicitly provided + newSuite.metadata = testData.metadata || suites[1]?.metadata || {}; if (typeof testData.fn === 'function') { testData.fn.call(newSuite); suites.shift(); diff --git a/test/tools/runner/vm_context_helper.ts b/test/tools/runner/vm_context_helper.ts index 17f1cdc3d88..5ceb1068e03 100644 --- a/test/tools/runner/vm_context_helper.ts +++ b/test/tools/runner/vm_context_helper.ts @@ -11,7 +11,6 @@ const allowedModules = new Set([ '@aws-sdk/credential-providers', '@mongodb-js/saslprep', '@mongodb-js/zstd', - 'bson', 'gcp-metadata', 'kerberos', 'mongodb-client-encryption', @@ -27,7 +26,6 @@ const exposedGlobals = new Set([ 'AbortController', 'AbortSignal', 'BigInt', - 'Buffer', 'Date', 'Error', 'Headers', @@ -42,8 +40,9 @@ const exposedGlobals = new Set([ 'console', 'crypto', 'performance', - 'process', + 'atob', + 'btoa', 'clearImmediate', 'clearInterval', 'clearTimeout', @@ -68,7 +67,9 @@ function createRestrictedRequire() { if (shouldBlock) { throw new Error(`Access to core module '${moduleName}' is restricted in this context`); } - return require(moduleName); + + const required = require(moduleName); + return required; } as NodeJS.Require; } @@ -80,7 +81,13 @@ const context = { // Needed for some modules global: undefined as any, - globalThis: undefined as any + globalThis: undefined as any, + + // These are needed for webByteUtils and are not available in the browser. + atob: undefined, + btoa: undefined, + TextEncoder: undefined, + TextDecoder: undefined }; // Expose allowed globals in the context @@ -90,12 +97,31 @@ for (const globalName of exposedGlobals) { } } +// Ensure TextEncoder/TextDecoder are always available (needed for webByteUtils) +if (!context.TextEncoder && typeof TextEncoder !== 'undefined') { + context.TextEncoder = TextEncoder; +} +if (!context.TextDecoder && typeof TextDecoder !== 'undefined') { + context.TextDecoder = TextDecoder; +} + +// Ensure btoa/atob are available (needed for webByteUtils base64 encoding) +if (!context.btoa && typeof btoa !== 'undefined') { + context.btoa = btoa; +} +if (!context.atob && typeof atob !== 'undefined') { + context.atob = atob; +} + // Create a sandbox context with necessary globals const sandbox = vm.createContext(context); // Make globalThis point to the sandbox sandbox.globalThis = sandbox; +// Export the sandbox for use in tests +export { sandbox }; + /** * Load the bundled MongoDB driver module in a VM context * This allows us to control the globals that the driver has access to diff --git a/test/tools/unified-spec-runner/operations.ts b/test/tools/unified-spec-runner/operations.ts index d0bf5969e79..de7f45a6f53 100644 --- a/test/tools/unified-spec-runner/operations.ts +++ b/test/tools/unified-spec-runner/operations.ts @@ -3,6 +3,7 @@ import { Readable } from 'node:stream'; import { pipeline } from 'node:stream/promises'; +import { ByteUtils } from 'bson'; import { AssertionError, expect } from 'chai'; import * as process from 'process'; @@ -125,7 +126,9 @@ operations.set('assertDifferentLsidOnLastTwoCommands', async ({ entities, operat expect(last.command).to.have.property('lsid'); expect(secondLast.command).to.have.property('lsid'); - expect(last.command.lsid.id.buffer.equals(secondLast.command.lsid.id.buffer)).to.be.false; + expect( + ByteUtils.compare(last.command.lsid.id.buffer, secondLast.command.lsid.id.buffer) + ).to.not.equal(0); }); operations.set('assertSameLsidOnLastTwoCommands', async ({ entities, operation }) => { @@ -144,7 +147,9 @@ operations.set('assertSameLsidOnLastTwoCommands', async ({ entities, operation } expect(last.command).to.have.property('lsid'); expect(secondLast.command).to.have.property('lsid'); - expect(last.command.lsid.id.buffer.equals(secondLast.command.lsid.id.buffer)).to.be.true; + expect( + ByteUtils.compare(last.command.lsid.id.buffer, secondLast.command.lsid.id.buffer) + ).to.equal(0); }); operations.set('assertSessionDirty', async ({ operation }) => { diff --git a/test/tools/utils.ts b/test/tools/utils.ts index 7a77b82a73d..5fecb7cf07d 100644 --- a/test/tools/utils.ts +++ b/test/tools/utils.ts @@ -21,6 +21,7 @@ import { OP_MSG, processTimeMS, resolveRuntimeAdapters, + runNodelessTests, type Runtime, type ServerApiVersion, Topology, @@ -623,3 +624,19 @@ export function configureMongocryptdSpawnHooks( * A `Runtime` that resolves to entirely Nodejs modules, useful when tests must provide a default `runtime` object to an API. */ export const runtime: Runtime = resolveRuntimeAdapters({}); + +/** + * Metadata that can be used to skip tests in nodeless environments. + * Use this for tests that rely on Node.js-specific features and cannot be run in nodeless environments like Deno or the browser. + */ +export const runOnlyInNodeMetadata: MongoDBMetadataUI = { + requires: { + predicate: _test => { + if (runNodelessTests) { + return 'Test is a node specific test and should be not be run in nodeless environments'; + } else { + return true; + } + } + } +}; diff --git a/test/unit/client-side-encryption/state_machine.test.ts b/test/unit/client-side-encryption/state_machine.test.ts index 0060954c31a..76f06849a83 100644 --- a/test/unit/client-side-encryption/state_machine.test.ts +++ b/test/unit/client-side-encryption/state_machine.test.ts @@ -15,6 +15,7 @@ import { Db, type FindOptions, MongoClient, + runNodelessTests, squashError, StateMachine } from '../../mongodb'; @@ -60,6 +61,10 @@ describe('StateMachine', function () { let clientStub; beforeEach(function () { + if (runNodelessTests) { + // sinon doesn't work in nodeless tests + this.skip(); + } this.sinon = sinon.createSandbox(); runCommandStub = this.sinon.stub().resolves({}); dbStub = this.sinon.createStubInstance(Db, { diff --git a/test/unit/cmap/commands.test.ts b/test/unit/cmap/commands.test.ts index 3bd13de98f6..b21ee4a4d47 100644 --- a/test/unit/cmap/commands.test.ts +++ b/test/unit/cmap/commands.test.ts @@ -1,8 +1,15 @@ import * as BSON from 'bson'; import { expect } from 'chai'; +// eslint-disable-next-line @typescript-eslint/no-restricted-imports +import { readInt32LE } from '../../../src/bson'; import { DocumentSequence, OpMsgRequest, OpReply } from '../../mongodb'; +// Helper to decode UTF-8 string from Uint8Array +function utf8Slice(buffer: Uint8Array, start: number, end: number): string { + return BSON.ByteUtils.toUTF8(buffer, start, end, false); +} + describe('commands', function () { describe('OpMsgRequest', function () { describe('#toBin', function () { @@ -41,12 +48,12 @@ describe('commands', function () { it('sets the length of the document sequence', function () { // Bytes starting at index 1 is a 4 byte length. - expect(buffers[3].readInt32LE(1)).to.equal(25); + expect(readInt32LE(buffers[3], 1)).to.equal(25); }); it('sets the name of the first field to be replaced', function () { // Bytes starting at index 5 is the field name. - expect(buffers[3].toString('utf8', 5, 10)).to.equal('field'); + expect(utf8Slice(buffers[3], 5, 10)).to.equal('field'); }); }); @@ -81,12 +88,12 @@ describe('commands', function () { it('sets the length of the first document sequence', function () { // Bytes starting at index 1 is a 4 byte length. - expect(buffers[3].readInt32LE(1)).to.equal(28); + expect(readInt32LE(buffers[3], 1)).to.equal(28); }); it('sets the name of the first field to be replaced', function () { // Bytes starting at index 5 is the field name. - expect(buffers[3].toString('utf8', 5, 13)).to.equal('fieldOne'); + expect(utf8Slice(buffers[3], 5, 13)).to.equal('fieldOne'); }); it('sets the document sequence sections second type to 1', function () { @@ -96,12 +103,12 @@ describe('commands', function () { it('sets the length of the second document sequence', function () { // Bytes starting at index 1 is a 4 byte length. - expect(buffers[3].readInt32LE(30)).to.equal(28); + expect(readInt32LE(buffers[3], 30)).to.equal(28); }); it('sets the name of the second field to be replaced', function () { // Bytes starting at index 33 is the field name. - expect(buffers[3].toString('utf8', 34, 42)).to.equal('fieldTwo'); + expect(utf8Slice(buffers[3], 34, 42)).to.equal('fieldTwo'); }); }); }); diff --git a/test/unit/cmap/connect.test.ts b/test/unit/cmap/connect.test.ts index 2920899e8cd..d42267487e9 100644 --- a/test/unit/cmap/connect.test.ts +++ b/test/unit/cmap/connect.test.ts @@ -21,7 +21,8 @@ import { MongoClientAuthProviders, MongoCredentials, MongoNetworkError, - prepareHandshakeDocument + prepareHandshakeDocument, + runNodelessTests } from '../../mongodb'; import { genClusterTime } from '../../tools/common'; import * as mock from '../../tools/mongodb-mock/index'; @@ -467,6 +468,11 @@ describe('Connect Tests', function () { ); before(function (done) { + if (runNodelessTests) { + // sinon doesn't work in nodeless tests + this.skip(); + } + // @SECLEVEL=0 allows the legacy test certificate (signed with SHA-1/1024-bit RSA) // to be accepted by OpenSSL 3.x, which rejects at the default security level. tlsServer = tls.createServer( diff --git a/test/unit/cmap/wire_protocol/on_demand/document.test.ts b/test/unit/cmap/wire_protocol/on_demand/document.test.ts index 2ccdac05972..34e337f85a7 100644 --- a/test/unit/cmap/wire_protocol/on_demand/document.test.ts +++ b/test/unit/cmap/wire_protocol/on_demand/document.test.ts @@ -1,7 +1,8 @@ import { Binary, BSON, BSONError, BSONType, ObjectId, Timestamp } from 'bson'; import { expect } from 'chai'; -import { OnDemandDocument } from '../../../../mongodb'; +import { OnDemandDocument, runNodelessTests } from '../../../../mongodb'; +import { ensureTypeByName } from '../../../../tools/utils'; describe('class OnDemandDocument', () => { context('when given an empty BSON sequence', () => { @@ -124,15 +125,34 @@ describe('class OnDemandDocument', () => { }); it('throws if required is set to true and element name does not exist', () => { - expect(() => document.get('blah!', BSONType.bool, true)).to.throw(BSONError); + if (runNodelessTests) { + try { + document.get('blah!', BSONType.bool, true); + expect.fail('expected exception'); + } catch (e) { + ensureTypeByName(e, 'BSONError'); + } + } else { + expect(() => document.get('blah!', BSONType.bool, true)).to.throw(BSONError); + } expect(document).to.have.nested.property('cache.blah!', false); }); it('throws if requested type is unsupported', () => { - expect(() => { - // @ts-expect-error: checking a bad BSON type - document.get('unsupportedType', BSONType.regex); - }).to.throw(BSONError, /unsupported/i); + if (runNodelessTests) { + try { + // @ts-expect-error: checking a bad BSON type + document.get('unsupportedType', BSONType.regex, true); + expect.fail('expected exception'); + } catch (e) { + ensureTypeByName(e, 'BSONError'); + } + } else { + expect(() => { + // @ts-expect-error: checking a bad BSON type + document.get('unsupportedType', BSONType.regex); + }).to.throw(BSONError, /unsupported/i); + } }); it('caches the value', () => { @@ -245,15 +265,32 @@ describe('class OnDemandDocument', () => { }); it('throws if required is set to true and element name does not exist', () => { - expect(() => document.getNumber('blah!', true)).to.throw(BSONError); + if (runNodelessTests) { + try { + document.getNumber('blah!', true); + expect.fail('expected exception'); + } catch (e) { + ensureTypeByName(e, 'BSONError'); + } + } else { + expect(() => document.getNumber('blah!', true)).to.throw(BSONError); + } }); it('throws if required is set to true and element is not numeric', () => { // just making sure this test does not fail for the non-exist reason expect(document.has('string')).to.be.true; - expect(() => { - document.getNumber('string', true); - }).to.throw(BSONError); + if (runNodelessTests) { + try { + document.getNumber('string', true); + } catch (e) { + ensureTypeByName(e, 'BSONError'); + } + } else { + expect(() => { + document.getNumber('string', true); + }).to.throw(BSONError); + } }); it('returns null if required is set to false and element does not exist', () => { diff --git a/test/unit/commands.test.ts b/test/unit/commands.test.ts index 4bda290ac3b..7d6c7d57403 100644 --- a/test/unit/commands.test.ts +++ b/test/unit/commands.test.ts @@ -1,6 +1,8 @@ import { expect } from 'chai'; import * as sinon from 'sinon'; +// eslint-disable-next-line @typescript-eslint/no-restricted-imports +import { readInt32LE, readUint8 } from '../../src/bson'; // eslint-disable-next-line @typescript-eslint/no-restricted-imports import * as compression from '../../src/cmap/wire_protocol/compression'; import { @@ -62,25 +64,25 @@ describe('class OpCompressedRequest', () => { const messageHeader = compressedCommand[0]; expect(messageHeader.byteLength, 'message header is incorrect length').to.equal(16); expect( - messageHeader.readInt32LE(), + readInt32LE(messageHeader, 0), 'message header reports incorrect message length' ).to.equal(16 + 9 + expectedCompressedCommand.length); - expect(messageHeader.readInt32LE(4), 'requestId incorrect').to.equal(1); - expect(messageHeader.readInt32LE(8), 'responseTo incorrect').to.equal(0); - expect(messageHeader.readInt32LE(12), 'opcode is not OP_COMPRESSED').to.equal(2012); + expect(readInt32LE(messageHeader, 4), 'requestId incorrect').to.equal(1); + expect(readInt32LE(messageHeader, 8), 'responseTo incorrect').to.equal(0); + expect(readInt32LE(messageHeader, 12), 'opcode is not OP_COMPRESSED').to.equal(2012); }); it('constructs the compression details for the request', async () => { const compressionDetails = compressedCommand[1]; expect(compressionDetails.byteLength, 'incorrect length').to.equal(9); - expect(compressionDetails.readInt32LE(), 'op code incorrect').to.equal( + expect(readInt32LE(compressionDetails, 0), 'op code incorrect').to.equal( protocol === OpMsgRequest ? OP_MSG : OP_QUERY ); expect( - compressionDetails.readInt32LE(4), + readInt32LE(compressionDetails, 4), 'uncompressed message length incorrect' ).to.equal(serializedFindCommand.length - 16); - expect(compressionDetails.readUint8(8), 'compressor incorrect').to.equal( + expect(readUint8(compressionDetails, 8), 'compressor incorrect').to.equal( Compressor['snappy'] ); }); @@ -106,7 +108,7 @@ describe('class OpCompressedRequest', () => { zlibCompressionLevel: 3 }).toBin(); - expect(messageHeader.readInt32LE(12), 'opcode is not OP_COMPRESSED').to.equal(2012); + expect(readInt32LE(messageHeader, 12), 'opcode is not OP_COMPRESSED').to.equal(2012); expect(spy).to.have.been.called; diff --git a/test/unit/nodeless.test.ts b/test/unit/nodeless.test.ts index 655c65cdd71..847c08c67aa 100644 --- a/test/unit/nodeless.test.ts +++ b/test/unit/nodeless.test.ts @@ -2,6 +2,7 @@ import { expect } from 'chai'; import { env } from 'process'; import { runNodelessTests } from '../mongodb'; +import { sandbox } from '../tools/runner/vm_context_helper'; describe('Nodeless tests', function () { it('runNodelessTests variable should match env vars', function () { @@ -14,4 +15,10 @@ describe('Nodeless tests', function () { " run 'npm run build:runtime-barrel' to update the barrel file" ); }); + + it('sandbox should not have node-specific properties', function () { + if (!runNodelessTests) this.skip(); + expect(typeof (sandbox as any).process).to.equal('undefined'); + expect(typeof (sandbox as any).Buffer).to.equal('undefined'); + }); }); diff --git a/test/unit/sessions.test.ts b/test/unit/sessions.test.ts index f6b3a09f17d..b14edbb4ac5 100644 --- a/test/unit/sessions.test.ts +++ b/test/unit/sessions.test.ts @@ -9,11 +9,13 @@ import { MongoClient, MongoRuntimeError, processTimeMS, + runNodelessTests, ServerSession, ServerSessionPool } from '../mongodb'; import { genClusterTime } from '../tools/common'; import * as mock from '../tools/mongodb-mock/index'; +import { ensureTypeByName } from '../tools/utils'; describe('Sessions - unit', function () { let client; @@ -463,7 +465,11 @@ describe('Sessions - unit', function () { expect(result).to.not.exist; expect(command).to.have.property('lsid'); - expect(command).to.have.property('txnNumber').instanceOf(Long); + if (runNodelessTests) { + ensureTypeByName(command.txnNumber, '_Long'); + } else { + expect(command).to.have.property('txnNumber').instanceOf(Long); + } expect(command.txnNumber.toNumber()).to.equal(3); expect(session).to.have.property('_serverSession').that.is.instanceOf(ServerSession); }); diff --git a/test/unit/utils.test.ts b/test/unit/utils.test.ts index 130c7a719c0..cb98f69dc3f 100644 --- a/test/unit/utils.test.ts +++ b/test/unit/utils.test.ts @@ -23,6 +23,7 @@ import { MongoDBNamespace, MongoInvalidArgumentError, MongoRuntimeError, + runNodelessTests, shuffle } from '../mongodb'; import { ensureTypeByName, sleep } from '../tools/utils'; @@ -940,9 +941,19 @@ describe('driver utils', function () { for (const { oid1, oid2, result } of table) { if (result === 'throws') { - it('passing non-objectId values throw', () => - // @ts-expect-error: Passing bad values to ensure thrown error - expect(() => compareObjectId(oid1, oid2)).to.throw()); + if (runNodelessTests) { + // web version of ByteUtils.compare doesn't throw on non-Buffer inputs, it just returns 0 + // TODO: NODE-7576 - compareObjectId has different behavior in web and node versions + it('passing non-objectId values returns 0', () => { + // @ts-expect-error: Passing bad values to ensure thrown error + expect(compareObjectId(oid1, oid2)).to.equal(0); + }); + } else { + it('passing non-objectId values throw', () => { + // @ts-expect-error: Passing bad values to ensure thrown error + expect(() => compareObjectId(oid1, oid2)).to.throw(); + }); + } continue; }