Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions etc/bundle-driver.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
6 changes: 6 additions & 0 deletions src/bson.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 => {
Comment thread
tadjik1 marked this conversation as resolved.
validateBufferInputs(buffer, offset, 1);
return buffer[offset];
};

export const setUint32LE = (destination: Uint8Array, offset: number, value: number): 4 => {
destination[offset] = value;
value >>>= 8;
Expand Down
11 changes: 5 additions & 6 deletions src/cmap/auth/scram.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)}`;
Expand Down Expand Up @@ -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++) {
Expand Down
11 changes: 8 additions & 3 deletions test/integration/change-streams/change_stream.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down Expand Up @@ -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);
}
}
});
});
Expand Down
4 changes: 2 additions & 2 deletions test/integration/client-side-encryption/driver.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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))]
}
}
}
Expand Down
7 changes: 4 additions & 3 deletions test/integration/crud/insert.test.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
});
Expand Down
3 changes: 2 additions & 1 deletion test/integration/node-specific/bson-options/raw.test.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand Down Expand Up @@ -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 = () =>
Expand Down
4 changes: 4 additions & 0 deletions test/mongodb_bundled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -176,6 +177,7 @@ export const {
ListIndexesOperation,
Long,
makeClientMetadata,
makeSocket,
MAX_SUPPORTED_SERVER_VERSION,
MAX_SUPPORTED_WIRE_VERSION,
MaxKey,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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;
Expand Down
3 changes: 2 additions & 1 deletion test/tools/runner/metadata_ui.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
36 changes: 31 additions & 5 deletions test/tools/runner/vm_context_helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -27,7 +26,6 @@ const exposedGlobals = new Set([
'AbortController',
'AbortSignal',
'BigInt',
'Buffer',
'Date',
'Error',
'Headers',
Expand All @@ -42,8 +40,9 @@ const exposedGlobals = new Set([
'console',
'crypto',
'performance',
'process',

'atob',
'btoa',
'clearImmediate',
'clearInterval',
'clearTimeout',
Expand All @@ -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;
}

Expand All @@ -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
Expand All @@ -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
Expand Down
9 changes: 7 additions & 2 deletions test/tools/unified-spec-runner/operations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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 }) => {
Expand All @@ -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 }) => {
Expand Down
17 changes: 17 additions & 0 deletions test/tools/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
OP_MSG,
processTimeMS,
resolveRuntimeAdapters,
runNodelessTests,
type Runtime,
type ServerApiVersion,
Topology,
Expand Down Expand Up @@ -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;
}
}
}
};
5 changes: 5 additions & 0 deletions test/unit/client-side-encryption/state_machine.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
Db,
type FindOptions,
MongoClient,
runNodelessTests,
squashError,
StateMachine
} from '../../mongodb';
Expand Down Expand Up @@ -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, {
Expand Down
Loading
Loading