diff --git a/.eslintrc.json b/.eslintrc.json index d009780f372..95c6e99a813 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -276,7 +276,8 @@ "patterns": [ "**/../lib/**", "mongodb-mock-server", - "node:*" + "node:*", + "os" ], "paths": [ { @@ -327,4 +328,4 @@ } } ] -} +} \ No newline at end of file diff --git a/src/cmap/auth/gssapi.ts b/src/cmap/auth/gssapi.ts index d18cb6b360e..0154057919f 100644 --- a/src/cmap/auth/gssapi.ts +++ b/src/cmap/auth/gssapi.ts @@ -1,5 +1,4 @@ import * as dns from 'dns'; -import * as os from 'os'; import { getKerberos, type Kerberos, type KerberosClient } from '../../deps'; import { MongoInvalidArgumentError, MongoMissingCredentialsError } from '../../error'; @@ -69,9 +68,13 @@ export class GSSAPI extends AuthProvider { } } -async function makeKerberosClient(authContext: AuthContext): Promise { - const { hostAddress } = authContext.options; - const { credentials } = authContext; +async function makeKerberosClient({ + options: { + hostAddress, + runtime: { os } + }, + credentials +}: AuthContext): Promise { if (!hostAddress || typeof hostAddress.host !== 'string' || !credentials) { throw new MongoInvalidArgumentError( 'Connection must have host and port and credentials defined.' diff --git a/src/cmap/connection.ts b/src/cmap/connection.ts index 9652e3a5e4f..dfffb15daeb 100644 --- a/src/cmap/connection.ts +++ b/src/cmap/connection.ts @@ -35,6 +35,7 @@ import { type MongoClientAuthProviders } from '../mongo_client_auth_providers'; import { MongoLoggableComponent, type MongoLogger, SeverityLevel } from '../mongo_logger'; import { type Abortable, type CancellationToken, TypedEventEmitter } from '../mongo_types'; import { ReadPreference, type ReadPreferenceLike } from '../read_preference'; +import { type Runtime } from '../runtime_adapters'; import { ServerType } from '../sdam/common'; import { applySession, type ClientSession, updateSessionFromResponse } from '../sessions'; import { type TimeoutContext, TimeoutError } from '../timeout'; @@ -143,6 +144,8 @@ export interface ConnectionOptions metadata: Promise; /** @internal */ mongoLogger?: MongoLogger | undefined; + /** @internal */ + runtime: Runtime; } /** @public */ diff --git a/src/cmap/handshake/client_metadata.ts b/src/cmap/handshake/client_metadata.ts index 48cb6a47350..3b79e1df48f 100644 --- a/src/cmap/handshake/client_metadata.ts +++ b/src/cmap/handshake/client_metadata.ts @@ -1,4 +1,3 @@ -import * as os from 'os'; import * as process from 'process'; import { BSON, type Document, Int32, NumberUtils } from '../../bson'; @@ -96,7 +95,8 @@ export class LimitedSizeDocument { } } -type MakeClientMetadataOptions = Pick; +type MakeClientMetadataOptions = Pick; + /** * From the specs: * Implementors SHOULD cumulatively update fields in the following order until the document is under the size limit: @@ -107,7 +107,7 @@ type MakeClientMetadataOptions = Pick; */ export async function makeClientMetadata( driverInfoList: DriverInfo[], - { appName = '' }: MakeClientMetadataOptions + { appName = '', runtime: { os } }: MakeClientMetadataOptions ): Promise { const metadataDocument = new LimitedSizeDocument(512); diff --git a/src/connection_string.ts b/src/connection_string.ts index df6dfc607a0..06315a96869 100644 --- a/src/connection_string.ts +++ b/src/connection_string.ts @@ -20,6 +20,7 @@ import { import { MongoLoggableComponent, MongoLogger, SeverityLevel } from './mongo_logger'; import { ReadConcern, type ReadConcernLevel } from './read_concern'; import { ReadPreference, type ReadPreferenceMode } from './read_preference'; +import { resolveRuntimeAdapters } from './runtime_adapters'; import { ServerMonitoringMode } from './sdam/monitor'; import type { TagSet } from './sdam/server_description'; import { @@ -538,6 +539,8 @@ export function parseOptions( } ); + mongoOptions.runtime = resolveRuntimeAdapters(options); + return mongoOptions; } @@ -1061,6 +1064,9 @@ export const OPTIONS = { default: true, type: 'boolean' }, + runtimeAdapters: { + type: 'record' + }, serializeFunctions: { type: 'boolean' }, diff --git a/src/index.ts b/src/index.ts index 8f5c4cfa60e..74803dfa2a0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -562,6 +562,7 @@ export type { ReadPreferenceLikeOptions, ReadPreferenceOptions } from './read_preference'; +export type { OsAdapter, Runtime, RuntimeAdapters } from './runtime_adapters'; export type { ClusterTime } from './sdam/common'; export type { Monitor, diff --git a/src/mongo_client.ts b/src/mongo_client.ts index 970f0f88061..87d969fee93 100644 --- a/src/mongo_client.ts +++ b/src/mongo_client.ts @@ -46,6 +46,7 @@ import { EndSessionsOperation } from './operations/end_sessions'; import { executeOperation } from './operations/execute_operation'; import type { ReadConcern, ReadConcernLevel, ReadConcernLike } from './read_concern'; import { ReadPreference, type ReadPreferenceMode } from './read_preference'; +import { type Runtime, type RuntimeAdapters } from './runtime_adapters'; import type { ServerMonitoringMode } from './sdam/monitor'; import type { TagSet } from './sdam/server_description'; import { DeprioritizedServers, readPreferenceServerSelector } from './sdam/server_selection'; @@ -318,6 +319,12 @@ export interface MongoClientOptions extends BSONSerializeOptions, SupportedNodeC connectionType?: typeof Connection; /** @internal */ __skipPingOnConnect?: boolean; + /** + * @experimental + * + * If provided, any adapters provided will be used in place of the corresponding Node.js module. + */ + runtimeAdapters?: RuntimeAdapters; } /** @public */ @@ -1152,4 +1159,7 @@ export interface MongoOptions timeoutMS?: number; /** @internal */ __skipPingOnConnect?: boolean; + + /** @internal */ + runtime: Runtime; } diff --git a/src/runtime_adapters.ts b/src/runtime_adapters.ts new file mode 100644 index 00000000000..bb998d4bd09 --- /dev/null +++ b/src/runtime_adapters.ts @@ -0,0 +1,49 @@ +/* eslint-disable no-restricted-imports, @typescript-eslint/no-require-imports */ + +// We squash the restricted import errors here because we are using type-only imports, which +// do not impact the driver's actual runtime dependencies. +// We also allow restricted imports in this file, because we expect this file to be the only place actually importing restricted Node APIs. + +import type * as os from 'os'; + +import { type MongoClientOptions } from './mongo_client'; + +/** + * @public + * @experimental + * + * Represents the set of dependencies that the driver uses from the [Node.js OS module](https://nodejs.org/api/os.html). + */ +export type OsAdapter = Pick; + +/** + * @public + * @experimental + * + * This type represents the set of dependencies that the driver needs from the Javascript runtime in order to function. + */ +export interface RuntimeAdapters { + os?: OsAdapter; +} + +/** + * @internal + * + * Represents a complete, parsed set of runtime adapters. After options parsing, all adapters + * are always present (either using the user's provided adapter, or defaulting to the Node.js module). + */ +export interface Runtime { + os: OsAdapter; +} + +/** + * @internal + * + * Given a MongoClientOptions, this function resolves the set of runtime options, providing Nodejs implementations if + * not provided by in `options`, and returns a `Runtime`. + */ +export function resolveRuntimeAdapters(options: MongoClientOptions): Runtime { + return { + os: options.runtimeAdapters?.os ?? require('os') + }; +} diff --git a/test/integration/connection-monitoring-and-pooling/connection.test.ts b/test/integration/connection-monitoring-and-pooling/connection.test.ts index a20f36c7b6e..b6aaf759691 100644 --- a/test/integration/connection-monitoring-and-pooling/connection.test.ts +++ b/test/integration/connection-monitoring-and-pooling/connection.test.ts @@ -22,7 +22,7 @@ import { LEGACY_HELLO_COMMAND } from '../../../src/constants'; import { Topology } from '../../../src/sdam/topology'; import { HostAddress, ns } from '../../../src/utils'; import * as mock from '../../tools/mongodb-mock/index'; -import { processTick, sleep } from '../../tools/utils'; +import { processTick, runtime, sleep } from '../../tools/utils'; import { assert as test, setupDatabase } from '../shared'; const commonConnectOptions = { @@ -49,7 +49,10 @@ describe('Connection', function () { ...commonConnectOptions, connectionType: Connection, ...this.configuration.options, - metadata: makeClientMetadata([], {}) + metadata: makeClientMetadata([], { + runtime + }), + runtime }; let conn; @@ -71,7 +74,8 @@ describe('Connection', function () { connectionType: Connection, ...this.configuration.options, monitorCommands: true, - metadata: makeClientMetadata([], {}) + runtime, + metadata: makeClientMetadata([], { runtime }) }; let conn; @@ -102,7 +106,10 @@ describe('Connection', function () { connectionType: Connection, ...this.configuration.options, monitorCommands: true, - metadata: makeClientMetadata([], {}) + runtime, + metadata: makeClientMetadata([], { + runtime + }) }; let conn; diff --git a/test/tools/utils.ts b/test/tools/utils.ts index 5a23fdd9706..d3948d773cd 100644 --- a/test/tools/utils.ts +++ b/test/tools/utils.ts @@ -18,10 +18,12 @@ import { type HostAddress, MongoClient, type MongoClientOptions, + type Runtime, type ServerApiVersion, type TopologyOptions } from '../../src'; import { OP_MSG } from '../../src/cmap/wire_protocol/constants'; +import { resolveRuntimeAdapters } from '../../src/runtime_adapters'; import { Topology } from '../../src/sdam/topology'; import { processTimeMS } from '../../src/utils'; import { type TestConfiguration } from './runner/config'; @@ -604,3 +606,8 @@ export function configureMongocryptdSpawnHooks( port }; } + +/** + * A `Runtime` that resolves to entirely Nodejs modules, useful when tests must provide a default `runtime` object to an API. + */ +export const runtime: Runtime = resolveRuntimeAdapters({}); diff --git a/test/unit/assorted/optional_require.test.ts b/test/unit/assorted/optional_require.test.ts index 5dc579ee304..463f7f95ffb 100644 --- a/test/unit/assorted/optional_require.test.ts +++ b/test/unit/assorted/optional_require.test.ts @@ -7,6 +7,7 @@ import { GSSAPI } from '../../../src/cmap/auth/gssapi'; import { compress } from '../../../src/cmap/wire_protocol/compression'; import { MongoMissingDependencyError } from '../../../src/error'; import { HostAddress } from '../../../src/utils'; +import { runtime } from '../../tools/utils'; function moduleExistsSync(moduleName) { return existsSync(resolve(__dirname, `../../../node_modules/${moduleName}`)); @@ -41,7 +42,13 @@ describe('optionalRequire', function () { const gssapi = new GSSAPI(); const error = await gssapi - .auth(new AuthContext(null, true, { hostAddress: new HostAddress('a'), credentials: true })) + .auth( + new AuthContext(null, true, { + hostAddress: new HostAddress('a'), + credentials: true, + runtime + }) + ) .then( () => null, e => e diff --git a/test/unit/cmap/connect.test.ts b/test/unit/cmap/connect.test.ts index a97cb7194a6..cd205de976c 100644 --- a/test/unit/cmap/connect.test.ts +++ b/test/unit/cmap/connect.test.ts @@ -15,6 +15,7 @@ import { CancellationToken } from '../../../src/mongo_types'; import { HostAddress, isHello } from '../../../src/utils'; import { genClusterTime } from '../../tools/common'; import * as mock from '../../tools/mongodb-mock/index'; +import { runtime } from '../../tools/utils'; const CONNECT_DEFAULTS = { id: 1, @@ -210,7 +211,9 @@ describe('Connect Tests', function () { connection: {}, options: { ...CONNECT_DEFAULTS, - metadata: makeClientMetadata([], {}) + metadata: makeClientMetadata([], { + runtime + }) } }; }); @@ -239,7 +242,10 @@ describe('Connect Tests', function () { name: 's'.repeat(128) } ], - { appName: longAppName } + { + appName: longAppName, + runtime + } ); const longAuthContext = { connection: {}, @@ -267,7 +273,9 @@ describe('Connect Tests', function () { connection: {}, options: { ...CONNECT_DEFAULTS, - metadata: makeClientMetadata([], {}) + metadata: makeClientMetadata([], { + runtime + }) } }; }); @@ -296,7 +304,10 @@ describe('Connect Tests', function () { name: 's'.repeat(128) } ], - { appName: longAppName } + { + appName: longAppName, + runtime + } ); const longAuthContext = { connection: {}, diff --git a/test/unit/cmap/handshake/client_metadata.test.ts b/test/unit/cmap/handshake/client_metadata.test.ts index 61288384481..a7c5e864d7e 100644 --- a/test/unit/cmap/handshake/client_metadata.test.ts +++ b/test/unit/cmap/handshake/client_metadata.test.ts @@ -12,6 +12,7 @@ import { makeClientMetadata } from '../../../../src/cmap/handshake/client_metadata'; import { MongoInvalidArgumentError } from '../../../../src/error'; +import { runtime } from '../../../tools/utils'; describe('client metadata module', () => { afterEach(() => sinon.restore()); @@ -141,7 +142,7 @@ describe('client metadata module', () => { describe('makeClientMetadata()', () => { context('when no FAAS environment is detected', () => { it('does not append FAAS metadata', async () => { - const metadata = await makeClientMetadata([], {}); + const metadata = await makeClientMetadata([], { runtime }); expect(metadata).not.to.have.property( 'env', 'faas metadata applied in a non-faas environment' @@ -164,14 +165,16 @@ describe('client metadata module', () => { context('when driverInfo.platform is provided', () => { it('throws an error if driverInfo.platform is too large', async () => { - const error = await makeClientMetadata([{ platform: 'a'.repeat(512) }], {}).catch(e => e); + const error = await makeClientMetadata([{ platform: 'a'.repeat(512) }], { runtime }).catch( + e => e + ); expect(error) .to.be.instanceOf(MongoInvalidArgumentError) .to.match(/platform/); }); it('appends driverInfo.platform to the platform field', async () => { - const metadata = await makeClientMetadata([{ platform: 'myPlatform' }], {}); + const metadata = await makeClientMetadata([{ platform: 'myPlatform' }], { runtime }); expect(metadata).to.deep.equal({ driver: { name: 'nodejs', @@ -190,12 +193,14 @@ describe('client metadata module', () => { context('when driverInfo.name is provided', () => { it('throws an error if driverInfo.name is too large', async () => { - const error = await makeClientMetadata([{ name: 'a'.repeat(512) }], {}).catch(e => e); + const error = await makeClientMetadata([{ name: 'a'.repeat(512) }], { runtime }).catch( + e => e + ); expect(error).to.be.instanceOf(MongoInvalidArgumentError).to.match(/name/); }); it('appends driverInfo.name to the driver.name field', async () => { - const metadata = await makeClientMetadata([{ name: 'myName' }], {}); + const metadata = await makeClientMetadata([{ name: 'myName' }], { runtime }); expect(metadata).to.deep.equal({ driver: { name: 'nodejs|myName', @@ -214,14 +219,16 @@ describe('client metadata module', () => { context('when driverInfo.version is provided', () => { it('throws an error if driverInfo.version is too large', async () => { - const error = await makeClientMetadata([{ version: 'a'.repeat(512) }], {}).catch(e => e); + const error = await makeClientMetadata([{ version: 'a'.repeat(512) }], { runtime }).catch( + e => e + ); expect(error) .to.be.instanceOf(MongoInvalidArgumentError) .to.match(/version/); }); it('appends driverInfo.version to the version field', async () => { - const metadata = await makeClientMetadata([{ version: 'myVersion' }], {}); + const metadata = await makeClientMetadata([{ version: 'myVersion' }], { runtime }); expect(metadata).to.deep.equal({ driver: { name: 'nodejs', @@ -240,7 +247,7 @@ describe('client metadata module', () => { context('when no custom driverInto is provided', () => { it('does not append the driver info to the metadata', async () => { - const metadata = await makeClientMetadata([], {}); + const metadata = await makeClientMetadata([], { runtime }); expect(metadata).to.deep.equal({ driver: { name: 'nodejs', @@ -257,7 +264,7 @@ describe('client metadata module', () => { }); it('does not set the application field', async () => { - const metadata = await makeClientMetadata([], {}); + const metadata = await makeClientMetadata([], { runtime }); expect(metadata).not.to.have.property('application'); }); }); @@ -267,6 +274,7 @@ describe('client metadata module', () => { it('truncates the application name to <=128 bytes', async () => { const longString = 'a'.repeat(300); const metadata = await makeClientMetadata([], { + runtime, appName: longString }); expect(metadata.application?.name).to.be.a('string'); @@ -283,6 +291,7 @@ describe('client metadata module', () => { it('truncates the application name to 129 bytes', async () => { const longString = '€'.repeat(300); const metadata = await makeClientMetadata([], { + runtime, appName: longString }); @@ -298,6 +307,7 @@ describe('client metadata module', () => { context('when the app name is under 128 bytes', () => { it('sets the application name to the value', async () => { const metadata = await makeClientMetadata([], { + runtime, appName: 'myApplication' }); expect(metadata.application?.name).to.equal('myApplication'); @@ -313,37 +323,37 @@ describe('client metadata module', () => { it('sets platform to Deno', async () => { globalThis.Deno = { version: { deno: '1.2.3' } }; - const metadata = await makeClientMetadata([], {}); + const metadata = await makeClientMetadata([], { runtime }); expect(metadata.platform).to.equal('Deno v1.2.3, LE'); }); it('sets platform to Deno with driverInfo.platform', async () => { globalThis.Deno = { version: { deno: '1.2.3' } }; - const metadata = await makeClientMetadata([{ platform: 'myPlatform' }], {}); + const metadata = await makeClientMetadata([{ platform: 'myPlatform' }], { runtime }); expect(metadata.platform).to.equal('Deno v1.2.3, LE|myPlatform'); }); it('ignores version if Deno.version.deno is not a string', async () => { globalThis.Deno = { version: { deno: 1 } }; - const metadata = await makeClientMetadata([], {}); + const metadata = await makeClientMetadata([], { runtime }); expect(metadata.platform).to.equal('Deno v0.0.0-unknown, LE'); }); it('ignores version if Deno.version does not have a deno property', async () => { globalThis.Deno = { version: { somethingElse: '1.2.3' } }; - const metadata = await makeClientMetadata([], {}); + const metadata = await makeClientMetadata([], { runtime }); expect(metadata.platform).to.equal('Deno v0.0.0-unknown, LE'); }); it('ignores version if Deno.version is null', async () => { globalThis.Deno = { version: null }; - const metadata = await makeClientMetadata([], {}); + const metadata = await makeClientMetadata([], { runtime }); expect(metadata.platform).to.equal('Deno v0.0.0-unknown, LE'); }); it('ignores version if Deno is nullish', async () => { globalThis.Deno = null; - const metadata = await makeClientMetadata([], {}); + const metadata = await makeClientMetadata([], { runtime }); expect(metadata.platform).to.equal('Deno v0.0.0-unknown, LE'); }); }); @@ -357,7 +367,7 @@ describe('client metadata module', () => { globalThis.Bun = class { static version = '1.2.3'; }; - const metadata = await makeClientMetadata([], {}); + const metadata = await makeClientMetadata([], { runtime }); expect(metadata.platform).to.equal('Bun v1.2.3, LE'); }); @@ -365,7 +375,7 @@ describe('client metadata module', () => { globalThis.Bun = class { static version = '1.2.3'; }; - const metadata = await makeClientMetadata([{ platform: 'myPlatform' }], {}); + const metadata = await makeClientMetadata([{ platform: 'myPlatform' }], { runtime }); expect(metadata.platform).to.equal('Bun v1.2.3, LE|myPlatform'); }); @@ -373,7 +383,7 @@ describe('client metadata module', () => { globalThis.Bun = class { static version = 1; }; - const metadata = await makeClientMetadata([], {}); + const metadata = await makeClientMetadata([], { runtime }); expect(metadata.platform).to.equal('Bun v0.0.0-unknown, LE'); }); @@ -381,13 +391,13 @@ describe('client metadata module', () => { globalThis.Bun = class { static version = 1; }; - const metadata = await makeClientMetadata([{ platform: 'myPlatform' }], {}); + const metadata = await makeClientMetadata([{ platform: 'myPlatform' }], { runtime }); expect(metadata.platform).to.equal('Bun v0.0.0-unknown, LE|myPlatform'); }); it('ignores version if Bun is nullish', async () => { globalThis.Bun = null; - const metadata = await makeClientMetadata([{ platform: 'myPlatform' }], {}); + const metadata = await makeClientMetadata([{ platform: 'myPlatform' }], { runtime }); expect(metadata.platform).to.equal('Bun v0.0.0-unknown, LE|myPlatform'); }); }); @@ -508,7 +518,7 @@ describe('client metadata module', () => { }); it(`returns ${inspect(outcome)} under env property`, async () => { - const { env } = await makeClientMetadata([], {}); + const { env } = await makeClientMetadata([], { runtime }); expect(env).to.deep.equal(outcome); }); @@ -532,7 +542,9 @@ describe('client metadata module', () => { }); it('does not attach it to the metadata', async () => { - expect(await makeClientMetadata([], {})).not.to.have.nested.property('aws.memory_mb'); + expect(await makeClientMetadata([], { runtime })).not.to.have.nested.property( + 'aws.memory_mb' + ); }); }); }); @@ -547,7 +559,7 @@ describe('client metadata module', () => { }); it('only includes env.name', async () => { - const metadata = await makeClientMetadata([], {}); + const metadata = await makeClientMetadata([], { runtime }); expect(metadata).to.not.have.nested.property('env.region'); expect(metadata).to.have.nested.property('env.name', 'aws.lambda'); expect(metadata.env).to.have.all.keys('name'); @@ -565,7 +577,7 @@ describe('client metadata module', () => { }); it('only includes env.name', async () => { - const metadata = await makeClientMetadata([], {}); + const metadata = await makeClientMetadata([], { runtime }); expect(metadata).to.have.property('env'); expect(metadata).to.have.nested.property('env.region', 'abc'); expect(metadata.os).to.have.all.keys('type'); @@ -582,7 +594,7 @@ describe('client metadata module', () => { }); it('omits os information', async () => { - const metadata = await makeClientMetadata([], {}); + const metadata = await makeClientMetadata([], { runtime }); expect(metadata).to.not.have.property('os'); }); }); @@ -598,7 +610,7 @@ describe('client metadata module', () => { }); it('omits the faas env', async () => { - const metadata = await makeClientMetadata([{ name: 'a'.repeat(350) }], {}); + const metadata = await makeClientMetadata([{ name: 'a'.repeat(350) }], { runtime }); expect(metadata).to.not.have.property('env'); }); }); diff --git a/test/unit/runtime_adapters.test.ts b/test/unit/runtime_adapters.test.ts new file mode 100644 index 00000000000..3980f9f1d71 --- /dev/null +++ b/test/unit/runtime_adapters.test.ts @@ -0,0 +1,31 @@ +import { expect } from 'chai'; +import * as os from 'os'; + +import { MongoClient, type OsAdapter } from '../../src'; + +describe('Runtime Adapters tests', function () { + describe('`os`', function () { + describe('when no os adapter is provided', function () { + it(`defaults to Node's os module`, function () { + const client = new MongoClient('mongodb://localhost:27017'); + + expect(client.options.runtime.os).to.equal(os); + }); + }); + + describe('when an os adapter is provided', function () { + it(`uses the user provided adapter`, function () { + const osAdapter: OsAdapter = { + ...os + }; + const client = new MongoClient('mongodb://localhost:27017', { + runtimeAdapters: { + os: osAdapter + } + }); + + expect(client.options.runtime.os).to.equal(osAdapter); + }); + }); + }); +}); diff --git a/test/unit/sdam/topology.test.ts b/test/unit/sdam/topology.test.ts index 1444e1a40c4..8db64288bd9 100644 --- a/test/unit/sdam/topology.test.ts +++ b/test/unit/sdam/topology.test.ts @@ -28,7 +28,7 @@ import { TopologyDescription } from '../../../src/sdam/topology_description'; import { TimeoutContext } from '../../../src/timeout'; import { isHello, ns } from '../../../src/utils'; import * as mock from '../../tools/mongodb-mock/index'; -import { topologyWithPlaceholderClient } from '../../tools/utils'; +import { runtime, topologyWithPlaceholderClient } from '../../tools/utils'; describe('Topology (unit)', function () { let client, topology; @@ -56,6 +56,7 @@ describe('Topology (unit)', function () { it('should correctly pass appname', async function () { const topology: Topology = topologyWithPlaceholderClient([`localhost:27017`], { metadata: makeClientMetadata([], { + runtime, appName: 'My application name' }) });