From 17f5952619a5389145eefcdfcfea67801176007f Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Tue, 7 Oct 2025 13:03:38 -0400 Subject: [PATCH] fix(NODE-7229): remove duplicate server selection when auto-connecting --- src/operations/execute_operation.ts | 6 +- src/sdam/topology.ts | 14 +- .../node-specific/abort_signal.test.ts | 63 ++-- .../node-specific/auto_connect.test.ts | 12 +- .../node-specific/mongo_client.test.ts | 5 +- .../server_selection.spec.test.ts | 9 +- .../server-selection/server_selection.test.ts | 12 - .../load-balanced.json | 135 ------- .../replica-set.json | 194 ---------- .../sharded.json | 352 ------------------ .../standalone.json | 349 ----------------- test/tools/unified-spec-runner/schema.ts | 16 +- .../unified-spec-runner/unified-utils.ts | 39 +- 13 files changed, 92 insertions(+), 1114 deletions(-) delete mode 100644 test/integration/server-selection/server_selection.test.ts delete mode 100644 test/integration/server-selection/unified-server-selection-node-specs-logging/load-balanced.json delete mode 100644 test/integration/server-selection/unified-server-selection-node-specs-logging/replica-set.json delete mode 100644 test/integration/server-selection/unified-server-selection-node-specs-logging/sharded.json delete mode 100644 test/integration/server-selection/unified-server-selection-node-specs-logging/standalone.json diff --git a/src/operations/execute_operation.ts b/src/operations/execute_operation.ts index 61d943e6424..0e1e74867d1 100644 --- a/src/operations/execute_operation.ts +++ b/src/operations/execute_operation.ts @@ -81,8 +81,6 @@ export async function executeOperation< session = client.startSession({ owner, explicit: false }); } else if (session.hasEnded) { throw new MongoExpiredSessionError('Use of expired sessions is not permitted'); - } else if (session.snapshotEnabled && !topology.capabilities.supportsSnapshotReads) { - throw new MongoCompatibilityError('Snapshot reads require MongoDB 5.0 or later'); } else if (session.client !== client) { throw new MongoInvalidArgumentError('ClientSession must be from the same MongoClient'); } @@ -206,6 +204,10 @@ async function tryOperation { }; try { - const server = await this.selectServer( - readPreferenceServerSelector(readPreference), - selectServerOptions - ); - const skipPingOnConnect = this.s.options.__skipPingOnConnect === true; if (!skipPingOnConnect) { + const server = await this.selectServer( + readPreferenceServerSelector(readPreference), + selectServerOptions + ); const connection = await server.pool.checkOut({ timeoutContext: timeoutContext }); server.pool.checkIn(connection); - stateTransition(this, STATE_CONNECTED); - this.emit(Topology.OPEN, this); - this.emit(Topology.CONNECT, this); - - return this; } stateTransition(this, STATE_CONNECTED); diff --git a/test/integration/node-specific/abort_signal.test.ts b/test/integration/node-specific/abort_signal.test.ts index c128156f2e2..85ef4e69b3b 100644 --- a/test/integration/node-specific/abort_signal.test.ts +++ b/test/integration/node-specific/abort_signal.test.ts @@ -18,10 +18,12 @@ import { type Log, type MongoClient, MongoServerError, + MongoServerSelectionError, promiseWithResolvers, ReadPreference, setDifference, - StateMachine + StateMachine, + Topology } from '../../mongodb'; import { clearFailPoint, @@ -612,7 +614,6 @@ describe('AbortSignal support', () => { let client: MongoClient; let db: Db; let collection: Collection<{ a: number; ssn: string }>; - const logs: Log[] = []; let connectStarted; let controller: AbortController; let signal: AbortSignal; @@ -620,27 +621,15 @@ describe('AbortSignal support', () => { describe('when connect succeeds', () => { beforeEach(async function () { - logs.length = 0; - const promise = promiseWithResolvers(); connectStarted = promise.promise; - client = this.configuration.newClient( - {}, - { - mongodbLogComponentSeverities: { serverSelection: 'debug' }, - mongodbLogPath: { - write: log => { - if (log.c === 'serverSelection' && log.operation === 'handshake') { - controller.abort(); - promise.resolve(); - } - logs.push(log); - } - }, - serverSelectionTimeoutMS: 1000 - } - ); + client = this.configuration.newClient({}, { serverSelectionTimeoutMS: 1000 }); + + client.once('open', () => { + controller.abort(); + promise.resolve(); + }); db = client.db('abortSignal'); collection = db.collection('support'); @@ -651,7 +640,6 @@ describe('AbortSignal support', () => { }); afterEach(async function () { - logs.length = 0; await client?.close(); }); @@ -667,22 +655,18 @@ describe('AbortSignal support', () => { describe('when connect fails', () => { beforeEach(async function () { - logs.length = 0; - const promise = promiseWithResolvers(); connectStarted = promise.promise; + const selectServerStub = sinon + .stub(Topology.prototype, 'selectServer') + .callsFake(async function (...args) { + controller.abort(); + promise.resolve(); + return selectServerStub.wrappedMethod.call(this, ...args); + }); + client = this.configuration.newClient('mongodb://iLoveJavaScript', { - mongodbLogComponentSeverities: { serverSelection: 'debug' }, - mongodbLogPath: { - write: log => { - if (log.c === 'serverSelection' && log.operation === 'handshake') { - controller.abort(); - promise.resolve(); - } - logs.push(log); - } - }, serverSelectionTimeoutMS: 200, maxPoolSize: 1 }); @@ -696,18 +680,23 @@ describe('AbortSignal support', () => { }); afterEach(async function () { - logs.length = 0; + sinon.restore(); await client?.close(); }); - it('escapes auto connect without interrupting it', async () => { + it('server selection error is thrown before reaching signal abort state check', async () => { const toArray = cursor.toArray().catch(error => error); await connectStarted; - expect(await toArray).to.be.instanceOf(DOMException); + const findError = await toArray; + expect(findError).to.be.instanceOf(MongoServerSelectionError); + if (process.platform !== 'win32') { + // linux / mac, unix in general will have this errno set, + // which is generally helpful if this is kept elevated in the error message + expect(findError).to.match(/ENOTFOUND/); + } await sleep(500); expect(client.topology).to.exist; expect(client.topology.description).to.have.property('type', 'Unknown'); - expect(findLast(logs, l => l.message.includes('Server selection failed'))).to.exist; }); }); }); diff --git a/test/integration/node-specific/auto_connect.test.ts b/test/integration/node-specific/auto_connect.test.ts index f0850049632..a8330578ed2 100644 --- a/test/integration/node-specific/auto_connect.test.ts +++ b/test/integration/node-specific/auto_connect.test.ts @@ -9,6 +9,7 @@ import { type Collection, MongoClient, MongoNotConnectedError, + MongoOperationTimeoutError, ProfilingLevel, Topology, TopologyType @@ -862,12 +863,17 @@ describe('When executing an operation for the first time', () => { sinon.restore(); }); - it('client.connect() takes as long as selectServer is delayed for and does not throw a timeout error', async function () { + it('client.connect() takes as long as selectServer is delayed for and throws a timeout error', async function () { const start = performance.now(); expect(client.topology).to.not.exist; // make sure not connected. - const res = await client.db().collection('test').insertOne({ a: 1 }, { timeoutMS: 500 }); // auto-connect + const error = await client + .db() + .collection('test') + .insertOne({ a: 1 }, { timeoutMS: 500 }) + .catch(error => error); const end = performance.now(); - expect(res).to.have.property('acknowledged', true); + expect(error).to.be.instanceOf(MongoOperationTimeoutError); + expect(error).to.match(/Timed out during server selection/); expect(end - start).to.be.within(1000, 1500); // timeoutMS is 1000, did not apply. }); } diff --git a/test/integration/node-specific/mongo_client.test.ts b/test/integration/node-specific/mongo_client.test.ts index 05120dd3eef..cbc0f3a7753 100644 --- a/test/integration/node-specific/mongo_client.test.ts +++ b/test/integration/node-specific/mongo_client.test.ts @@ -626,10 +626,6 @@ describe('class MongoClient', function () { 'checks out connection to confirm connectivity even when authentication is disabled', { requires: { auth: 'disabled' } }, async function () { - const checkoutStartedEvents = []; - client.on('connectionCheckOutStarted', event => { - checkoutStartedEvents.push(event); - }); const checkoutStarted = once(client, 'connectionCheckOutStarted'); await client.connect(); const checkout = await checkoutStarted; @@ -725,6 +721,7 @@ describe('class MongoClient', function () { expect(result).to.be.instanceOf(MongoServerSelectionError); expect(client).to.be.instanceOf(MongoClient); expect(client).to.have.property('topology').that.is.instanceOf(Topology); + await client.close(); } ); }); diff --git a/test/integration/server-selection/server_selection.spec.test.ts b/test/integration/server-selection/server_selection.spec.test.ts index 280c157de12..63b03d17a77 100644 --- a/test/integration/server-selection/server_selection.spec.test.ts +++ b/test/integration/server-selection/server_selection.spec.test.ts @@ -3,18 +3,19 @@ import * as path from 'path'; import { loadSpecTests } from '../../spec'; import { runUnifiedSuite } from '../../tools/unified-spec-runner/runner'; -describe.skip('Server Selection Unified Tests (Spec)', function () { +describe('Server Selection Unified Tests (Spec)', function () { const tests = loadSpecTests(path.join('server-selection', 'logging')); runUnifiedSuite(tests, test => { if ( [ 'Failed bulkWrite operation: log messages have operationIds', - 'Successful bulkWrite operation: log messages have operationIds' + 'Failed client bulkWrite operation: log messages have operationIds', + 'Successful bulkWrite operation: log messages have operationIds', + 'Successful client bulkWrite operation: log messages have operationIds' ].includes(test.description) ) { return 'not applicable: operationId not supported'; } return false; }); -}).skipReason = - 'TODO: unskip these tests - NODE-2471 (ping on connect) and NODE-5774 (duplicate server selection for bulkWrite and other wrapper operations'; +}); diff --git a/test/integration/server-selection/server_selection.test.ts b/test/integration/server-selection/server_selection.test.ts deleted file mode 100644 index 8d58285a5cd..00000000000 --- a/test/integration/server-selection/server_selection.test.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { loadSpecTests } from '../../spec'; -import { runUnifiedSuite } from '../../tools/unified-spec-runner/runner'; - -describe('Server Selection Unified Tests (Node Driver)', function () { - /* TODO(NODE-5774) duplicate server selection for bulkWrite and other wrapper operations - * Remove once the actual unified tests (test/spec/server-selection/logging) are passing - */ - const clonedAndAlteredSpecTests = loadSpecTests( - '../integration/server-selection/unified-server-selection-node-specs-logging' - ); - runUnifiedSuite(clonedAndAlteredSpecTests); -}); diff --git a/test/integration/server-selection/unified-server-selection-node-specs-logging/load-balanced.json b/test/integration/server-selection/unified-server-selection-node-specs-logging/load-balanced.json deleted file mode 100644 index 125766ad1e4..00000000000 --- a/test/integration/server-selection/unified-server-selection-node-specs-logging/load-balanced.json +++ /dev/null @@ -1,135 +0,0 @@ -{ - "description": "load-balanced-logging-node-driver", - "schemaVersion": "1.13", - "runOnRequirements": [ - { - "topologies": [ - "load-balanced" - ] - } - ], - "createEntities": [ - { - "client": { - "id": "client", - "uriOptions": { - "heartbeatFrequencyMS": 500 - }, - "observeLogMessages": { - "serverSelection": "debug" - }, - "observeEvents": [ - "serverDescriptionChangedEvent" - ] - } - }, - { - "database": { - "id": "database", - "client": "client", - "databaseName": "logging-tests" - } - }, - { - "collection": { - "id": "collection", - "database": "database", - "collectionName": "server-selection" - } - } - ], - "tests": [ - { - "description": "A successful operation - load balanced cluster", - "operations": [ - { - "name": "waitForEvent", - "object": "testRunner", - "arguments": { - "client": "client", - "event": { - "serverDescriptionChangedEvent": { - "newDescription": { - "type": "LoadBalancer" - } - } - }, - "count": 1 - } - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "document": { - "x": 1 - } - } - } - ], - "expectLogMessages": [ - { - "client": "client", - "messages": [ - { - "level": "debug", - "component": "serverSelection", - "data": { - "message": "Server selection started", - "selector": { - "$$exists": true - }, - "operation": "handshake", - "topologyDescription": { - "$$exists": true - } - } - }, - { - "level": "debug", - "component": "serverSelection", - "data": { - "message": "Server selection succeeded", - "selector": { - "$$exists": true - }, - "operation": "handshake", - "topologyDescription": { - "$$exists": true - } - } - }, - { - "level": "debug", - "component": "serverSelection", - "data": { - "message": "Server selection started", - "selector": { - "$$exists": true - }, - "operation": "insert", - "topologyDescription": { - "$$exists": true - } - } - }, - { - "level": "debug", - "component": "serverSelection", - "data": { - "message": "Server selection succeeded", - "selector": { - "$$exists": true - }, - "operation": "insert", - "topologyDescription": { - "$$exists": true - } - } - } - ] - } - ] - } - ] -} \ No newline at end of file diff --git a/test/integration/server-selection/unified-server-selection-node-specs-logging/replica-set.json b/test/integration/server-selection/unified-server-selection-node-specs-logging/replica-set.json deleted file mode 100644 index b462f3b80e3..00000000000 --- a/test/integration/server-selection/unified-server-selection-node-specs-logging/replica-set.json +++ /dev/null @@ -1,194 +0,0 @@ -{ - "description": "replica-set-logging-node-driver", - "schemaVersion": "1.14", - "runOnRequirements": [ - { - "topologies": [ - "replicaset" - ] - } - ], - "createEntities": [ - { - "client": { - "id": "client", - "uriOptions": { - "retryWrites": false, - "heartbeatFrequencyMS": 500, - "serverSelectionTimeoutMS": 2000 - }, - "observeLogMessages": { - "serverSelection": "debug" - }, - "observeEvents": [ - "serverDescriptionChangedEvent", - "topologyDescriptionChangedEvent" - ] - } - }, - { - "database": { - "id": "database", - "client": "client", - "databaseName": "logging-tests" - } - }, - { - "collection": { - "id": "collection", - "database": "database", - "collectionName": "server-selection" - } - }, - { - "client": { - "id": "failPointClient" - } - }, - { - "collection": { - "id": "unsatisfiableRPColl", - "database": "database", - "collectionName": "unsatisfiableRPColl", - "collectionOptions": { - "readPreference": { - "mode": "secondary", - "tagSets": [ - { - "nonexistenttag": "a" - } - ] - } - } - } - } - ], - "tests": [ - { - "description": "A successful operation", - "operations": [ - { - "name": "waitForEvent", - "object": "testRunner", - "arguments": { - "client": "client", - "event": { - "topologyDescriptionChangedEvent": {} - }, - "count": 4 - } - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "document": { - "x": 1 - } - } - } - ], - "expectLogMessages": [ - { - "client": "client", - "messages": [ - { - "level": "debug", - "component": "serverSelection", - "data": { - "message": "Server selection started", - "selector": { - "$$exists": true - }, - "operation": "handshake", - "topologyDescription": { - "$$exists": true - } - } - }, - { - "level": "info", - "component": "serverSelection", - "data": { - "message": "Waiting for suitable server to become available", - "selector": { - "$$exists": true - }, - "operation": "handshake", - "topologyDescription": { - "$$exists": true - }, - "remainingTimeMS": { - "$$type": [ - "int", - "long" - ] - } - } - }, - { - "level": "debug", - "component": "serverSelection", - "data": { - "message": "Server selection succeeded", - "selector": { - "$$exists": true - }, - "operation": "handshake", - "topologyDescription": { - "$$exists": true - }, - "serverHost": { - "$$type": "string" - }, - "serverPort": { - "$$type": [ - "int", - "long" - ] - } - } - }, - { - "level": "debug", - "component": "serverSelection", - "data": { - "message": "Server selection started", - "selector": { - "$$exists": true - }, - "operation": "insert", - "topologyDescription": { - "$$exists": true - } - } - }, - { - "level": "debug", - "component": "serverSelection", - "data": { - "message": "Server selection succeeded", - "selector": { - "$$exists": true - }, - "operation": "insert", - "topologyDescription": { - "$$exists": true - }, - "serverHost": { - "$$type": "string" - }, - "serverPort": { - "$$type": [ - "int", - "long" - ] - } - } - } - ] - } - ] - } - ] -} diff --git a/test/integration/server-selection/unified-server-selection-node-specs-logging/sharded.json b/test/integration/server-selection/unified-server-selection-node-specs-logging/sharded.json deleted file mode 100644 index 345fc84bc00..00000000000 --- a/test/integration/server-selection/unified-server-selection-node-specs-logging/sharded.json +++ /dev/null @@ -1,352 +0,0 @@ -{ - "description": "sharded-logging-node-driver", - - "schemaVersion": "1.14", - "runOnRequirements": [ - { - "topologies": [ - "sharded" - ] - } - ], - "createEntities": [ - { - "client": { - "id": "client", - "uriOptions": { - "retryWrites": false, - "heartbeatFrequencyMS": 500, - "appName": "loggingClient", - "serverSelectionTimeoutMS": 2000 - }, - "observeLogMessages": { - "serverSelection": "debug" - }, - "observeEvents": [ - "serverDescriptionChangedEvent", - "topologyDescriptionChangedEvent" - ], - "useMultipleMongoses": false - } - }, - { - "database": { - "id": "database", - "client": "client", - "databaseName": "logging-tests" - } - }, - { - "collection": { - "id": "collection", - "database": "database", - "collectionName": "server-selection" - } - }, - { - "client": { - "id": "failPointClient", - "useMultipleMongoses": false - } - } - ], - "tests": [ - { - "description": "A successful operation", - "operations": [ - { - "name": "waitForEvent", - "object": "testRunner", - "arguments": { - "client": "client", - "event": { - "topologyDescriptionChangedEvent": {} - }, - "count": 2 - } - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "document": { - "x": 1 - } - } - } - ], - "expectLogMessages": [ - { - "client": "client", - "messages": [ - { - "level": "debug", - "component": "serverSelection", - "data": { - "message": "Server selection started", - "selector": { - "$$exists": true - }, - "operation": "handshake", - "topologyDescription": { - "$$exists": true - } - } - }, - { - "level": "info", - "component": "serverSelection", - "data": { - "message": "Waiting for suitable server to become available", - "selector": { - "$$exists": true - }, - "operation": "handshake", - "topologyDescription": { - "$$exists": true - }, - "remainingTimeMS": { - "$$type": [ - "int", - "long" - ] - } - } - }, - { - "level": "debug", - "component": "serverSelection", - "data": { - "message": "Server selection succeeded", - "selector": { - "$$exists": true - }, - "operation": "handshake", - "topologyDescription": { - "$$exists": true - }, - "serverHost": { - "$$type": "string" - }, - "serverPort": { - "$$type": [ - "int", - "long" - ] - } - } - }, - { - "level": "debug", - "component": "serverSelection", - "data": { - "message": "Server selection started", - "selector": { - "$$exists": true - }, - "operation": "insert", - "topologyDescription": { - "$$exists": true - } - } - }, - { - "level": "debug", - "component": "serverSelection", - "data": { - "message": "Server selection succeeded", - "selector": { - "$$exists": true - }, - "operation": "insert", - "topologyDescription": { - "$$exists": true - }, - "serverHost": { - "$$type": "string" - }, - "serverPort": { - "$$type": [ - "int", - "long" - ] - } - } - } - ] - } - ] - }, - { - "description": "Failure due to unreachable server", - "runOnRequirements": [ - { - "minServerVersion": "4.4" - } - ], - "operations": [ - { - "name": "failPoint", - "object": "testRunner", - "arguments": { - "client": "failPointClient", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": "alwaysOn", - "data": { - "failCommands": [ - "hello", - "ismaster" - ], - "appName": "loggingClient", - "closeConnection": true - } - } - } - }, - { - "name": "waitForEvent", - "object": "testRunner", - "arguments": { - "client": "client", - "event": { - "serverDescriptionChangedEvent": { - "newDescription": { - "type": "Unknown" - } - } - }, - "count": 1 - } - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "document": { - "x": 1 - } - }, - "expectError": { - "isClientError": true - } - } - ], - "expectLogMessages": [ - { - "client": "client", - "messages": [ - { - "level": "debug", - "component": "serverSelection", - "data": { - "message": "Server selection started", - "selector": { - "$$exists": true - }, - "operation": "handshake", - "topologyDescription": { - "$$exists": true - } - } - }, - { - "level": "info", - "component": "serverSelection", - "data": { - "message": "Waiting for suitable server to become available", - "selector": { - "$$exists": true - }, - "operation": "handshake", - "topologyDescription": { - "$$exists": true - }, - "remainingTimeMS": { - "$$type": [ - "int", - "long" - ] - } - } - }, - { - "level": "debug", - "component": "serverSelection", - "data": { - "message": "Server selection succeeded", - "selector": { - "$$exists": true - }, - "operation": "handshake", - "topologyDescription": { - "$$exists": true - }, - "serverHost": { - "$$type": "string" - }, - "serverPort": { - "$$type": [ - "int", - "long" - ] - } - } - }, - { - "level": "debug", - "component": "serverSelection", - "data": { - "message": "Server selection started", - "selector": { - "$$exists": true - }, - "operation": "insert", - "topologyDescription": { - "$$exists": true - } - } - }, - { - "level": "info", - "component": "serverSelection", - "data": { - "message": "Waiting for suitable server to become available", - "selector": { - "$$exists": true - }, - "operation": "insert", - "topologyDescription": { - "$$exists": true - }, - "remainingTimeMS": { - "$$type": [ - "int", - "long" - ] - } - } - }, - { - "level": "debug", - "component": "serverSelection", - "data": { - "message": "Server selection failed", - "selector": { - "$$exists": true - }, - "operation": "insert", - "topologyDescription": { - "$$exists": true - }, - "failure": { - "$$exists": true - } - } - } - ] - } - ] - } - ] -} diff --git a/test/integration/server-selection/unified-server-selection-node-specs-logging/standalone.json b/test/integration/server-selection/unified-server-selection-node-specs-logging/standalone.json deleted file mode 100644 index 2b5ee88338b..00000000000 --- a/test/integration/server-selection/unified-server-selection-node-specs-logging/standalone.json +++ /dev/null @@ -1,349 +0,0 @@ -{ - "description": "standalone-logging-node-driver", - "schemaVersion": "1.14", - "runOnRequirements": [ - { - "topologies": [ - "single" - ] - } - ], - "createEntities": [ - { - "client": { - "id": "client", - "uriOptions": { - "retryWrites": false, - "heartbeatFrequencyMS": 500, - "appName": "loggingClient", - "serverSelectionTimeoutMS": 2000 - }, - "observeLogMessages": { - "serverSelection": "debug" - }, - "observeEvents": [ - "serverDescriptionChangedEvent", - "topologyDescriptionChangedEvent" - ] - } - }, - { - "database": { - "id": "database", - "client": "client", - "databaseName": "logging-tests" - } - }, - { - "collection": { - "id": "collection", - "database": "database", - "collectionName": "server-selection" - } - }, - { - "client": { - "id": "failPointClient" - } - } - ], - "tests": [ - { - "description": "A successful operation", - "operations": [ - { - "name": "waitForEvent", - "object": "testRunner", - "arguments": { - "client": "client", - "event": { - "topologyDescriptionChangedEvent": {} - }, - "count": 2 - } - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "document": { - "x": 1 - } - } - } - ], - "expectLogMessages": [ - { - "client": "client", - "messages": [ - { - "level": "debug", - "component": "serverSelection", - "data": { - "message": "Server selection started", - "selector": { - "$$exists": true - }, - "operation": "handshake", - "topologyDescription": { - "$$exists": true - } - } - }, - { - "level": "info", - "component": "serverSelection", - "data": { - "message": "Waiting for suitable server to become available", - "selector": { - "$$exists": true - }, - "operation": "handshake", - "topologyDescription": { - "$$exists": true - }, - "remainingTimeMS": { - "$$type": [ - "int", - "long" - ] - } - } - }, - { - "level": "debug", - "component": "serverSelection", - "data": { - "message": "Server selection succeeded", - "selector": { - "$$exists": true - }, - "operation": "handshake", - "topologyDescription": { - "$$exists": true - }, - "serverHost": { - "$$type": "string" - }, - "serverPort": { - "$$type": [ - "int", - "long" - ] - } - } - }, - { - "level": "debug", - "component": "serverSelection", - "data": { - "message": "Server selection started", - "selector": { - "$$exists": true - }, - "operation": "insert", - "topologyDescription": { - "$$exists": true - } - } - }, - { - "level": "debug", - "component": "serverSelection", - "data": { - "message": "Server selection succeeded", - "selector": { - "$$exists": true - }, - "operation": "insert", - "topologyDescription": { - "$$exists": true - }, - "serverHost": { - "$$type": "string" - }, - "serverPort": { - "$$type": [ - "int", - "long" - ] - } - } - } - ] - } - ] - }, - { - "description": "Failure due to unreachable server", - "runOnRequirements": [ - { - "minServerVersion": "4.4" - } - ], - "operations": [ - { - "name": "failPoint", - "object": "testRunner", - "arguments": { - "client": "failPointClient", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": "alwaysOn", - "data": { - "failCommands": [ - "hello", - "ismaster" - ], - "appName": "loggingClient", - "closeConnection": true - } - } - } - }, - { - "name": "waitForEvent", - "object": "testRunner", - "arguments": { - "client": "client", - "event": { - "serverDescriptionChangedEvent": { - "newDescription": { - "type": "Unknown" - } - } - }, - "count": 1 - } - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "document": { - "x": 1 - } - }, - "expectError": { - "isClientError": true - } - } - ], - "expectLogMessages": [ - { - "client": "client", - "messages": [ - { - "level": "debug", - "component": "serverSelection", - "data": { - "message": "Server selection started", - "selector": { - "$$exists": true - }, - "operation": "handshake", - "topologyDescription": { - "$$exists": true - } - } - }, - { - "level": "info", - "component": "serverSelection", - "data": { - "message": "Waiting for suitable server to become available", - "selector": { - "$$exists": true - }, - "operation": "handshake", - "topologyDescription": { - "$$exists": true - }, - "remainingTimeMS": { - "$$type": [ - "int", - "long" - ] - } - } - }, - { - "level": "debug", - "component": "serverSelection", - "data": { - "message": "Server selection succeeded", - "selector": { - "$$exists": true - }, - "operation": "handshake", - "topologyDescription": { - "$$exists": true - }, - "serverHost": { - "$$type": "string" - }, - "serverPort": { - "$$type": [ - "int", - "long" - ] - } - } - }, - { - "level": "debug", - "component": "serverSelection", - "data": { - "message": "Server selection started", - "selector": { - "$$exists": true - }, - "operation": "insert", - "topologyDescription": { - "$$exists": true - } - } - }, - { - "level": "info", - "component": "serverSelection", - "data": { - "message": "Waiting for suitable server to become available", - "selector": { - "$$exists": true - }, - "operation": "insert", - "topologyDescription": { - "$$exists": true - }, - "remainingTimeMS": { - "$$type": [ - "int", - "long" - ] - } - } - }, - { - "level": "debug", - "component": "serverSelection", - "data": { - "message": "Server selection failed", - "selector": { - "$$exists": true - }, - "operation": "insert", - "topologyDescription": { - "$$exists": true - }, - "failure": { - "$$exists": true - } - } - } - ] - } - ] - } - ] -} diff --git a/test/tools/unified-spec-runner/schema.ts b/test/tools/unified-spec-runner/schema.ts index 8ec850979fa..b0ba6abed99 100644 --- a/test/tools/unified-spec-runner/schema.ts +++ b/test/tools/unified-spec-runner/schema.ts @@ -4,7 +4,6 @@ import type { MongoLoggableComponent, ObjectId, ReadConcernLevel, - ReadPreferenceMode, ServerApiVersion, SeverityLevel, TagSet, @@ -259,16 +258,19 @@ export interface ServerApi { strict?: boolean; deprecationErrors?: boolean; } + +export type UnifiedReadPreference = { + mode: string; + tagSets?: TagSet[]; + maxStalenessSeconds?: number; + hedge?: { enabled: boolean }; +}; + export interface CollectionOrDatabaseOptions { readConcern?: { level: ReadConcernLevel; }; - readPreference?: { - mode: ReadPreferenceMode; - maxStalenessSeconds: number; - tags: TagSet[]; - hedge: { enabled: boolean }; - }; + readPreference?: UnifiedReadPreference; writeConcern?: { w: W; wtimeoutMS: number; diff --git a/test/tools/unified-spec-runner/unified-utils.ts b/test/tools/unified-spec-runner/unified-utils.ts index 1b599a7ed9d..b56ae9bb00a 100644 --- a/test/tools/unified-spec-runner/unified-utils.ts +++ b/test/tools/unified-spec-runner/unified-utils.ts @@ -12,6 +12,8 @@ import { type Document, getMongoDBClientEncryption, type MongoClient, + ReadPreference, + ReadPreferenceMode, ReturnDocument } from '../../mongodb'; import type { CmapEvent, CommandEvent, EntitiesMap, SdamEvent } from './entities'; @@ -23,7 +25,8 @@ import type { ExpectedEventsForClient, KMSProvidersEntity, RunOnRequirement, - StringOrPlaceholder + StringOrPlaceholder, + UnifiedReadPreference } from './schema'; const ENABLE_UNIFIED_TEST_LOGGING = false; @@ -190,16 +193,42 @@ export function patchVersion(version: string): string { return `${major}.${minor ?? 0}.${patch ?? 0}`; } +export function makeNodeReadPrefFromUnified( + readPreferenceFromTest: UnifiedReadPreference +): ReadPreference { + const validModes = Object.values(ReadPreferenceMode); + const testModeLowered = readPreferenceFromTest.mode?.toLowerCase(); + const mode = validModes.find(m => m.toLowerCase() === testModeLowered); + expect(mode, 'test file defines unsupported readPreference mode').to.not.be.null; + + return ReadPreference.fromOptions({ + readPreference: { + mode, + tags: readPreferenceFromTest.tagSets, + maxStalenessSeconds: readPreferenceFromTest.maxStalenessSeconds, + hedge: readPreferenceFromTest.hedge + } + }); +} + export function patchDbOptions(options: CollectionOrDatabaseOptions = {}): DbOptions { - // TODO - return { ...options } as DbOptions; + // @ts-expect-error: there is incompatibilities between unified options and node options, but it mostly works as is. See the readPref fixing below. + const dbOptions: DbOptions = { ...options }; + if (dbOptions.readPreference) { + dbOptions.readPreference = makeNodeReadPrefFromUnified(options.readPreference); + } + return dbOptions; } export function patchCollectionOptions( options: CollectionOrDatabaseOptions = {} ): CollectionOptions { - // TODO - return { ...options } as CollectionOptions; + // @ts-expect-error: there is incompatibilities between unified options and node options, but it mostly works as is. See the readPref fixing below. + const collectionOptions: CollectionOptions = { ...options }; + if (collectionOptions.readPreference) { + collectionOptions.readPreference = makeNodeReadPrefFromUnified(options.readPreference); + } + return collectionOptions; } export function translateOptions(options: Document): Document {