Skip to content

Commit 056c86f

Browse files
authored
test(NODE-4919): import mongodb-legacy in tests (#3514)
1 parent a35ede3 commit 056c86f

File tree

6 files changed

+195
-2
lines changed

6 files changed

+195
-2
lines changed

package-lock.json

Lines changed: 53 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@
8181
"js-yaml": "^4.1.0",
8282
"mocha": "^9.2.2",
8383
"mocha-sinon": "^2.1.2",
84+
"mongodb-legacy": "^4.0.0",
8485
"nyc": "^15.1.0",
8586
"prettier": "^2.7.1",
8687
"rimraf": "^3.0.2",

test/mongodb.ts

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
/* eslint-disable @typescript-eslint/no-restricted-imports */
22
import * as fs from 'node:fs';
33
import * as path from 'node:path';
4+
import * as process from 'node:process';
5+
import * as vm from 'node:vm';
46

57
// eslint-disable-next-line @typescript-eslint/no-unused-vars
68
function printExports() {
@@ -23,6 +25,75 @@ function printExports() {
2325
}
2426
}
2527

28+
/**
29+
* Using node's require resolution logic this function will locate the entrypoint for the `'mongodb-legacy'` module,
30+
* then execute the `mongodb-legacy` module in a `vm` context that replaces the global require function with a custom
31+
* implementation. The custom version of `require` will return the local instance of the driver import (magically compiled by ts-node) when
32+
* the module specifier is 'mongodb' and otherwise defer to the normal require behavior to import relative files and stdlib modules.
33+
* Each of the legacy module's patched classes are placed on the input object.
34+
*
35+
* @param exportsToOverride - An object that is an import of the MongoDB driver to be modified by this function
36+
*/
37+
function importMongoDBLegacy(exportsToOverride: Record<string, unknown>) {
38+
const mongodbLegacyEntryPoint = require.resolve('mongodb-legacy');
39+
const mongodbLegacyLocation = path.dirname(mongodbLegacyEntryPoint);
40+
const mongodbLegacyIndex = fs.readFileSync(mongodbLegacyEntryPoint, {
41+
encoding: 'utf8'
42+
});
43+
// eslint-disable-next-line @typescript-eslint/no-var-requires
44+
const localMongoDB = require('../src/index');
45+
const ctx = vm.createContext({
46+
module: { exports: null },
47+
require: (mod: string) => {
48+
if (mod === 'mongodb') {
49+
return localMongoDB;
50+
} else if (mod.startsWith('.')) {
51+
return require(path.join(mongodbLegacyLocation, mod));
52+
}
53+
return require(mod);
54+
}
55+
});
56+
vm.runInContext(mongodbLegacyIndex, ctx);
57+
58+
const mongodbLegacy = ctx.module.exports;
59+
60+
Object.defineProperty(exportsToOverride, 'Admin', { get: () => mongodbLegacy.Admin });
61+
Object.defineProperty(exportsToOverride, 'FindCursor', { get: () => mongodbLegacy.FindCursor });
62+
Object.defineProperty(exportsToOverride, 'ListCollectionsCursor', {
63+
get: () => mongodbLegacy.ListCollectionsCursor
64+
});
65+
Object.defineProperty(exportsToOverride, 'ListIndexesCursor', {
66+
get: () => mongodbLegacy.ListIndexesCursor
67+
});
68+
Object.defineProperty(exportsToOverride, 'AggregationCursor', {
69+
get: () => mongodbLegacy.AggregationCursor
70+
});
71+
Object.defineProperty(exportsToOverride, 'ChangeStream', {
72+
get: () => mongodbLegacy.ChangeStream
73+
});
74+
Object.defineProperty(exportsToOverride, 'Collection', { get: () => mongodbLegacy.Collection });
75+
Object.defineProperty(exportsToOverride, 'Db', { get: () => mongodbLegacy.Db });
76+
Object.defineProperty(exportsToOverride, 'GridFSBucket', {
77+
get: () => mongodbLegacy.GridFSBucket
78+
});
79+
Object.defineProperty(exportsToOverride, 'ClientSession', {
80+
get: () => mongodbLegacy.ClientSession
81+
});
82+
Object.defineProperty(exportsToOverride, 'MongoClient', { get: () => mongodbLegacy.MongoClient });
83+
Object.defineProperty(exportsToOverride, 'ClientSession', {
84+
get: () => mongodbLegacy.ClientSession
85+
});
86+
Object.defineProperty(exportsToOverride, 'GridFSBucketWriteStream', {
87+
get: () => mongodbLegacy.GridFSBucketWriteStream
88+
});
89+
Object.defineProperty(exportsToOverride, 'OrderedBulkOperation', {
90+
get: () => mongodbLegacy.OrderedBulkOperation
91+
});
92+
Object.defineProperty(exportsToOverride, 'UnorderedBulkOperation', {
93+
get: () => mongodbLegacy.UnorderedBulkOperation
94+
});
95+
}
96+
2697
export * from '../src/admin';
2798
export * from '../src/bson';
2899
export * from '../src/bulk/common';
@@ -125,3 +196,16 @@ export * from '../src/write_concern';
125196

126197
// Must be last for precedence
127198
export * from '../src/index';
199+
200+
/**
201+
* TODO(NODE-4979): ENABLE_MONGODB_LEGACY is 'true' by default for now
202+
*/
203+
const ENABLE_MONGODB_LEGACY =
204+
typeof process.env.ENABLE_MONGODB_LEGACY === 'string' && process.env.ENABLE_MONGODB_LEGACY !== ''
205+
? process.env.ENABLE_MONGODB_LEGACY
206+
: 'true';
207+
208+
if (ENABLE_MONGODB_LEGACY === 'true') {
209+
// Override our own exports with the legacy patched ones
210+
importMongoDBLegacy(module.exports);
211+
}

test/unit/assorted/client.test.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const { isHello } = require('../../mongodb');
77

88
describe('Client (unit)', function () {
99
let server, client;
10+
const isLegacyMongoClient = MongoClient.name === 'LegacyMongoClient';
1011

1112
afterEach(async () => {
1213
await client.close();
@@ -38,7 +39,12 @@ describe('Client (unit)', function () {
3839

3940
return client.connect().then(() => {
4041
expect(handshake).to.have.nested.property('client.driver');
41-
expect(handshake).nested.property('client.driver.name').to.equal('nodejs|mongoose');
42+
expect(handshake)
43+
.nested.property('client.driver.name')
44+
// Currently the tests import either MongoClient or LegacyMongoClient, the latter of which overrides the client metadata
45+
// We still are confirming here that a third party wrapper can set the metadata but it will change depending on the
46+
// MongoClient constructor that is imported
47+
.to.equal(isLegacyMongoClient ? 'nodejs|mongodb-legacy|mongoose' : 'nodejs|mongoose');
4248
expect(handshake)
4349
.nested.property('client.driver.version')
4450
.to.match(/|5.7.10/);

test/unit/cursor/abstract_cursor.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
Server
1212
} from '../../mongodb';
1313

14-
/** Minimal do nothing cursor to focus on testing the base cusor behavior */
14+
/** Minimal do nothing cursor to focus on testing the base cursor behavior */
1515
class ConcreteCursor extends AbstractCursor {
1616
constructor(client: MongoClient, options: AbstractCursorOptions = {}) {
1717
super(client, ns('test.test'), options);
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { expect } from 'chai';
2+
3+
import {
4+
Admin,
5+
AggregationCursor,
6+
ChangeStream,
7+
ClientSession,
8+
Collection,
9+
Db,
10+
FindCursor,
11+
GridFSBucket,
12+
GridFSBucketWriteStream,
13+
ListCollectionsCursor,
14+
ListIndexesCursor,
15+
MongoClient,
16+
OrderedBulkOperation,
17+
UnorderedBulkOperation
18+
} from '../../mongodb';
19+
20+
const classesWithAsyncAPIs = new Map<string, any>([
21+
['Admin', Admin],
22+
['FindCursor', FindCursor],
23+
['ListCollectionsCursor', ListCollectionsCursor],
24+
['ListIndexesCursor', ListIndexesCursor],
25+
['AggregationCursor', AggregationCursor],
26+
['ChangeStream', ChangeStream],
27+
['Collection', Collection],
28+
['Db', Db],
29+
['GridFSBucket', GridFSBucket],
30+
['ClientSession', ClientSession],
31+
['GridFSBucketWriteStream', GridFSBucketWriteStream],
32+
['OrderedBulkOperation', OrderedBulkOperation],
33+
['UnorderedBulkOperation', UnorderedBulkOperation]
34+
]);
35+
36+
describe('mongodb-legacy', () => {
37+
for (const [className, ctor] of classesWithAsyncAPIs) {
38+
it(`test suite imports a ${className} with the legacy symbol`, () => {
39+
// Just confirming that the mongodb-legacy import is correctly overriding the local copies
40+
// of these classes from "src". See test/mongodb.ts for more.
41+
expect(ctor.prototype).to.have.property(Symbol.for('@@mdb.callbacks.toLegacy'));
42+
});
43+
}
44+
it('test suite imports a LegacyMongoClient as MongoClient', () => {
45+
// Just confirming that the mongodb-legacy import is correctly overriding the local copy
46+
// of MongoClient from "src". See test/mongodb.ts for more.
47+
expect(MongoClient).to.have.property('name', 'LegacyMongoClient');
48+
});
49+
});

0 commit comments

Comments
 (0)