Skip to content

Commit

Permalink
chore: error name and unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
nbbeeken committed Jan 9, 2025
1 parent e46e198 commit e3c447d
Show file tree
Hide file tree
Showing 9 changed files with 75 additions and 27 deletions.
26 changes: 13 additions & 13 deletions src/client-side-encryption/state_machine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ export class StateMachine {
*/
async kmsRequest(
request: MongoCryptKMSRequest,
options: { timeoutContext?: TimeoutContext } & Abortable
options?: { timeoutContext?: TimeoutContext } & Abortable
): Promise<void> {
const parsedUrl = request.endpoint.split(':');
const port = parsedUrl[1] != null ? Number.parseInt(parsedUrl[1], 10) : HTTPS_PORT;
Expand Down Expand Up @@ -428,7 +428,7 @@ export class StateMachine {
resolve
} = promiseWithResolvers<void>();

abortListener = addAbortListener(options.signal, error => {
abortListener = addAbortListener(options?.signal, error => {
destroySockets();
rejectOnTlsSocketError(error);
});
Expand All @@ -447,7 +447,7 @@ export class StateMachine {
resolve();
}
});
await (options.timeoutContext?.csotEnabled()
await (options?.timeoutContext?.csotEnabled()
? Promise.all([
willResolveKmsRequest,
Timeout.expires(options.timeoutContext?.remainingTimeMS)
Expand All @@ -464,7 +464,7 @@ export class StateMachine {
}
}

*requests(context: MongoCryptContext, options: { timeoutContext?: TimeoutContext } & Abortable) {
*requests(context: MongoCryptContext, options?: { timeoutContext?: TimeoutContext } & Abortable) {
for (
let request = context.nextKMSRequest();
request != null;
Expand Down Expand Up @@ -531,16 +531,16 @@ export class StateMachine {
client: MongoClient,
ns: string,
filter: Document,
options: { timeoutContext?: TimeoutContext } & Abortable
options?: { timeoutContext?: TimeoutContext } & Abortable
): Promise<Uint8Array | null> {
const { db } = MongoDBCollectionNamespace.fromString(ns);

const cursor = client.db(db).listCollections(filter, {
promoteLongs: false,
promoteValues: false,
timeoutContext:
options.timeoutContext && new CursorTimeoutContext(options.timeoutContext, Symbol()),
signal: options.signal
options?.timeoutContext && new CursorTimeoutContext(options?.timeoutContext, Symbol()),
signal: options?.signal
});

// There is always exactly zero or one matching documents, so this should always exhaust the cursor
Expand All @@ -564,7 +564,7 @@ export class StateMachine {
client: MongoClient,
ns: string,
command: Uint8Array,
options: { timeoutContext?: TimeoutContext } & Abortable
options?: { timeoutContext?: TimeoutContext } & Abortable
): Promise<Uint8Array> {
const { db } = MongoDBCollectionNamespace.fromString(ns);
const bsonOptions = { promoteLongs: false, promoteValues: false };
Expand All @@ -578,10 +578,10 @@ export class StateMachine {
signal: undefined
};

if (options.timeoutContext?.csotEnabled()) {
if (options?.timeoutContext?.csotEnabled()) {
commandOptions.timeoutMS = options.timeoutContext.remainingTimeMS;
}
if (options.signal) {
if (options?.signal) {
commandOptions.signal = options.signal;
}

Expand All @@ -605,7 +605,7 @@ export class StateMachine {
client: MongoClient,
keyVaultNamespace: string,
filter: Uint8Array,
options: { timeoutContext?: TimeoutContext } & Abortable
options?: { timeoutContext?: TimeoutContext } & Abortable
): Promise<Array<DataKey>> {
const { db: dbName, collection: collectionName } =
MongoDBCollectionNamespace.fromString(keyVaultNamespace);
Expand All @@ -618,10 +618,10 @@ export class StateMachine {
signal: undefined
};

if (options.timeoutContext != null) {
if (options?.timeoutContext != null) {
commandOptions.timeoutContext = new CursorTimeoutContext(options.timeoutContext, Symbol());
}
if (options.signal != null) {
if (options?.signal != null) {
commandOptions.signal = options.signal;
}

Expand Down
22 changes: 19 additions & 3 deletions src/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,12 +199,28 @@ export class MongoError extends Error {
/**
* An error thrown when a signal is aborted
*
* A MongoAbortError has the name "AbortError" to match the name
* given to a DOMException thrown from web APIs that support AbortSignals.
*
* @example
* ```js
* try {
* const res = await fetch('...', { signal });
* await collection.insertOne(await res.json(), { signal });
* catch (error) {
* if (error.name === 'AbortError') {
* // error is MongoAbortError or DOMException,
* // both represent the signal being aborted
* }
* }
* ```
*
* @public
* @category Error
*/
export class MongoAbortedError extends MongoError {
override get name(): string {
return 'MongoAbortedError';
export class MongoAbortError extends MongoError {
override get name(): 'AbortError' {
return 'AbortError';
}
}

Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export {
export { ClientEncryption } from './client-side-encryption/client_encryption';
export { ChangeStreamCursor } from './cursor/change_stream_cursor';
export {
MongoAbortError,
MongoAPIError,
MongoAWSError,
MongoAzureError,
Expand Down
24 changes: 24 additions & 0 deletions src/mongo_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,30 @@ export class CancellationToken extends TypedEventEmitter<{ cancel(): void }> {}
export type Abortable = {
/**
* When provided the corresponding `AbortController` can be used to cancel an asynchronous action.
*
* The driver will convert the abort event into a promise rejection with an error that has the name `'AbortError'`.
*
* The cause of the error will be set to `signal.reason`
*
* @example
* ```js
* const controller = new AbortController();
* const { signal } = controller;
* req,on('close', () => controller.abort(new Error('Request aborted by user')));
*
* try {
* const res = await fetch('...', { signal });
* await collection.insertOne(await res.json(), { signal });
* catch (error) {
* if (error.name === 'AbortError') {
* // error is MongoAbortError or DOMException,
* // both represent the signal being aborted
* error.cause === signal.reason; // true
* }
* }
* ```
*
* @see MongoAbortError
*/
signal?: AbortSignal | undefined;
};
Expand Down
6 changes: 3 additions & 3 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import type { FindCursor } from './cursor/find_cursor';
import type { Db } from './db';
import {
type AnyError,
MongoAbortedError,
MongoAbortError,
MongoAPIError,
MongoCompatibilityError,
MongoInvalidArgumentError,
Expand Down Expand Up @@ -1484,7 +1484,7 @@ export function addAbortListener(
if (signal == null) return;

const convertReasonToError = () =>
listener(new MongoAbortedError('Operation was aborted', { cause: signal.reason }));
listener(new MongoAbortError('Operation was aborted', { cause: signal.reason }));

signal.addEventListener('abort', convertReasonToError);

Expand All @@ -1493,6 +1493,6 @@ export function addAbortListener(

export function throwIfAborted(signal?: { aborted?: boolean; reason?: any }): void {
if (signal?.aborted) {
throw new MongoAbortedError('Operation was aborted', { cause: signal.reason });
throw new MongoAbortError('Operation was aborted', { cause: signal.reason });
}
}
8 changes: 4 additions & 4 deletions test/integration/node-specific/abort_signal.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
type Collection,
type Db,
FindCursor,
MongoAbortedError,
MongoAbortError,
type MongoClient,
ReadPreference,
setDifference
Expand Down Expand Up @@ -155,7 +155,7 @@ describe('AbortSignal support', () => {
for (const [cursorAPI, { value: args }] of getAllOwnProps(cursorAPIs)) {
it(`rejects ${cursorAPI.toString()} with MongoAbortedError and the cause is signal.reason`, async () => {
const result = await captureCursorError(cursor, cursorAPI, args);
expect(result).to.be.instanceOf(MongoAbortedError);
expect(result).to.be.instanceOf(MongoAbortError);
expect(result.cause).to.equal(signal.reason);
});
}
Expand Down Expand Up @@ -190,7 +190,7 @@ describe('AbortSignal support', () => {
controller.abort('The operation was aborted');

const error = await captureCursorError(cursor, cursorAPI, args);
expect(error).to.be.instanceOf(MongoAbortedError);
expect(error).to.be.instanceOf(MongoAbortError);
});
}
});
Expand Down Expand Up @@ -235,7 +235,7 @@ describe('AbortSignal support', () => {
const end = performance.now();
expect(end - start).to.be.lessThan(1000); // should be way less than 30s server selection timeout

expect(result).to.be.instanceOf(MongoAbortedError);
expect(result).to.be.instanceOf(MongoAbortError);
expect(result.cause).to.equal(signal.reason);
});
}
Expand Down
8 changes: 5 additions & 3 deletions test/unit/client-side-encryption/state_machine.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -493,7 +493,7 @@ describe('StateMachine', function () {
});

await stateMachine
.fetchKeys(client, 'keyVault', BSON.serialize({ a: 1 }), context)
.fetchKeys(client, 'keyVault', BSON.serialize({ a: 1 }), { timeoutContext: context })
.catch(e => squashError(e));

const { timeoutContext } = findSpy.getCalls()[0].args[1] as FindOptions;
Expand Down Expand Up @@ -535,7 +535,7 @@ describe('StateMachine', function () {
});
await sleep(300);
await stateMachine
.markCommand(client, 'keyVault', BSON.serialize({ a: 1 }), timeoutContext)
.markCommand(client, 'keyVault', BSON.serialize({ a: 1 }), { timeoutContext })
.catch(e => squashError(e));
expect(dbCommandSpy.getCalls()[0].args[1].timeoutMS).to.not.be.undefined;
expect(dbCommandSpy.getCalls()[0].args[1].timeoutMS).to.be.lessThanOrEqual(205);
Expand Down Expand Up @@ -576,7 +576,9 @@ describe('StateMachine', function () {
});
await sleep(300);
await stateMachine
.fetchCollectionInfo(client, 'keyVault', BSON.serialize({ a: 1 }), context)
.fetchCollectionInfo(client, 'keyVault', BSON.serialize({ a: 1 }), {
timeoutContext: context
})
.catch(e => squashError(e));
const [_filter, { timeoutContext }] = listCollectionsSpy.getCalls()[0].args;
expect(timeoutContext).to.exist;
Expand Down
6 changes: 5 additions & 1 deletion test/unit/error.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,11 @@ describe('MongoErrors', () => {
expect(errorNameDescriptor).to.have.property('set').that.does.not.exist;
expect(errorNameDescriptor).to.not.have.property('value');
expect(errorNameDescriptor).to.have.property('get');
expect(errorNameDescriptor.get.call(undefined)).to.equal(errorName);
if (errorName === 'MongoAbortError') {
expect(errorNameDescriptor.get.call(undefined)).to.equal('AbortError');
} else {
expect(errorNameDescriptor.get.call(undefined)).to.equal(errorName);
}
});
}
});
Expand Down
1 change: 1 addition & 0 deletions test/unit/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ const EXPECTED_EXPORTS = [
'Long',
'MaxKey',
'MinKey',
'MongoAbortError',
'MongoAPIError',
'MongoAWSError',
'MongoAzureError',
Expand Down

0 comments on commit e3c447d

Please sign in to comment.