Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -149,14 +149,6 @@ describe('Client Side Encryption (Unified)', function () {
if (typeof shouldSkip === 'string') return shouldSkip;
}

const flakyTests = {
'rewrap to azure:name1': 'TODO(NODE-6860): fix flaky tests'
};

const skipReason = flakyTests[description];

if (skipReason) return skipReason;

return isServerless ? 'Unified CSFLE tests to not run on serverless' : false;
}
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,40 +146,37 @@ describe('Server Discovery and Monitoring Prose Tests', function () {
await client.close();
});

it.skip(
'ensure monitors properly create and unpause connection pools when they discover servers',
{
metadata: { requires: { mongodb: '>=4.2.9', topology: '!load-balanced' } },
test: async function () {
await client.connect();
expect(events.shift()).to.equal(SERVER_HEARTBEAT_SUCCEEDED);
expect(events.shift()).to.equal(CONNECTION_POOL_READY);

expect(events).to.be.empty;

const heartBeatFailedEvent = once(client, SERVER_HEARTBEAT_FAILED);
await client.db('admin').command({
configureFailPoint: 'failCommand',
mode: { times: 2 },
data: {
failCommands: ['hello'],
errorCode: 1234,
appName: 'SDAMPoolManagementTest'
}
});
await heartBeatFailedEvent;
expect(events.shift()).to.equal(SERVER_HEARTBEAT_FAILED);
expect(events.shift()).to.equal(CONNECTION_POOL_CLEARED);

expect(events).to.be.empty;

await once(client, SERVER_HEARTBEAT_SUCCEEDED);
expect(events.shift()).to.equal(SERVER_HEARTBEAT_SUCCEEDED);
expect(events.shift()).to.equal(CONNECTION_POOL_READY);

expect(events).to.be.empty;
}
it('ensure monitors properly create and unpause connection pools when they discover servers', {
metadata: { requires: { mongodb: '>=4.2.9', topology: '!load-balanced' } },
test: async function () {
await client.connect();
expect(events.shift()).to.equal(SERVER_HEARTBEAT_SUCCEEDED);
expect(events.shift()).to.equal(CONNECTION_POOL_READY);

expect(events).to.be.empty;

const heartBeatFailedEvent = once(client, SERVER_HEARTBEAT_FAILED);
await client.db('admin').command({
configureFailPoint: 'failCommand',
mode: { times: 2 },
data: {
failCommands: ['hello'],
errorCode: 1234,
appName: 'SDAMPoolManagementTest'
}
});
await heartBeatFailedEvent;
expect(events.shift()).to.equal(SERVER_HEARTBEAT_FAILED);
expect(events.shift()).to.equal(CONNECTION_POOL_CLEARED);

expect(events).to.be.empty;

await once(client, SERVER_HEARTBEAT_SUCCEEDED);
expect(events.shift()).to.equal(SERVER_HEARTBEAT_SUCCEEDED);
expect(events.shift()).to.equal(CONNECTION_POOL_READY);

expect(events).to.be.empty;
}
).skipReason = 'TODO(NODE-5206): fix flaky test';
});
});
});
66 changes: 66 additions & 0 deletions test/tools/runner/config.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,27 @@
import * as util from 'node:util';
import * as types from 'node:util/types';

import { expect } from 'chai';
import { type Context } from 'mocha';
import ConnectionString from 'mongodb-connection-string-url';
import * as qs from 'querystring';
import * as url from 'url';

import {
type AuthMechanism,
Double,
HostAddress,
Long,
MongoClient,
type MongoClientOptions,
ObjectId,
type ServerApi,
TopologyType,
type WriteConcernSettings
} from '../../mongodb';
import { getEnvironmentalOptions } from '../utils';
import { type Filter } from './filters/filter';
import { flakyTests } from './flaky';

interface ProxyParams {
proxyHost?: string;
Expand Down Expand Up @@ -205,6 +213,11 @@ export class TestConfiguration {

newClient(urlOrQueryOptions?: string | Record<string, any>, serverOptions?: MongoClientOptions) {
serverOptions = Object.assign({}, getEnvironmentalOptions(), serverOptions);

if (this.loggingEnabled && !Object.hasOwn(serverOptions, 'mongodbLogPath')) {
serverOptions = this.setupLogging(serverOptions);
}

// Support MongoClient constructor form (url, options) for `newClient`.
if (typeof urlOrQueryOptions === 'string') {
if (Reflect.has(serverOptions, 'host') || Reflect.has(serverOptions, 'port')) {
Expand Down Expand Up @@ -427,6 +440,59 @@ export class TestConfiguration {
makeAtlasTestConfiguration(): AtlasTestConfiguration {
return new AtlasTestConfiguration(this.uri, this.context);
}

loggingEnabled = false;
logs = [];
/**
* Known flaky tests that we want to turn on logging for
* so that we can get a better idea of what is failing when it fails
*/
testsToEnableLogging = flakyTests;

setupLogging(options: MongoClientOptions, id?: string) {
id ??= new ObjectId().toString();
this.logs = [];
const write = log => this.logs.push({ t: log.t, id, ...log });
options.mongodbLogPath = { write };
options.mongodbLogComponentSeverities = { default: 'trace' };
options.mongodbLogMaxDocumentLength = 300;
return options;
}

beforeEachLogging(ctx: Context) {
this.loggingEnabled = this.testsToEnableLogging.includes(ctx.currentTest.fullTitle());
}

afterEachLogging(ctx: Context) {
if (this.loggingEnabled && ctx.currentTest.state === 'failed') {
for (const log of this.logs) {
console.error(
JSON.stringify(
log,
function (_, value) {
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 === '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() };
}
if (Buffer.isBuffer(value))
return { [value.constructor.name]: Buffer.prototype.base64Slice.call(value) };
if (value === undefined) return { undefined: 'key was set but equal to undefined' };
return value;
},
0
)
);
}
}
this.loggingEnabled = false;
this.logs = [];
}
}

/**
Expand Down
31 changes: 31 additions & 0 deletions test/tools/runner/flaky.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
export const flakyTests = [
'Client Side Encryption (Unified) namedKMS-rewrapManyDataKey rewrap to azure:name1',
'Server Discovery and Monitoring Prose Tests Connection Pool Management ensure monitors properly create and unpause connection pools when they discover servers',
'CSOT spec tests legacy timeouts behave correctly for retryable operations operation succeeds after one socket timeout - aggregate on collection',
'Client Side Encryption Prose Tests 16. Rewrap Case 1: Rewrap with separate ClientEncryption should rewrap data key from aws to aws',
'Client Side Encryption Prose Tests 16. Rewrap Case 1: Rewrap with separate ClientEncryption should rewrap data key from aws to azure',
'Client Side Encryption Prose Tests 16. Rewrap Case 1: Rewrap with separate ClientEncryption should rewrap data key from aws to gcp',
'Client Side Encryption Prose Tests 16. Rewrap Case 1: Rewrap with separate ClientEncryption should rewrap data key from aws to kmip',
'Client Side Encryption Prose Tests 16. Rewrap Case 1: Rewrap with separate ClientEncryption should rewrap data key from aws to local',
'Client Side Encryption Prose Tests 16. Rewrap Case 1: Rewrap with separate ClientEncryption should rewrap data key from azure to aws',
'Client Side Encryption Prose Tests 16. Rewrap Case 1: Rewrap with separate ClientEncryption should rewrap data key from azure to azure',
'Client Side Encryption Prose Tests 16. Rewrap Case 1: Rewrap with separate ClientEncryption should rewrap data key from azure to gcp',
'Client Side Encryption Prose Tests 16. Rewrap Case 1: Rewrap with separate ClientEncryption should rewrap data key from azure to kmip',
'Client Side Encryption Prose Tests 16. Rewrap Case 1: Rewrap with separate ClientEncryption should rewrap data key from azure to local',
'Client Side Encryption Prose Tests 16. Rewrap Case 1: Rewrap with separate ClientEncryption should rewrap data key from gcp to aws',
'Client Side Encryption Prose Tests 16. Rewrap Case 1: Rewrap with separate ClientEncryption should rewrap data key from gcp to azure',
'Client Side Encryption Prose Tests 16. Rewrap Case 1: Rewrap with separate ClientEncryption should rewrap data key from gcp to gcp',
'Client Side Encryption Prose Tests 16. Rewrap Case 1: Rewrap with separate ClientEncryption should rewrap data key from gcp to kmip',
'Client Side Encryption Prose Tests 16. Rewrap Case 1: Rewrap with separate ClientEncryption should rewrap data key from gcp to local',
'Client Side Encryption Prose Tests 16. Rewrap Case 1: Rewrap with separate ClientEncryption should rewrap data key from kmip to aws',
'Client Side Encryption Prose Tests 16. Rewrap Case 1: Rewrap with separate ClientEncryption should rewrap data key from kmip to azure',
'Client Side Encryption Prose Tests 16. Rewrap Case 1: Rewrap with separate ClientEncryption should rewrap data key from kmip to gcp',
'Client Side Encryption Prose Tests 16. Rewrap Case 1: Rewrap with separate ClientEncryption should rewrap data key from kmip to kmip',
'Client Side Encryption Prose Tests 16. Rewrap Case 1: Rewrap with separate ClientEncryption should rewrap data key from kmip to local',
'Client Side Encryption Prose Tests 16. Rewrap Case 1: Rewrap with separate ClientEncryption should rewrap data key from local to aws',
'Client Side Encryption Prose Tests 16. Rewrap Case 1: Rewrap with separate ClientEncryption should rewrap data key from local to azure',
'Client Side Encryption Prose Tests 16. Rewrap Case 1: Rewrap with separate ClientEncryption should rewrap data key from local to gcp',
'Client Side Encryption Prose Tests 16. Rewrap Case 1: Rewrap with separate ClientEncryption should rewrap data key from local to kmip',
'Client Side Encryption Prose Tests 16. Rewrap Case 1: Rewrap with separate ClientEncryption should rewrap data key from local to local',
'Client Side Encryption Prose Tests 16. Rewrap Case 2: RewrapManyDataKeyOpts.provider is not optional when provider field is missing raises an error'
];
14 changes: 13 additions & 1 deletion test/tools/runner/hooks/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { NodeVersionFilter } from '../filters/node_version_filter';
import { OSFilter } from '../filters/os_filter';
import { ServerlessFilter } from '../filters/serverless_filter';
import { type Filter } from '../filters/filter';
import { type Context } from 'mocha';

// Default our tests to have auth enabled
// A better solution will be tackled in NODE-3714
Expand Down Expand Up @@ -211,8 +212,19 @@ const beforeAllPluginImports = () => {
require('mocha-sinon');
};

async function beforeEachLogging(this: Context) {
if (this.currentTest == null) return;
this.configuration.beforeEachLogging(this);
}

async function afterEachLogging(this: Context) {
if (this.currentTest == null) return;
this.configuration.afterEachLogging(this);
}

export const mochaHooks = {
beforeAll: [beforeAllPluginImports, testConfigBeforeHook],
beforeEach: [testSkipBeforeEachHook],
beforeEach: [testSkipBeforeEachHook, beforeEachLogging],
afterEach: [afterEachLogging],
afterAll: [cleanUpMocksAfterHook]
};
59 changes: 37 additions & 22 deletions test/tools/unified-spec-runner/entities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
type HostAddress,
type Log,
MongoClient,
type MongoClientOptions,
type MongoCredentials,
ReadConcern,
ReadPreference,
Expand Down Expand Up @@ -126,7 +127,7 @@ export class UnifiedMongoClient extends MongoClient {
cmapEvents: CmapEvent[] = [];
sdamEvents: SdamEvent[] = [];
failPoints: Document[] = [];
logCollector: { buffer: LogMessage[]; write: (log: Log) => void };
logCollector?: { buffer: LogMessage[]; write: (log: Log) => void };

ignoredEvents: string[];
observeSensitiveCommands: boolean;
Expand Down Expand Up @@ -197,32 +198,45 @@ export class UnifiedMongoClient extends MongoClient {
topology: 'MONGODB_LOG_TOPOLOGY'
} as const;

constructor(uri: string, description: ClientEntity) {
const logCollector: { buffer: LogMessage[]; write: (log: Log) => void } = {
buffer: [],
write(log: Log): void {
const transformedLog = {
level: log.s,
component: log.c,
data: { ...log }
};

this.buffer.push(transformedLog);
}
};
const mongodbLogComponentSeverities = description.observeLogMessages;

super(uri, {
constructor(
uri: string,
description: ClientEntity,
config: {
loggingEnabled?: boolean;
setupLogging?: (options: Record<string, any>, id: string) => Record<string, any>;
}
) {
const options: MongoClientOptions = {
monitorCommands: true,
__skipPingOnConnect: true,
mongodbLogComponentSeverities,
...getEnvironmentalOptions(),
...(description.serverApi ? { serverApi: description.serverApi } : {}),
mongodbLogPath: logCollector,
// TODO(NODE-5785): We need to increase the truncation length because signature.hash is a Buffer making hellos too long
mongodbLogMaxDocumentLength: 1250
} as any);
};

let logCollector: { buffer: LogMessage[]; write: (log: Log) => void } | undefined;

if (description.observeLogMessages != null) {
options.mongodbLogComponentSeverities = description.observeLogMessages;
logCollector = {
buffer: [],
write(log: Log): void {
const transformedLog = {
level: log.s,
component: log.c,
data: { ...log }
};

this.buffer.push(transformedLog);
}
};
options.mongodbLogPath = logCollector;
} else if (config.loggingEnabled) {
config.setupLogging?.(options, description.id);
}

super(uri, options);
this.observedEventEmitter.on('error', () => null);
this.logCollector = logCollector;
this.observeSensitiveCommands = description.observeSensitiveCommands ?? false;
Expand Down Expand Up @@ -337,7 +351,7 @@ export class UnifiedMongoClient extends MongoClient {
}

get collectedLogs(): LogMessage[] {
return this.logCollector.buffer;
return this.logCollector?.buffer ?? [];
}
}

Expand Down Expand Up @@ -578,7 +592,8 @@ export class EntitiesMap<E = Entity> extends Map<string, E> {
} else {
uri = makeConnectionString(config.url({ useMultipleMongoses }), entity.client.uriOptions);
}
const client = new UnifiedMongoClient(uri, entity.client);

const client = new UnifiedMongoClient(uri, entity.client, config);
try {
new EntityEventRegistry(client, entity.client, map).register();
await client.connect();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ describe('EntityEventRegistry', function () {
};
const entitesMap = new EntitiesMap();
const uri = 'mongodb://127.0.0.1:27017';
const client = new UnifiedMongoClient(uri, clientEntity);
const client = new UnifiedMongoClient(uri, clientEntity, {});
const registry = new EntityEventRegistry(client, clientEntity, entitesMap);

before(function () {
Expand Down Expand Up @@ -120,7 +120,7 @@ describe('EntityEventRegistry', function () {
const clientEntity = { id: 'client0' };
const entitesMap = new EntitiesMap();
const uri = 'mongodb://127.0.0.1:27017';
const client = new UnifiedMongoClient(uri, clientEntity);
const client = new UnifiedMongoClient(uri, clientEntity, {});
const registry = new EntityEventRegistry(client, clientEntity, entitesMap);

before(function () {
Expand Down