Skip to content

Commit 37613f1

Browse files
W-A-Jamesbaileympearson
andauthoredDec 9, 2024··
feat(NODE-6605): add error message when invalidating primary (#4340)
Co-authored-by: Bailey Pearson <bailey.pearson@mongodb.com>
1 parent ea8a33f commit 37613f1

23 files changed

+187
-101
lines changed
 

‎src/error.ts

+37-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import type { Document } from './bson';
1+
import type { Document, ObjectId } from './bson';
22
import {
33
type ClientBulkWriteError,
44
type ClientBulkWriteResult
55
} from './operations/client_bulk_write/common';
66
import type { ServerType } from './sdam/common';
7-
import type { TopologyVersion } from './sdam/server_description';
7+
import type { ServerDescription, TopologyVersion } from './sdam/server_description';
88
import type { TopologyDescription } from './sdam/topology_description';
99

1010
/** @public */
@@ -340,6 +340,41 @@ export class MongoRuntimeError extends MongoDriverError {
340340
}
341341
}
342342

343+
/**
344+
* An error generated when a primary server is marked stale, never directly thrown
345+
*
346+
* @public
347+
* @category Error
348+
*/
349+
export class MongoStalePrimaryError extends MongoRuntimeError {
350+
/**
351+
* **Do not use this constructor!**
352+
*
353+
* Meant for internal use only.
354+
*
355+
* @remarks
356+
* This class is only meant to be constructed within the driver. This constructor is
357+
* not subject to semantic versioning compatibility guarantees and may change at any time.
358+
*
359+
* @public
360+
**/
361+
constructor(
362+
serverDescription: ServerDescription,
363+
maxSetVersion: number | null,
364+
maxElectionId: ObjectId | null,
365+
options?: { cause?: Error }
366+
) {
367+
super(
368+
`primary marked stale due to electionId/setVersion mismatch: server setVersion: ${serverDescription.setVersion}, server electionId: ${serverDescription.electionId}, topology setVersion: ${maxSetVersion}, topology electionId: ${maxElectionId}`,
369+
options
370+
);
371+
}
372+
373+
override get name(): string {
374+
return 'MongoStalePrimaryError';
375+
}
376+
}
377+
343378
/**
344379
* An error generated when a batch command is re-executed after one of the commands in the batch
345380
* has failed

‎src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ export {
7777
MongoServerClosedError,
7878
MongoServerError,
7979
MongoServerSelectionError,
80+
MongoStalePrimaryError,
8081
MongoSystemError,
8182
MongoTailableCursorError,
8283
MongoTopologyClosedError,

‎src/sdam/server_description.ts

+3
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,10 @@ export class ServerDescription {
112112
this.minRoundTripTime = options?.minRoundTripTime ?? 0;
113113
this.lastUpdateTime = now();
114114
this.lastWriteDate = hello?.lastWrite?.lastWriteDate ?? 0;
115+
// NOTE: This actually builds the stack string instead of holding onto the getter and all its
116+
// associated references. This is done to prevent a memory leak.
115117
this.error = options.error ?? null;
118+
this.error?.stack;
116119
// TODO(NODE-2674): Preserve int64 sent from MongoDB
117120
this.topologyVersion = this.error?.topologyVersion ?? hello?.topologyVersion ?? null;
118121
this.setName = hello?.setName ?? null;

‎src/sdam/topology_description.ts

+13-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { EJSON, type ObjectId } from '../bson';
22
import * as WIRE_CONSTANTS from '../cmap/wire_protocol/constants';
3-
import { type MongoError, MongoRuntimeError } from '../error';
3+
import { type MongoError, MongoRuntimeError, MongoStalePrimaryError } from '../error';
44
import { compareObjectId, shuffle } from '../utils';
55
import { ServerType, TopologyType } from './common';
66
import { ServerDescription } from './server_description';
@@ -400,7 +400,9 @@ function updateRsFromPrimary(
400400
// replace serverDescription with a default ServerDescription of type "Unknown"
401401
serverDescriptions.set(
402402
serverDescription.address,
403-
new ServerDescription(serverDescription.address)
403+
new ServerDescription(serverDescription.address, undefined, {
404+
error: new MongoStalePrimaryError(serverDescription, maxSetVersion, maxElectionId)
405+
})
404406
);
405407

406408
return [checkHasPrimary(serverDescriptions), setName, maxSetVersion, maxElectionId];
@@ -416,7 +418,9 @@ function updateRsFromPrimary(
416418
// this primary is stale, we must remove it
417419
serverDescriptions.set(
418420
serverDescription.address,
419-
new ServerDescription(serverDescription.address)
421+
new ServerDescription(serverDescription.address, undefined, {
422+
error: new MongoStalePrimaryError(serverDescription, maxSetVersion, maxElectionId)
423+
})
420424
);
421425

422426
return [checkHasPrimary(serverDescriptions), setName, maxSetVersion, maxElectionId];
@@ -438,7 +442,12 @@ function updateRsFromPrimary(
438442
for (const [address, server] of serverDescriptions) {
439443
if (server.type === ServerType.RSPrimary && server.address !== serverDescription.address) {
440444
// Reset old primary's type to Unknown.
441-
serverDescriptions.set(address, new ServerDescription(server.address));
445+
serverDescriptions.set(
446+
address,
447+
new ServerDescription(server.address, undefined, {
448+
error: new MongoStalePrimaryError(serverDescription, maxSetVersion, maxElectionId)
449+
})
450+
);
442451

443452
// There can only be one primary
444453
break;

‎test/spec/server-discovery-and-monitoring/rs/new_primary_new_electionid.json

+4-2
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,8 @@
7676
"a:27017": {
7777
"type": "Unknown",
7878
"setName": null,
79-
"electionId": null
79+
"electionId": null,
80+
"error": "primary marked stale due to electionId/setVersion mismatch"
8081
},
8182
"b:27017": {
8283
"type": "RSPrimary",
@@ -123,7 +124,8 @@
123124
"a:27017": {
124125
"type": "Unknown",
125126
"setName": null,
126-
"electionId": null
127+
"electionId": null,
128+
"error": "primary marked stale due to electionId/setVersion mismatch"
127129
},
128130
"b:27017": {
129131
"type": "RSPrimary",

‎test/spec/server-discovery-and-monitoring/rs/new_primary_new_electionid.yml

+4-2
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,8 @@ phases: [
6363
"a:27017": {
6464
type: "Unknown",
6565
setName: ,
66-
electionId:
66+
electionId: ,
67+
error: "primary marked stale due to electionId/setVersion mismatch"
6768
},
6869
"b:27017": {
6970
type: "RSPrimary",
@@ -100,7 +101,8 @@ phases: [
100101
"a:27017": {
101102
type: "Unknown",
102103
setName: ,
103-
electionId:
104+
electionId:,
105+
error: "primary marked stale due to electionId/setVersion mismatch"
104106
},
105107
"b:27017": {
106108
type: "RSPrimary",

‎test/spec/server-discovery-and-monitoring/rs/new_primary_new_setversion.json

+4-2
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,8 @@
7676
"a:27017": {
7777
"type": "Unknown",
7878
"setName": null,
79-
"electionId": null
79+
"electionId": null,
80+
"error": "primary marked stale due to electionId/setVersion mismatch"
8081
},
8182
"b:27017": {
8283
"type": "RSPrimary",
@@ -123,7 +124,8 @@
123124
"a:27017": {
124125
"type": "Unknown",
125126
"setName": null,
126-
"electionId": null
127+
"electionId": null,
128+
"error": "primary marked stale due to electionId/setVersion mismatch"
127129
},
128130
"b:27017": {
129131
"type": "RSPrimary",

‎test/spec/server-discovery-and-monitoring/rs/new_primary_new_setversion.yml

+4-2
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,8 @@ phases: [
6363
"a:27017": {
6464
type: "Unknown",
6565
setName: ,
66-
electionId:
66+
electionId:,
67+
error: "primary marked stale due to electionId/setVersion mismatch"
6768
},
6869
"b:27017": {
6970
type: "RSPrimary",
@@ -100,7 +101,8 @@ phases: [
100101
"a:27017": {
101102
type: "Unknown",
102103
setName: ,
103-
electionId:
104+
electionId:,
105+
error: "primary marked stale due to electionId/setVersion mismatch"
104106
},
105107
"b:27017": {
106108
type: "RSPrimary",

‎test/spec/server-discovery-and-monitoring/rs/null_election_id-pre-6.0.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@
6666
"$oid": "000000000000000000000002"
6767
},
6868
"minWireVersion": 0,
69-
"maxWireVersion": 21
69+
"maxWireVersion": 7
7070
}
7171
]
7272
],

‎test/spec/server-discovery-and-monitoring/rs/primary_disconnect_electionid.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@
4848
"a:27017": {
4949
"type": "Unknown",
5050
"setName": null,
51-
"electionId": null
51+
"electionId": null,
52+
"error": "primary marked stale due to electionId/setVersion mismatch"
5253
},
5354
"b:27017": {
5455
"type": "RSPrimary",

‎test/spec/server-discovery-and-monitoring/rs/primary_disconnect_electionid.yml

+2-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ phases: [
3636
"a:27017": {
3737
type: "Unknown",
3838
setName: ,
39-
electionId:
39+
electionId:,
40+
error: "primary marked stale due to electionId/setVersion mismatch"
4041
},
4142
"b:27017": {
4243
type: "RSPrimary",

‎test/spec/server-discovery-and-monitoring/rs/primary_disconnect_setversion.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@
4848
"a:27017": {
4949
"type": "Unknown",
5050
"setName": null,
51-
"electionId": null
51+
"electionId": null,
52+
"error": "primary marked stale due to electionId/setVersion mismatch"
5253
},
5354
"b:27017": {
5455
"type": "RSPrimary",

‎test/spec/server-discovery-and-monitoring/rs/primary_disconnect_setversion.yml

+2-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ phases: [
3636
"a:27017": {
3737
type: "Unknown",
3838
setName: ,
39-
electionId:
39+
electionId:,
40+
error: "primary marked stale due to electionId/setVersion mismatch"
4041
},
4142
"b:27017": {
4243
type: "RSPrimary",

‎test/spec/server-discovery-and-monitoring/rs/setversion_greaterthan_max_without_electionid.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,8 @@
6565
"a:27017": {
6666
"type": "Unknown",
6767
"setName": null,
68-
"electionId": null
68+
"electionId": null,
69+
"error": "primary marked stale due to electionId/setVersion mismatch"
6970
},
7071
"b:27017": {
7172
"type": "RSPrimary",

‎test/spec/server-discovery-and-monitoring/rs/setversion_greaterthan_max_without_electionid.yml

+2-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,8 @@ phases: [
6161
"a:27017": {
6262
type: "Unknown",
6363
setName: ,
64-
electionId:
64+
electionId:,
65+
error: "primary marked stale due to electionId/setVersion mismatch"
6566
},
6667
"b:27017": {
6768
type: "RSPrimary",

‎test/spec/server-discovery-and-monitoring/rs/setversion_without_electionid-pre-6.0.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,8 @@
6565
"a:27017": {
6666
"type": "Unknown",
6767
"setName": null,
68-
"electionId": null
68+
"electionId": null,
69+
"error": "primary marked stale due to electionId/setVersion mismatch"
6970
},
7071
"b:27017": {
7172
"type": "RSPrimary",

‎test/spec/server-discovery-and-monitoring/rs/setversion_without_electionid-pre-6.0.yml

+2-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,8 @@ phases: [
6161
"a:27017": {
6262
type: "Unknown",
6363
setName: ,
64-
electionId:
64+
electionId:,
65+
error: "primary marked stale due to electionId/setVersion mismatch"
6566
},
6667
"b:27017": {
6768
type: "RSPrimary",

‎test/spec/server-discovery-and-monitoring/rs/use_setversion_without_electionid-pre-6.0.json

+4-2
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,8 @@
7373
"a:27017": {
7474
"type": "Unknown",
7575
"setName": null,
76-
"electionId": null
76+
"electionId": null,
77+
"error": "primary marked stale due to electionId/setVersion mismatch"
7778
},
7879
"b:27017": {
7980
"type": "RSPrimary",
@@ -117,7 +118,8 @@
117118
"a:27017": {
118119
"type": "Unknown",
119120
"setName": null,
120-
"electionId": null
121+
"electionId": null,
122+
"error": "primary marked stale due to electionId/setVersion mismatch"
121123
},
122124
"b:27017": {
123125
"type": "RSPrimary",

‎test/spec/server-discovery-and-monitoring/rs/use_setversion_without_electionid-pre-6.0.yml

+4-2
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,8 @@ phases: [
6262
"a:27017": {
6363
type: "Unknown",
6464
setName: ,
65-
electionId:
65+
electionId:,
66+
error: "primary marked stale due to electionId/setVersion mismatch"
6667
},
6768
"b:27017": {
6869
type: "RSPrimary",
@@ -99,7 +100,8 @@ phases: [
99100
"a:27017": {
100101
type: "Unknown",
101102
setName: ,
102-
electionId:
103+
electionId:,
104+
error: "primary marked stale due to electionId/setVersion mismatch"
103105
},
104106
"b:27017": {
105107
type: "RSPrimary",

‎test/spec/server-discovery-and-monitoring/rs/use_setversion_without_electionid.json

+4-2
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,8 @@
8181
"b:27017": {
8282
"type": "Unknown",
8383
"setName": null,
84-
"electionId": null
84+
"electionId": null,
85+
"error": "primary marked stale due to electionId/setVersion mismatch"
8586
}
8687
},
8788
"topologyType": "ReplicaSetWithPrimary",
@@ -128,7 +129,8 @@
128129
"b:27017": {
129130
"type": "Unknown",
130131
"setName": null,
131-
"electionId": null
132+
"electionId": null,
133+
"error": "primary marked stale due to electionId/setVersion mismatch"
132134
}
133135
},
134136
"topologyType": "ReplicaSetWithPrimary",

‎test/spec/server-discovery-and-monitoring/rs/use_setversion_without_electionid.yml

+4-2
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,8 @@ phases: [
6868
"b:27017": {
6969
type: "Unknown",
7070
setName: ,
71-
electionId:
71+
electionId:,
72+
error: "primary marked stale due to electionId/setVersion mismatch"
7273
}
7374
},
7475
topologyType: "ReplicaSetWithPrimary",
@@ -106,7 +107,8 @@ phases: [
106107
"b:27017":{
107108
type: "Unknown",
108109
setName: ,
109-
electionId:
110+
electionId:,
111+
error: "primary marked stale due to electionId/setVersion mismatch"
110112
}
111113
},
112114
topologyType: "ReplicaSetWithPrimary",

‎test/unit/assorted/server_discovery_and_monitoring.spec.test.ts

+83-70
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
isRecord,
1111
MongoClient,
1212
MongoCompatibilityError,
13+
MongoError,
1314
MongoNetworkError,
1415
MongoNetworkTimeoutError,
1516
MongoServerError,
@@ -25,6 +26,7 @@ import {
2526
ServerHeartbeatStartedEvent,
2627
ServerHeartbeatSucceededEvent,
2728
ServerOpeningEvent,
29+
squashError,
2830
Topology,
2931
TOPOLOGY_CLOSED,
3032
TOPOLOGY_DESCRIPTION_CHANGED,
@@ -108,6 +110,7 @@ interface MonitoringOutcome {
108110
interface OutcomeServerDescription {
109111
type?: string;
110112
setName?: string;
113+
error?: { message: string };
111114
setVersion?: number;
112115
electionId?: ObjectId | null;
113116
logicalSessionTimeoutMinutes?: number;
@@ -322,84 +325,88 @@ async function executeSDAMTest(testData: SDAMTest) {
322325
// connect the topology
323326
await client.connect();
324327

325-
for (const phase of testData.phases) {
326-
// Determine which of the two kinds of phases we're running
327-
if ('responses' in phase && phase.responses != null) {
328-
// phase with responses for hello simulations
329-
for (const [address, hello] of phase.responses) {
330-
client.topology.serverUpdateHandler(new ServerDescription(address, hello));
331-
}
332-
} else if ('applicationErrors' in phase && phase.applicationErrors) {
333-
// phase with applicationErrors simulating error's from network, timeouts, server
334-
for (const appError of phase.applicationErrors) {
335-
// Stub will return appError to SDAM machinery
336-
const checkOutStub = sinon
337-
.stub(ConnectionPool.prototype, 'checkOut')
338-
.callsFake(checkoutStubImpl(appError));
339-
340-
const server = client.topology.s.servers.get(appError.address);
341-
342-
// Run a dummy command to encounter the error
343-
const res = server.command.bind(server)(ns('admin.$cmd'), { ping: 1 }, {});
344-
const thrownError = await res.catch(error => error);
345-
346-
// Restore the stub before asserting anything in case of errors
347-
checkOutStub.restore();
348-
349-
const isApplicationError = error => {
350-
// These errors all come from the withConnection stub
351-
return (
352-
error instanceof MongoNetworkError ||
353-
error instanceof MongoNetworkTimeoutError ||
354-
error instanceof MongoServerError
355-
);
356-
};
357-
expect(
358-
thrownError,
359-
`expected the error thrown to be one of MongoNetworkError, MongoNetworkTimeoutError or MongoServerError (referred to in the spec as an "Application Error") got ${thrownError.name} ${thrownError.stack}`
360-
).to.satisfy(isApplicationError);
328+
try {
329+
for (const phase of testData.phases) {
330+
// Determine which of the two kinds of phases we're running
331+
if ('responses' in phase && phase.responses != null) {
332+
// phase with responses for hello simulations
333+
for (const [address, hello] of phase.responses) {
334+
client.topology.serverUpdateHandler(new ServerDescription(address, hello));
335+
}
336+
} else if ('applicationErrors' in phase && phase.applicationErrors) {
337+
// phase with applicationErrors simulating error's from network, timeouts, server
338+
for (const appError of phase.applicationErrors) {
339+
// Stub will return appError to SDAM machinery
340+
const checkOutStub = sinon
341+
.stub(ConnectionPool.prototype, 'checkOut')
342+
.callsFake(checkoutStubImpl(appError));
343+
344+
const server = client.topology.s.servers.get(appError.address);
345+
346+
// Run a dummy command to encounter the error
347+
const res = server.command.bind(server)(ns('admin.$cmd'), { ping: 1 }, {});
348+
const thrownError = await res.catch(error => error);
349+
350+
// Restore the stub before asserting anything in case of errors
351+
checkOutStub.restore();
352+
353+
const isApplicationError = error => {
354+
// These errors all come from the withConnection stub
355+
return (
356+
error instanceof MongoNetworkError ||
357+
error instanceof MongoNetworkTimeoutError ||
358+
error instanceof MongoServerError
359+
);
360+
};
361+
expect(
362+
thrownError,
363+
`expected the error thrown to be one of MongoNetworkError, MongoNetworkTimeoutError or MongoServerError (referred to in the spec as an "Application Error") got ${thrownError.name} ${thrownError.stack}`
364+
).to.satisfy(isApplicationError);
365+
}
366+
} else if (phase.outcome != null && Object.keys(phase).length === 1) {
367+
// Load Balancer SDAM tests have no "work" to be done for the phase
368+
} else {
369+
expect.fail(ejson`Unknown phase shape - ${phase}`);
361370
}
362-
} else if (phase.outcome != null && Object.keys(phase).length === 1) {
363-
// Load Balancer SDAM tests have no "work" to be done for the phase
364-
} else {
365-
expect.fail(ejson`Unknown phase shape - ${phase}`);
366-
}
367371

368-
if ('outcome' in phase && phase.outcome != null) {
369-
if (isMonitoringOutcome(phase.outcome)) {
370-
// Test for monitoring events
371-
const expectedEvents = convertOutcomeEvents(phase.outcome.events);
372+
if ('outcome' in phase && phase.outcome != null) {
373+
if (isMonitoringOutcome(phase.outcome)) {
374+
// Test for monitoring events
375+
const expectedEvents = convertOutcomeEvents(phase.outcome.events);
372376

373-
expect(events).to.have.length(expectedEvents.length);
374-
for (const [i, actualEvent] of Object.entries(events)) {
375-
const actualEventClone = cloneForCompare(actualEvent);
376-
expect(actualEventClone).to.matchMongoSpec(expectedEvents[i]);
377-
}
378-
} else if (isTopologyDescriptionOutcome(phase.outcome)) {
379-
// Test for SDAM machinery correctly changing the topology type among other properties
380-
assertTopologyDescriptionOutcomeExpectations(client.topology, phase.outcome);
381-
if (phase.outcome.compatible === false) {
382-
// driver specific error throwing
383-
if (testData.description === 'Multiple mongoses with large minWireVersion') {
384-
// TODO(DRIVERS-2250): There is test bug that causes two errors
385-
// this will start failing when the test is synced and fixed
386-
expect(errorsThrown).to.have.lengthOf(2);
377+
expect(events).to.have.length(expectedEvents.length);
378+
for (const [i, actualEvent] of Object.entries(events)) {
379+
const actualEventClone = cloneForCompare(actualEvent);
380+
expect(actualEventClone).to.matchMongoSpec(expectedEvents[i]);
381+
}
382+
} else if (isTopologyDescriptionOutcome(phase.outcome)) {
383+
// Test for SDAM machinery correctly changing the topology type among other properties
384+
assertTopologyDescriptionOutcomeExpectations(client.topology, phase.outcome);
385+
if (phase.outcome.compatible === false) {
386+
// driver specific error throwing
387+
if (testData.description === 'Multiple mongoses with large minWireVersion') {
388+
// TODO(DRIVERS-2250): There is test bug that causes two errors
389+
// this will start failing when the test is synced and fixed
390+
expect(errorsThrown).to.have.lengthOf(2);
391+
} else {
392+
expect(errorsThrown).to.have.lengthOf(1);
393+
}
394+
expect(errorsThrown[0]).to.be.instanceOf(MongoCompatibilityError);
395+
expect(errorsThrown[0].message).to.match(/but this version of the driver/);
387396
} else {
388-
expect(errorsThrown).to.have.lengthOf(1);
397+
// unset or true means no errors should be thrown
398+
expect(errorsThrown).to.be.empty;
389399
}
390-
expect(errorsThrown[0]).to.be.instanceOf(MongoCompatibilityError);
391-
expect(errorsThrown[0].message).to.match(/but this version of the driver/);
392400
} else {
393-
// unset or true means no errors should be thrown
394-
expect(errorsThrown).to.be.empty;
401+
expect.fail(ejson`Unknown outcome shape - ${phase.outcome}`);
395402
}
396-
} else {
397-
expect.fail(ejson`Unknown outcome shape - ${phase.outcome}`);
398-
}
399403

400-
events = [];
401-
errorsThrown = [];
404+
events = [];
405+
errorsThrown = [];
406+
}
402407
}
408+
} finally {
409+
await client.close().catch(squashError);
403410
}
404411
}
405412

@@ -464,8 +471,14 @@ function assertTopologyDescriptionOutcomeExpectations(
464471
if (WIRE_VERSION_KEYS.has(expectedKey) && expectedValue === null) {
465472
// For wireVersion keys we default to zero instead of null
466473
expect(actualServer).to.have.property(expectedKey, 0);
467-
} else {
474+
} else if (expectedKey !== 'error') {
468475
expect(actualServer).to.have.deep.property(expectedKey, expectedValue);
476+
} else {
477+
expect(typeof expectedValue).to.equal('string');
478+
expect(actualServer)
479+
.to.have.property(expectedKey)
480+
.instanceof(MongoError)
481+
.to.match(new RegExp(expectedValue as string));
469482
}
470483
}
471484
}

‎test/unit/index.test.ts

+1
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ const EXPECTED_EXPORTS = [
106106
'MongoTailableCursorError',
107107
'MongoTopologyClosedError',
108108
'MongoTransactionError',
109+
'MongoStalePrimaryError',
109110
'MongoUnexpectedServerResponseError',
110111
'MongoWriteConcernError',
111112
'ObjectId',

0 commit comments

Comments
 (0)
Please sign in to comment.