@@ -2,9 +2,10 @@ import { CommonErrors } from '@mongosh/errors';
2
2
import chai , { expect } from 'chai' ;
3
3
import { Collection , Db , MongoClient } from 'mongodb' ;
4
4
import sinonChai from 'sinon-chai' ;
5
- import sinon , { StubbedInstance , stubInterface } from 'ts-sinon' ;
5
+ import sinon , { StubbedInstance , stubInterface , stubConstructor } from 'ts-sinon' ;
6
6
import CliServiceProvider , { connectMongoClient } from './cli-service-provider' ;
7
7
import { ConnectionString } from '@mongosh/service-provider-core' ;
8
+ import { EventEmitter } from 'events' ;
8
9
9
10
chai . use ( sinonChai ) ;
10
11
@@ -36,48 +37,50 @@ describe('CliServiceProvider', () => {
36
37
let collectionStub : StubbedInstance < Collection > ;
37
38
38
39
describe ( 'connectMongoClient' , ( ) => {
40
+ class FakeMongoClient extends EventEmitter {
41
+ connect ( ) { }
42
+ db ( ) { }
43
+ close ( ) { }
44
+ }
45
+
39
46
it ( 'connects once when no AutoEncryption set' , async ( ) => {
40
47
const uri = 'localhost:27017' ;
41
- const mClientType = stubInterface < typeof MongoClient > ( ) ;
42
- const mClient = stubInterface < MongoClient > ( ) ;
43
- mClientType . connect . onFirstCall ( ) . resolves ( mClient ) ;
44
- const result = await connectMongoClient ( uri , { } , mClientType ) ;
45
- const calls = mClientType . connect . getCalls ( ) ;
46
- expect ( calls . length ) . to . equal ( 1 ) ;
47
- expect ( calls [ 0 ] . args ) . to . deep . equal ( [
48
- uri , { }
49
- ] ) ;
48
+ const mClient = stubConstructor ( FakeMongoClient ) ;
49
+ const mClientType = sinon . stub ( ) . returns ( mClient ) ;
50
+ mClient . connect . onFirstCall ( ) . resolves ( mClient ) ;
51
+ const result = await connectMongoClient ( uri , { } , mClientType as any ) ;
52
+ expect ( mClientType . getCalls ( ) ) . to . have . lengthOf ( 1 ) ;
53
+ expect ( mClientType . getCalls ( ) [ 0 ] . args ) . to . deep . equal ( [ uri , { } ] ) ;
54
+ expect ( mClient . connect . getCalls ( ) ) . to . have . lengthOf ( 1 ) ;
50
55
expect ( result ) . to . equal ( mClient ) ;
51
56
} ) ;
52
57
it ( 'connects once when bypassAutoEncryption is true' , async ( ) => {
53
58
const uri = 'localhost:27017' ;
54
59
const opts = { autoEncryption : { bypassAutoEncryption : true } } ;
55
- const mClientType = stubInterface < typeof MongoClient > ( ) ;
56
- const mClient = stubInterface < MongoClient > ( ) ;
57
- mClientType . connect . onFirstCall ( ) . resolves ( mClient ) ;
58
- const result = await connectMongoClient ( uri , opts , mClientType ) ;
59
- const calls = mClientType . connect . getCalls ( ) ;
60
- expect ( calls . length ) . to . equal ( 1 ) ;
61
- expect ( calls [ 0 ] . args ) . to . deep . equal ( [
62
- uri , opts
63
- ] ) ;
60
+ const mClient = stubConstructor ( FakeMongoClient ) ;
61
+ const mClientType = sinon . stub ( ) . returns ( mClient ) ;
62
+ mClient . connect . onFirstCall ( ) . resolves ( mClient ) ;
63
+ const result = await connectMongoClient ( uri , opts , mClientType as any ) ;
64
+ expect ( mClientType . getCalls ( ) ) . to . have . lengthOf ( 1 ) ;
65
+ expect ( mClientType . getCalls ( ) [ 0 ] . args ) . to . deep . equal ( [ uri , opts ] ) ;
66
+ expect ( mClient . connect . getCalls ( ) ) . to . have . lengthOf ( 1 ) ;
64
67
expect ( result ) . to . equal ( mClient ) ;
65
68
} ) ;
66
69
it ( 'connects twice when bypassAutoEncryption is false and enterprise via modules' , async ( ) => {
67
70
const uri = 'localhost:27017' ;
68
71
const opts = { autoEncryption : { bypassAutoEncryption : false } } ;
69
- const mClientType = stubInterface < typeof MongoClient > ( ) ;
70
- const mClientFirst = stubInterface < MongoClient > ( ) ;
72
+ const mClientFirst = stubConstructor ( FakeMongoClient ) ;
73
+ const mClientSecond = stubConstructor ( FakeMongoClient ) ;
74
+ const mClientType = sinon . stub ( ) ;
71
75
const commandSpy = sinon . spy ( ) ;
72
76
mClientFirst . db . returns ( { admin : ( ) => ( { command : ( ...args ) => {
73
77
commandSpy ( ...args ) ;
74
78
return { modules : [ 'enterprise' ] } ;
75
79
} } as any ) } as any ) ;
76
- const mClientSecond = stubInterface < MongoClient > ( ) ;
77
- mClientType . connect . onFirstCall ( ) . resolves ( mClientFirst ) ;
78
- mClientType . connect . onSecondCall ( ) . resolves ( mClientSecond ) ;
79
- const result = await connectMongoClient ( uri , opts , mClientType ) ;
80
- const calls = mClientType . connect . getCalls ( ) ;
80
+ mClientType . onFirstCall ( ) . returns ( mClientFirst ) ;
81
+ mClientType . onSecondCall ( ) . returns ( mClientSecond ) ;
82
+ const result = await connectMongoClient ( uri , opts , mClientType as any ) ;
83
+ const calls = mClientType . getCalls ( ) ;
81
84
expect ( calls . length ) . to . equal ( 2 ) ;
82
85
expect ( calls [ 0 ] . args ) . to . deep . equal ( [
83
86
uri , { }
@@ -88,18 +91,18 @@ describe('CliServiceProvider', () => {
88
91
it ( 'errors when bypassAutoEncryption is falsy and not enterprise' , async ( ) => {
89
92
const uri = 'localhost:27017' ;
90
93
const opts = { autoEncryption : { } } ;
91
- const mClientType = stubInterface < typeof MongoClient > ( ) ;
92
- const mClientFirst = stubInterface < MongoClient > ( ) ;
94
+ const mClientFirst = stubConstructor ( FakeMongoClient ) ;
95
+ const mClientSecond = stubConstructor ( FakeMongoClient ) ;
96
+ const mClientType = sinon . stub ( ) ;
93
97
const commandSpy = sinon . spy ( ) ;
94
98
mClientFirst . db . returns ( { admin : ( ) => ( { command : ( ...args ) => {
95
99
commandSpy ( ...args ) ;
96
100
return { modules : [ ] } ;
97
101
} } as any ) } as any ) ;
98
- const mClientSecond = stubInterface < MongoClient > ( ) ;
99
- mClientType . connect . onFirstCall ( ) . resolves ( mClientFirst ) ;
100
- mClientType . connect . onSecondCall ( ) . resolves ( mClientSecond ) ;
102
+ mClientType . onFirstCall ( ) . returns ( mClientFirst ) ;
103
+ mClientType . onSecondCall ( ) . returns ( mClientSecond ) ;
101
104
try {
102
- await connectMongoClient ( uri , opts , mClientType ) ;
105
+ await connectMongoClient ( uri , opts , mClientType as any ) ;
103
106
} catch ( e ) {
104
107
return expect ( e . message . toLowerCase ( ) ) . to . include ( 'automatic encryption' ) ;
105
108
}
@@ -108,23 +111,47 @@ describe('CliServiceProvider', () => {
108
111
it ( 'errors when bypassAutoEncryption is falsy, missing modules' , async ( ) => {
109
112
const uri = 'localhost:27017' ;
110
113
const opts = { autoEncryption : { } } ;
111
- const mClientType = stubInterface < typeof MongoClient > ( ) ;
112
- const mClientFirst = stubInterface < MongoClient > ( ) ;
114
+ const mClientFirst = stubConstructor ( FakeMongoClient ) ;
115
+ const mClientSecond = stubConstructor ( FakeMongoClient ) ;
116
+ const mClientType = sinon . stub ( ) ;
113
117
const commandSpy = sinon . spy ( ) ;
114
118
mClientFirst . db . returns ( { admin : ( ) => ( { command : ( ...args ) => {
115
119
commandSpy ( ...args ) ;
116
120
return { } ;
117
121
} } as any ) } as any ) ;
118
- const mClientSecond = stubInterface < MongoClient > ( ) ;
119
- mClientType . connect . onFirstCall ( ) . resolves ( mClientFirst ) ;
120
- mClientType . connect . onSecondCall ( ) . resolves ( mClientSecond ) ;
122
+ mClientType . onFirstCall ( ) . returns ( mClientFirst ) ;
123
+ mClientType . onSecondCall ( ) . returns ( mClientSecond ) ;
121
124
try {
122
- await connectMongoClient ( uri , opts , mClientType ) ;
125
+ await connectMongoClient ( uri , opts , mClientType as any ) ;
123
126
} catch ( e ) {
124
127
return expect ( e . message . toLowerCase ( ) ) . to . include ( 'automatic encryption' ) ;
125
128
}
126
129
expect . fail ( 'Failed to throw expected error' ) ;
127
130
} ) ;
131
+
132
+ it ( 'fails fast if there is a fail-fast connection error' , async ( ) => {
133
+ const err = Object . assign ( new Error ( 'ENOTFOUND' ) , { name : 'MongoNetworkError' } ) ;
134
+ const uri = 'localhost:27017' ;
135
+ const mClient = new FakeMongoClient ( ) ;
136
+ const mClientType = sinon . stub ( ) . returns ( mClient ) ;
137
+ let rejectConnect ;
138
+ mClient . close = sinon . stub ( ) . callsFake ( ( ) => {
139
+ rejectConnect ( new Error ( 'discarded error' ) ) ;
140
+ } ) ;
141
+ mClient . connect = ( ) => new Promise ( ( resolve , reject ) => {
142
+ rejectConnect = reject ;
143
+ setImmediate ( ( ) => {
144
+ mClient . emit ( 'serverHeartbeatFailed' , { failure : err } ) ;
145
+ } ) ;
146
+ } ) ;
147
+ try {
148
+ await connectMongoClient ( uri , { } , mClientType as any ) ;
149
+ } catch ( e ) {
150
+ expect ( ( mClient . close as any ) . getCalls ( ) ) . to . have . lengthOf ( 1 ) ;
151
+ return expect ( e ) . to . equal ( err ) ;
152
+ }
153
+ expect . fail ( 'Failed to throw expected error' ) ;
154
+ } ) ;
128
155
} ) ;
129
156
130
157
describe ( '#constructor' , ( ) => {
0 commit comments