Skip to content

Commit 446da95

Browse files
fix(NODE-3948): Add error code to MongoSystemError (#3149)
1 parent b0d4413 commit 446da95

File tree

3 files changed

+101
-38
lines changed

3 files changed

+101
-38
lines changed

src/error.ts

+2
Original file line numberDiff line numberDiff line change
@@ -625,6 +625,8 @@ export class MongoSystemError extends MongoError {
625625
if (reason) {
626626
this.reason = reason;
627627
}
628+
629+
this.code = reason.error?.code;
628630
}
629631

630632
get name(): string {

src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ export {
6262
MongoServerError,
6363
MongoServerSelectionError,
6464
MongoSystemError,
65+
MongoTailableCursorError,
6566
MongoTopologyClosedError,
6667
MongoTransactionError,
6768
MongoWriteConcernError

test/unit/error.test.js renamed to test/unit/error.test.ts

+98-38
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,77 @@
1-
/* eslint no-empty: ["error", { "allowEmptyCatch": true }] */
2-
'use strict';
3-
4-
const expect = require('chai').expect;
5-
const mock = require('../tools/mongodb-mock/index');
6-
const { getSymbolFrom } = require('../tools/utils');
7-
const { ReplSetFixture } = require('../tools/common');
8-
const { ns, isHello } = require('../../src/utils');
9-
const { Topology } = require('../../src/sdam/topology');
10-
const {
11-
MongoNetworkError,
12-
MongoWriteConcernError,
1+
import { expect } from 'chai';
2+
3+
import {
4+
PoolClosedError as MongoPoolClosedError,
5+
WaitQueueTimeoutError as MongoWaitQueueTimeoutError
6+
} from '../../src/cmap/errors';
7+
import {
8+
isRetryableEndTransactionError,
9+
isSDAMUnrecoverableError,
10+
LEGACY_NOT_PRIMARY_OR_SECONDARY_ERROR_MESSAGE,
11+
LEGACY_NOT_WRITABLE_PRIMARY_ERROR_MESSAGE,
12+
MongoSystemError,
13+
NODE_IS_RECOVERING_ERROR_MESSAGE
14+
} from '../../src/error';
15+
import * as importsFromErrorSrc from '../../src/error';
16+
import {
1317
MongoError,
18+
MongoNetworkError,
19+
MongoParseError,
1420
MongoServerError,
15-
MongoParseError
16-
} = require('../../src/index');
17-
const {
18-
LEGACY_NOT_WRITABLE_PRIMARY_ERROR_MESSAGE,
19-
LEGACY_NOT_PRIMARY_OR_SECONDARY_ERROR_MESSAGE,
20-
NODE_IS_RECOVERING_ERROR_MESSAGE,
21-
isRetryableEndTransactionError,
22-
isSDAMUnrecoverableError
23-
} = require('../../src/error');
24-
const {
25-
PoolClosedError: MongoPoolClosedError,
26-
WaitQueueTimeoutError: MongoWaitQueueTimeoutError
27-
} = require('../../src/cmap/errors');
21+
MongoWriteConcernError,
22+
TopologyDescription
23+
} from '../../src/index';
24+
import * as importsFromEntryPoint from '../../src/index';
25+
import { Topology, TopologyOptions } from '../../src/sdam/topology';
26+
import { isHello, ns, setDifference } from '../../src/utils';
27+
import { ReplSetFixture } from '../tools/common';
28+
import { cleanup } from '../tools/mongodb-mock/index';
29+
import { getSymbolFrom } from '../tools/utils';
2830

2931
describe('MongoErrors', () => {
30-
// import errors as object
31-
let errorClasses = Object.fromEntries(
32-
Object.entries(require('../../src/index')).filter(([key]) => key.endsWith('Error'))
32+
let errorClassesFromEntryPoint = Object.fromEntries(
33+
Object.entries(importsFromEntryPoint).filter(
34+
([key, value]) => key.endsWith('Error') && value.toString().startsWith('class')
35+
)
36+
) as any;
37+
errorClassesFromEntryPoint = {
38+
...errorClassesFromEntryPoint,
39+
MongoPoolClosedError,
40+
MongoWaitQueueTimeoutError
41+
};
42+
43+
const errorClassesFromErrorSrc = Object.fromEntries(
44+
Object.entries(importsFromErrorSrc).filter(
45+
([key, value]) => key.endsWith('Error') && value.toString().startsWith('class')
46+
)
3347
);
34-
errorClasses = { ...errorClasses, MongoPoolClosedError, MongoWaitQueueTimeoutError };
3548

36-
for (const errorName in errorClasses) {
37-
describe(errorName, () => {
38-
it(`name should be read-only`, () => {
49+
it('all defined errors should be public', () => {
50+
expect(
51+
setDifference(Object.keys(errorClassesFromEntryPoint), Object.keys(errorClassesFromErrorSrc))
52+
).to.have.property('size', 3);
53+
54+
expect(
55+
setDifference(Object.keys(errorClassesFromErrorSrc), Object.keys(errorClassesFromEntryPoint))
56+
).to.have.property('size', 0);
57+
});
58+
59+
describe('error names should be read-only', () => {
60+
for (const [errorName, errorClass] of Object.entries(errorClassesFromEntryPoint)) {
61+
it(`${errorName} should be read-only`, () => {
3962
// Dynamically create error class with message
40-
let error = new errorClasses[errorName]('generated by test');
63+
const error = new (errorClass as any)('generated by test', {});
4164
// expect name property to be class name
4265
expect(error).to.have.property('name', errorName);
4366

4467
try {
4568
error.name = 'renamed by test';
69+
// eslint-disable-next-line no-empty
4670
} catch (err) {}
4771
expect(error).to.have.property('name', errorName);
4872
});
49-
});
50-
}
73+
}
74+
});
5175

5276
describe('MongoError#constructor', () => {
5377
it('should accept a string', function () {
@@ -89,6 +113,39 @@ describe('MongoErrors', () => {
89113
});
90114
});
91115

116+
describe('MongoSystemError#constructor', () => {
117+
context('when the topology description contains an error code', () => {
118+
it('contains the specified code as a top level property', () => {
119+
const topologyDescription = {
120+
error: {
121+
code: 123
122+
}
123+
} as TopologyDescription;
124+
125+
const error = new MongoSystemError('something went wrong', topologyDescription);
126+
expect(error).to.haveOwnProperty('code', 123);
127+
});
128+
});
129+
130+
context('when the topology description does not contain an error code', () => {
131+
it('contains the code as a top level property that is undefined', () => {
132+
const topologyDescription = { error: {} } as TopologyDescription;
133+
134+
const error = new MongoSystemError('something went wrong', topologyDescription);
135+
expect(error).to.haveOwnProperty('code', undefined);
136+
});
137+
});
138+
139+
context('when the topology description does not contain an error property', () => {
140+
it('contains the code as a top level property that is undefined', () => {
141+
const topologyDescription = {} as TopologyDescription;
142+
143+
const error = new MongoSystemError('something went wrong', topologyDescription);
144+
expect(error).to.haveOwnProperty('code', undefined);
145+
});
146+
});
147+
});
148+
92149
describe('#isRetryableEndTransactionError', function () {
93150
context('when the error has a RetryableWriteError label', function () {
94151
const error = new MongoNetworkError('');
@@ -202,7 +259,10 @@ describe('MongoErrors', () => {
202259
const errorWithOptionFalse = new MongoNetworkError('', { beforeHandshake: false });
203260
expect(getSymbolFrom(errorWithOptionFalse, 'beforeHandshake', false)).to.be.a('symbol');
204261

205-
const errorWithBadOption = new MongoNetworkError('', { beforeHandshake: 'not boolean' });
262+
const errorWithBadOption = new MongoNetworkError('', {
263+
// @ts-expect-error: beforeHandshake must be a boolean value
264+
beforeHandshake: 'not boolean'
265+
});
206266
expect(getSymbolFrom(errorWithBadOption, 'beforeHandshake', false)).to.be.an('undefined');
207267

208268
const errorWithoutOption = new MongoNetworkError('');
@@ -254,14 +314,14 @@ describe('MongoErrors', () => {
254314
};
255315

256316
before(() => (test = new ReplSetFixture()));
257-
afterEach(() => mock.cleanup());
317+
afterEach(() => cleanup());
258318
beforeEach(() => test.setup());
259319

260320
function makeAndConnectReplSet(cb) {
261321
let invoked = false;
262322
const replSet = new Topology(
263323
[test.primaryServer.hostAddress(), test.firstSecondaryServer.hostAddress()],
264-
{ replicaSet: 'rs' }
324+
{ replicaSet: 'rs' } as TopologyOptions
265325
);
266326

267327
replSet.once('error', err => {

0 commit comments

Comments
 (0)