diff --git a/src/sdam/topology_description.ts b/src/sdam/topology_description.ts index 5073429866..436321c7f1 100644 --- a/src/sdam/topology_description.ts +++ b/src/sdam/topology_description.ts @@ -1,4 +1,4 @@ -import type { ObjectId } from '../bson'; +import { EJSON, type ObjectId } from '../bson'; import * as WIRE_CONSTANTS from '../cmap/wire_protocol/constants'; import { type MongoError, MongoRuntimeError } from '../error'; import { compareObjectId, shuffle } from '../utils'; @@ -342,6 +342,16 @@ export class TopologyDescription { hasServer(address: string): boolean { return this.servers.has(address); } + + /** + * Returns a JSON-serializable representation of the TopologyDescription. This is primarily + * intended for use with JSON.stringify(). + * + * This method will not throw. + */ + toJSON() { + return EJSON.serialize(this); + } } function topologyTypeForServerType(serverType: ServerType): TopologyType { diff --git a/test/integration/node-specific/errors.test.ts b/test/integration/node-specific/errors.test.ts index c0890083eb..ff00db6564 100644 --- a/test/integration/node-specific/errors.test.ts +++ b/test/integration/node-specific/errors.test.ts @@ -1,6 +1,6 @@ import { expect } from 'chai'; -import { MongoClient, MongoServerSelectionError } from '../../mongodb'; +import { MongoClient, MongoServerSelectionError, ReadPreference } from '../../mongodb'; describe('Error (Integration)', function () { it('NODE-5296: handles aggregate errors from dns lookup', async function () { @@ -10,4 +10,37 @@ describe('Error (Integration)', function () { expect(error).to.be.instanceOf(MongoServerSelectionError); expect(error.message).not.to.be.empty; }); + + context('when a server selection error is stringified', function () { + it( + 'the error"s topology description correctly displays the `servers`', + { requires: { topology: 'replicaset' } }, + async function () { + const client: MongoClient = this.configuration.newClient({ + serverSelectionTimeoutMS: 1000 + }); + try { + await client.connect(); + + const error = await client + .db('foo') + .collection('bar') + .find( + {}, + { + // Use meaningless read preference tags to ensure that the server selection fails + readPreference: new ReadPreference('secondary', [{ ny: 'ny' }]) + } + ) + .toArray() + .catch(e => JSON.parse(JSON.stringify(e))); + + const servers = error.reason.servers; + expect(Object.keys(servers).length > 0).to.be.true; + } finally { + await client.close(); + } + } + ); + }); }); diff --git a/test/integration/server-discovery-and-monitoring/topology_description.test.ts b/test/integration/server-discovery-and-monitoring/topology_description.test.ts index 1169176ffb..291e13dd70 100644 --- a/test/integration/server-discovery-and-monitoring/topology_description.test.ts +++ b/test/integration/server-discovery-and-monitoring/topology_description.test.ts @@ -14,7 +14,22 @@ describe('TopologyDescription (integration tests)', function () { await client.close(); }); + beforeEach(async function () { + client = this.configuration.newClient(); + await client.connect(); + }); + context('options', function () { + let client: MongoClient; + + afterEach(async function () { + await client.close(); + }); + + beforeEach(async function () { + client = this.configuration.newClient(); + }); + context('localThresholdMS', function () { it('should default to 15ms', async function () { const options: MongoClientOptions = {}; @@ -35,15 +50,6 @@ describe('TopologyDescription (integration tests)', function () { }); context('topology types', function () { - let client: MongoClient; - beforeEach(async function () { - client = this.configuration.newClient(); - }); - - afterEach(async function () { - await client.close(); - }); - const topologyTypesMap = new Map([ ['single', TopologyType.Single], ['replicaset', TopologyType.ReplicaSetWithPrimary], @@ -65,4 +71,23 @@ describe('TopologyDescription (integration tests)', function () { ); } }); + + describe('json stringification', function () { + it('can be stringified without error', function () { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-non-null-asserted-optional-chain + const description = client.topology?.description!; + expect(description).to.exist; + + expect(() => JSON.stringify(description)).not.to.throw; + }); + + it('properly stringifies the server description map', function () { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-non-null-asserted-optional-chain + const description = client.topology?.description!; + expect(description).to.exist; + + const { servers } = JSON.parse(JSON.stringify(description)); + expect(Object.keys(servers).length > 0, '`servers` stringified with no servers.').to.be.true; + }); + }); });