@mongosh/service-provider-node-driver 2.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1302 @@
1
+ import { CommonErrors } from '@mongosh/errors';
2
+ import chai, { expect } from 'chai';
3
+ import type { ClientSession, SearchIndexDescription } from 'mongodb';
4
+ import { Collection, Db, MongoClient } from 'mongodb';
5
+ import sinonChai from 'sinon-chai';
6
+ import type { StubbedInstance } from 'ts-sinon';
7
+ import sinon, { stubInterface } from 'ts-sinon';
8
+ import type { DevtoolsConnectOptions } from './node-driver-service-provider';
9
+ import { NodeDriverServiceProvider } from './node-driver-service-provider';
10
+ import ConnectionString from 'mongodb-connection-string-url';
11
+ import { EventEmitter } from 'events';
12
+ import type {
13
+ ClientEncryption,
14
+ ClientEncryptionDataKeyProvider,
15
+ Document,
16
+ IndexDescription,
17
+ } from '@mongosh/service-provider-core';
18
+
19
+ chai.use(sinonChai);
20
+
21
+ export const dummyOptions: DevtoolsConnectOptions = Object.freeze({
22
+ productName: 'Test Product',
23
+ productDocsLink: 'https://example.com/',
24
+ });
25
+
26
+ const DEFAULT_BASE_OPTS = { serializeFunctions: true, promoteLongs: false };
27
+
28
+ /**
29
+ * Create a client stub from the provided collection stub.
30
+ *
31
+ * @note: We basically only care about the method under test
32
+ * which is always mocked on a new collection stub each
33
+ * test run. We we can use the boilerplate creation of the
34
+ * db and client here.
35
+ *
36
+ * @param {Stub} collectionStub - The collection stub.
37
+ *
38
+ * @returns {Stub} The client stub to pass to the transport.
39
+ */
40
+ const createClientStub = (
41
+ collectionStub: StubbedInstance<Collection>
42
+ ): StubbedInstance<MongoClient> => {
43
+ const clientStub = stubInterface<MongoClient>();
44
+ const dbStub = stubInterface<Db>();
45
+ dbStub.collection.returns(collectionStub);
46
+ clientStub.db.returns(dbStub);
47
+ return clientStub;
48
+ };
49
+
50
+ describe('NodeDriverServiceProvider', function () {
51
+ let serviceProvider: NodeDriverServiceProvider;
52
+ let collectionStub: StubbedInstance<Collection>;
53
+ let bus: EventEmitter;
54
+
55
+ beforeEach(function () {
56
+ bus = new EventEmitter();
57
+ collectionStub = stubInterface<Collection>();
58
+ serviceProvider = new NodeDriverServiceProvider(
59
+ {} as any,
60
+ bus,
61
+ dummyOptions
62
+ );
63
+ });
64
+
65
+ describe('#constructor', function () {
66
+ const mongoClient: any = sinon.spy();
67
+ beforeEach(function () {
68
+ serviceProvider = new NodeDriverServiceProvider(
69
+ mongoClient,
70
+ bus,
71
+ dummyOptions
72
+ );
73
+ });
74
+
75
+ it('sets the mongo client on the instance', function () {
76
+ expect((serviceProvider as any).mongoClient).to.equal(mongoClient);
77
+ });
78
+ });
79
+
80
+ describe('#aggregate', function () {
81
+ const pipeline = [{ $match: { name: 'Aphex Twin' } }];
82
+ const aggResult = [{ name: 'Aphex Twin' }];
83
+
84
+ beforeEach(function () {
85
+ collectionStub = stubInterface<Collection>();
86
+ collectionStub.aggregate.returns({
87
+ toArray: () => Promise.resolve(aggResult),
88
+ } as any);
89
+ serviceProvider = new NodeDriverServiceProvider(
90
+ createClientStub(collectionStub),
91
+ bus,
92
+ dummyOptions
93
+ );
94
+ });
95
+
96
+ it('executes the command against the database', async function () {
97
+ const cursor = serviceProvider.aggregate('music', 'bands', pipeline);
98
+ const result = await cursor.toArray();
99
+ expect(result).to.deep.equal(aggResult);
100
+ expect(collectionStub.aggregate).to.have.been.calledWith(pipeline);
101
+ });
102
+ });
103
+
104
+ describe('#bulkWrite', function () {
105
+ const requests = [{ insertOne: { name: 'Aphex Twin' } } as any];
106
+ const commandResult = { result: { nInserted: 1, ok: 1 } };
107
+
108
+ beforeEach(function () {
109
+ collectionStub = stubInterface<Collection>();
110
+ collectionStub.bulkWrite.resolves(commandResult as any);
111
+ serviceProvider = new NodeDriverServiceProvider(
112
+ createClientStub(collectionStub),
113
+ bus,
114
+ dummyOptions
115
+ );
116
+ });
117
+
118
+ it('executes the command against the database', async function () {
119
+ const result = await serviceProvider.bulkWrite(
120
+ 'music',
121
+ 'bands',
122
+ requests
123
+ );
124
+ expect(result).to.deep.equal(commandResult);
125
+ expect(collectionStub.bulkWrite).to.have.been.calledWith(requests);
126
+ });
127
+ });
128
+
129
+ describe('#countDocuments', function () {
130
+ const countResult = 10;
131
+
132
+ beforeEach(function () {
133
+ collectionStub = stubInterface<Collection>();
134
+ collectionStub.countDocuments.resolves(countResult);
135
+ serviceProvider = new NodeDriverServiceProvider(
136
+ createClientStub(collectionStub),
137
+ bus,
138
+ dummyOptions
139
+ );
140
+ });
141
+
142
+ it('executes the command against the database', async function () {
143
+ const result = await serviceProvider.countDocuments('music', 'bands');
144
+ expect(result).to.deep.equal(countResult);
145
+ expect(collectionStub.countDocuments).to.have.been.calledWith({});
146
+ });
147
+ });
148
+
149
+ describe('#deleteMany', function () {
150
+ const commandResult = { result: { n: 1, ok: 1 } };
151
+
152
+ beforeEach(function () {
153
+ collectionStub = stubInterface<Collection>();
154
+ collectionStub.deleteMany.resolves(commandResult as any);
155
+ serviceProvider = new NodeDriverServiceProvider(
156
+ createClientStub(collectionStub),
157
+ bus,
158
+ dummyOptions
159
+ );
160
+ });
161
+
162
+ it('executes the command against the database', async function () {
163
+ const result = await serviceProvider.deleteMany('music', 'bands', {});
164
+ expect(result).to.deep.equal(commandResult);
165
+ expect(collectionStub.deleteMany).to.have.been.calledWith({});
166
+ });
167
+ });
168
+
169
+ describe('#deleteOne', function () {
170
+ const commandResult = { result: { n: 1, ok: 1 } };
171
+
172
+ beforeEach(function () {
173
+ collectionStub = stubInterface<Collection>();
174
+ collectionStub.deleteOne.resolves(commandResult as any);
175
+ serviceProvider = new NodeDriverServiceProvider(
176
+ createClientStub(collectionStub),
177
+ bus,
178
+ dummyOptions
179
+ );
180
+ });
181
+
182
+ it('executes the command against the database', async function () {
183
+ const result = await serviceProvider.deleteOne('music', 'bands', {});
184
+ expect(result).to.deep.equal(commandResult);
185
+ expect(collectionStub.deleteOne).to.have.been.calledWith({});
186
+ });
187
+ });
188
+
189
+ describe('#distinct', function () {
190
+ const distinctResult = ['Aphex Twin'];
191
+
192
+ beforeEach(function () {
193
+ collectionStub = stubInterface<Collection>();
194
+ collectionStub.distinct.resolves(distinctResult);
195
+ serviceProvider = new NodeDriverServiceProvider(
196
+ createClientStub(collectionStub),
197
+ bus,
198
+ dummyOptions
199
+ );
200
+ });
201
+
202
+ it('executes the command against the database', async function () {
203
+ const result = await serviceProvider.distinct('music', 'bands', 'name');
204
+ expect(result).to.deep.equal(distinctResult);
205
+ expect(collectionStub.distinct).to.have.been.calledWith(
206
+ 'name',
207
+ {},
208
+ DEFAULT_BASE_OPTS
209
+ );
210
+ });
211
+ });
212
+
213
+ describe('#estimatedDocumentCount', function () {
214
+ const countResult = 10;
215
+
216
+ beforeEach(function () {
217
+ collectionStub = stubInterface<Collection>();
218
+ collectionStub.estimatedDocumentCount.resolves(countResult);
219
+ serviceProvider = new NodeDriverServiceProvider(
220
+ createClientStub(collectionStub),
221
+ bus,
222
+ dummyOptions
223
+ );
224
+ });
225
+
226
+ it('executes the command against the database', async function () {
227
+ const result = await serviceProvider.estimatedDocumentCount(
228
+ 'music',
229
+ 'bands'
230
+ );
231
+ expect(result).to.deep.equal(countResult);
232
+ expect(collectionStub.estimatedDocumentCount).to.have.been.calledWith(
233
+ DEFAULT_BASE_OPTS
234
+ );
235
+ });
236
+ });
237
+
238
+ describe('#find', function () {
239
+ const filter = { name: 'Aphex Twin' };
240
+ const findResult = [{ name: 'Aphex Twin' }];
241
+
242
+ beforeEach(function () {
243
+ collectionStub = stubInterface<Collection>();
244
+ collectionStub.find.returns({
245
+ toArray: () => Promise.resolve(findResult),
246
+ } as any);
247
+ serviceProvider = new NodeDriverServiceProvider(
248
+ createClientStub(collectionStub),
249
+ bus,
250
+ dummyOptions
251
+ );
252
+ });
253
+
254
+ it('executes the command against the database', async function () {
255
+ const cursor = serviceProvider.find('music', 'bands', filter);
256
+ const result = await cursor.toArray();
257
+ expect(result).to.deep.equal(findResult);
258
+ expect(collectionStub.find).to.have.been.calledWith(filter);
259
+ });
260
+ });
261
+
262
+ describe('#find with options', function () {
263
+ const filter = { name: 'Aphex Twin' };
264
+ const findResult = [{ name: 'Aphex Twin' }];
265
+ const options = {
266
+ allowPartialResults: true,
267
+ noCursorTimeout: true,
268
+ tailable: true,
269
+ };
270
+
271
+ beforeEach(function () {
272
+ collectionStub = stubInterface<Collection>();
273
+ collectionStub.find.returns({
274
+ toArray: () => Promise.resolve(findResult),
275
+ } as any);
276
+ serviceProvider = new NodeDriverServiceProvider(
277
+ createClientStub(collectionStub),
278
+ bus,
279
+ dummyOptions
280
+ );
281
+ });
282
+
283
+ it('executes the command against the database', async function () {
284
+ const cursor = serviceProvider.find('music', 'bands', filter, options);
285
+ const result = await cursor.toArray();
286
+ expect(result).to.deep.equal(findResult);
287
+ expect(collectionStub.find).to.have.been.calledWith(filter, {
288
+ ...DEFAULT_BASE_OPTS,
289
+ ...options,
290
+ partial: true,
291
+ timeout: true,
292
+ });
293
+ });
294
+ });
295
+
296
+ describe('#findOneAndDelete', function () {
297
+ const commandResult = { result: { n: 1, ok: 1 } };
298
+
299
+ beforeEach(function () {
300
+ collectionStub = stubInterface<Collection>();
301
+ collectionStub.findOneAndDelete.resolves(commandResult as any);
302
+ serviceProvider = new NodeDriverServiceProvider(
303
+ createClientStub(collectionStub),
304
+ bus,
305
+ dummyOptions
306
+ );
307
+ });
308
+
309
+ it('executes the command against the database', async function () {
310
+ const result = await serviceProvider.findOneAndDelete(
311
+ 'music',
312
+ 'bands',
313
+ {}
314
+ );
315
+ expect(result).to.deep.equal(commandResult);
316
+ expect(collectionStub.findOneAndDelete).to.have.been.calledWith({});
317
+ });
318
+ });
319
+
320
+ describe('#findOneAndReplace', function () {
321
+ const commandResult = { result: { n: 1, ok: 1 } };
322
+ const filter = { name: 'Aphex Twin' };
323
+ const replacement = { name: 'Richard James' };
324
+
325
+ beforeEach(function () {
326
+ collectionStub = stubInterface<Collection>();
327
+ collectionStub.findOneAndReplace.resolves(commandResult as any);
328
+ serviceProvider = new NodeDriverServiceProvider(
329
+ createClientStub(collectionStub),
330
+ bus,
331
+ dummyOptions
332
+ );
333
+ });
334
+
335
+ it('executes the command against the database', async function () {
336
+ const result = await serviceProvider.findOneAndReplace(
337
+ 'music',
338
+ 'bands',
339
+ filter,
340
+ replacement
341
+ );
342
+ expect(result).to.deep.equal(commandResult);
343
+ expect(collectionStub.findOneAndReplace).to.have.been.calledWith(
344
+ filter,
345
+ replacement
346
+ );
347
+ });
348
+ });
349
+
350
+ describe('#findOneAndUpdate', function () {
351
+ const commandResult = { result: { n: 1, ok: 1 } };
352
+ const filter = { name: 'Aphex Twin' };
353
+ const update = { $set: { name: 'Richard James' } };
354
+
355
+ beforeEach(function () {
356
+ collectionStub = stubInterface<Collection>();
357
+ collectionStub.findOneAndUpdate.resolves(commandResult as any);
358
+ serviceProvider = new NodeDriverServiceProvider(
359
+ createClientStub(collectionStub),
360
+ bus,
361
+ dummyOptions
362
+ );
363
+ });
364
+
365
+ it('executes the command against the database', async function () {
366
+ const result = await serviceProvider.findOneAndUpdate(
367
+ 'music',
368
+ 'bands',
369
+ filter,
370
+ update
371
+ );
372
+ expect(result).to.deep.equal(commandResult);
373
+ expect(collectionStub.findOneAndUpdate).to.have.been.calledWith(
374
+ filter,
375
+ update
376
+ );
377
+ });
378
+ });
379
+
380
+ describe('#insertMany', function () {
381
+ const doc = { name: 'Aphex Twin' };
382
+ const commandResult = { result: { n: 1, ok: 1 } };
383
+
384
+ beforeEach(function () {
385
+ collectionStub = stubInterface<Collection>();
386
+ collectionStub.insertMany.resolves(commandResult as any);
387
+ serviceProvider = new NodeDriverServiceProvider(
388
+ createClientStub(collectionStub),
389
+ bus,
390
+ dummyOptions
391
+ );
392
+ });
393
+
394
+ it('executes the command against the database', async function () {
395
+ const result = await serviceProvider.insertMany('music', 'bands', [doc]);
396
+ expect(result).to.deep.equal(commandResult);
397
+ expect(collectionStub.insertMany).to.have.been.calledWith([doc]);
398
+ });
399
+ });
400
+
401
+ describe('#insertOne', function () {
402
+ const doc = { name: 'Aphex Twin' };
403
+ const commandResult = { result: { n: 1, ok: 1 } };
404
+
405
+ beforeEach(function () {
406
+ collectionStub = stubInterface<Collection>();
407
+ collectionStub.insertOne.resolves(commandResult as any);
408
+ serviceProvider = new NodeDriverServiceProvider(
409
+ createClientStub(collectionStub),
410
+ bus,
411
+ dummyOptions
412
+ );
413
+ });
414
+
415
+ it('executes the command against the database', async function () {
416
+ const result = await serviceProvider.insertOne('music', 'bands', doc);
417
+ expect(result).to.deep.equal(commandResult);
418
+ expect(collectionStub.insertOne).to.have.been.calledWith(doc);
419
+ });
420
+ });
421
+
422
+ describe('#replaceOne', function () {
423
+ const filter = { name: 'Aphex Twin' };
424
+ const replacement = { name: 'Richard James' };
425
+ const commandResult = { result: { n: 1, ok: 1 } };
426
+
427
+ beforeEach(function () {
428
+ collectionStub = stubInterface<Collection>();
429
+ collectionStub.replaceOne.resolves(commandResult);
430
+ serviceProvider = new NodeDriverServiceProvider(
431
+ createClientStub(collectionStub),
432
+ bus,
433
+ dummyOptions
434
+ );
435
+ });
436
+
437
+ it('executes the command against the database', async function () {
438
+ const result = await serviceProvider.replaceOne(
439
+ 'music',
440
+ 'bands',
441
+ filter,
442
+ replacement
443
+ );
444
+ expect(result).to.deep.equal(commandResult);
445
+ expect(collectionStub.replaceOne).to.have.been.calledWith(
446
+ filter,
447
+ replacement
448
+ );
449
+ });
450
+ });
451
+
452
+ describe('#runCommand', function () {
453
+ let clientStub: any;
454
+ let dbStub: any;
455
+ const commandResult = { ismaster: true };
456
+
457
+ beforeEach(function () {
458
+ dbStub = stubInterface<Db>();
459
+ clientStub = stubInterface<MongoClient>();
460
+ dbStub.command.resolves(commandResult);
461
+ clientStub.db.returns(dbStub);
462
+ serviceProvider = new NodeDriverServiceProvider(
463
+ clientStub,
464
+ bus,
465
+ dummyOptions
466
+ );
467
+ });
468
+
469
+ it('executes the command against the database', async function () {
470
+ const result = await serviceProvider.runCommand('admin', { ismaster: 1 });
471
+ expect(result).to.deep.equal(commandResult);
472
+ expect(dbStub.command).to.have.been.calledWith({ ismaster: 1 });
473
+ });
474
+ });
475
+
476
+ describe('#runCommandWithCheck', function () {
477
+ let clientStub: any;
478
+ let dbStub: any;
479
+ const commandResult = { ok: 0 };
480
+
481
+ beforeEach(function () {
482
+ dbStub = stubInterface<Db>();
483
+ clientStub = stubInterface<MongoClient>();
484
+ dbStub.command.resolves(commandResult);
485
+ clientStub.db.returns(dbStub);
486
+ serviceProvider = new NodeDriverServiceProvider(
487
+ clientStub,
488
+ bus,
489
+ dummyOptions
490
+ );
491
+ });
492
+
493
+ it('executes the command against the database and throws if ok: 0', async function () {
494
+ try {
495
+ await serviceProvider.runCommandWithCheck('admin', { ismaster: 1 });
496
+ } catch (e: any) {
497
+ expect(e.message).to.include(JSON.stringify({ ismaster: 1 }));
498
+ expect(e.name).to.equal('MongoshCommandFailed');
499
+ expect(e.code).to.equal(CommonErrors.CommandFailed);
500
+ return;
501
+ }
502
+ expect.fail('Error not thrown');
503
+ });
504
+ });
505
+
506
+ describe('#runCursorCommand', function () {
507
+ let clientStub: any;
508
+ let dbStub: any;
509
+ const commandResult = 'a-cursor';
510
+
511
+ beforeEach(function () {
512
+ dbStub = stubInterface<Db>();
513
+ clientStub = stubInterface<MongoClient>();
514
+ dbStub.runCursorCommand.returns(commandResult);
515
+ clientStub.db.returns(dbStub);
516
+ serviceProvider = new NodeDriverServiceProvider(
517
+ clientStub,
518
+ bus,
519
+ dummyOptions
520
+ );
521
+ });
522
+
523
+ it('executes the command against the database', function () {
524
+ const result = serviceProvider.runCursorCommand('admin', {
525
+ checkMetadataConsistency: 1,
526
+ });
527
+ expect(result).to.deep.equal(commandResult);
528
+ expect(dbStub.runCursorCommand).to.have.been.calledWith({
529
+ checkMetadataConsistency: 1,
530
+ });
531
+ });
532
+ });
533
+
534
+ describe('#updateOne', function () {
535
+ const filter = { name: 'Aphex Twin' };
536
+ const update = { $set: { name: 'Richard James' } };
537
+ const commandResult = { result: { n: 1, ok: 1 } };
538
+
539
+ beforeEach(function () {
540
+ collectionStub = stubInterface<Collection>();
541
+ collectionStub.updateOne.resolves(commandResult as any);
542
+ serviceProvider = new NodeDriverServiceProvider(
543
+ createClientStub(collectionStub),
544
+ bus,
545
+ dummyOptions
546
+ );
547
+ });
548
+
549
+ it('executes the command against the database', async function () {
550
+ const result = await serviceProvider.updateOne(
551
+ 'music',
552
+ 'bands',
553
+ filter,
554
+ update
555
+ );
556
+ expect(result).to.deep.equal(commandResult);
557
+ expect(collectionStub.updateOne).to.have.been.calledWith(filter, update);
558
+ });
559
+ });
560
+
561
+ describe('#updateMany', function () {
562
+ const filter = { name: 'Aphex Twin' };
563
+ const update = { $set: { name: 'Richard James' } };
564
+ const commandResult = { result: { n: 1, ok: 1 } };
565
+
566
+ beforeEach(function () {
567
+ collectionStub = stubInterface<Collection>();
568
+ collectionStub.updateMany.resolves(commandResult as any);
569
+ serviceProvider = new NodeDriverServiceProvider(
570
+ createClientStub(collectionStub),
571
+ bus,
572
+ dummyOptions
573
+ );
574
+ });
575
+
576
+ it('executes the command against the database', async function () {
577
+ const result = await serviceProvider.updateMany(
578
+ 'music',
579
+ 'bands',
580
+ filter,
581
+ update
582
+ );
583
+ expect(result).to.deep.equal(commandResult);
584
+ expect(collectionStub.updateMany).to.have.been.calledWith(filter, update);
585
+ });
586
+ });
587
+
588
+ describe('#dropDatabase', function () {
589
+ let clientStub: StubbedInstance<MongoClient>;
590
+ let dbStub: StubbedInstance<Db>;
591
+
592
+ beforeEach(function () {
593
+ dbStub = stubInterface<Db>();
594
+ clientStub = stubInterface<MongoClient>();
595
+ clientStub.db.returns(dbStub);
596
+
597
+ serviceProvider = new NodeDriverServiceProvider(
598
+ clientStub,
599
+ bus,
600
+ dummyOptions
601
+ );
602
+ });
603
+
604
+ it('returns ok: 1 if dropped', async function () {
605
+ dbStub.dropDatabase.resolves(true);
606
+ const result = await serviceProvider.dropDatabase('db1');
607
+ expect(result).to.contain({ ok: 1 });
608
+ });
609
+
610
+ it('returns ok: 0 if not dropped', async function () {
611
+ dbStub.dropDatabase.resolves(false);
612
+ const result = await serviceProvider.dropDatabase('db1');
613
+ expect(result).to.contain({ ok: 0 });
614
+ });
615
+
616
+ it('returns dropped: "db name" if dropped', async function () {
617
+ dbStub.dropDatabase.resolves(true);
618
+ const result = await serviceProvider.dropDatabase('db1');
619
+ expect(result).to.contain({ dropped: 'db1' });
620
+ });
621
+
622
+ context('when write concern is omitted', function () {
623
+ it('runs against the database with default write concern', async function () {
624
+ dbStub.dropDatabase.resolves(true);
625
+ await serviceProvider.dropDatabase('db1');
626
+ expect(clientStub.db).to.have.been.calledOnceWith('db1');
627
+ });
628
+ });
629
+
630
+ context('with write concern', function () {
631
+ it('runs against the database passing write concern', async function () {
632
+ const opts = { serializeFunctions: true, w: 1 };
633
+ dbStub.dropDatabase.resolves(true);
634
+ await serviceProvider.dropDatabase('db1', opts);
635
+ expect(clientStub.db).to.have.been.calledOnceWith('db1');
636
+ });
637
+ });
638
+ });
639
+
640
+ describe('#createIndexes', function () {
641
+ let indexSpecs: IndexDescription[];
642
+ let nativeMethodResult: string[];
643
+
644
+ beforeEach(function () {
645
+ indexSpecs = [{ key: { key: 1 } }];
646
+
647
+ nativeMethodResult = ['key_1'];
648
+
649
+ collectionStub = stubInterface<Collection>();
650
+ collectionStub.createIndexes.resolves(nativeMethodResult);
651
+ serviceProvider = new NodeDriverServiceProvider(
652
+ createClientStub(collectionStub),
653
+ bus,
654
+ dummyOptions
655
+ );
656
+ });
657
+
658
+ it('executes the command against the database', async function () {
659
+ const result = await serviceProvider.createIndexes(
660
+ 'db1',
661
+ 'coll1',
662
+ indexSpecs
663
+ );
664
+ expect(result).to.deep.equal(nativeMethodResult);
665
+ expect(collectionStub.createIndexes).to.have.been.calledWith(indexSpecs);
666
+ });
667
+ });
668
+
669
+ describe('#getIndexes', function () {
670
+ let indexSpecs: Document[];
671
+ let nativeMethodResult: any;
672
+
673
+ beforeEach(function () {
674
+ indexSpecs = [{ key: 'x' }];
675
+
676
+ nativeMethodResult = {
677
+ toArray: (): Promise<any[]> => Promise.resolve(indexSpecs),
678
+ };
679
+
680
+ collectionStub = stubInterface<Collection>();
681
+ collectionStub.listIndexes.returns(nativeMethodResult);
682
+
683
+ serviceProvider = new NodeDriverServiceProvider(
684
+ createClientStub(collectionStub),
685
+ bus,
686
+ dummyOptions
687
+ );
688
+ });
689
+
690
+ it('executes the command against the database', async function () {
691
+ const result = await serviceProvider.getIndexes('db1', 'coll1');
692
+
693
+ expect(result).to.deep.equal(indexSpecs);
694
+ expect(collectionStub.listIndexes).to.have.been.calledWith(
695
+ DEFAULT_BASE_OPTS
696
+ );
697
+ });
698
+ });
699
+
700
+ describe('#listCollections', function () {
701
+ let dbStub: StubbedInstance<Db>;
702
+ let clientStub: StubbedInstance<MongoClient>;
703
+
704
+ beforeEach(function () {
705
+ dbStub = stubInterface<Db>();
706
+ clientStub = stubInterface<MongoClient>();
707
+ dbStub.listCollections.returns({
708
+ toArray: () => {
709
+ return Promise.resolve([
710
+ {
711
+ name: 'coll1',
712
+ },
713
+ ]);
714
+ },
715
+ } as any);
716
+ clientStub.db.returns(dbStub);
717
+ serviceProvider = new NodeDriverServiceProvider(
718
+ clientStub,
719
+ bus,
720
+ dummyOptions
721
+ );
722
+ });
723
+
724
+ it('executes the command', async function () {
725
+ const result = await serviceProvider.listCollections('db1');
726
+ expect(result).to.deep.equal([
727
+ {
728
+ name: 'coll1',
729
+ },
730
+ ]);
731
+
732
+ expect(dbStub.listCollections).to.have.been.calledWith(
733
+ {},
734
+ DEFAULT_BASE_OPTS
735
+ );
736
+ expect(clientStub.db).to.have.been.calledWith('db1');
737
+ });
738
+ });
739
+
740
+ describe('#renameCollection', function () {
741
+ let dbStub: StubbedInstance<Db>;
742
+ let clientStub: StubbedInstance<MongoClient>;
743
+
744
+ beforeEach(function () {
745
+ dbStub = stubInterface<Db>();
746
+ clientStub = stubInterface<MongoClient>();
747
+ dbStub.renameCollection.resolves({ ok: 1 } as any);
748
+ clientStub.db.returns(dbStub);
749
+ serviceProvider = new NodeDriverServiceProvider(
750
+ clientStub,
751
+ bus,
752
+ dummyOptions
753
+ );
754
+ });
755
+
756
+ it('executes the command against the database', async function () {
757
+ const result = await serviceProvider.renameCollection(
758
+ 'db1',
759
+ 'coll1',
760
+ 'newName',
761
+ { dropTarget: true, session: {} as any }
762
+ );
763
+ expect(result).to.deep.equal({ ok: 1 });
764
+ expect(dbStub.renameCollection).to.have.been.calledOnceWith(
765
+ 'coll1',
766
+ 'newName',
767
+ {
768
+ ...DEFAULT_BASE_OPTS,
769
+ dropTarget: true,
770
+ session: {},
771
+ }
772
+ );
773
+ expect(clientStub.db).to.have.been.calledOnceWith('db1');
774
+ });
775
+ });
776
+
777
+ describe('#createCollection', function () {
778
+ let dbStub: StubbedInstance<Db>;
779
+ let clientStub: StubbedInstance<MongoClient>;
780
+
781
+ beforeEach(function () {
782
+ dbStub = stubInterface<Db>();
783
+ clientStub = stubInterface<MongoClient>();
784
+ dbStub.createCollection.resolves({} as any);
785
+ clientStub.db.returns(dbStub);
786
+ serviceProvider = new NodeDriverServiceProvider(
787
+ clientStub,
788
+ bus,
789
+ dummyOptions
790
+ );
791
+ });
792
+
793
+ it('executes the command', async function () {
794
+ const result = await serviceProvider.createCollection(
795
+ 'db1',
796
+ 'newcoll',
797
+ {}
798
+ );
799
+ expect(result).to.deep.equal({ ok: 1 });
800
+ expect(dbStub.createCollection).to.have.been.calledOnceWith(
801
+ 'newcoll',
802
+ DEFAULT_BASE_OPTS
803
+ );
804
+ expect(clientStub.db).to.have.been.calledOnceWith('db1');
805
+ });
806
+ });
807
+
808
+ describe('#createEncryptedCollection', function () {
809
+ let dbStub: StubbedInstance<Db>;
810
+ let clientStub: StubbedInstance<MongoClient>;
811
+ let libmongoc: StubbedInstance<ClientEncryption>;
812
+ const createCollOptions = {
813
+ provider: 'local' as ClientEncryptionDataKeyProvider,
814
+ createCollectionOptions: {
815
+ encryptedFields: {
816
+ fields: [
817
+ {
818
+ path: 'ssn',
819
+ bsonType: 'string',
820
+ },
821
+ ],
822
+ },
823
+ },
824
+ };
825
+
826
+ beforeEach(function () {
827
+ dbStub = stubInterface<Db>();
828
+ clientStub = stubInterface<MongoClient>();
829
+ clientStub.db.returns(dbStub);
830
+ serviceProvider = new NodeDriverServiceProvider(
831
+ clientStub,
832
+ bus,
833
+ dummyOptions
834
+ );
835
+ libmongoc = stubInterface<ClientEncryption>();
836
+ });
837
+
838
+ it('calls calls libmongocrypt.createEncryptedCollection', async function () {
839
+ await serviceProvider.createEncryptedCollection(
840
+ 'db1',
841
+ 'coll1',
842
+ createCollOptions,
843
+ libmongoc
844
+ );
845
+ expect(libmongoc.createEncryptedCollection).calledOnceWithExactly(
846
+ dbStub,
847
+ 'coll1',
848
+ createCollOptions
849
+ );
850
+ });
851
+
852
+ it('returns whatever libmongocrypt.createEncryptedCollection returns', async function () {
853
+ const resolvedValue = {
854
+ collection: { name: 'secretCol' },
855
+ encryptedFields: [],
856
+ } as any;
857
+ libmongoc.createEncryptedCollection.resolves(resolvedValue);
858
+ const returnValue = await serviceProvider.createEncryptedCollection(
859
+ 'db1',
860
+ 'coll1',
861
+ createCollOptions,
862
+ libmongoc
863
+ );
864
+ expect(returnValue).to.deep.equal(resolvedValue);
865
+ });
866
+ });
867
+
868
+ describe('sessions', function () {
869
+ let clientStub: StubbedInstance<MongoClient>;
870
+ let serviceProvider: NodeDriverServiceProvider;
871
+ let db: StubbedInstance<Db>;
872
+ let driverSession: ClientSession;
873
+ beforeEach(function () {
874
+ clientStub = stubInterface<MongoClient>();
875
+ serviceProvider = new NodeDriverServiceProvider(
876
+ clientStub,
877
+ bus,
878
+ dummyOptions
879
+ );
880
+ driverSession = { dSession: 1 } as any;
881
+ clientStub.startSession.returns(driverSession);
882
+ db = stubInterface<Db>();
883
+ clientStub.db.returns(db);
884
+ });
885
+ describe('startSession', function () {
886
+ it('calls startSession without args', function () {
887
+ const opts = {};
888
+ const result = serviceProvider.startSession(opts);
889
+ expect(clientStub.startSession).to.have.been.calledOnceWith(opts);
890
+ expect(result).to.equal(driverSession);
891
+ });
892
+ });
893
+ });
894
+
895
+ describe('#watch', function () {
896
+ let options: Document;
897
+ let expectedResult: Document;
898
+ let watchMock: sinon.SinonStub;
899
+ let watchMock2: sinon.SinonStub;
900
+ let watchMock3: sinon.SinonStub;
901
+ let pipeline: Document[];
902
+
903
+ beforeEach(function () {
904
+ pipeline = [{ $match: { operationType: 'insertOne' } }];
905
+ options = { batchSize: 1 };
906
+ expectedResult = { ChangeStream: 1 };
907
+
908
+ watchMock = sinon
909
+ .mock()
910
+ .once()
911
+ .withArgs(pipeline, options)
912
+ .returns(expectedResult);
913
+ watchMock2 = sinon
914
+ .mock()
915
+ .once()
916
+ .withArgs(pipeline, options)
917
+ .returns(expectedResult);
918
+ watchMock3 = sinon
919
+ .mock()
920
+ .once()
921
+ .withArgs(pipeline, options)
922
+ .returns(expectedResult);
923
+
924
+ const collectionStub = sinon.createStubInstance(Collection, {
925
+ watch: watchMock3 as any,
926
+ });
927
+ const dbStub = sinon.createStubInstance(Db, {
928
+ watch: watchMock2 as any,
929
+ collection: sinon.stub().returns(collectionStub) as any,
930
+ });
931
+ const clientStub = sinon.createStubInstance(MongoClient, {
932
+ db: sinon.stub().returns(dbStub) as any,
933
+ watch: watchMock as any,
934
+ }) as any;
935
+
936
+ serviceProvider = new NodeDriverServiceProvider(
937
+ clientStub,
938
+ bus,
939
+ dummyOptions
940
+ );
941
+ });
942
+
943
+ it('executes watch on MongoClient', function () {
944
+ const result = serviceProvider.watch(pipeline, options);
945
+ expect(result).to.deep.equal(expectedResult);
946
+ expect(watchMock).to.have.been.calledOnce;
947
+ });
948
+ it('executes watch on Db', function () {
949
+ const result = serviceProvider.watch(pipeline, options, {}, 'dbname');
950
+ expect(result).to.deep.equal(expectedResult);
951
+ expect(watchMock2).to.have.been.calledOnce;
952
+ });
953
+ it('executes watch on collection', function () {
954
+ const result = serviceProvider.watch(
955
+ pipeline,
956
+ options,
957
+ {},
958
+ 'dbname',
959
+ 'collname'
960
+ );
961
+ expect(result).to.deep.equal(expectedResult);
962
+ expect(watchMock3).to.have.been.calledOnce;
963
+ });
964
+ });
965
+
966
+ describe('#getConnectionInfo', function () {
967
+ let clientStub: any;
968
+ let dbStub: StubbedInstance<Db>;
969
+
970
+ beforeEach(function () {
971
+ dbStub = stubInterface<Db>();
972
+ clientStub = stubInterface<MongoClient>();
973
+ // eslint-disable-next-line @typescript-eslint/require-await
974
+ dbStub.command.callsFake(async () => {
975
+ return { ok: 1 };
976
+ });
977
+ dbStub.collection.callsFake((): any => {
978
+ // eslint-disable-next-line @typescript-eslint/require-await
979
+ return { countDocuments: async () => 0 };
980
+ });
981
+ clientStub.db.returns(dbStub);
982
+ clientStub.topology = {
983
+ s: {
984
+ servers: new Map().set('localhost', {
985
+ description: { address: 'localhost' },
986
+ }),
987
+ },
988
+ };
989
+ serviceProvider = new NodeDriverServiceProvider(
990
+ clientStub,
991
+ bus,
992
+ dummyOptions,
993
+ new ConnectionString('mongodb://localhost/')
994
+ );
995
+ serviceProvider.getNewConnection = () => Promise.resolve(serviceProvider);
996
+ });
997
+
998
+ it('returns some connection info data', async function () {
999
+ const info = await serviceProvider.getConnectionInfo();
1000
+ expect(info.extraInfo?.is_atlas).to.equal(false);
1001
+ expect(info.extraInfo?.is_local_atlas).to.equal(false);
1002
+ expect(info.extraInfo?.is_localhost).to.equal(true);
1003
+ expect(info.extraInfo?.fcv).to.equal(undefined);
1004
+ expect(dbStub.command).to.have.callCount(3);
1005
+ expect(
1006
+ dbStub.collection,
1007
+ 'calls countDocument on collection to check local atlas cli support'
1008
+ ).to.have.callCount(1);
1009
+ });
1010
+
1011
+ context('when connected to an Atlas deployment', function () {
1012
+ it('correctly gathers info on the fake deployment', async function () {
1013
+ const serviceProvider = new NodeDriverServiceProvider(
1014
+ clientStub,
1015
+ bus,
1016
+ dummyOptions,
1017
+ new ConnectionString(
1018
+ 'mongodb+srv://test-data-sets-a011bb.mongodb.net/admin'
1019
+ )
1020
+ );
1021
+
1022
+ const info = await serviceProvider.getConnectionInfo();
1023
+ expect(info.extraInfo?.is_genuine).to.be.true;
1024
+ expect(info.extraInfo?.is_atlas).to.be.true;
1025
+ });
1026
+ });
1027
+
1028
+ context('when connected to a DocumentDB deployment', function () {
1029
+ it('correctly gathers info on the fake deployment', async function () {
1030
+ const serviceProvider = new NodeDriverServiceProvider(
1031
+ clientStub,
1032
+ bus,
1033
+ dummyOptions,
1034
+ new ConnectionString(
1035
+ 'mongodb://elastic-docdb-123456789.eu-central-1.docdb-elastic.amazonaws.com:27017'
1036
+ )
1037
+ );
1038
+
1039
+ const info = await serviceProvider.getConnectionInfo();
1040
+ expect(info.extraInfo?.is_genuine).to.be.false;
1041
+ expect(info.extraInfo?.non_genuine_server_name).to.equal('documentdb');
1042
+ });
1043
+ });
1044
+
1045
+ context('when connected to a CosmosDB deployment', function () {
1046
+ it('correctly gathers info on the fake deployment', async function () {
1047
+ const serviceProvider = new NodeDriverServiceProvider(
1048
+ clientStub,
1049
+ bus,
1050
+ dummyOptions,
1051
+ new ConnectionString(
1052
+ 'mongodb+srv://compass-vcore.mongocluster.cosmos.azure.com'
1053
+ )
1054
+ );
1055
+
1056
+ const info = await serviceProvider.getConnectionInfo();
1057
+ expect(info.extraInfo?.is_genuine).to.be.false;
1058
+ expect(info.extraInfo?.non_genuine_server_name).to.equal('cosmosdb');
1059
+ });
1060
+ });
1061
+ });
1062
+
1063
+ // Security-relevant tests -- description covered in e2e-oidc tests.
1064
+ describe('processDriverOptions', function () {
1065
+ it('shares user configuration options from an existing NodeDriverServiceProvider instance', function () {
1066
+ const cloneableOidcOptions = {
1067
+ redirectURI: 'http://localhost',
1068
+ openBrowser: { command: '/usr/bin/browser' },
1069
+ notifyDeviceFlow: () => {},
1070
+ allowedFlows: ['device-auth'],
1071
+ };
1072
+ const productInfo = {
1073
+ productDocsLink: 'https://example.com',
1074
+ productName: 'test',
1075
+ };
1076
+ expect(
1077
+ NodeDriverServiceProvider.prototype.processDriverOptions.call(
1078
+ {
1079
+ currentClientOptions: {
1080
+ oidc: {
1081
+ ...cloneableOidcOptions,
1082
+ throwOnIncompatibleSerializedState: true,
1083
+ },
1084
+ ...productInfo,
1085
+ readConcern: 'local',
1086
+ } as DevtoolsConnectOptions,
1087
+ uri: new ConnectionString('mongodb://localhost/'),
1088
+ } as any,
1089
+ new ConnectionString('mongodb://localhost/'),
1090
+ {}
1091
+ )
1092
+ ).to.deep.equal({
1093
+ oidc: { ...cloneableOidcOptions },
1094
+ ...productInfo,
1095
+ });
1096
+ });
1097
+
1098
+ it('shares OIDC state if the auth options match', function () {
1099
+ const parentState: any = {};
1100
+
1101
+ expect(
1102
+ NodeDriverServiceProvider.prototype.processDriverOptions.call(
1103
+ {
1104
+ uri: new ConnectionString('mongodb://localhost/'),
1105
+ currentClientOptions: {
1106
+ auth: { username: 'meow' },
1107
+ parentState,
1108
+ },
1109
+ },
1110
+ new ConnectionString('mongodb://localhost'),
1111
+ { auth: { username: 'meow' } }
1112
+ ).parentState
1113
+ ).to.equal(parentState);
1114
+ });
1115
+
1116
+ it('does not share OIDC state if the auth options mismatch', function () {
1117
+ const parentState: any = {};
1118
+
1119
+ expect(
1120
+ NodeDriverServiceProvider.prototype.processDriverOptions.call(
1121
+ {
1122
+ uri: new ConnectionString('mongodb://localhost/'),
1123
+ currentClientOptions: {
1124
+ auth: { username: 'meow' },
1125
+ parentState,
1126
+ },
1127
+ },
1128
+ new ConnectionString('mongodb://localhost'),
1129
+ { auth: { username: 'moo' } }
1130
+ ).parentState
1131
+ ).to.equal(undefined);
1132
+ });
1133
+
1134
+ it('does not share OIDC state if the endpoints mismatch', function () {
1135
+ const parentState: any = {};
1136
+
1137
+ expect(
1138
+ NodeDriverServiceProvider.prototype.processDriverOptions.call(
1139
+ {
1140
+ uri: new ConnectionString('mongodb://localhost/'),
1141
+ currentClientOptions: {
1142
+ auth: { username: 'meow' },
1143
+ parentState,
1144
+ },
1145
+ },
1146
+ new ConnectionString('mongodb://localghost'),
1147
+ { auth: { username: 'meow' } }
1148
+ ).parentState
1149
+ ).to.equal(undefined);
1150
+ });
1151
+ });
1152
+
1153
+ describe('#getSearchIndexes', function () {
1154
+ let descriptions: Document[];
1155
+ let nativeMethodResult: any;
1156
+ let getSearchIndexesOptions: Document;
1157
+
1158
+ beforeEach(function () {
1159
+ descriptions = [{ name: 'foo' }, { name: 'bar' }];
1160
+
1161
+ nativeMethodResult = {
1162
+ toArray: () => {
1163
+ return Promise.resolve(descriptions);
1164
+ },
1165
+ };
1166
+
1167
+ getSearchIndexesOptions = { allowDiskUse: true };
1168
+
1169
+ collectionStub = stubInterface<Collection>();
1170
+ collectionStub.listSearchIndexes.returns(nativeMethodResult);
1171
+ serviceProvider = new NodeDriverServiceProvider(
1172
+ createClientStub(collectionStub),
1173
+ bus,
1174
+ dummyOptions
1175
+ );
1176
+ });
1177
+
1178
+ context('without indexName', function () {
1179
+ it('calls listSearchIndexes and toArray on the resulting cursor', async function () {
1180
+ const result = await serviceProvider.getSearchIndexes(
1181
+ 'db1',
1182
+ 'coll1',
1183
+ undefined,
1184
+ getSearchIndexesOptions
1185
+ );
1186
+ expect(result).to.deep.equal(descriptions);
1187
+ expect(collectionStub.listSearchIndexes).to.have.been.calledWith(
1188
+ getSearchIndexesOptions
1189
+ );
1190
+ });
1191
+ });
1192
+
1193
+ context('with indexName', function () {
1194
+ it('calls listSearchIndexes and toArray on the resulting cursor', async function () {
1195
+ const result = await serviceProvider.getSearchIndexes(
1196
+ 'db1',
1197
+ 'coll1',
1198
+ 'my-index',
1199
+ getSearchIndexesOptions
1200
+ );
1201
+ expect(result).to.deep.equal(descriptions);
1202
+ expect(collectionStub.listSearchIndexes).to.have.been.calledWith(
1203
+ 'my-index',
1204
+ getSearchIndexesOptions
1205
+ );
1206
+ });
1207
+ });
1208
+ });
1209
+
1210
+ describe('#createSearchIndexes', function () {
1211
+ let descriptions: SearchIndexDescription[];
1212
+ let nativeMethodResult: string[];
1213
+
1214
+ beforeEach(function () {
1215
+ descriptions = [
1216
+ { name: 'foo', definition: {} },
1217
+ { name: 'bar', definition: {} },
1218
+ ];
1219
+
1220
+ nativeMethodResult = ['index_1'];
1221
+
1222
+ collectionStub = stubInterface<Collection>();
1223
+ collectionStub.createSearchIndexes.resolves(nativeMethodResult);
1224
+ serviceProvider = new NodeDriverServiceProvider(
1225
+ createClientStub(collectionStub),
1226
+ bus,
1227
+ dummyOptions
1228
+ );
1229
+ });
1230
+
1231
+ it('executes the command against the database', async function () {
1232
+ const result = await serviceProvider.createSearchIndexes(
1233
+ 'db1',
1234
+ 'coll1',
1235
+ descriptions
1236
+ );
1237
+ expect(result).to.deep.equal(nativeMethodResult);
1238
+ expect(collectionStub.createSearchIndexes).to.have.been.calledWith(
1239
+ descriptions
1240
+ );
1241
+ });
1242
+ });
1243
+
1244
+ describe('#dropSearchIndex', function () {
1245
+ let indexName: string;
1246
+
1247
+ beforeEach(function () {
1248
+ indexName = 'foo';
1249
+
1250
+ collectionStub = stubInterface<Collection>();
1251
+ collectionStub.dropSearchIndex.resolves();
1252
+ serviceProvider = new NodeDriverServiceProvider(
1253
+ createClientStub(collectionStub),
1254
+ bus,
1255
+ dummyOptions
1256
+ );
1257
+ });
1258
+
1259
+ it('executes the command against the database', async function () {
1260
+ const result = await serviceProvider.dropSearchIndex(
1261
+ 'db1',
1262
+ 'coll1',
1263
+ indexName
1264
+ );
1265
+ expect(result).to.deep.equal(undefined);
1266
+ expect(collectionStub.dropSearchIndex).to.have.been.calledWith(indexName);
1267
+ });
1268
+ });
1269
+
1270
+ describe('#updateSearchIndex', function () {
1271
+ let indexName: string;
1272
+ let description: SearchIndexDescription;
1273
+
1274
+ beforeEach(function () {
1275
+ indexName = 'foo';
1276
+ description = { definition: { x: 1, y: 2 } };
1277
+
1278
+ collectionStub = stubInterface<Collection>();
1279
+
1280
+ collectionStub.updateSearchIndex.resolves();
1281
+ serviceProvider = new NodeDriverServiceProvider(
1282
+ createClientStub(collectionStub),
1283
+ bus,
1284
+ dummyOptions
1285
+ );
1286
+ });
1287
+
1288
+ it('executes the command against the database', async function () {
1289
+ const result = await serviceProvider.updateSearchIndex(
1290
+ 'db1',
1291
+ 'coll1',
1292
+ indexName,
1293
+ description
1294
+ );
1295
+ expect(result).to.deep.equal(undefined);
1296
+ expect(collectionStub.updateSearchIndex).to.have.been.calledWith(
1297
+ indexName,
1298
+ description
1299
+ );
1300
+ });
1301
+ });
1302
+ });