Skip to content

test(NODE-4663): add csfle prose test 20 #4585

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

Merged
merged 8 commits into from
Jul 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ declare global {
idmsMockServer?: true;
nodejs?: string;
predicate?: (test?: Mocha.Test) => true | string;
crypt_shared?: 'enabled' | 'disabled'
};

sessions?: {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { expect } from 'chai';
import { once } from 'events';
import { createServer, type Server } from 'net';

import { getCSFLEKMSProviders } from '../../csfle-kms-providers';
import { type MongoClient } from '../../mongodb';
import { getEncryptExtraOptions } from '../../tools/utils';

describe('20. Bypass creating mongocryptd client when shared library is loaded', function () {
let server: Server;
let hasConnection = false;
let client: MongoClient;

beforeEach(function () {
// Start a new thread (referred to as listenerThread)
// On listenerThread, create a TcpListener on 127.0.0.1 endpoint and port 27021. Start the listener and wait for establishing connections. If any connection is established, then signal about this to the main thread.
// Drivers MAY pass a different port if they expect their testing infrastructure to be using port 27021. Pass a port that should be free.
// In Node, we don't need to create a separate thread for the server.
server = createServer({});
server.listen(27021);
server.on('connection', () => (hasConnection = true));

// Create a MongoClient configured with auto encryption (referred to as client_encrypted)
// Configure the required options. Use the local KMS provider as follows:
// { "local": { "key": <base64 decoding of LOCAL_MASTERKEY> } }
// Configure with the keyVaultNamespace set to keyvault.datakeys.
// Configure the following extraOptions:
// {
// "mongocryptdURI": "mongodb://localhost:27021/?serverSelectionTimeoutMS=1000"
// }
client = this.configuration.newClient(
{},
{
autoEncryption: {
kmsProviders: { local: getCSFLEKMSProviders().local },
keyVaultNamespace: 'keyvault.datakeys',
extraOptions: {
cryptSharedLibPath: getEncryptExtraOptions().cryptSharedLibPath,
mongocryptdURI: 'mongodb://localhost:27021'
}
}
}
);
});

afterEach(async function () {
server && (await once(server.close(), 'close'));
await client?.close();
});

it(
'does not create or use a mongocryptd client when the shared library is loaded',
{
requires: {
clientSideEncryption: true,
crypt_shared: 'enabled'
}
},
async function () {
// Use client_encrypted to insert the document {"unencrypted": "test"} into db.coll.
await client.db('db').collection('coll').insertOne({ unencrypted: 'test' });

// Expect no signal from listenerThread.
expect(hasConnection).to.be.false;

// Note: this assertion is not in the spec test. However, unlike other drivers, Node's client
// does not connect when instantiated. So, we won't receive any TCP connections to the
// server if the mongocryptd client is only instantiated. This assertion captures the
// spirit of this test, causing it to fail if we do instantiate a client. I left the
// TCP server in, although it isn't necessary for Node's test, just because its nice to have
// in case Node's client behavior ever changes.
expect(client.autoEncrypter._mongocryptdClient).to.be.undefined;
}
);
});
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ const getKmsProviders = (localKey, kmipEndpoint, azureEndpoint, gcpEndpoint) =>
};

const noop = () => {};
/** @type { MongoDBMetadataUI } */
const metadata = {
requires: {
clientSideEncryption: true,
Expand Down Expand Up @@ -1146,47 +1147,43 @@ describe('Client Side Encryption Prose Tests', metadata, function () {
);
});

beforeEach('precondition: the shared library must NOT be loaded', function () {
const { cryptSharedLibPath } = getEncryptExtraOptions();
if (cryptSharedLibPath) {
this.currentTest.skipReason =
'test requires that the shared library NOT is present, but CRYPT_SHARED_LIB_PATH is set.';
this.skip();
}
// the presence of the shared library can only be reliably determine after
// libmongocrypt has been initialized, and can be detected with the
// cryptSharedLibVersionInfo getter on the autoEncrypter.
expect(!!clientEncrypted.autoEncrypter.cryptSharedLibVersionInfo).to.be.false;
});

afterEach(async function () {
await clientEncrypted?.close();
});

it('does not spawn mongocryptd', metadata, async function () {
// Use client_encrypted to insert the document {"encrypted": "test"} into db.coll.
// Expect a server selection error propagated from the internal MongoClient failing to connect to mongocryptd on port 27021.
const insertError = await clientEncrypted
.db(dataDbName)
.collection(dataCollName)
.insertOne({ encrypted: 'test' })
.catch(e => e);
it(
'does not spawn mongocryptd',
{
requires: {
...metadata.requires,
crypt_shared: 'enabled'
}
},
async function () {
// Use client_encrypted to insert the document {"encrypted": "test"} into db.coll.
// Expect a server selection error propagated from the internal MongoClient failing to connect to mongocryptd on port 27021.
const insertError = await clientEncrypted
.db(dataDbName)
.collection(dataCollName)
.insertOne({ encrypted: 'test' })
.catch(e => e);

expect(insertError)
.to.be.instanceOf(MongoRuntimeError)
.to.match(
/Unable to connect to `mongocryptd`, please make sure it is running or in your PATH for auto-spawn/
);
expect(insertError)
.to.be.instanceOf(MongoRuntimeError)
.to.match(
/Unable to connect to `mongocryptd`, please make sure it is running or in your PATH for auto-spawn/
);

const { cause } = insertError;
const { cause } = insertError;

expect(cause).to.be.instanceOf(MongoServerSelectionError);
expect(cause, 'Error must contain ECONNREFUSED').to.satisfy(
error =>
/ECONNREFUSED/.test(error.message) ||
!!error.cause?.cause?.errors?.every(e => e.code === 'ECONNREFUSED')
);
});
expect(cause).to.be.instanceOf(MongoServerSelectionError);
expect(cause, 'Error must contain ECONNREFUSED').to.satisfy(
error =>
/ECONNREFUSED/.test(error.message) ||
!!error.cause?.cause?.errors?.every(e => e.code === 'ECONNREFUSED')
);
}
);
});

describe('via bypassAutoEncryption', function () {
Expand Down Expand Up @@ -1241,19 +1238,6 @@ describe('Client Side Encryption Prose Tests', metadata, function () {
expect(insertResult).to.have.property('insertedId');
});

beforeEach('precondition: the shared library must NOT be loaded', function () {
const { cryptSharedLibPath } = getEncryptExtraOptions();
if (cryptSharedLibPath) {
this.currentTest.skipReason =
'test requires that the shared library NOT is present, but CRYPT_SHARED_LIB_PATH is set.';
this.skip();
}
// the presence of the shared library can only be reliably determine after
// libmongocrypt has been initialized, and can be detected with the
// cryptSharedLibVersionInfo getter on the autoEncrypter.
expect(!!clientEncrypted.autoEncrypter.cryptSharedLibVersionInfo).to.be.false;
});

afterEach(async function () {
await clientEncrypted?.close();
await client?.close();
Expand All @@ -1262,34 +1246,34 @@ describe('Client Side Encryption Prose Tests', metadata, function () {
// Validate that mongocryptd was not spawned. Create a MongoClient to localhost:27021
// (or whatever was passed via --port) with serverSelectionTimeoutMS=1000. Run a handshake
// command and ensure it fails with a server selection timeout.
it('does not spawn mongocryptd', metadata, async function () {
client = new MongoClient('mongodb://localhost:27021/db?serverSelectionTimeoutMS=1000');
const error = await client.connect().catch(e => e);
it(
'does not spawn mongocryptd',
{
requires: {
...metadata.requires,
crypt_shared: 'enabled'
}
},
async function () {
client = new MongoClient('mongodb://localhost:27021/db?serverSelectionTimeoutMS=1000');
const error = await client.connect().catch(e => e);

expect(error, 'Error MUST be a MongoServerSelectionError error').to.be.instanceOf(
MongoServerSelectionError
);
expect(error, 'Error MUST contain ECONNREFUSED information').to.satisfy(
error =>
/ECONNREFUSED/.test(error.message) ||
!!error.cause?.cause?.errors?.every(e => e.code === 'ECONNREFUSED')
);
});
expect(error, 'Error MUST be a MongoServerSelectionError error').to.be.instanceOf(
MongoServerSelectionError
);
expect(error, 'Error MUST contain ECONNREFUSED information').to.satisfy(
error =>
/ECONNREFUSED/.test(error.message) ||
!!error.cause?.cause?.errors?.every(e => e.code === 'ECONNREFUSED')
);
}
);
});

describe('via loading shared library', function () {
let clientEncrypted;
let client;

beforeEach(function () {
const { cryptSharedLibPath } = getEncryptExtraOptions();
if (!cryptSharedLibPath) {
this.currentTest.skipReason =
'test requires that the shared library is present, but CRYPT_SHARED_LIB_PATH is unset.';
this.skip();
}
});

// Setup
beforeEach(async function () {
const { cryptSharedLibPath } = getEncryptExtraOptions();
Expand Down Expand Up @@ -1345,11 +1329,23 @@ describe('Client Side Encryption Prose Tests', metadata, function () {
// 4. Validate that mongocryptd was not spawned. Create a MongoClient to localhost:27021 (or
// whatever was passed via `--port` with serverSelectionTimeoutMS=1000.) Run a handshake
// command and ensure it fails with a server selection timeout
it('should not spawn mongocryptd', metadata, async function () {
client = new MongoClient('mongodb://localhost:27021/db?serverSelectionTimeoutMS=1000');
const error = await client.connect().catch(e => e);
expect(error).to.be.instanceOf(MongoServerSelectionError, /'Server selection timed out'/i);
});
it(
'should not spawn mongocryptd',
{
requires: {
...metadata.requires,
crypt_shared: 'enabled'
}
},
async function () {
client = new MongoClient('mongodb://localhost:27021/db?serverSelectionTimeoutMS=1000');
const error = await client.connect().catch(e => e);
expect(error).to.be.instanceOf(
MongoServerSelectionError,
/'Server selection timed out'/i
);
}
);
});
});

Expand Down
21 changes: 5 additions & 16 deletions test/integration/node-specific/auto_encrypter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,6 @@ import {
StateMachine,
type UUID
} from '../../mongodb';
import { ClientSideEncryptionFilter } from '../../tools/runner/filters/client_encryption_filter';

export const cryptShared = (status: 'enabled' | 'disabled') => () => {
const isCryptSharedLoaded = ClientSideEncryptionFilter.cryptShared != null;

if (status === 'enabled') {
return isCryptSharedLoaded ? true : 'Test requires the shared library.';
}

return isCryptSharedLoaded ? 'Test requires that the crypt shared library NOT be present' : true;
};

describe('mongocryptd auto spawn', function () {
let client: MongoClient;
Expand Down Expand Up @@ -75,7 +64,7 @@ describe('mongocryptd auto spawn', function () {

it(
'should autoSpawn a mongocryptd on init by default',
{ requires: { clientSideEncryption: true, predicate: cryptShared('disabled') } },
{ requires: { clientSideEncryption: true, crypt_shared: 'disabled' } },
async function () {
const autoEncrypter = client.autoEncrypter;
const mongocryptdManager = autoEncrypter._mongocryptdManager;
Expand All @@ -90,7 +79,7 @@ describe('mongocryptd auto spawn', function () {

it(
'should not attempt to kick off mongocryptd on a non-network error from mongocrpytd',
{ requires: { clientSideEncryption: true, predicate: cryptShared('disabled') } },
{ requires: { clientSideEncryption: true, crypt_shared: 'disabled' } },
async function () {
let called = false;
sinon
Expand Down Expand Up @@ -124,7 +113,7 @@ describe('mongocryptd auto spawn', function () {

it(
'should respawn the mongocryptd after a MongoNetworkTimeoutError is returned when communicating with mongocryptd',
{ requires: { clientSideEncryption: true, predicate: cryptShared('disabled') } },
{ requires: { clientSideEncryption: true, crypt_shared: 'disabled' } },
async function () {
let called = false;
sinon
Expand Down Expand Up @@ -158,7 +147,7 @@ describe('mongocryptd auto spawn', function () {

it(
'should propagate error if MongoNetworkTimeoutError is experienced twice in a row',
{ requires: { clientSideEncryption: true, predicate: cryptShared('disabled') } },
{ requires: { clientSideEncryption: true, crypt_shared: 'disabled' } },
async function () {
const stub = sinon
.stub(StateMachine.prototype, 'markCommand')
Expand Down Expand Up @@ -193,7 +182,7 @@ describe('mongocryptd auto spawn', function () {

it(
'should return a useful message if mongocryptd fails to autospawn',
{ requires: { clientSideEncryption: true, predicate: cryptShared('disabled') } },
{ requires: { clientSideEncryption: true, crypt_shared: 'disabled' } },
async function () {
client = this.configuration.newClient(
{},
Expand Down
5 changes: 2 additions & 3 deletions test/integration/node-specific/crypt_shared_lib.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { dirname } from 'path';

import { BSON } from '../../mongodb';
import { getEncryptExtraOptions } from '../../tools/utils';
import { cryptShared } from './auto_encrypter.test';

const { EJSON } = BSON;

Expand All @@ -30,7 +29,7 @@ describe('crypt shared library', () => {
'should load a shared library by specifying its path',
{
requires: {
predicate: cryptShared('enabled')
crypt_shared: 'enabled'
}
},
async function () {
Expand All @@ -57,7 +56,7 @@ describe('crypt shared library', () => {
'should load a shared library by specifying a search path',
{
requires: {
predicate: cryptShared('enabled')
crypt_shared: 'enabled'
}
},
async function () {
Expand Down
2 changes: 2 additions & 0 deletions test/tools/runner/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ export class TestConfiguration {
version: string;
libmongocrypt: string | null;
};
cryptSharedVersion: MongoClient['autoEncrypter']['cryptSharedLibVersionInfo'] | null;
parameters: Record<string, any>;
singleMongosLoadBalancerUri: string;
multiMongosLoadBalancerUri: string;
Expand Down Expand Up @@ -121,6 +122,7 @@ export class TestConfiguration {
const hostAddresses = hosts.map(HostAddress.fromString);
this.version = context.version;
this.clientSideEncryption = context.clientSideEncryption;
this.cryptSharedVersion = context.cryptShared;
this.parameters = { ...context.parameters };
this.singleMongosLoadBalancerUri = context.singleMongosLoadBalancerUri;
this.multiMongosLoadBalancerUri = context.multiMongosLoadBalancerUri;
Expand Down
Loading