Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
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(',');
Comment thread
addaleax marked this conversation as resolved.

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
6 changes: 4 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,16 @@ describe('CSOT', function () {
'test.test': {
bsonType: 'object',
encryptMetadata: {
keyId: [new UUID(dataKey)]
// NODE-7581: should be able to pass dataKey as-is, as UUID, w/o needing toHexString
keyId: [new UUID(dataKey.toHexString(true))]
},
properties: {
a: {
encrypt: {
bsonType: 'int',
algorithm: 'AEAD_AES_256_CBC_HMAC_SHA_512-Random',
keyId: [new UUID(dataKey)]
// NODE-7581: should be able to pass dataKey as-is, as UUID, w/o needing toHexString
keyId: [new UUID(dataKey.toHexString(true))]
Comment thread
addaleax marked this conversation as resolved.
}
}
}
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
12 changes: 8 additions & 4 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 Down Expand Up @@ -96,6 +97,9 @@ 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
19 changes: 13 additions & 6 deletions test/unit/cmap/commands.test.ts
Original file line number Diff line number Diff line change
@@ -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 () {
Expand Down Expand Up @@ -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');
});
});

Expand Down Expand Up @@ -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 () {
Expand All @@ -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');
});
});
});
Expand Down
Loading
Loading