diff --git a/source/mongodb-handshake/handshake.md b/source/mongodb-handshake/handshake.md index bff5d730b7..aa5574718e 100644 --- a/source/mongodb-handshake/handshake.md +++ b/source/mongodb-handshake/handshake.md @@ -405,10 +405,29 @@ class DriverInfoOptions { } ``` -Note that how these options are provided to a driver is left up to the implementer. +Note that how these options are provided to a driver during `MongoClient` initialization is left up to the implementer. -If provided, these options MUST NOT replace the values used for metadata generation. The provided options MUST be -appended to their respective fields, and be delimited by a `|` character. For example, when +### Metadata updates after MongoClient initialization + +Drivers MUST provide an API that allows appending `DriverInfoOptions` to a `MongoClient` instance after initialization, +adhering to the pattern described below while following idioms of the language of the driver: + +```java +interface MongoClient { + void appendMetadata(DriverInfoOptions driverInfoOptions); + // other existing members of MongoClient +} +``` + +After client metadata update, drivers MUST apply updated metadata to newly created connections. Drivers MUST NOT apply +updated metadata to already established connections, create new connections, or close existing connections solely for +the purpose of transferring updated metadata. + +### Appending metadata + +If `DriverInfoOptions` are provided during or after `MongoClient` initialization, these options MUST NOT replace any +existing metadata values, including driver-generated metadata and previously provided options. The provided options MUST +be appended to their respective fields, and be delimited by a `|` character. For example, when [Motor](https://www.mongodb.com/docs/drivers/motor/) wraps PyMongo, the following fields are updated to include Motor's "driver info": @@ -534,6 +553,7 @@ support the `hello` command, the `helloOk: true` argument is ignored and the leg ## Changelog +- 2025-06-09: Add requirement to allow appending to client metadata after `MongoClient` initialization. - 2024-11-05: Move handshake prose tests from spec file to prose test file. - 2024-10-09: Clarify that FaaS and container metadata must both be populated when both are present. - 2024-08-16: Migrated from reStructuredText to Markdown. diff --git a/source/mongodb-handshake/tests/README.md b/source/mongodb-handshake/tests/README.md index 02e90f3805..848c6351fd 100644 --- a/source/mongodb-handshake/tests/README.md +++ b/source/mongodb-handshake/tests/README.md @@ -82,3 +82,128 @@ the following sets of environment variables: 2. Create and connect a `Connection` object that connects to the server that returns the mocked response. 3. Assert that no error is raised. + +## Client Metadata Update Prose Tests + +Drivers that do not emit events for commands issued as part of the handshake with the server will need to create a +test-only backdoor mechanism to intercept the handshake `hello` command for verification purposes. + +### Test 1: Test that the driver updates metadata + +Drivers should verify that metadata provided after `MongoClient` initialization is appended, not replaced, and is +visible in the `hello` command of new connections. + +There are multiple test cases parameterized with `DriverInfoOptions` to be appended after `MongoClient` initialization. +Before each test case, perform the setup. + +#### Setup + +1. Create a `MongoClient` instance with the following: + + - `maxIdleTimeMS` set to `1ms` + + - Wrapping library metadata: + + | Field | Value | + | -------- | ---------------- | + | name | library | + | version | 1.2 | + | platform | Library Platform | + +2. Send a `ping` command to the server and verify that the command succeeds. + +3. Save intercepted `client` document as `initialClientMetadata`. + +4. Wait 5ms for the connection to become idle. + +#### Parameterized test cases + +| Case | Name | Version | Platform | +| ---- | --------- | ------- | ------------------ | +| 1 | framework | 2.0 | Framework Platform | +| 2 | framework | 2.0 | null | +| 3 | framework | null | Framework Platform | +| 4 | framework | null | null | + +#### Running a test case + +1. Append the `DriverInfoOptions` from the selected test case to the `MongoClient` metadata. + +2. Send a `ping` command to the server and verify: + + - The command succeeds. + + - The framework metadata is appended to the existing `DriverInfoOptions` in the `client.driver` fields of the `hello` + command, with values separated by a pipe `|`. + + - `client.driver.name`: + - If test case's name is non-null: `library|` + - Otherwise, the field remains unchanged: `library` + - `client.driver.version`: + - If test case's version is non-null: `1.2|` + - Otherwise, the field remains unchanged: `1.2` + - `client.driver.platform`: + - If test case's platform is non-null: `Library Platform|` + - Otherwise, the field remains unchanged: `Library Platform` + + - All other subfields in the `client` document remain unchanged from `initialClientMetadata`. + +## Test 2: Multiple Successive Metadata Updates + +Drivers should verify that after `MongoClient` initialization, metadata can be updated multiple times, not replaced, and +is visible in the `hello` command of new connections. + +There are multiple test cases parameterized with `DriverInfoOptions` to be appended after a previous metadata update. +Before each test case, perform the setup. + +### Setup + +1. Create a `MongoClient` instance with: + + - `maxIdleTimeMS` set to `1ms` + +2. Append the following `DriverInfoOptions` to the `MongoClient` metadata: + + | Field | Value | + | -------- | ---------------- | + | name | library | + | version | 1.2 | + | platform | Library Platform | + +3. Send a `ping` command to the server and verify that the command succeeds. + +4. Save intercepted `client` document as `updatedClientMetadata`. + +5. Wait 5ms for the connection to become idle. + +#### Parameterized test cases + +| Case | Name | Version | Platform | +| ---- | --------- | ------- | ------------------ | +| 1 | framework | 2.0 | Framework Platform | +| 2 | framework | 2.0 | null | +| 3 | framework | null | Framework Platform | +| 4 | framework | null | null | + +#### Running a test case + +1. Append the `DriverInfoOptions` from the selected test case to the `MongoClient` metadata. + +2. Send a `ping` command to the server and verify: + + - The command succeeds. + + - The framework metadata is appended to the existing `DriverInfoOptions` in the `client.driver` fields of the `hello` + command, with values separated by a pipe `|`. + + - `client.driver.name`: + - If test case's name is non-null: `library|` + - Otherwise, the field remains unchanged: `library` + - `client.driver.version`: + - If test case's version is non-null: `1.2|` + - Otherwise, the field remains unchanged: `1.2` + - `client.driver.platform`: + - If test case's platform is non-null: `Library Platform|` + - Otherwise, the field remains unchanged: `Library Platform` + + - All other subfields in the `client` document remain unchanged from `updatedClientMetadata`. diff --git a/source/mongodb-handshake/tests/unified/metadata-not-propagated.json b/source/mongodb-handshake/tests/unified/metadata-not-propagated.json new file mode 100644 index 0000000000..500b579b89 --- /dev/null +++ b/source/mongodb-handshake/tests/unified/metadata-not-propagated.json @@ -0,0 +1,100 @@ +{ + "description": "client metadata is not propagated to the server", + "schemaVersion": "1.9", + "runOnRequirements": [ + { + "minServerVersion": "6.0" + } + ], + "createEntities": [ + { + "client": { + "id": "client", + "observeEvents": [ + "commandSucceededEvent", + "commandFailedEvent", + "connectionClosedEvent", + "connectionCreatedEvent" + ] + } + }, + { + "database": { + "id": "database", + "client": "client", + "databaseName": "test" + } + } + ], + "tests": [ + { + "description": "metadata append does not create new connections or close existing ones and no hello command is sent", + "operations": [ + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "ping", + "command": { + "ping": 1 + } + }, + "expectResult": { + "ok": 1 + } + }, + { + "name": "appendMetadata", + "object": "client", + "arguments": { + "driverInfoOptions": { + "name": "framework", + "version": "2.0", + "platform": "Framework Platform" + } + } + }, + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "ping", + "command": { + "ping": 1 + } + }, + "expectResult": { + "ok": 1 + } + } + ], + "expectEvents": [ + { + "client": "client", + "eventType": "cmap", + "events": [ + { + "connectionCreatedEvent": {} + } + ] + }, + { + "client": "client", + "eventType": "command", + "events": [ + { + "commandSucceededEvent": { + "commandName": "ping" + } + }, + { + "commandSucceededEvent": { + "commandName": "ping" + } + } + ] + } + ] + } + ] +} diff --git a/source/mongodb-handshake/tests/unified/metadata-not-propagated.yml b/source/mongodb-handshake/tests/unified/metadata-not-propagated.yml new file mode 100644 index 0000000000..4ff911a739 --- /dev/null +++ b/source/mongodb-handshake/tests/unified/metadata-not-propagated.yml @@ -0,0 +1,58 @@ +description: client metadata is not propagated to the server +schemaVersion: '1.9' +runOnRequirements: + - minServerVersion: '6.0' +createEntities: + - client: + id: &client client + observeEvents: + - commandSucceededEvent + - commandFailedEvent + - connectionClosedEvent + - connectionCreatedEvent + - database: + id: &database database + client: *client + databaseName: test +tests: + # Test that appending metadata after `MongoClient` initialization does not close existing + # connections, create new ones, and that no new `hello` command is sent. + - description: metadata append does not create new connections or close existing ones and no hello command is sent + operations: + - name: runCommand + object: *database + arguments: + commandName: ping + command: + ping: 1 + expectResult: + ok: 1 + - name: appendMetadata + object: *client + arguments: + driverInfoOptions: + name: framework + version: '2.0' + platform: Framework Platform + - name: runCommand + object: *database + arguments: + commandName: ping + command: + ping: 1 + expectResult: + ok: 1 + expectEvents: + - client: *client + eventType: cmap + events: + # Expect only one connection to be created for the first 'ping' command. + - connectionCreatedEvent: { } + - client: *client + eventType: command + events: + - commandSucceededEvent: + commandName: ping + - commandSucceededEvent: + commandName: ping + diff --git a/source/unified-test-format/unified-test-format.md b/source/unified-test-format/unified-test-format.md index 9c8625895f..c49fff02e5 100644 --- a/source/unified-test-format/unified-test-format.md +++ b/source/unified-test-format/unified-test-format.md @@ -1491,6 +1491,20 @@ driver behavior when an operation is attempted on a closed client or one of its +#### appendMetadata + +Appends client metadata to the client. + +The following arguments are supported: + +- `driverInfoOptions`: Required object with the following fields: + - `name`: Required string. The name of the wrapping library or framework. + - `version`: Optional string. The version of the wrapping library or framework. + - `platform`: Optional string. The platform of the wrapping library or framework. + +See the [handshake spec](../mongodb-handshake/handshake.md#metadata-updates-after-mongoclient-initialization) for more +details. + #### createChangeStream Creates a cluster-level change stream and ensures that the server-side cursor has been created. @@ -3583,6 +3597,8 @@ other specs *and* collating spec changes developed in parallel or during the sam ## Changelog +- 2025-06-09: Add `appendMetadata` operation. + - 2025-06-04: Deprecate the `serverless` runOnRequirement - 2025-04-25: Drop `_enxcol` collections.