diff --git a/package.json b/package.json index b92a81ed8f..1038941425 100644 --- a/package.json +++ b/package.json @@ -143,6 +143,7 @@ "check:unit": "mocha test/unit", "check:ts": "node ./node_modules/typescript/bin/tsc -v && node ./node_modules/typescript/bin/tsc --noEmit", "check:atlas": "mocha --config test/manual/mocharc.json test/manual/atlas_connectivity.test.js", + "check:drivers-atlas-testing": "mocha --config test/mocha_mongodb.json test/atlas/drivers_atlas_testing.test.ts", "check:adl": "mocha --config test/mocha_mongodb.json test/manual/atlas-data-lake-testing", "check:aws": "nyc mocha --config test/mocha_mongodb.json test/integration/auth/mongodb_aws.test.ts", "check:oidc": "mocha --config test/mocha_mongodb.json test/manual/mongodb_oidc.prose.test.ts", diff --git a/test/atlas/drivers_atlas_testing.test.ts b/test/atlas/drivers_atlas_testing.test.ts new file mode 100644 index 0000000000..d883946a32 --- /dev/null +++ b/test/atlas/drivers_atlas_testing.test.ts @@ -0,0 +1,9 @@ +import { runUnifiedSuite } from '../tools/unified-spec-runner/runner'; + +describe('Node Driver Atlas Testing', async function () { + // Astrolabe can, well, take some time. + this.timeout(120000); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const spec = JSON.parse(process.env.WORKLOAD_SPECIFICATION!); + runUnifiedSuite([spec]); +}); diff --git a/test/tools/reporter/mongodb_reporter.js b/test/tools/reporter/mongodb_reporter.js index 0fef35bf8e..2866fc1f39 100644 --- a/test/tools/reporter/mongodb_reporter.js +++ b/test/tools/reporter/mongodb_reporter.js @@ -103,7 +103,7 @@ class MongoDBMochaReporter extends mocha.reporters.Spec { catchErr(test => this.testEnd(test)) ); - process.on('SIGINT', () => this.end(true)); + process.prependListener('SIGINT', () => this.end(true)); } start() {} @@ -183,7 +183,11 @@ class MongoDBMochaReporter extends mocha.reporters.Spec { } catch (error) { console.error(chalk.red(`Failed to output xunit report! ${error}`)); } finally { - if (ctrlC) process.exit(1); + // Dont exit the process on Astrolabe testing, let it interrupt and + // finish naturally. + if (!process.env.WORKLOAD_SPECIFICATION) { + if (ctrlC) process.exit(1); + } } } diff --git a/test/tools/runner/config.ts b/test/tools/runner/config.ts index a07f6373cc..a9f41e5c78 100644 --- a/test/tools/runner/config.ts +++ b/test/tools/runner/config.ts @@ -159,6 +159,10 @@ export class TestConfiguration { } newClient(dbOptions?: string | Record, serverOptions?: Record) { + if (process.env.DRIVERS_ATLAS_TESTING_URI) { + return new MongoClient(process.env.DRIVERS_ATLAS_TESTING_URI); + } + serverOptions = Object.assign({}, getEnvironmentalOptions(), serverOptions); // support MongoClient constructor form (url, options) for `newClient` @@ -258,6 +262,10 @@ export class TestConfiguration { ...options }; + if (process.env.DRIVERS_ATLAS_TESTING_URI) { + return process.env.DRIVERS_ATLAS_TESTING_URI; + } + const FILLER_HOST = 'fillerHost'; const protocol = this.isServerless ? 'mongodb+srv' : 'mongodb'; diff --git a/test/tools/runner/hooks/configuration.js b/test/tools/runner/hooks/configuration.js index 0aeb0966d2..009de7a9d9 100644 --- a/test/tools/runner/hooks/configuration.js +++ b/test/tools/runner/hooks/configuration.js @@ -105,6 +105,10 @@ const skipBrokenAuthTestBeforeEachHook = function ({ skippedTests } = { skippedT }; const testConfigBeforeHook = async function () { + if (process.env.DRIVERS_ATLAS_TESTING_URI) { + this.configuration = new TestConfiguration(process.env.DRIVERS_ATLAS_TESTING_URI, {}); + return; + } // TODO(NODE-5035): Implement OIDC support. Creating the MongoClient will fail // with "MongoInvalidArgumentError: AuthMechanism 'MONGODB-OIDC' not supported" // as is expected until that ticket goes in. Then this condition gets removed. diff --git a/test/tools/unified-spec-runner/astrolabe_results_writer.ts b/test/tools/unified-spec-runner/astrolabe_results_writer.ts new file mode 100644 index 0000000000..635c22b22f --- /dev/null +++ b/test/tools/unified-spec-runner/astrolabe_results_writer.ts @@ -0,0 +1,44 @@ +import { writeFile } from 'node:fs/promises'; + +import * as path from 'path'; + +import type { EntitiesMap } from './entities'; +import { trace } from './runner'; + +/** + * Writes the entities saved from the loop operations run in the + * Astrolabe workload executor to the required files. + */ +export class AstrolabeResultsWriter { + constructor(private entities: EntitiesMap) { + this.entities = entities; + } + + async write(): Promise { + // Write the events.json to the execution directory. + const errors = this.entities.getEntity('errors', 'errors', false); + const failures = this.entities.getEntity('failures', 'failures', false); + const events = this.entities.getEntity('events', 'events', false); + const iterations = this.entities.getEntity('iterations', 'iterations', false); + const successes = this.entities.getEntity('successes', 'successes', false); + + // Write the events.json to the execution directory. + trace('writing events.json'); + await writeFile( + path.join(process.env.OUTPUT_DIRECTORY ?? '', 'events.json'), + JSON.stringify({ events: events ?? [], errors: errors ?? [], failures: failures ?? [] }) + ); + + // Write the results.json to the execution directory. + trace('writing results.json'); + await writeFile( + path.join(process.env.OUTPUT_DIRECTORY ?? '', 'results.json'), + JSON.stringify({ + numErrors: errors?.length ?? 0, + numFailures: failures?.length ?? 0, + numSuccesses: successes ?? 0, + numIterations: iterations ?? 0 + }) + ); + } +} diff --git a/test/tools/unified-spec-runner/entities.ts b/test/tools/unified-spec-runner/entities.ts index c437a940e7..623b2ffe82 100644 --- a/test/tools/unified-spec-runner/entities.ts +++ b/test/tools/unified-spec-runner/entities.ts @@ -5,6 +5,7 @@ import { EventEmitter } from 'events'; import { AbstractCursor, ChangeStream, + ClientEncryption, ClientSession, Collection, type CommandFailedEvent, @@ -37,13 +38,9 @@ import { } from '../../mongodb'; import { ejson, getEnvironmentalOptions } from '../../tools/utils'; import type { TestConfiguration } from '../runner/config'; +import { EntityEventRegistry } from './entity_event_registry'; import { trace } from './runner'; -import type { - ClientEncryption, - ClientEntity, - EntityDescription, - ExpectedLogMessage -} from './schema'; +import type { ClientEntity, EntityDescription, ExpectedLogMessage } from './schema'; import { createClientEncryption, makeConnectionString, @@ -357,9 +354,10 @@ export type Entity = | AbstractCursor | UnifiedChangeStream | GridFSBucket + | Document | ClientEncryption | TopologyDescription // From recordTopologyDescription operation - | Document; // Results from operations + | number; export type EntityCtor = | typeof UnifiedMongoClient @@ -370,7 +368,7 @@ export type EntityCtor = | typeof AbstractCursor | typeof GridFSBucket | typeof UnifiedThread - | ClientEncryption; + | typeof ClientEncryption; export type EntityTypeId = | 'client' @@ -381,11 +379,17 @@ export type EntityTypeId = | 'thread' | 'cursor' | 'stream' - | 'clientEncryption'; + | 'clientEncryption' + | 'errors' + | 'failures' + | 'events' + | 'iterations' + | 'successes'; const ENTITY_CTORS = new Map(); ENTITY_CTORS.set('client', UnifiedMongoClient); ENTITY_CTORS.set('db', Db); +ENTITY_CTORS.set('clientEncryption', ClientEncryption); ENTITY_CTORS.set('collection', Collection); ENTITY_CTORS.set('session', ClientSession); ENTITY_CTORS.set('bucket', GridFSBucket); @@ -393,6 +397,8 @@ ENTITY_CTORS.set('thread', UnifiedThread); ENTITY_CTORS.set('cursor', AbstractCursor); ENTITY_CTORS.set('stream', ChangeStream); +const NO_INSTANCE_CHECK = ['errors', 'failures', 'events', 'successes', 'iterations']; + export class EntitiesMap extends Map { failPoints: FailPointMap; @@ -435,6 +441,11 @@ export class EntitiesMap extends Map { getEntity(type: 'thread', key: string, assertExists?: boolean): UnifiedThread; getEntity(type: 'cursor', key: string, assertExists?: boolean): AbstractCursor; getEntity(type: 'stream', key: string, assertExists?: boolean): UnifiedChangeStream; + getEntity(type: 'iterations', key: string, assertExists?: boolean): number; + getEntity(type: 'successes', key: string, assertExists?: boolean): number; + getEntity(type: 'errors', key: string, assertExists?: boolean): Document[]; + getEntity(type: 'failures', key: string, assertExists?: boolean): Document[]; + getEntity(type: 'events', key: string, assertExists?: boolean): Document[]; getEntity(type: 'clientEncryption', key: string, assertExists?: boolean): ClientEncryption; getEntity(type: EntityTypeId, key: string, assertExists = true): Entity | undefined { const entity = this.get(key); @@ -442,8 +453,8 @@ export class EntitiesMap extends Map { if (assertExists) throw new Error(`Entity '${key}' does not exist`); return; } - if (type === 'clientEncryption') { - // we do not have instanceof checking here since csfle might not be installed + if (NO_INSTANCE_CHECK.includes(type)) { + // Skip constructor checks for interfaces. return entity; } const ctor = ENTITY_CTORS.get(type); @@ -499,6 +510,7 @@ export class EntitiesMap extends Map { entity.client.uriOptions ); const client = new UnifiedMongoClient(uri, entity.client); + new EntityEventRegistry(client, entity.client, map).register(); try { await client.connect(); } catch (error) { diff --git a/test/tools/unified-spec-runner/entity_event_registry.ts b/test/tools/unified-spec-runner/entity_event_registry.ts new file mode 100644 index 0000000000..3c64416b60 --- /dev/null +++ b/test/tools/unified-spec-runner/entity_event_registry.ts @@ -0,0 +1,76 @@ +import { + COMMAND_FAILED, + COMMAND_STARTED, + COMMAND_SUCCEEDED, + CONNECTION_CHECK_OUT_FAILED, + CONNECTION_CHECK_OUT_STARTED, + CONNECTION_CHECKED_IN, + CONNECTION_CHECKED_OUT, + CONNECTION_CLOSED, + CONNECTION_CREATED, + CONNECTION_POOL_CLEARED, + CONNECTION_POOL_CLOSED, + CONNECTION_POOL_CREATED, + CONNECTION_POOL_READY, + CONNECTION_READY +} from '../../mongodb'; +import { type EntitiesMap, type UnifiedMongoClient } from './entities'; +import { type ClientEntity } from './schema'; + +/** + * Maps the names of the events the unified runner passes and maps + * them to the names of the events emitted in the driver. + */ +const MAPPINGS = { + PoolCreatedEvent: CONNECTION_POOL_CREATED, + PoolReadyEvent: CONNECTION_POOL_READY, + PoolClearedEvent: CONNECTION_POOL_CLEARED, + PoolClosedEvent: CONNECTION_POOL_CLOSED, + ConnectionCreatedEvent: CONNECTION_CREATED, + ConnectionReadyEvent: CONNECTION_READY, + ConnectionClosedEvent: CONNECTION_CLOSED, + ConnectionCheckOutStartedEvent: CONNECTION_CHECK_OUT_STARTED, + ConnectionCheckOutFailedEvent: CONNECTION_CHECK_OUT_FAILED, + ConnectionCheckedOutEvent: CONNECTION_CHECKED_OUT, + ConnectionCheckedInEvent: CONNECTION_CHECKED_IN, + CommandStartedEvent: COMMAND_STARTED, + CommandSucceededEvent: COMMAND_SUCCEEDED, + CommandFailedEvent: COMMAND_FAILED +}; + +/** + * Registers events that need to be stored in the entities map, since + * the UnifiedMongoClient does not contain a ciclical dependency on the + * entities map itself. + */ +export class EntityEventRegistry { + constructor( + private client: UnifiedMongoClient, + private clientEntity: ClientEntity, + private entitiesMap: EntitiesMap + ) { + this.client = client; + this.clientEntity = clientEntity; + this.entitiesMap = entitiesMap; + } + + /** + * Connect the event listeners on the client and the entities map. + */ + register(): void { + if (this.clientEntity.storeEventsAsEntities) { + for (const { id, events } of this.clientEntity.storeEventsAsEntities) { + this.entitiesMap.set(id, []); + for (const eventName of events) { + // Need to map the event names to the Node event names. + this.client.on(MAPPINGS[eventName], () => { + this.entitiesMap.getEntity('events', id).push({ + name: eventName, + observedAt: Date.now() + }); + }); + } + } + } + } +} diff --git a/test/tools/unified-spec-runner/operations.ts b/test/tools/unified-spec-runner/operations.ts index b5e5752aea..757733363f 100644 --- a/test/tools/unified-spec-runner/operations.ts +++ b/test/tools/unified-spec-runner/operations.ts @@ -2,7 +2,7 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ import { once, Writable } from 'node:stream'; -import { expect } from 'chai'; +import { AssertionError, expect } from 'chai'; import { AbstractCursor, @@ -377,6 +377,76 @@ operations.set('listIndexes', async ({ entities, operation }) => { return collection.listIndexes(operation.arguments!).toArray(); }); +operations.set('loop', async ({ entities, operation, client, testConfig }) => { + const controller = new AbortController(); + // We always want the process to exit on SIGINT last, so all other + // SIGINT events listeners must be prepended. + process.prependListener('SIGINT', () => { + controller.abort('Process received SIGINT, aborting operation loop.'); + }); + const args = operation.arguments!; + const storeIterationsAsEntity = args.storeIterationsAsEntity; + const storeSuccessesAsEntity = args.storeSuccessesAsEntity; + const storeErrorsAsEntity = args.storeErrorsAsEntity; + const storeFailuresAsEntity = args.storeFailuresAsEntity; + + if (storeErrorsAsEntity) { + entities.set(storeErrorsAsEntity, []); + } + if (storeFailuresAsEntity) { + entities.set(storeFailuresAsEntity, []); + } + + let iterations = 0; + let successes = 0; + while (!controller.signal.aborted) { + if (storeIterationsAsEntity) { + entities.set(storeIterationsAsEntity, iterations++); + } + for (const op of args.operations) { + try { + await executeOperationAndCheck(op, entities, client, testConfig); + if (storeSuccessesAsEntity) { + entities.set(storeSuccessesAsEntity, successes++); + } + } catch (error) { + // From the unified spec: + // If neither storeErrorsAsEntity nor storeFailuresAsEntity are specified, + // the loop MUST terminate and raise the error/failure (i.e. the error/failure + // will interrupt the test). + if (!storeErrorsAsEntity && !storeFailuresAsEntity) { + entities.set('errors', [ + { + error: 'Neither storeErrorsAsEntity or storeFailuresAsEntity specified', + time: Date.now() + } + ]); + controller.abort('Neither storeErrorsAsEntity or storeFailuresAsEntity specified'); + return; + } + + // From the unified spec format specification for the loop operation: + // A failure is when the result or outcome of an operation executed by the test + // runner differs from its expected outcome. For example, an expectResult assertion + // failing to match a BSON document or an expectError assertion failing to match + // an error message would be considered a failure. + // An error is any other type of error raised by the test runner. For example, an + // unsupported operation or inability to resolve an entity name would be considered + // an error. + if (storeFailuresAsEntity && error instanceof AssertionError) { + entities + .getEntity('failures', storeFailuresAsEntity) + .push({ error: error.message, time: Date.now() }); + } else if (storeErrorsAsEntity) { + entities + .getEntity('errors', storeErrorsAsEntity) + .push({ error: error.message, time: Date.now() }); + } + } + } + } +}); + operations.set('replaceOne', async ({ entities, operation }) => { const collection = entities.getEntity('collection', operation.object); const { filter, replacement, ...opts } = operation.arguments!; diff --git a/test/tools/unified-spec-runner/runner.ts b/test/tools/unified-spec-runner/runner.ts index 5eed3faa3c..a2c02213c6 100644 --- a/test/tools/unified-spec-runner/runner.ts +++ b/test/tools/unified-spec-runner/runner.ts @@ -5,6 +5,7 @@ import { gte as semverGte, satisfies as semverSatisfies } from 'semver'; import type { MongoClient } from '../../mongodb'; import { MONGODB_ERROR_CODES, ns, ReadPreference, TopologyType } from '../../mongodb'; import { ejson } from '../utils'; +import { AstrolabeResultsWriter } from './astrolabe_results_writer'; import { EntitiesMap, type UnifiedMongoClient } from './entities'; import { compareLogs, matchesEvents } from './match'; import { executeOperationAndCheck } from './operations'; @@ -255,6 +256,11 @@ async function runUnifiedTest( } } finally { await utilClient.close(); + // For astrolabe testing we need to write the entities to files. + if (process.env.WORKLOAD_SPECIFICATION) { + const writer = new AstrolabeResultsWriter(entities!); + await writer.write(); + } await entities?.cleanup(); } } diff --git a/test/tools/unified-spec-runner/schema.ts b/test/tools/unified-spec-runner/schema.ts index 04629560c0..6094dcf8e3 100644 --- a/test/tools/unified-spec-runner/schema.ts +++ b/test/tools/unified-spec-runner/schema.ts @@ -139,6 +139,7 @@ export interface ClientEntity { ignoreCommandMonitoringEvents?: string[]; serverApi?: ServerApi; observeSensitiveCommands?: boolean; + storeEventsAsEntities?: StoreEventsAsEntity[]; } export interface DatabaseEntity { id: string; @@ -308,6 +309,10 @@ export interface ExpectedSdamEvent { }; }; } +export interface StoreEventsAsEntity { + id: string; + events: string[]; +} export interface ExpectedError { isError?: true; isClientError?: boolean; diff --git a/test/unit/tools/unified_spec_runner/entity_event_registry.test.ts b/test/unit/tools/unified_spec_runner/entity_event_registry.test.ts new file mode 100644 index 0000000000..f807ab5b9c --- /dev/null +++ b/test/unit/tools/unified_spec_runner/entity_event_registry.test.ts @@ -0,0 +1,187 @@ +import { expect } from 'chai'; + +import { + COMMAND_FAILED, + COMMAND_STARTED, + COMMAND_SUCCEEDED, + CONNECTION_CHECK_OUT_FAILED, + CONNECTION_CHECK_OUT_STARTED, + CONNECTION_CHECKED_IN, + CONNECTION_CHECKED_OUT, + CONNECTION_CLOSED, + CONNECTION_CREATED, + CONNECTION_POOL_CLEARED, + CONNECTION_POOL_CLOSED, + CONNECTION_POOL_CREATED, + CONNECTION_POOL_READY, + CONNECTION_READY +} from '../../../mongodb'; +import { EntitiesMap, UnifiedMongoClient } from '../../../tools/unified-spec-runner/entities'; +import { EntityEventRegistry } from '../../../tools/unified-spec-runner/entity_event_registry'; + +describe('EntityEventRegistry', function () { + describe('#register', function () { + context('when storeEventsAsEntities exists on the client entity', function () { + const clientEntity = { + id: 'client0', + storeEventsAsEntities: [ + { + id: 'eventList', + events: [ + 'PoolCreatedEvent', + 'PoolReadyEvent', + 'PoolClearedEvent', + 'PoolClosedEvent', + 'ConnectionCreatedEvent', + 'ConnectionReadyEvent', + 'ConnectionClosedEvent', + 'ConnectionCheckOutStartedEvent', + 'ConnectionCheckOutFailedEvent', + 'ConnectionCheckedOutEvent', + 'ConnectionCheckedInEvent', + 'CommandStartedEvent', + 'CommandSucceededEvent', + 'CommandFailedEvent' + ] + } + ] + }; + const entitesMap = new EntitiesMap(); + const uri = 'mongodb://127.0.0.1:27017'; + const client = new UnifiedMongoClient(uri, clientEntity); + const registry = new EntityEventRegistry(client, clientEntity, entitesMap); + + before(function () { + registry.register(); + }); + + it('initializes the events in the entities map', function () { + expect(entitesMap.getEntity('events', 'eventList')).to.deep.equal([]); + }); + + it('maps PoolCreatedEvent to connectionPoolCreated', function () { + expect(client.listeners(CONNECTION_POOL_CREATED)).to.have.length(1); + }); + + it('maps PoolReadyEvent to connectionPoolReady', function () { + expect(client.listeners(CONNECTION_POOL_READY)).to.have.length(1); + }); + + it('maps PoolClearedEvent to connectionPoolCleared', function () { + expect(client.listeners(CONNECTION_POOL_CLEARED)).to.have.length(1); + }); + + it('maps PoolClosedEvent to connectionPoolClosed', function () { + expect(client.listeners(CONNECTION_POOL_CLOSED)).to.have.length(1); + }); + + it('maps ConnectionCreatedEvent to connectionCreated', function () { + expect(client.listeners(CONNECTION_CREATED)).to.have.length(1); + }); + + it('maps ConnectionReadyEvent to connectionReady', function () { + expect(client.listeners(CONNECTION_READY)).to.have.length(1); + }); + + it('maps ConnectionClosedEvent to connectionClosed', function () { + expect(client.listeners(CONNECTION_CLOSED)).to.have.length(1); + }); + + it('maps ConnectionCheckOutStartedEvent to connectionCheckoutStarted', function () { + expect(client.listeners(CONNECTION_CHECK_OUT_STARTED)).to.have.length(1); + }); + + it('maps ConnectionCheckOutFailedEvent to connectionCheckoutFailed', function () { + expect(client.listeners(CONNECTION_CHECK_OUT_FAILED)).to.have.length(1); + }); + + it('maps ConnectionCheckedOutEvent to connectionCheckedOut', function () { + expect(client.listeners(CONNECTION_CHECKED_OUT)).to.have.length(1); + }); + + it('maps ConnectionCheckedInEvent to connectionCheckedIn', function () { + expect(client.listeners(CONNECTION_CHECKED_IN)).to.have.length(1); + }); + + it('maps CommandStartedEvent to commandStarted', function () { + expect(client.listeners(COMMAND_STARTED)).to.have.length(1); + }); + + it('maps CommandSucceededEvent to commandSucceeded', function () { + expect(client.listeners(COMMAND_SUCCEEDED)).to.have.length(1); + }); + + it('maps CommandFailedEvent to commandFailed', function () { + expect(client.listeners(COMMAND_FAILED)).to.have.length(1); + }); + }); + + context('when storeEventsAsEntities does not exist on the client entity', function () { + const clientEntity = { id: 'client0' }; + const entitesMap = new EntitiesMap(); + const uri = 'mongodb://127.0.0.1:27017'; + const client = new UnifiedMongoClient(uri, clientEntity); + const registry = new EntityEventRegistry(client, clientEntity, entitesMap); + + before(function () { + registry.register(); + }); + + it('does not listen for connectionPoolCreated', function () { + expect(client.listeners(CONNECTION_POOL_CREATED)).to.be.empty; + }); + + it('does not listen for connectionPoolReady', function () { + expect(client.listeners(CONNECTION_POOL_READY)).to.be.empty; + }); + + it('does not listen for connectionPoolCleared', function () { + expect(client.listeners(CONNECTION_POOL_CLEARED)).to.be.empty; + }); + + it('does not listen for connectionPoolClosed', function () { + expect(client.listeners(CONNECTION_POOL_CLOSED)).to.be.empty; + }); + + it('mdoes not listen for connectionCreated', function () { + expect(client.listeners(CONNECTION_CREATED)).to.be.empty; + }); + + it('does not listen for connectionReady', function () { + expect(client.listeners(CONNECTION_READY)).to.be.empty; + }); + + it('does not listen for connectionClosed', function () { + expect(client.listeners(CONNECTION_CLOSED)).to.be.empty; + }); + + it('does not listen for connectionCheckoutStarted', function () { + expect(client.listeners(CONNECTION_CHECK_OUT_STARTED)).to.be.empty; + }); + + it('does not listen for connectionCheckoutFailed', function () { + expect(client.listeners(CONNECTION_CHECK_OUT_FAILED)).to.be.empty; + }); + + it('does not listen for connectionCheckedOut', function () { + expect(client.listeners(CONNECTION_CHECKED_OUT)).to.be.empty; + }); + + it('does not listen for connectionCheckedIn', function () { + expect(client.listeners(CONNECTION_CHECKED_IN)).to.be.empty; + }); + + it('does not listen for commandStarted', function () { + expect(client.listeners(COMMAND_STARTED)).to.be.empty; + }); + + it('does not listen for commandSucceeded', function () { + expect(client.listeners(COMMAND_SUCCEEDED)).to.be.empty; + }); + + it('does not listen for commandFailed', function () { + expect(client.listeners(COMMAND_FAILED)).to.be.empty; + }); + }); + }); +});