Skip to content

Commit 6a0e502

Browse files
authored
fix(NODE-4533): session support error message and unified test runner (#3355)
1 parent 6425c7a commit 6a0e502

File tree

7 files changed

+167
-141
lines changed

7 files changed

+167
-141
lines changed

src/sessions.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -383,7 +383,7 @@ export class ClientSession extends TypedEventEmitter<ClientSessionEvents> {
383383
*/
384384
startTransaction(options?: TransactionOptions): void {
385385
if (this[kSnapshotEnabled]) {
386-
throw new MongoCompatibilityError('Transactions are not allowed with snapshot sessions');
386+
throw new MongoCompatibilityError('Transactions are not supported in snapshot sessions');
387387
}
388388

389389
if (this.inTransaction()) {

test/tools/unified-spec-runner/entities.ts

+21-19
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* eslint-disable @typescript-eslint/no-non-null-assertion */
12
import { expect } from 'chai';
23

34
import { ChangeStream } from '../../../src/change_stream';
@@ -64,8 +65,6 @@ function getClient(address) {
6465
return new MongoClient(`mongodb://${address}`, getEnvironmentalOptions());
6566
}
6667

67-
type PushFunction = (e: any) => void;
68-
6968
export class UnifiedMongoClient extends MongoClient {
7069
commandEvents: CommandEvent[];
7170
cmapEvents: CmapEvent[];
@@ -139,6 +138,17 @@ export class UnifiedMongoClient extends MongoClient {
139138
return this.ignoredEvents.includes(e.commandName);
140139
}
141140

141+
getCapturedEvents(eventType: string): CommandEvent[] | CmapEvent[] {
142+
switch (eventType) {
143+
case 'command':
144+
return this.commandEvents;
145+
case 'cmap':
146+
return this.cmapEvents;
147+
default:
148+
throw new Error(`Unknown eventType: ${eventType}`);
149+
}
150+
}
151+
142152
// NOTE: pushCommandEvent must be an arrow function
143153
pushCommandEvent: (e: CommandEvent) => void = e => {
144154
if (!this.isIgnored(e)) {
@@ -151,22 +161,14 @@ export class UnifiedMongoClient extends MongoClient {
151161
this.cmapEvents.push(e);
152162
};
153163

154-
stopCapturingEvents(pushFn: PushFunction): void {
155-
const observedEvents = [...this.observedCommandEvents, ...this.observedCmapEvents];
156-
for (const eventName of observedEvents) {
157-
this.off(eventName, pushFn);
158-
}
159-
}
160-
161164
/** Disables command monitoring for the client and returns a list of the captured events. */
162-
stopCapturingCommandEvents(): CommandEvent[] {
163-
this.stopCapturingEvents(this.pushCommandEvent);
164-
return this.commandEvents;
165-
}
166-
167-
stopCapturingCmapEvents(): CmapEvent[] {
168-
this.stopCapturingEvents(this.pushCmapEvent);
169-
return this.cmapEvents;
165+
stopCapturingEvents(): void {
166+
for (const eventName of this.observedCommandEvents) {
167+
this.off(eventName, this.pushCommandEvent);
168+
}
169+
for (const eventName of this.observedCmapEvents) {
170+
this.off(eventName, this.pushCmapEvent);
171+
}
170172
}
171173
}
172174

@@ -179,7 +181,7 @@ export class FailPointMap extends Map<string, Document> {
179181
let address: string;
180182
if (addressOrClient instanceof MongoClient) {
181183
client = addressOrClient;
182-
address = client.topology.s.seedlist.join(',');
184+
address = client.topology!.s.seedlist.join(',');
183185
} else {
184186
// create a new client
185187
address = addressOrClient.toString();
@@ -300,7 +302,7 @@ export class EntitiesMap<E = Entity> extends Map<string, E> {
300302
getEntity(type: 'cursor', key: string, assertExists?: boolean): AbstractCursor;
301303
getEntity(type: 'stream', key: string, assertExists?: boolean): UnifiedChangeStream;
302304
getEntity(type: 'clientEncryption', key: string, assertExists?: boolean): ClientEncryption;
303-
getEntity(type: EntityTypeId, key: string, assertExists = true): Entity {
305+
getEntity(type: EntityTypeId, key: string, assertExists = true): Entity | undefined {
304306
const entity = this.get(key);
305307
if (!entity) {
306308
if (assertExists) throw new Error(`Entity '${key}' does not exist`);

test/tools/unified-spec-runner/match.ts

+54-36
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* eslint-disable @typescript-eslint/no-non-null-assertion */
12
import { expect } from 'chai';
23
import { inspect } from 'util';
34

@@ -7,6 +8,7 @@ import {
78
Document,
89
Long,
910
MongoError,
11+
MongoServerError,
1012
ObjectId,
1113
OneOrMore
1214
} from '../../../src';
@@ -264,11 +266,11 @@ export function specialCheck(
264266
entities: EntitiesMap,
265267
path: string[] = [],
266268
checkExtraKeys: boolean
267-
): boolean {
269+
): void {
268270
if (isUnsetOrMatchesOperator(expected)) {
269271
if (actual === null || actual === undefined) return;
270272

271-
resultCheck(actual, expected.$$unsetOrMatches, entities, path, checkExtraKeys);
273+
resultCheck(actual, expected.$$unsetOrMatches as any, entities, path, checkExtraKeys);
272274
} else if (isMatchesEntityOperator(expected)) {
273275
// $$matchesEntity
274276
const entity = entities.get(expected.$$matchesEntity);
@@ -290,15 +292,15 @@ export function specialCheck(
290292
// $$sessionLsid
291293
const session = entities.getEntity('session', expected.$$sessionLsid, false);
292294
expect(session, `Session ${expected.$$sessionLsid} does not exist in entities`).to.exist;
293-
const entitySessionHex = session.id.id.buffer.toString('hex').toUpperCase();
295+
const entitySessionHex = session.id!.id.buffer.toString('hex').toUpperCase();
294296
const actualSessionHex = actual.id.buffer.toString('hex').toUpperCase();
295297
expect(
296298
entitySessionHex,
297299
`Session entity ${expected.$$sessionLsid} does not match lsid`
298300
).to.equal(actualSessionHex);
299301
} else if (isTypeOperator(expected)) {
300302
// $$type
301-
let ok: boolean;
303+
let ok = false;
302304
const types = Array.isArray(expected.$$type) ? expected.$$type : [expected.$$type];
303305
for (const type of types) {
304306
ok ||= TYPE_MAP.get(type)(actual);
@@ -364,19 +366,23 @@ function compareCommandStartedEvents(
364366
entities: EntitiesMap,
365367
prefix: string
366368
) {
367-
if (expected.command) {
368-
resultCheck(actual.command, expected.command, entities, [`${prefix}.command`]);
369+
if (expected!.command) {
370+
resultCheck(actual.command, expected!.command, entities, [`${prefix}.command`]);
369371
}
370-
if (expected.commandName) {
372+
if (expected!.commandName) {
371373
expect(
372-
expected.commandName,
373-
`expected ${prefix}.commandName to equal ${expected.commandName} but received ${actual.commandName}`
374+
expected!.commandName,
375+
`expected ${prefix}.commandName to equal ${expected!.commandName} but received ${
376+
actual.commandName
377+
}`
374378
).to.equal(actual.commandName);
375379
}
376-
if (expected.databaseName) {
380+
if (expected!.databaseName) {
377381
expect(
378-
expected.databaseName,
379-
`expected ${prefix}.databaseName to equal ${expected.databaseName} but received ${actual.databaseName}`
382+
expected!.databaseName,
383+
`expected ${prefix}.databaseName to equal ${expected!.databaseName} but received ${
384+
actual.databaseName
385+
}`
380386
).to.equal(actual.databaseName);
381387
}
382388
}
@@ -387,13 +393,15 @@ function compareCommandSucceededEvents(
387393
entities: EntitiesMap,
388394
prefix: string
389395
) {
390-
if (expected.reply) {
391-
resultCheck(actual.reply, expected.reply, entities, [prefix]);
396+
if (expected!.reply) {
397+
resultCheck(actual.reply as Document, expected!.reply, entities, [prefix]);
392398
}
393-
if (expected.commandName) {
399+
if (expected!.commandName) {
394400
expect(
395-
expected.commandName,
396-
`expected ${prefix}.commandName to equal ${expected.commandName} but received ${actual.commandName}`
401+
expected!.commandName,
402+
`expected ${prefix}.commandName to equal ${expected!.commandName} but received ${
403+
actual.commandName
404+
}`
397405
).to.equal(actual.commandName);
398406
}
399407
}
@@ -404,10 +412,12 @@ function compareCommandFailedEvents(
404412
entities: EntitiesMap,
405413
prefix: string
406414
) {
407-
if (expected.commandName) {
415+
if (expected!.commandName) {
408416
expect(
409-
expected.commandName,
410-
`expected ${prefix}.commandName to equal ${expected.commandName} but received ${actual.commandName}`
417+
expected!.commandName,
418+
`expected ${prefix}.commandName to equal ${expected!.commandName} but received ${
419+
actual.commandName
420+
}`
411421
).to.equal(actual.commandName);
412422
}
413423
}
@@ -489,28 +499,34 @@ export function matchesEvents(
489499
}
490500
}
491501

502+
function isMongoCryptError(err): boolean {
503+
if (err.constructor.name === 'MongoCryptError') {
504+
return true;
505+
}
506+
return err.stack.includes('at ClientEncryption');
507+
}
508+
492509
export function expectErrorCheck(
493510
error: Error | MongoError,
494511
expected: ExpectedError,
495512
entities: EntitiesMap
496-
): boolean {
497-
if (Object.keys(expected)[0] === 'isClientError' || Object.keys(expected)[0] === 'isError') {
498-
// FIXME: We cannot tell if Error arose from driver and not from server
499-
return;
513+
): void {
514+
const expectMessage = `\n\nOriginal Error Stack:\n${error.stack}\n\n`;
515+
516+
if (!isMongoCryptError(error)) {
517+
expect(error, expectMessage).to.be.instanceOf(MongoError);
500518
}
501519

502-
const expectMessage = `\n\nOriginal Error Stack:\n${error.stack}\n\n`;
520+
if (expected.isClientError === false) {
521+
expect(error).to.be.instanceOf(MongoServerError);
522+
} else if (expected.isClientError === true) {
523+
expect(error).not.to.be.instanceOf(MongoServerError);
524+
}
503525

504526
if (expected.errorContains != null) {
505527
expect(error.message, expectMessage).to.include(expected.errorContains);
506528
}
507529

508-
if (!(error instanceof MongoError)) {
509-
// if statement asserts type for TS, expect will always fail
510-
expect(error, expectMessage).to.be.instanceOf(MongoError);
511-
return;
512-
}
513-
514530
if (expected.errorCode != null) {
515531
expect(error, expectMessage).to.have.property('code', expected.errorCode);
516532
}
@@ -520,24 +536,26 @@ export function expectErrorCheck(
520536
}
521537

522538
if (expected.errorLabelsContain != null) {
539+
const mongoError = error as MongoError;
523540
for (const errorLabel of expected.errorLabelsContain) {
524541
expect(
525-
error.hasErrorLabel(errorLabel),
526-
`Error was supposed to have label ${errorLabel}, has [${error.errorLabels}] -- ${expectMessage}`
542+
mongoError.hasErrorLabel(errorLabel),
543+
`Error was supposed to have label ${errorLabel}, has [${mongoError.errorLabels}] -- ${expectMessage}`
527544
).to.be.true;
528545
}
529546
}
530547

531548
if (expected.errorLabelsOmit != null) {
549+
const mongoError = error as MongoError;
532550
for (const errorLabel of expected.errorLabelsOmit) {
533551
expect(
534-
error.hasErrorLabel(errorLabel),
535-
`Error was supposed to have label ${errorLabel}, has [${error.errorLabels}] -- ${expectMessage}`
552+
mongoError.hasErrorLabel(errorLabel),
553+
`Error was not supposed to have label ${errorLabel}, has [${mongoError.errorLabels}] -- ${expectMessage}`
536554
).to.be.false;
537555
}
538556
}
539557

540558
if (expected.expectResult != null) {
541-
resultCheck(error, expected.expectResult, entities);
559+
resultCheck(error, expected.expectResult as any, entities);
542560
}
543561
}

0 commit comments

Comments
 (0)