Skip to content

Commit

Permalink
test(NODE-4919): import mongodb-legacy in tests (#3514)
Browse files Browse the repository at this point in the history
  • Loading branch information
nbbeeken authored Jan 20, 2023
1 parent a35ede3 commit 056c86f
Show file tree
Hide file tree
Showing 6 changed files with 195 additions and 2 deletions.
53 changes: 53 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
"js-yaml": "^4.1.0",
"mocha": "^9.2.2",
"mocha-sinon": "^2.1.2",
"mongodb-legacy": "^4.0.0",
"nyc": "^15.1.0",
"prettier": "^2.7.1",
"rimraf": "^3.0.2",
Expand Down
84 changes: 84 additions & 0 deletions test/mongodb.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
/* eslint-disable @typescript-eslint/no-restricted-imports */
import * as fs from 'node:fs';
import * as path from 'node:path';
import * as process from 'node:process';
import * as vm from 'node:vm';

// eslint-disable-next-line @typescript-eslint/no-unused-vars
function printExports() {
Expand All @@ -23,6 +25,75 @@ function printExports() {
}
}

/**
* Using node's require resolution logic this function will locate the entrypoint for the `'mongodb-legacy'` module,
* then execute the `mongodb-legacy` module in a `vm` context that replaces the global require function with a custom
* implementation. The custom version of `require` will return the local instance of the driver import (magically compiled by ts-node) when
* the module specifier is 'mongodb' and otherwise defer to the normal require behavior to import relative files and stdlib modules.
* Each of the legacy module's patched classes are placed on the input object.
*
* @param exportsToOverride - An object that is an import of the MongoDB driver to be modified by this function
*/
function importMongoDBLegacy(exportsToOverride: Record<string, unknown>) {
const mongodbLegacyEntryPoint = require.resolve('mongodb-legacy');
const mongodbLegacyLocation = path.dirname(mongodbLegacyEntryPoint);
const mongodbLegacyIndex = fs.readFileSync(mongodbLegacyEntryPoint, {
encoding: 'utf8'
});
// eslint-disable-next-line @typescript-eslint/no-var-requires
const localMongoDB = require('../src/index');
const ctx = vm.createContext({
module: { exports: null },
require: (mod: string) => {
if (mod === 'mongodb') {
return localMongoDB;
} else if (mod.startsWith('.')) {
return require(path.join(mongodbLegacyLocation, mod));
}
return require(mod);
}
});
vm.runInContext(mongodbLegacyIndex, ctx);

const mongodbLegacy = ctx.module.exports;

Object.defineProperty(exportsToOverride, 'Admin', { get: () => mongodbLegacy.Admin });
Object.defineProperty(exportsToOverride, 'FindCursor', { get: () => mongodbLegacy.FindCursor });
Object.defineProperty(exportsToOverride, 'ListCollectionsCursor', {
get: () => mongodbLegacy.ListCollectionsCursor
});
Object.defineProperty(exportsToOverride, 'ListIndexesCursor', {
get: () => mongodbLegacy.ListIndexesCursor
});
Object.defineProperty(exportsToOverride, 'AggregationCursor', {
get: () => mongodbLegacy.AggregationCursor
});
Object.defineProperty(exportsToOverride, 'ChangeStream', {
get: () => mongodbLegacy.ChangeStream
});
Object.defineProperty(exportsToOverride, 'Collection', { get: () => mongodbLegacy.Collection });
Object.defineProperty(exportsToOverride, 'Db', { get: () => mongodbLegacy.Db });
Object.defineProperty(exportsToOverride, 'GridFSBucket', {
get: () => mongodbLegacy.GridFSBucket
});
Object.defineProperty(exportsToOverride, 'ClientSession', {
get: () => mongodbLegacy.ClientSession
});
Object.defineProperty(exportsToOverride, 'MongoClient', { get: () => mongodbLegacy.MongoClient });
Object.defineProperty(exportsToOverride, 'ClientSession', {
get: () => mongodbLegacy.ClientSession
});
Object.defineProperty(exportsToOverride, 'GridFSBucketWriteStream', {
get: () => mongodbLegacy.GridFSBucketWriteStream
});
Object.defineProperty(exportsToOverride, 'OrderedBulkOperation', {
get: () => mongodbLegacy.OrderedBulkOperation
});
Object.defineProperty(exportsToOverride, 'UnorderedBulkOperation', {
get: () => mongodbLegacy.UnorderedBulkOperation
});
}

export * from '../src/admin';
export * from '../src/bson';
export * from '../src/bulk/common';
Expand Down Expand Up @@ -125,3 +196,16 @@ export * from '../src/write_concern';

// Must be last for precedence
export * from '../src/index';

/**
* TODO(NODE-4979): ENABLE_MONGODB_LEGACY is 'true' by default for now
*/
const ENABLE_MONGODB_LEGACY =
typeof process.env.ENABLE_MONGODB_LEGACY === 'string' && process.env.ENABLE_MONGODB_LEGACY !== ''
? process.env.ENABLE_MONGODB_LEGACY
: 'true';

if (ENABLE_MONGODB_LEGACY === 'true') {
// Override our own exports with the legacy patched ones
importMongoDBLegacy(module.exports);
}
8 changes: 7 additions & 1 deletion test/unit/assorted/client.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const { isHello } = require('../../mongodb');

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

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

return client.connect().then(() => {
expect(handshake).to.have.nested.property('client.driver');
expect(handshake).nested.property('client.driver.name').to.equal('nodejs|mongoose');
expect(handshake)
.nested.property('client.driver.name')
// Currently the tests import either MongoClient or LegacyMongoClient, the latter of which overrides the client metadata
// We still are confirming here that a third party wrapper can set the metadata but it will change depending on the
// MongoClient constructor that is imported
.to.equal(isLegacyMongoClient ? 'nodejs|mongodb-legacy|mongoose' : 'nodejs|mongoose');
expect(handshake)
.nested.property('client.driver.version')
.to.match(/|5.7.10/);
Expand Down
2 changes: 1 addition & 1 deletion test/unit/cursor/abstract_cursor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
Server
} from '../../mongodb';

/** Minimal do nothing cursor to focus on testing the base cusor behavior */
/** Minimal do nothing cursor to focus on testing the base cursor behavior */
class ConcreteCursor extends AbstractCursor {
constructor(client: MongoClient, options: AbstractCursorOptions = {}) {
super(client, ns('test.test'), options);
Expand Down
49 changes: 49 additions & 0 deletions test/unit/tools/mongodb-legacy.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { expect } from 'chai';

import {
Admin,
AggregationCursor,
ChangeStream,
ClientSession,
Collection,
Db,
FindCursor,
GridFSBucket,
GridFSBucketWriteStream,
ListCollectionsCursor,
ListIndexesCursor,
MongoClient,
OrderedBulkOperation,
UnorderedBulkOperation
} from '../../mongodb';

const classesWithAsyncAPIs = new Map<string, any>([
['Admin', Admin],
['FindCursor', FindCursor],
['ListCollectionsCursor', ListCollectionsCursor],
['ListIndexesCursor', ListIndexesCursor],
['AggregationCursor', AggregationCursor],
['ChangeStream', ChangeStream],
['Collection', Collection],
['Db', Db],
['GridFSBucket', GridFSBucket],
['ClientSession', ClientSession],
['GridFSBucketWriteStream', GridFSBucketWriteStream],
['OrderedBulkOperation', OrderedBulkOperation],
['UnorderedBulkOperation', UnorderedBulkOperation]
]);

describe('mongodb-legacy', () => {
for (const [className, ctor] of classesWithAsyncAPIs) {
it(`test suite imports a ${className} with the legacy symbol`, () => {
// Just confirming that the mongodb-legacy import is correctly overriding the local copies
// of these classes from "src". See test/mongodb.ts for more.
expect(ctor.prototype).to.have.property(Symbol.for('@@mdb.callbacks.toLegacy'));
});
}
it('test suite imports a LegacyMongoClient as MongoClient', () => {
// Just confirming that the mongodb-legacy import is correctly overriding the local copy
// of MongoClient from "src". See test/mongodb.ts for more.
expect(MongoClient).to.have.property('name', 'LegacyMongoClient');
});
});

0 comments on commit 056c86f

Please sign in to comment.