Skip to content

Commit

Permalink
fix(NODE-3948): Add error code to MongoSystemError (#3149)
Browse files Browse the repository at this point in the history
  • Loading branch information
baileympearson committed Feb 22, 2022
1 parent b0d4413 commit 446da95
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 38 deletions.
2 changes: 2 additions & 0 deletions src/error.ts
Expand Up @@ -625,6 +625,8 @@ export class MongoSystemError extends MongoError {
if (reason) {
this.reason = reason;
}

this.code = reason.error?.code;
}

get name(): string {
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Expand Up @@ -62,6 +62,7 @@ export {
MongoServerError,
MongoServerSelectionError,
MongoSystemError,
MongoTailableCursorError,
MongoTopologyClosedError,
MongoTransactionError,
MongoWriteConcernError
Expand Down
136 changes: 98 additions & 38 deletions test/unit/error.test.js → test/unit/error.test.ts
@@ -1,53 +1,77 @@
/* eslint no-empty: ["error", { "allowEmptyCatch": true }] */
'use strict';

const expect = require('chai').expect;
const mock = require('../tools/mongodb-mock/index');
const { getSymbolFrom } = require('../tools/utils');
const { ReplSetFixture } = require('../tools/common');
const { ns, isHello } = require('../../src/utils');
const { Topology } = require('../../src/sdam/topology');
const {
MongoNetworkError,
MongoWriteConcernError,
import { expect } from 'chai';

import {
PoolClosedError as MongoPoolClosedError,
WaitQueueTimeoutError as MongoWaitQueueTimeoutError
} from '../../src/cmap/errors';
import {
isRetryableEndTransactionError,
isSDAMUnrecoverableError,
LEGACY_NOT_PRIMARY_OR_SECONDARY_ERROR_MESSAGE,
LEGACY_NOT_WRITABLE_PRIMARY_ERROR_MESSAGE,
MongoSystemError,
NODE_IS_RECOVERING_ERROR_MESSAGE
} from '../../src/error';
import * as importsFromErrorSrc from '../../src/error';
import {
MongoError,
MongoNetworkError,
MongoParseError,
MongoServerError,
MongoParseError
} = require('../../src/index');
const {
LEGACY_NOT_WRITABLE_PRIMARY_ERROR_MESSAGE,
LEGACY_NOT_PRIMARY_OR_SECONDARY_ERROR_MESSAGE,
NODE_IS_RECOVERING_ERROR_MESSAGE,
isRetryableEndTransactionError,
isSDAMUnrecoverableError
} = require('../../src/error');
const {
PoolClosedError: MongoPoolClosedError,
WaitQueueTimeoutError: MongoWaitQueueTimeoutError
} = require('../../src/cmap/errors');
MongoWriteConcernError,
TopologyDescription
} from '../../src/index';
import * as importsFromEntryPoint from '../../src/index';
import { Topology, TopologyOptions } from '../../src/sdam/topology';
import { isHello, ns, setDifference } from '../../src/utils';
import { ReplSetFixture } from '../tools/common';
import { cleanup } from '../tools/mongodb-mock/index';
import { getSymbolFrom } from '../tools/utils';

describe('MongoErrors', () => {
// import errors as object
let errorClasses = Object.fromEntries(
Object.entries(require('../../src/index')).filter(([key]) => key.endsWith('Error'))
let errorClassesFromEntryPoint = Object.fromEntries(
Object.entries(importsFromEntryPoint).filter(
([key, value]) => key.endsWith('Error') && value.toString().startsWith('class')
)
) as any;
errorClassesFromEntryPoint = {
...errorClassesFromEntryPoint,
MongoPoolClosedError,
MongoWaitQueueTimeoutError
};

const errorClassesFromErrorSrc = Object.fromEntries(
Object.entries(importsFromErrorSrc).filter(
([key, value]) => key.endsWith('Error') && value.toString().startsWith('class')
)
);
errorClasses = { ...errorClasses, MongoPoolClosedError, MongoWaitQueueTimeoutError };

for (const errorName in errorClasses) {
describe(errorName, () => {
it(`name should be read-only`, () => {
it('all defined errors should be public', () => {
expect(
setDifference(Object.keys(errorClassesFromEntryPoint), Object.keys(errorClassesFromErrorSrc))
).to.have.property('size', 3);

expect(
setDifference(Object.keys(errorClassesFromErrorSrc), Object.keys(errorClassesFromEntryPoint))
).to.have.property('size', 0);
});

describe('error names should be read-only', () => {
for (const [errorName, errorClass] of Object.entries(errorClassesFromEntryPoint)) {
it(`${errorName} should be read-only`, () => {
// Dynamically create error class with message
let error = new errorClasses[errorName]('generated by test');
const error = new (errorClass as any)('generated by test', {});
// expect name property to be class name
expect(error).to.have.property('name', errorName);

try {
error.name = 'renamed by test';
// eslint-disable-next-line no-empty
} catch (err) {}
expect(error).to.have.property('name', errorName);
});
});
}
}
});

describe('MongoError#constructor', () => {
it('should accept a string', function () {
Expand Down Expand Up @@ -89,6 +113,39 @@ describe('MongoErrors', () => {
});
});

describe('MongoSystemError#constructor', () => {
context('when the topology description contains an error code', () => {
it('contains the specified code as a top level property', () => {
const topologyDescription = {
error: {
code: 123
}
} as TopologyDescription;

const error = new MongoSystemError('something went wrong', topologyDescription);
expect(error).to.haveOwnProperty('code', 123);
});
});

context('when the topology description does not contain an error code', () => {
it('contains the code as a top level property that is undefined', () => {
const topologyDescription = { error: {} } as TopologyDescription;

const error = new MongoSystemError('something went wrong', topologyDescription);
expect(error).to.haveOwnProperty('code', undefined);
});
});

context('when the topology description does not contain an error property', () => {
it('contains the code as a top level property that is undefined', () => {
const topologyDescription = {} as TopologyDescription;

const error = new MongoSystemError('something went wrong', topologyDescription);
expect(error).to.haveOwnProperty('code', undefined);
});
});
});

describe('#isRetryableEndTransactionError', function () {
context('when the error has a RetryableWriteError label', function () {
const error = new MongoNetworkError('');
Expand Down Expand Up @@ -202,7 +259,10 @@ describe('MongoErrors', () => {
const errorWithOptionFalse = new MongoNetworkError('', { beforeHandshake: false });
expect(getSymbolFrom(errorWithOptionFalse, 'beforeHandshake', false)).to.be.a('symbol');

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

const errorWithoutOption = new MongoNetworkError('');
Expand Down Expand Up @@ -254,14 +314,14 @@ describe('MongoErrors', () => {
};

before(() => (test = new ReplSetFixture()));
afterEach(() => mock.cleanup());
afterEach(() => cleanup());
beforeEach(() => test.setup());

function makeAndConnectReplSet(cb) {
let invoked = false;
const replSet = new Topology(
[test.primaryServer.hostAddress(), test.firstSecondaryServer.hostAddress()],
{ replicaSet: 'rs' }
{ replicaSet: 'rs' } as TopologyOptions
);

replSet.once('error', err => {
Expand Down

0 comments on commit 446da95

Please sign in to comment.