Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(NODE-4533): session support error message and unified test runner #3355

Merged
merged 8 commits into from
Aug 16, 2022
2 changes: 1 addition & 1 deletion src/sessions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,7 @@ export class ClientSession extends TypedEventEmitter<ClientSessionEvents> {
*/
startTransaction(options?: TransactionOptions): void {
if (this[kSnapshotEnabled]) {
throw new MongoCompatibilityError('Transactions are not allowed with snapshot sessions');
throw new MongoCompatibilityError('Transactions are not supported in snapshot sessions');
}

if (this.inTransaction()) {
Expand Down
40 changes: 21 additions & 19 deletions test/tools/unified-spec-runner/entities.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import { expect } from 'chai';

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

type PushFunction = (e: any) => void;

export class UnifiedMongoClient extends MongoClient {
commandEvents: CommandEvent[];
cmapEvents: CmapEvent[];
Expand Down Expand Up @@ -139,6 +138,17 @@ export class UnifiedMongoClient extends MongoClient {
return this.ignoredEvents.includes(e.commandName);
}

getCapturedEvents(eventType: string): CommandEvent[] | CmapEvent[] {
switch (eventType) {
case 'command':
return this.commandEvents;
case 'cmap':
return this.cmapEvents;
default:
throw new Error(`Unknown eventType: ${eventType}`);
}
}

// NOTE: pushCommandEvent must be an arrow function
pushCommandEvent: (e: CommandEvent) => void = e => {
if (!this.isIgnored(e)) {
Expand All @@ -151,22 +161,14 @@ export class UnifiedMongoClient extends MongoClient {
this.cmapEvents.push(e);
};

stopCapturingEvents(pushFn: PushFunction): void {
const observedEvents = [...this.observedCommandEvents, ...this.observedCmapEvents];
for (const eventName of observedEvents) {
this.off(eventName, pushFn);
}
}

/** Disables command monitoring for the client and returns a list of the captured events. */
stopCapturingCommandEvents(): CommandEvent[] {
this.stopCapturingEvents(this.pushCommandEvent);
return this.commandEvents;
}

stopCapturingCmapEvents(): CmapEvent[] {
this.stopCapturingEvents(this.pushCmapEvent);
return this.cmapEvents;
stopCapturingEvents(): void {
for (const eventName of this.observedCommandEvents) {
this.off(eventName, this.pushCommandEvent);
}
for (const eventName of this.observedCmapEvents) {
this.off(eventName, this.pushCmapEvent);
}
}
}

Expand All @@ -179,7 +181,7 @@ export class FailPointMap extends Map<string, Document> {
let address: string;
if (addressOrClient instanceof MongoClient) {
client = addressOrClient;
address = client.topology.s.seedlist.join(',');
address = client.topology!.s.seedlist.join(',');
} else {
// create a new client
address = addressOrClient.toString();
Expand Down Expand Up @@ -300,7 +302,7 @@ export class EntitiesMap<E = Entity> extends Map<string, E> {
getEntity(type: 'cursor', key: string, assertExists?: boolean): AbstractCursor;
getEntity(type: 'stream', key: string, assertExists?: boolean): UnifiedChangeStream;
getEntity(type: 'clientEncryption', key: string, assertExists?: boolean): ClientEncryption;
getEntity(type: EntityTypeId, key: string, assertExists = true): Entity {
getEntity(type: EntityTypeId, key: string, assertExists = true): Entity | undefined {
const entity = this.get(key);
if (!entity) {
if (assertExists) throw new Error(`Entity '${key}' does not exist`);
Expand Down
90 changes: 54 additions & 36 deletions test/tools/unified-spec-runner/match.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import { expect } from 'chai';
import { inspect } from 'util';

Expand All @@ -7,6 +8,7 @@ import {
Document,
Long,
MongoError,
MongoServerError,
ObjectId,
OneOrMore
} from '../../../src';
Expand Down Expand Up @@ -264,11 +266,11 @@ export function specialCheck(
entities: EntitiesMap,
path: string[] = [],
checkExtraKeys: boolean
): boolean {
): void {
if (isUnsetOrMatchesOperator(expected)) {
if (actual === null || actual === undefined) return;

resultCheck(actual, expected.$$unsetOrMatches, entities, path, checkExtraKeys);
resultCheck(actual, expected.$$unsetOrMatches as any, entities, path, checkExtraKeys);
} else if (isMatchesEntityOperator(expected)) {
// $$matchesEntity
const entity = entities.get(expected.$$matchesEntity);
Expand All @@ -290,15 +292,15 @@ export function specialCheck(
// $$sessionLsid
const session = entities.getEntity('session', expected.$$sessionLsid, false);
expect(session, `Session ${expected.$$sessionLsid} does not exist in entities`).to.exist;
const entitySessionHex = session.id.id.buffer.toString('hex').toUpperCase();
const entitySessionHex = session.id!.id.buffer.toString('hex').toUpperCase();
const actualSessionHex = actual.id.buffer.toString('hex').toUpperCase();
expect(
entitySessionHex,
`Session entity ${expected.$$sessionLsid} does not match lsid`
).to.equal(actualSessionHex);
} else if (isTypeOperator(expected)) {
// $$type
let ok: boolean;
let ok = false;
const types = Array.isArray(expected.$$type) ? expected.$$type : [expected.$$type];
for (const type of types) {
ok ||= TYPE_MAP.get(type)(actual);
Expand Down Expand Up @@ -364,19 +366,23 @@ function compareCommandStartedEvents(
entities: EntitiesMap,
prefix: string
) {
if (expected.command) {
resultCheck(actual.command, expected.command, entities, [`${prefix}.command`]);
if (expected!.command) {
resultCheck(actual.command, expected!.command, entities, [`${prefix}.command`]);
}
if (expected.commandName) {
if (expected!.commandName) {
expect(
expected.commandName,
`expected ${prefix}.commandName to equal ${expected.commandName} but received ${actual.commandName}`
expected!.commandName,
`expected ${prefix}.commandName to equal ${expected!.commandName} but received ${
actual.commandName
}`
).to.equal(actual.commandName);
}
if (expected.databaseName) {
if (expected!.databaseName) {
expect(
expected.databaseName,
`expected ${prefix}.databaseName to equal ${expected.databaseName} but received ${actual.databaseName}`
expected!.databaseName,
`expected ${prefix}.databaseName to equal ${expected!.databaseName} but received ${
actual.databaseName
}`
).to.equal(actual.databaseName);
}
}
Expand All @@ -387,13 +393,15 @@ function compareCommandSucceededEvents(
entities: EntitiesMap,
prefix: string
) {
if (expected.reply) {
resultCheck(actual.reply, expected.reply, entities, [prefix]);
if (expected!.reply) {
resultCheck(actual.reply as Document, expected!.reply, entities, [prefix]);
}
if (expected.commandName) {
if (expected!.commandName) {
expect(
expected.commandName,
`expected ${prefix}.commandName to equal ${expected.commandName} but received ${actual.commandName}`
expected!.commandName,
`expected ${prefix}.commandName to equal ${expected!.commandName} but received ${
actual.commandName
}`
).to.equal(actual.commandName);
}
}
Expand All @@ -404,10 +412,12 @@ function compareCommandFailedEvents(
entities: EntitiesMap,
prefix: string
) {
if (expected.commandName) {
if (expected!.commandName) {
expect(
expected.commandName,
`expected ${prefix}.commandName to equal ${expected.commandName} but received ${actual.commandName}`
expected!.commandName,
`expected ${prefix}.commandName to equal ${expected!.commandName} but received ${
actual.commandName
}`
).to.equal(actual.commandName);
}
}
Expand Down Expand Up @@ -489,28 +499,34 @@ export function matchesEvents(
}
}

function isMongoCryptError(err): boolean {
if (err.constructor.name === 'MongoCryptError') {
return true;
}
return err.stack.includes('at ClientEncryption');
dariakp marked this conversation as resolved.
Show resolved Hide resolved
}

export function expectErrorCheck(
error: Error | MongoError,
expected: ExpectedError,
entities: EntitiesMap
): boolean {
if (Object.keys(expected)[0] === 'isClientError' || Object.keys(expected)[0] === 'isError') {
// FIXME: We cannot tell if Error arose from driver and not from server
return;
): void {
const expectMessage = `\n\nOriginal Error Stack:\n${error.stack}\n\n`;

if (!isMongoCryptError(error)) {
expect(error, expectMessage).to.be.instanceOf(MongoError);
}

const expectMessage = `\n\nOriginal Error Stack:\n${error.stack}\n\n`;
if (expected.isClientError === false) {
expect(error).to.be.instanceOf(MongoServerError);
} else if (expected.isClientError === true) {
expect(error).not.to.be.instanceOf(MongoServerError);
}

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

if (!(error instanceof MongoError)) {
// if statement asserts type for TS, expect will always fail
expect(error, expectMessage).to.be.instanceOf(MongoError);
return;
}

if (expected.errorCode != null) {
expect(error, expectMessage).to.have.property('code', expected.errorCode);
}
Expand All @@ -520,24 +536,26 @@ export function expectErrorCheck(
}

if (expected.errorLabelsContain != null) {
const mongoError = error as MongoError;
for (const errorLabel of expected.errorLabelsContain) {
expect(
error.hasErrorLabel(errorLabel),
`Error was supposed to have label ${errorLabel}, has [${error.errorLabels}] -- ${expectMessage}`
mongoError.hasErrorLabel(errorLabel),
`Error was supposed to have label ${errorLabel}, has [${mongoError.errorLabels}] -- ${expectMessage}`
).to.be.true;
}
}

if (expected.errorLabelsOmit != null) {
const mongoError = error as MongoError;
for (const errorLabel of expected.errorLabelsOmit) {
expect(
error.hasErrorLabel(errorLabel),
`Error was supposed to have label ${errorLabel}, has [${error.errorLabels}] -- ${expectMessage}`
mongoError.hasErrorLabel(errorLabel),
`Error was not supposed to have label ${errorLabel}, has [${mongoError.errorLabels}] -- ${expectMessage}`
).to.be.false;
}
}

if (expected.expectResult != null) {
resultCheck(error, expected.expectResult, entities);
resultCheck(error, expected.expectResult as any, entities);
}
}
Loading