Skip to content

Commit 15394bf

Browse files
committed
test: first section prose tests
1 parent 12a4cc2 commit 15394bf

File tree

4 files changed

+144
-7
lines changed

4 files changed

+144
-7
lines changed

src/cmap/connection_pool.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -610,13 +610,17 @@ export class ConnectionPool extends TypedEventEmitter<ConnectionPoolEvents> {
610610
}
611611

612612
private createConnection(callback: Callback<Connection>) {
613+
// Note that metadata and extendedMetadata may have changed on the client but have
614+
// been frozen here, so we pull the extendedMetadata promise always from the client
615+
// no mattter what options were set at the construction of the pool.
613616
const connectOptions: ConnectionOptions = {
614617
...this.options,
615618
id: this.connectionCounter.next().value,
616619
generation: this.generation,
617620
cancellationToken: this.cancellationToken,
618621
mongoLogger: this.mongoLogger,
619-
authProviders: this.server.topology.client.s.authProviders
622+
authProviders: this.server.topology.client.s.authProviders,
623+
extendedMetadata: this.server.topology.client.options.extendedMetadata
620624
};
621625

622626
this.pending++;

src/cmap/handshake/client_metadata.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,10 @@ export class LimitedSizeDocument {
9090
}
9191
}
9292

93-
type MakeClientMetadataOptions = Pick<MongoOptions, 'appName' | 'driverInfo'>;
93+
type MakeClientMetadataOptions = Pick<
94+
MongoOptions,
95+
'appName' | 'driverInfo' | 'additionalDriverInfo'
96+
>;
9497
/**
9598
* From the specs:
9699
* Implementors SHOULD cumulatively update fields in the following order until the document is under the size limit:
@@ -119,6 +122,17 @@ export function makeClientMetadata(options: MakeClientMetadataOptions): ClientMe
119122
version: version.length > 0 ? `${NODE_DRIVER_VERSION}|${version}` : NODE_DRIVER_VERSION
120123
};
121124

125+
// This is where we handle additional driver info added after client construction.
126+
if (options.additionalDriverInfo) {
127+
const { name: n = '', version: v = '' } = options.additionalDriverInfo;
128+
if (n.length > 0) {
129+
driverInfo.name = `${driverInfo.name}|${n}`;
130+
}
131+
if (v.length > 0) {
132+
driverInfo.version = `${driverInfo.version}|${v}`;
133+
}
134+
}
135+
122136
if (!metadataDocument.ifItFitsItSits('driver', driverInfo)) {
123137
throw new MongoInvalidArgumentError(
124138
'Unable to include driverInfo name and version, metadata cannot exceed 512 bytes'
@@ -129,6 +143,12 @@ export function makeClientMetadata(options: MakeClientMetadataOptions): ClientMe
129143
if (platform.length > 0) {
130144
runtimeInfo = `${runtimeInfo}|${platform}`;
131145
}
146+
if (options.additionalDriverInfo) {
147+
const { platform: p = '' } = options.additionalDriverInfo;
148+
if (p.length > 0) {
149+
runtimeInfo = `${runtimeInfo}|${p}`;
150+
}
151+
}
132152

133153
if (!metadataDocument.ifItFitsItSits('platform', runtimeInfo)) {
134154
throw new MongoInvalidArgumentError(

src/mongo_client.ts

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,11 @@ import { type TokenCache } from './cmap/auth/mongodb_oidc/token_cache';
1414
import { AuthMechanism } from './cmap/auth/providers';
1515
import type { LEGAL_TCP_SOCKET_OPTIONS, LEGAL_TLS_SOCKET_OPTIONS } from './cmap/connect';
1616
import type { Connection } from './cmap/connection';
17-
import { type ClientMetadata, makeClientMetadata } from './cmap/handshake/client_metadata';
17+
import {
18+
addContainerMetadata,
19+
type ClientMetadata,
20+
makeClientMetadata
21+
} from './cmap/handshake/client_metadata';
1822
import type { CompressorName } from './cmap/wire_protocol/compression';
1923
import { parseOptions, resolveSRVRecord } from './connection_string';
2024
import { MONGO_CLIENT_EVENTS } from './constants';
@@ -394,9 +398,29 @@ export class MongoClient extends TypedEventEmitter<MongoClientEvents> implements
394398
* The consolidate, parsed, transformed and merged options.
395399
*/
396400
public readonly options: Readonly<
397-
Omit<MongoOptions, 'monitorCommands' | 'ca' | 'crl' | 'key' | 'cert'>
401+
Omit<
402+
MongoOptions,
403+
| 'monitorCommands'
404+
| 'ca'
405+
| 'crl'
406+
| 'key'
407+
| 'cert'
408+
| 'additionalDriverInfo'
409+
| 'metadata'
410+
| 'extendedMetadata'
411+
>
398412
> &
399-
Pick<MongoOptions, 'monitorCommands' | 'ca' | 'crl' | 'key' | 'cert'>;
413+
Pick<
414+
MongoOptions,
415+
| 'monitorCommands'
416+
| 'ca'
417+
| 'crl'
418+
| 'key'
419+
| 'cert'
420+
| 'additionalDriverInfo'
421+
| 'metadata'
422+
| 'extendedMetadata'
423+
>;
400424

401425
constructor(url: string, options?: MongoClientOptions) {
402426
super();
@@ -460,8 +484,11 @@ export class MongoClient extends TypedEventEmitter<MongoClientEvents> implements
460484
* @param driverInfo - Information abou the application or libraary.
461485
*/
462486
appendMetadata(driverInfo: DriverInfo) {
463-
this.s.options.driverInfo = driverInfo;
464-
this.s.options.metadata = makeClientMetadata(this.s.options);
487+
this.options.additionalDriverInfo = driverInfo;
488+
this.options.metadata = makeClientMetadata(this.options);
489+
this.options.extendedMetadata = addContainerMetadata(this.options.metadata)
490+
.then(undefined, squashError)
491+
.then(result => result ?? {}); // ensure Promise<Document>
465492
}
466493

467494
/** @internal */
@@ -1049,6 +1076,8 @@ export interface MongoOptions
10491076
/** @internal */
10501077
extendedMetadata: Promise<Document>;
10511078
/** @internal */
1079+
additionalDriverInfo: DriverInfo;
1080+
/** @internal */
10521081
autoEncrypter?: AutoEncrypter;
10531082
/** @internal */
10541083
tokenCache?: TokenCache;

test/integration/mongodb-handshake/mongodb-handshake.prose.test.ts

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
LEGACY_HELLO_COMMAND,
99
type MongoClient
1010
} from '../../mongodb';
11+
import { sleep } from '../../tools/utils';
1112

1213
type EnvironmentVariables = Array<[string, string]>;
1314

@@ -200,5 +201,88 @@ describe('Client Metadata Update Prose Tests', function () {
200201

201202
afterEach(async function () {
202203
await client?.close();
204+
sinon.restore();
205+
});
206+
207+
describe('Test 1: Test that the driver updates metadata', function () {
208+
let initialClientMetadata;
209+
let updatedClientMetadata;
210+
211+
const tests = [
212+
{ testCase: 1, name: 'framework', version: '2.0', platform: 'Framework Platform' },
213+
{ testCase: 2, name: 'framework', version: '2.0' },
214+
{ testCase: 3, name: 'framework', platform: 'Framework Platform' },
215+
{ testCase: 4, name: 'framework' }
216+
];
217+
218+
for (const { testCase, name, version, platform } of tests) {
219+
context(`Case: ${testCase}`, function () {
220+
// 1. Create a `MongoClient` instance with the following:
221+
// - `maxIdleTimeMS` set to `1ms`
222+
// - Wrapping library metadata:
223+
// | Field | Value |
224+
// | -------- | ---------------- |
225+
// | name | library |
226+
// | version | 1.2 |
227+
// | platform | Library Platform |
228+
// 2. Send a `ping` command to the server and verify that the command succeeds.
229+
// 3. Save intercepted `client` document as `initialClientMetadata`.
230+
// 4. Wait 5ms for the connection to become idle.
231+
beforeEach(async function () {
232+
client = this.configuration.newClient(
233+
{},
234+
{
235+
maxIdleTimeMS: 1,
236+
monitorCommands: true,
237+
driverInfo: { name: 'library', version: '1.2', platform: 'Library Platform' }
238+
}
239+
);
240+
241+
sinon.stub(Connection.prototype, 'command').callsFake(async function (ns, cmd, options) {
242+
// @ts-expect-error: sinon will place wrappedMethod on the command method.
243+
const command = Connection.prototype.command.wrappedMethod.bind(this);
244+
245+
if (cmd.hello || cmd[LEGACY_HELLO_COMMAND]) {
246+
if (!initialClientMetadata) {
247+
initialClientMetadata = cmd.client;
248+
} else {
249+
updatedClientMetadata = cmd.client;
250+
}
251+
}
252+
return command(ns, cmd, options);
253+
});
254+
255+
await client.db('test').command({ ping: 1 });
256+
await sleep(5);
257+
});
258+
259+
it('appends the metadata', async function () {
260+
// 1. Append the `DriverInfoOptions` from the selected test case to the `MongoClient` metadata.
261+
// 2. Send a `ping` command to the server and verify:
262+
// - The command succeeds.
263+
// - The framework metadata is appended to the existing `DriverInfoOptions` in the `client.driver` fields of the `hello`
264+
// command, with values separated by a pipe `|`.
265+
client.appendMetadata({ name, version, platform });
266+
await client.db('test').command({ ping: 1 });
267+
268+
expect(updatedClientMetadata.driver.name).to.equal(
269+
name
270+
? `${initialClientMetadata.driver.name}|${name}`
271+
: initialClientMetadata.driver.name
272+
);
273+
expect(updatedClientMetadata.driver.version).to.equal(
274+
version
275+
? `${initialClientMetadata.driver.version}|${version}`
276+
: initialClientMetadata.driver.version
277+
);
278+
expect(updatedClientMetadata.platform).to.equal(
279+
platform
280+
? `${initialClientMetadata.platform}|${platform}`
281+
: initialClientMetadata.platform
282+
);
283+
expect(updatedClientMetadata.os).to.deep.equal(initialClientMetadata.os);
284+
});
285+
});
286+
}
203287
});
204288
});

0 commit comments

Comments
 (0)