diff --git a/.eslintrc.json b/.eslintrc.json index 0910afd..2d68e9b 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -38,6 +38,15 @@ "prettier/prettier": "error", "no-console": "error", "valid-typeof": "error", + "@typescript-eslint/no-unused-vars": [ + "error", + { + "argsIgnorePattern": "^_", + "caughtErrorsIgnorePattern": "^_", + "destructuredArrayIgnorePattern": "^_", + "varsIgnorePattern": "^_" + } + ], "eqeqeq": [ "error", "always", diff --git a/src/bindings.ts b/src/bindings.ts new file mode 100644 index 0000000..3971d9c --- /dev/null +++ b/src/bindings.ts @@ -0,0 +1,138 @@ +function load() { + try { + return require('../build/Release/mongocrypt.node'); + } catch { + // Webpack will fail when just returning the require, so we need to wrap + // in a try/catch and rethrow. + /* eslint no-useless-catch: 0 */ + try { + return require('../build/Debug/mongocrypt.node'); + } catch (error) { + throw error; + } + } +} + +export const mc: MongoCryptBindings = load(); + +export interface MongoCryptConstructor { + new (options: MongoCryptConstructorOptions): IMongoCrypt; + libmongocryptVersion: string; +} + +interface MongoCryptContextCtor { + new (): IMongoCryptContext; +} + +/** + * The value returned by the native bindings + * reference the `Init(Env env, Object exports)` function in the c++ + */ +type MongoCryptBindings = { + MongoCrypt: MongoCryptConstructor; + MongoCryptContextCtor: MongoCryptContextCtor; + MongoCryptKMSRequestCtor: MongoCryptKMSRequest; +}; + +export interface MongoCryptStatus { + type: number; + code: number; + message?: string; +} + +export interface MongoCryptKMSRequest { + addResponse(response: Uint8Array): void; + fail(): boolean; + readonly status: MongoCryptStatus; + readonly bytesNeeded: number; + readonly uSleep: number; + readonly kmsProvider: string; + readonly endpoint: string; + readonly message: Buffer; +} + +export interface IMongoCryptContext { + nextMongoOperation(): Buffer; + addMongoOperationResponse(response: Uint8Array): void; + finishMongoOperation(): void; + nextKMSRequest(): MongoCryptKMSRequest | null; + provideKMSProviders(providers: Uint8Array): void; + finishKMSRequests(): void; + finalize(): Buffer; + + get status(): MongoCryptStatus; + get state(): number; +} + +export type MongoCryptConstructorOptions = { + kmsProviders?: Uint8Array; + schemaMap?: Uint8Array; + encryptedFieldsMap?: Uint8Array; + logger?: unknown; + cryptoCallbacks?: Record; + cryptSharedLibSearchPaths?: string[]; + cryptSharedLibPath?: string; + bypassQueryAnalysis?: boolean; + /** Configure the time to expire the DEK from the cache. */ + keyExpirationMS?: number; + + /** + * A function that wraps any errors that are thrown by the bindings in this package + * into a new error type. + * + * Example wrapper function, using the MongoDB driver: + * ```typescript + * (error: Error) => new MongoClientEncryptionError(error.message, { cause: error }); + * ``` + */ + errorWrapper: (error: Error) => Error; +}; + +export interface IMongoCrypt { + makeEncryptionContext(ns: string, command: Uint8Array): IMongoCryptContext; + makeExplicitEncryptionContext( + value: Uint8Array, + options?: { + keyId?: Uint8Array; + keyAltName?: Uint8Array; + algorithm?: string; + rangeOptions?: Uint8Array; + textOptions?: Uint8Array; + contentionFactor?: bigint | number; + queryType?: string; + + /** + * node-binding specific option + * + * When true, creates a `mongocrypt_ctx_explicit_encrypt_expression` context. + * When false, creates a `mongocrypt_ctx_explicit_encrypt` + */ + expressionMode: boolean; + } + ): IMongoCryptContext; + makeDecryptionContext(buffer: Uint8Array): IMongoCryptContext; + makeExplicitDecryptionContext(buffer: Uint8Array): IMongoCryptContext; + makeDataKeyContext( + optionsBuffer: Uint8Array, + options: { + keyAltNames?: Uint8Array[]; + keyMaterial?: Uint8Array; + } + ): IMongoCryptContext; + makeRewrapManyDataKeyContext(filter: Uint8Array, encryptionKey?: Uint8Array): IMongoCryptContext; + readonly status: MongoCryptStatus; + readonly cryptSharedLibVersionInfo: { + version: bigint; + versionStr: string; + } | null; + readonly cryptoHooksProvider: 'js' | 'native_openssl' | null; +} + +export type ExplicitEncryptionContextOptions = NonNullable< + Parameters[1] +>; +export type DataKeyContextOptions = NonNullable[1]>; +export type MongoCryptOptions = MongoCryptConstructorOptions; +export type MongoCryptErrorWrapper = MongoCryptOptions['errorWrapper']; + +// export const diff --git a/src/index.ts b/src/index.ts index eeb6288..0cbba09 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,83 +1,120 @@ +import { + IMongoCrypt, + IMongoCryptContext, + mc, + MongoCryptErrorWrapper, + MongoCryptKMSRequest, + MongoCryptOptions, + MongoCryptStatus +} from './bindings'; import { cryptoCallbacks } from './crypto_callbacks'; export { cryptoCallbacks }; -function load() { - try { - return require('../build/Release/mongocrypt.node'); - } catch { - // Webpack will fail when just returning the require, so we need to wrap - // in a try/catch and rethrow. - /* eslint no-useless-catch: 0 */ +export class MongoCryptContext implements IMongoCryptContext { + private context: IMongoCryptContext; + private errorWrapper: MongoCryptOptions['errorWrapper']; + + constructor(context: IMongoCryptContext, errorWrapper: MongoCryptOptions['errorWrapper']) { + this.context = context; + this.errorWrapper = errorWrapper; + } + + nextMongoOperation(): Buffer { try { - return require('../build/Debug/mongocrypt.node'); + return this.context.nextMongoOperation(); } catch (error) { - throw error; + throw this.errorWrapper(error); } } -} - -const mc: MongoCryptBindings = load(); - -/** - * The value returned by the native bindings - * reference the `Init(Env env, Object exports)` function in the c++ - */ -type MongoCryptBindings = { - MongoCrypt: MongoCryptConstructor; - MongoCryptContextCtor: MongoCryptContextCtor; - MongoCryptKMSRequestCtor: MongoCryptKMSRequest; -}; -export interface MongoCryptKMSRequest { - addResponse(response: Uint8Array): void; - fail(): boolean; - readonly status: MongoCryptStatus; - readonly bytesNeeded: number; - readonly uSleep: number; - readonly kmsProvider: string; - readonly endpoint: string; - readonly message: Buffer; + addMongoOperationResponse(response: Uint8Array): void { + try { + return this.context.addMongoOperationResponse(response); + } catch (error) { + throw this.errorWrapper(error); + } + } + finishMongoOperation(): void { + try { + return this.context.finishMongoOperation(); + } catch (error) { + throw this.errorWrapper(error); + } + } + nextKMSRequest(): MongoCryptKMSRequest | null { + try { + return this.context.nextKMSRequest(); + } catch (error) { + throw this.errorWrapper(error); + } + } + provideKMSProviders(providers: Uint8Array): void { + try { + return this.context.provideKMSProviders(providers); + } catch (error) { + throw this.errorWrapper(error); + } + } + finishKMSRequests(): void { + try { + return this.context.finishKMSRequests(); + } catch (error) { + throw this.errorWrapper(error); + } + } + finalize(): Buffer { + try { + return this.context.finalize(); + } catch (error) { + throw this.errorWrapper(error); + } + } + get status(): MongoCryptStatus { + try { + return this.context.status; + } catch (error) { + throw this.errorWrapper(error); + } + } + get state(): number { + try { + return this.context.state; + } catch (error) { + throw this.errorWrapper(error); + } + } } -export interface MongoCryptStatus { - type: number; - code: number; - message?: string; -} +export class MongoCrypt implements IMongoCrypt { + private errorWrapper: MongoCryptErrorWrapper; + private mc: IMongoCrypt; + readonly cryptSharedLibVersionInfo: { version: bigint; versionStr: string } | null; + readonly cryptoHooksProvider: 'js' | 'native_openssl' | null; -export interface MongoCryptContext { - nextMongoOperation(): Buffer; - addMongoOperationResponse(response: Uint8Array): void; - finishMongoOperation(): void; - nextKMSRequest(): MongoCryptKMSRequest | null; - provideKMSProviders(providers: Uint8Array): void; - finishKMSRequests(): void; - finalize(): Buffer; + static readonly libmongocryptVersion: string = mc.MongoCrypt.libmongocryptVersion; - get status(): MongoCryptStatus; - get state(): number; -} + constructor(options: MongoCryptOptions) { + // Pass in JS cryptoCallbacks implementation by default. + // If the Node.js openssl version is supported this will be ignored. + this.mc = new mc.MongoCrypt( + // @ts-expect-error: intentionally passing in an argument that will throw to preserve existing behavior + options == null || typeof options !== 'object' ? undefined : { cryptoCallbacks, ...options } + ); -type MongoCryptConstructorOptions = { - kmsProviders?: Uint8Array; - schemaMap?: Uint8Array; - encryptedFieldsMap?: Uint8Array; - logger?: unknown; - cryptoCallbacks?: Record; - cryptSharedLibSearchPaths?: string[]; - cryptSharedLibPath?: string; - bypassQueryAnalysis?: boolean; - /** Configure the time to expire the DEK from the cache. */ - keyExpirationMS?: number; -}; + this.errorWrapper = options.errorWrapper; -export interface MongoCryptConstructor { - new (options: MongoCryptConstructorOptions): MongoCrypt; - libmongocryptVersion: string; -} + this.cryptSharedLibVersionInfo = this.mc.cryptSharedLibVersionInfo; + this.cryptoHooksProvider = this.mc.cryptoHooksProvider; + } + + makeEncryptionContext(ns: string, command: Uint8Array): MongoCryptContext { + try { + return new MongoCryptContext(this.mc.makeEncryptionContext(ns, command), this.errorWrapper); + } catch (error) { + throw this.errorWrapper(error); + } + } -export interface MongoCrypt { - makeEncryptionContext(ns: string, command: Uint8Array): MongoCryptContext; makeExplicitEncryptionContext( value: Uint8Array, options?: { @@ -97,44 +134,70 @@ export interface MongoCrypt { */ expressionMode: boolean; } - ): MongoCryptContext; - makeDecryptionContext(buffer: Uint8Array): MongoCryptContext; - makeExplicitDecryptionContext(buffer: Uint8Array): MongoCryptContext; + ): MongoCryptContext { + try { + return new MongoCryptContext( + this.mc.makeExplicitEncryptionContext(value, options), + this.errorWrapper + ); + } catch (error) { + throw this.errorWrapper(error); + } + } + makeDecryptionContext(buffer: Uint8Array): MongoCryptContext { + try { + return new MongoCryptContext(this.mc.makeDecryptionContext(buffer), this.errorWrapper); + } catch (error) { + throw this.errorWrapper(error); + } + } + makeExplicitDecryptionContext(buffer: Uint8Array): MongoCryptContext { + try { + return new MongoCryptContext( + this.mc.makeExplicitDecryptionContext(buffer), + this.errorWrapper + ); + } catch (error) { + throw this.errorWrapper(error); + } + } makeDataKeyContext( optionsBuffer: Uint8Array, options: { keyAltNames?: Uint8Array[]; keyMaterial?: Uint8Array; } - ): MongoCryptContext; - makeRewrapManyDataKeyContext(filter: Uint8Array, encryptionKey?: Uint8Array): MongoCryptContext; - readonly status: MongoCryptStatus; - readonly cryptSharedLibVersionInfo: { - version: bigint; - versionStr: string; - } | null; - readonly cryptoHooksProvider: 'js' | 'native_openssl' | null; -} - -export type ExplicitEncryptionContextOptions = NonNullable< - Parameters[1] ->; -export type DataKeyContextOptions = NonNullable[1]>; -export type MongoCryptOptions = NonNullable[0]>; - -export const MongoCrypt: MongoCryptConstructor = class MongoCrypt extends mc.MongoCrypt { - constructor(options: MongoCryptConstructorOptions) { - // Pass in JS cryptoCallbacks implementation by default. - // If the Node.js openssl version is supported this will be ignored. - super( - // @ts-expect-error: intentionally passing in an argument that will throw to preserve existing behavior - options == null || typeof options !== 'object' ? undefined : { cryptoCallbacks, ...options } - ); + ): MongoCryptContext { + try { + return new MongoCryptContext( + this.mc.makeDataKeyContext(optionsBuffer, options), + this.errorWrapper + ); + } catch (error) { + throw this.errorWrapper(error); + } + } + makeRewrapManyDataKeyContext(filter: Uint8Array, encryptionKey?: Uint8Array): MongoCryptContext { + try { + return new MongoCryptContext( + this.mc.makeRewrapManyDataKeyContext(filter, encryptionKey), + this.errorWrapper + ); + } catch (error) { + throw this.errorWrapper(error); + } + } + get status(): MongoCryptStatus { + try { + return this.mc.status; + } catch (error) { + throw this.errorWrapper(error); + } } -}; - -/** exported for testing only. */ -interface MongoCryptContextCtor { - new (): MongoCryptContext; } -export const MongoCryptContextCtor: MongoCryptContextCtor = mc.MongoCryptContextCtor; + +export type { + MongoCryptOptions, + ExplicitEncryptionContextOptions, + MongoCryptKMSRequest +} from './bindings'; diff --git a/test/bundling/webpack/src/index.ts b/test/bundling/webpack/src/index.ts index 7eb5c01..531484f 100644 --- a/test/bundling/webpack/src/index.ts +++ b/test/bundling/webpack/src/index.ts @@ -1,4 +1,8 @@ import { MongoCrypt } from 'mongodb-client-encryption'; // eslint-disable-next-line no-console -console.log(new MongoCrypt({})); +console.log( + new MongoCrypt({ + errorWrapper: (e: Error) => e + }) +); diff --git a/test/unit/bindings.test.ts b/test/unit/bindings.test.ts index 75604c4..1818770 100644 --- a/test/unit/bindings.test.ts +++ b/test/unit/bindings.test.ts @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { MongoCrypt, MongoCryptContext, MongoCryptContextCtor } from '../../src'; +import { MongoCrypt, MongoCryptContext } from '../../src'; import { serialize, Binary, Long } from 'bson'; import * as crypto from 'crypto'; @@ -15,6 +15,8 @@ export function randomHook(buffer: Buffer, count: number): number | Error { return count; } +const defaultErrorWrapper = (e: Error) => e; + describe('MongoCryptConstructor', () => { const mc = new MongoCrypt({ kmsProviders: serialize({ aws: {} }), @@ -28,11 +30,8 @@ describe('MongoCryptConstructor', () => { hmacSha256Hook: () => {}, sha256Hook: () => {}, signRsaSha256Hook: () => {} - } - }); - - it('requires an options argument', () => { - expect(() => new MongoCrypt()).to.throw(/First parameter must be an object/); + }, + errorWrapper: defaultErrorWrapper }); it('creates a MongoCrypt when provided valid options', () => { @@ -54,7 +53,8 @@ describe('MongoCryptConstructor', () => { signRsaSha256Hook: () => {} }, - bypassQueryAnalysis: false + bypassQueryAnalysis: false, + errorWrapper: defaultErrorWrapper }) ).to.be.instanceOf(MongoCrypt); }); @@ -65,14 +65,24 @@ describe('MongoCryptConstructor', () => { describe('options.kmsProviders', () => { it('throws if provided and are not a Uint8Array', () => { - expect(() => new MongoCrypt({ kmsProviders: 3 })).to.throw( - /Parameter `options.kmsProviders` must be a Uint8Array./ - ); + expect( + () => + new MongoCrypt({ + kmsProviders: 3, + errorWrapper: defaultErrorWrapper + }) + ).to.throw(/Parameter `options.kmsProviders` must be a Uint8Array./); }); it('throws when explicitly set to undefined', () => { // the error is different because it is thrown from libmongocrypt - expect(() => new MongoCrypt({ kmsProviders: undefined })).to.throw(/no kms provider set/); + expect( + () => + new MongoCrypt({ + kmsProviders: undefined, + errorWrapper: defaultErrorWrapper + }) + ).to.throw(/no kms provider set/); }); }); @@ -82,7 +92,8 @@ describe('MongoCryptConstructor', () => { () => new MongoCrypt({ kmsProviders: serialize({ aws: {} }), - schemaMap: 3 + schemaMap: 3, + errorWrapper: defaultErrorWrapper }) ).to.throw(/Parameter `options.schemaMap` must be a Uint8Array./); }); @@ -91,7 +102,8 @@ describe('MongoCryptConstructor', () => { expect( new MongoCrypt({ kmsProviders: serialize({ aws: {} }), - schemaMap: undefined + schemaMap: undefined, + errorWrapper: defaultErrorWrapper }) ).to.be.instanceOf(MongoCrypt); }); @@ -101,7 +113,11 @@ describe('MongoCryptConstructor', () => { context('when the number is positive', () => { it('does not error', () => { expect( - new MongoCrypt({ kmsProviders: serialize({ aws: {} }), keyExpirationMS: 1000000 }) + new MongoCrypt({ + kmsProviders: serialize({ aws: {} }), + keyExpirationMS: 1000000, + errorWrapper: defaultErrorWrapper + }) ).to.be.instanceOf(MongoCrypt); }); }); @@ -109,7 +125,11 @@ describe('MongoCryptConstructor', () => { context('when the number is negative', () => { it('throws an error', () => { expect(() => { - new MongoCrypt({ kmsProviders: serialize({ aws: {} }), keyExpirationMS: -1000000 }); + new MongoCrypt({ + kmsProviders: serialize({ aws: {} }), + keyExpirationMS: -1000000, + errorWrapper: defaultErrorWrapper + }); }).to.throw(/must be a non-negative number/); }); }); @@ -130,7 +150,8 @@ describe('MongoCryptConstructor', () => { expect( new MongoCrypt({ kmsProviders: serialize({ aws: {} }), - encryptedFieldsMap: undefined + encryptedFieldsMap: undefined, + errorWrapper: defaultErrorWrapper }) ).to.be.instanceOf(MongoCrypt); }); @@ -141,13 +162,17 @@ describe('MongoCryptConstructor', () => { () => new MongoCrypt({ kmsProviders: serialize({ aws: {} }), - cryptSharedLibSearchPaths: 3 + cryptSharedLibSearchPaths: 3, + errorWrapper: defaultErrorWrapper }) ).to.throw(/Option `cryptSharedLibSearchPaths` must be an array/); }); it('has an instance property `status`', () => { - const mc = new MongoCrypt({ kmsProviders: serialize({ aws: {} }) }); + const mc = new MongoCrypt({ + kmsProviders: serialize({ aws: {} }), + errorWrapper: defaultErrorWrapper + }); expect(mc).to.have.property('status'); expect(mc).to.have.property('cryptSharedLibVersionInfo'); }); @@ -161,7 +186,7 @@ describe('MongoCryptConstructor', () => { it('returns a MongoCryptContext', () => { expect(mc.makeEncryptionContext('foo.bar', serialize({ ping: 1 }))).to.be.instanceOf( - MongoCryptContextCtor + MongoCryptContext ); }); }); @@ -174,9 +199,7 @@ describe('MongoCryptConstructor', () => { }); it('returns a MongoCryptContext', () => { - expect(mc.makeDecryptionContext(serialize({ ping: 1 }))).to.be.instanceOf( - MongoCryptContextCtor - ); + expect(mc.makeDecryptionContext(serialize({ ping: 1 }))).to.be.instanceOf(MongoCryptContext); }); }); @@ -190,15 +213,13 @@ describe('MongoCryptConstructor', () => { it('returns a MongoCryptContext', () => { expect( mc.makeExplicitDecryptionContext(serialize({ v: new Binary(Buffer.from([]), 6) })) - ).to.be.instanceOf(MongoCryptContextCtor); + ).to.be.instanceOf(MongoCryptContext); }); }); describe('.makeRewrapManyDataKeyContext()', () => { it('returns a MongoCryptContext', () => { - expect(mc.makeRewrapManyDataKeyContext(serialize({}))).to.be.instanceOf( - MongoCryptContextCtor - ); + expect(mc.makeRewrapManyDataKeyContext(serialize({}))).to.be.instanceOf(MongoCryptContext); }); describe('when a filter buffer is provided', () => { @@ -210,7 +231,7 @@ describe('MongoCryptConstructor', () => { it('can be explicitly passed `undefined`', () => { expect(mc.makeRewrapManyDataKeyContext(serialize({}), undefined)).to.be.instanceOf( - MongoCryptContextCtor + MongoCryptContext ); }); }); @@ -223,7 +244,7 @@ describe('MongoCryptConstructor', () => { key: 'key' }); it('returns a MongoCryptContext', () => { - expect(mc.makeDataKeyContext(providers, {})).to.be.instanceOf(MongoCryptContextCtor); + expect(mc.makeDataKeyContext(providers, {})).to.be.instanceOf(MongoCryptContext); }); it('throws when the first parameter is not a Uint8Array', () => { @@ -238,7 +259,7 @@ describe('MongoCryptConstructor', () => { mc.makeDataKeyContext(providers, { keyAltNames: undefined }) - ).to.be.instanceOf(MongoCryptContextCtor); + ).to.be.instanceOf(MongoCryptContext); }); it('throws a TypeError when options.keyAltNames includes values that are not Uint8Arrays', () => { @@ -258,7 +279,7 @@ describe('MongoCryptConstructor', () => { mc.makeDataKeyContext(providers, { keyMaterial: undefined }) - ).to.be.instanceOf(MongoCryptContextCtor); + ).to.be.instanceOf(MongoCryptContext); }); it('throws a TypeError when provided and is not a Uint8Array', () => { @@ -285,7 +306,7 @@ describe('MongoCryptConstructor', () => { expressionMode: false, algorithm: 'Unindexed' }) - ).to.be.instanceOf(MongoCryptContextCtor); + ).to.be.instanceOf(MongoCryptContext); }); it('throws a TypeError when `value` is not a Uint8Array', () => { @@ -402,7 +423,7 @@ describe('MongoCryptConstructor', () => { // is enforced in libmongocrypt, not our bindings contentionFactor: 2 }) - ).to.be.instanceOf(MongoCryptContextCtor); + ).to.be.instanceOf(MongoCryptContext); }); }); @@ -428,7 +449,8 @@ describe('MongoCryptContext', () => { beforeEach(() => { let crypt = new MongoCrypt({ - kmsProviders: serialize({ aws: {} }) + kmsProviders: serialize({ aws: {} }), + errorWrapper: defaultErrorWrapper }); context = crypt.makeDecryptionContext(serialize({})); weakMongoCryptRef = new WeakRef(crypt); diff --git a/test/unit/error_wrapper.test.ts b/test/unit/error_wrapper.test.ts new file mode 100644 index 0000000..807e486 --- /dev/null +++ b/test/unit/error_wrapper.test.ts @@ -0,0 +1,180 @@ +import { expect } from 'chai'; +import { MongoCrypt, MongoCryptContext } from '../../src'; +import { + IMongoCrypt, + IMongoCryptContext, + MongoCryptKMSRequest, + MongoCryptStatus +} from '../../src/bindings'; +import { serialize } from 'bson'; + +class CustomError extends Error {} + +describe('custom error wrapper functionality', function () { + describe('class MongoCryptContext', function () { + let context: MongoCryptContext; + + beforeEach(function () { + class MockBindingsContext implements IMongoCryptContext { + nextMongoOperation(): Buffer { + throw new Error('ahh'); + } + addMongoOperationResponse(_response: Uint8Array): void { + throw new Error('ahh'); + } + finishMongoOperation(): void { + throw new Error('ahh'); + } + nextKMSRequest(): MongoCryptKMSRequest | null { + throw new Error('ahh'); + } + provideKMSProviders(_providers: Uint8Array): void { + throw new Error('ahh'); + } + finishKMSRequests(): void { + throw new Error('ahh'); + } + finalize(): Buffer { + throw new Error('ahh'); + } + get status(): MongoCryptStatus { + throw new Error('ahh'); + } + get state(): number { + throw new Error('ahh'); + } + } + + context = new MongoCryptContext( + new MockBindingsContext(), + error => new CustomError('custom error', { cause: error }) + ); + }); + + it('#nextMongoOperation() wraps errors from the bindings', function () { + expect(() => context.nextMongoOperation()).to.throw(CustomError); + }); + + it('#addMongoOperationResponse() wraps errors from the bindings', function () { + expect(() => context.addMongoOperationResponse(Buffer.from([1, 2, 3]))).to.throw(CustomError); + }); + + it('#finishMongoOperation() wraps errors from the bindings', function () { + expect(() => context.finishMongoOperation()).to.throw(CustomError); + }); + + it('#nextKMSRequest() wraps errors from the bindings', function () { + expect(() => context.nextKMSRequest()).to.throw(CustomError); + }); + + it('#provideKMSProviders() wraps errors from the bindings', function () { + expect(() => context.provideKMSProviders(Buffer.from([1, 2, 3]))).to.throw(CustomError); + }); + + it('#finishKMSRequests() wraps errors from the bindings', function () { + expect(() => context.finishKMSRequests()).to.throw(CustomError); + }); + + it('#finalize() wraps errors from the bindings', function () { + expect(() => context.finalize()).to.throw(CustomError); + }); + + it('.status wraps errors from the bindings', function () { + expect(() => context.status).to.throw(CustomError); + }); + + it('.state wraps errors from the bindings', function () { + expect(() => context.state).to.throw(CustomError); + }); + }); + + describe('class MongoCrypt', function () { + let context: IMongoCrypt; + + beforeEach(function () { + class MockMongoCrypt implements IMongoCrypt { + makeEncryptionContext(_ns: string, _command: Uint8Array): IMongoCryptContext { + throw new Error('Method not implemented.'); + } + makeExplicitEncryptionContext( + _value: Uint8Array, + _options?: { + keyId?: Uint8Array; + keyAltName?: Uint8Array; + algorithm?: string; + rangeOptions?: Uint8Array; + textOptions?: Uint8Array; + contentionFactor?: bigint | number; + queryType?: string; + expressionMode: boolean; + } + ): IMongoCryptContext { + throw new Error('Method not implemented.'); + } + makeDecryptionContext(_buffer: Uint8Array): IMongoCryptContext { + throw new Error('Method not implemented.'); + } + makeExplicitDecryptionContext(_buffer: Uint8Array): IMongoCryptContext { + throw new Error('Method not implemented.'); + } + makeDataKeyContext( + _optionsBuffer: Uint8Array, + _options: { keyAltNames?: Uint8Array[]; keyMaterial?: Uint8Array } + ): IMongoCryptContext { + throw new Error('Method not implemented.'); + } + makeRewrapManyDataKeyContext( + _filter: Uint8Array, + _encryptionKey?: Uint8Array + ): IMongoCryptContext { + throw new Error('Method not implemented.'); + } + get status(): MongoCryptStatus { + throw new Error('Method not implemented.'); + } + cryptSharedLibVersionInfo: { version: bigint; versionStr: string }; + cryptoHooksProvider: 'js' | 'native_openssl'; + } + + context = new MongoCrypt({ + errorWrapper: error => new CustomError('custom error', { cause: error }), + kmsProviders: serialize({ aws: {} }) + }); + + // @ts-expect-error accessing private property + context.mc = new MockMongoCrypt(); + }); + + it('#makeEncryptionContext() wraps errors from the bindings', function () { + expect(() => context.makeEncryptionContext('db.collection', Buffer.from([1, 2, 3]))).to.throw( + CustomError + ); + }); + + it('#makeExplicitEncryptionContext() wraps errors from the bindings', function () { + expect(() => context.makeExplicitEncryptionContext(Buffer.from([1, 2, 3]))).to.throw( + CustomError + ); + }); + + it('#makeDecryptionContext() wraps errors from the bindings', function () { + expect(() => context.makeDecryptionContext(Buffer.from([1, 2, 3]))).to.throw(CustomError); + }); + + it('#makeExplicitDecryptionContext() wraps errors from the bindings', function () { + expect(() => context.makeExplicitDecryptionContext(Buffer.from([1, 2, 3]))).to.throw( + CustomError + ); + }); + + it('#makeRewrapManyDataKeyContext() wraps errors from the bindings', function () { + expect(() => context.makeRewrapManyDataKeyContext(Buffer.from([1, 2, 3]))).to.throw( + CustomError + ); + }); + + it('.status wraps errors from the bindings', function () { + expect(() => context.status).to.throw(CustomError); + }); + }); +}); diff --git a/test/unit/index.test.ts b/test/unit/index.test.ts index f0f61b2..a46d85e 100644 --- a/test/unit/index.test.ts +++ b/test/unit/index.test.ts @@ -11,7 +11,7 @@ describe('index.ts', () => { }); it('exposes MongoCryptContextCtor', () => { - expect(bindings).to.have.property('MongoCryptContextCtor').that.is.a('function'); + expect(bindings).to.have.property('MongoCryptContext').that.is.a('function'); }); it('exposes MongoCryptKMSRequestCtor', () => { diff --git a/test/unit/release.test.ts b/test/unit/release.test.ts index f82c473..8018044 100644 --- a/test/unit/release.test.ts +++ b/test/unit/release.test.ts @@ -25,7 +25,13 @@ const REQUIRED_FILES = [ 'package/lib/crypto_callbacks.d.ts', 'package/lib/crypto_callbacks.d.ts.map', 'package/lib/crypto_callbacks.js', - 'package/lib/crypto_callbacks.js.map' + 'package/lib/crypto_callbacks.js.map', + + 'package/src/bindings.ts', + 'package/lib/bindings.d.ts', + 'package/lib/bindings.d.ts.map', + 'package/lib/bindings.js', + 'package/lib/bindings.js.map' ]; describe(`Release ${packFile}`, function () {