@@ -3,7 +3,10 @@ import { describe, it } from 'mocha';
3
3
import { GridFSBucket , MongoClient } from 'mongodb/lib/beta' ;
4
4
import { Readable } from 'stream' ;
5
5
import { pipeline } from 'stream/promises' ;
6
+ import { expect } from 'chai' ;
6
7
import { setTimeout } from 'timers/promises' ;
8
+ import { createReadStream } from 'fs' ;
9
+ import { join } from 'path' ;
7
10
8
11
async function setUpCollection ( client : MongoClient ) {
9
12
const collection = client . db ( 'foo' ) . collection < { name : string } > ( 'bar' ) ;
@@ -17,8 +20,31 @@ async function setUpCollection(client: MongoClient) {
17
20
describe ( 'explicit resource management feature integration tests' , function ( ) {
18
21
describe ( 'MongoClient' , function ( ) {
19
22
it ( 'does not crash or error when used with await-using syntax' , async function ( ) {
20
- await using client = new MongoClient ( process . env . MONGODB_URI ! ) ;
21
- await client . connect ( ) ;
23
+ await using client = new MongoClient ( process . env . MONGODB_URI ! ) ;
24
+ await client . connect ( ) ;
25
+ } )
26
+
27
+ it ( 'always cleans up the client, regardless of thrown errors' , async function ( ) {
28
+ const error = await ( async ( ) => {
29
+ await using client = new MongoClient ( process . env . MONGODB_URI ! ) ;
30
+ await client . connect ( ) ;
31
+
32
+ throw new Error ( 'error thrown' ) ;
33
+ } ) ( ) . catch ( e => e ) ;
34
+
35
+ expect ( error ) . to . match ( / e r r o r t h r o w n / ) ;
36
+ } ) ;
37
+
38
+ it ( 'works if client is explicitly closed' , async function ( ) {
39
+ const expected = await ( async ( ) => {
40
+ await using client = new MongoClient ( process . env . MONGODB_URI ! ) ;
41
+ await client . connect ( ) ;
42
+ await client . close ( ) ;
43
+
44
+ return 'not error' ;
45
+ } ) ( ) ;
46
+
47
+ expect ( expected ) . to . equal ( 'not error' ) ;
22
48
} )
23
49
} )
24
50
@@ -33,15 +59,82 @@ describe('explicit resource management feature integration tests', function () {
33
59
await cursor . next ( ) ;
34
60
} )
35
61
36
- describe ( 'cursor streams' , function ( ) {
37
- it ( 'does not crash or error when used with await-using syntax' , async function ( ) {
62
+ it ( 'always cleans up the cursor, regardless of thrown errors' , async function ( ) {
63
+ const error = await ( async ( ) => {
64
+ await using client = new MongoClient ( process . env . MONGODB_URI ! ) ;
65
+ await client . connect ( ) ;
66
+
67
+ const collection = await setUpCollection ( client ) ;
68
+
69
+ await using cursor = collection . find ( ) ;
70
+ await cursor . next ( ) ;
71
+
72
+ throw new Error ( 'error thrown' ) ;
73
+ } ) ( ) . catch ( e => e ) ;
74
+
75
+ expect ( error ) . to . match ( / e r r o r t h r o w n / ) ;
76
+ } ) ;
77
+
78
+ it ( 'works if cursor is explicitly closed' , async function ( ) {
79
+ const expected = await ( async ( ) => {
80
+ await using client = new MongoClient ( process . env . MONGODB_URI ! ) ;
81
+ await client . connect ( ) ;
82
+
83
+ const collection = await setUpCollection ( client ) ;
84
+
85
+ await using cursor = collection . find ( ) ;
86
+ await cursor . next ( ) ;
87
+
88
+ await cursor . close ( ) ;
89
+
90
+ return 'not error' ;
91
+ } ) ( ) ;
92
+
93
+ expect ( expected ) . to . equal ( 'not error' ) ;
94
+ } )
95
+
96
+ describe ( 'cursor streams' , function ( ) {
97
+ it ( 'does not crash or error when used with await-using syntax' , async function ( ) {
38
98
await using client = new MongoClient ( process . env . MONGODB_URI ! ) ;
39
99
await client . connect ( ) ;
40
100
41
101
const collection = await setUpCollection ( client ) ;
42
102
43
103
await using readable = collection . find ( ) . stream ( ) ;
44
104
} )
105
+
106
+ it ( 'always cleans up the stream, regardless of thrown errors' , async function ( ) {
107
+ const error = await ( async ( ) => {
108
+ await using client = new MongoClient ( process . env . MONGODB_URI ! ) ;
109
+ await client . connect ( ) ;
110
+
111
+ const collection = await setUpCollection ( client ) ;
112
+
113
+ await using readable = collection . find ( ) . stream ( ) ;
114
+
115
+ throw new Error ( 'error thrown' ) ;
116
+ } ) ( ) . catch ( e => e ) ;
117
+
118
+ expect ( error ) . to . match ( / e r r o r t h r o w n / ) ;
119
+ } ) ;
120
+
121
+ it ( 'works if stream is explicitly closed' , async function ( ) {
122
+ const expected = await ( async ( ) => {
123
+ await using client = new MongoClient ( process . env . MONGODB_URI ! ) ;
124
+ await client . connect ( ) ;
125
+
126
+ const collection = await setUpCollection ( client ) ;
127
+
128
+ await using readable = collection . find ( ) . stream ( ) ;
129
+
130
+ readable . destroy ( ) ;
131
+
132
+ return 'not error' ;
133
+ } ) ( ) ;
134
+
135
+ expect ( expected ) . to . equal ( 'not error' ) ;
136
+ } )
137
+
45
138
} )
46
139
} )
47
140
@@ -52,6 +145,34 @@ describe('explicit resource management feature integration tests', function () {
52
145
53
146
await using session = client . startSession ( ) ;
54
147
} )
148
+
149
+ it ( 'always cleans up the session, regardless of thrown errors' , async function ( ) {
150
+ const error = await ( async ( ) => {
151
+ await using client = new MongoClient ( process . env . MONGODB_URI ! ) ;
152
+ await client . connect ( ) ;
153
+
154
+ await using session = client . startSession ( ) ;
155
+
156
+ throw new Error ( 'error thrown' ) ;
157
+ } ) ( ) . catch ( e => e ) ;
158
+
159
+ expect ( error ) . to . match ( / e r r o r t h r o w n / ) ;
160
+ } ) ;
161
+
162
+ it ( 'works if session is explicitly closed' , async function ( ) {
163
+ const expected = await ( async ( ) => {
164
+ await using client = new MongoClient ( process . env . MONGODB_URI ! ) ;
165
+ await client . connect ( ) ;
166
+
167
+ await using session = client . startSession ( ) ;
168
+
169
+ await session . endSession ( ) ;
170
+
171
+ return 'not error' ;
172
+ } ) ( ) ;
173
+
174
+ expect ( expected ) . to . equal ( 'not error' ) ;
175
+ } )
55
176
} )
56
177
57
178
describe ( 'ChangeStreams' , function ( ) {
@@ -65,6 +186,41 @@ describe('explicit resource management feature integration tests', function () {
65
186
setTimeout ( 1000 ) . then ( ( ) => collection . insertOne ( { name : 'bailey' } ) ) ;
66
187
await cs . next ( ) ;
67
188
} )
189
+
190
+ it ( 'always cleans up the change stream, regardless of thrown errors' , async function ( ) {
191
+ const error = await ( async ( ) => {
192
+ await using client = new MongoClient ( process . env . MONGODB_URI ! ) ;
193
+ await client . connect ( ) ;
194
+
195
+ const collection = await setUpCollection ( client ) ;
196
+ await using cs = collection . watch ( ) ;
197
+
198
+ setTimeout ( 1000 ) . then ( ( ) => collection . insertOne ( { name : 'bailey' } ) ) ;
199
+ await cs . next ( ) ;
200
+
201
+ throw new Error ( 'error thrown' ) ;
202
+ } ) ( ) . catch ( e => e ) ;
203
+
204
+ expect ( error ) . to . match ( / e r r o r t h r o w n / ) ;
205
+ } ) ;
206
+
207
+ it ( 'works if change stream is explicitly closed' , async function ( ) {
208
+ const expected = await ( async ( ) => {
209
+ await using client = new MongoClient ( process . env . MONGODB_URI ! ) ;
210
+ await client . connect ( ) ;
211
+
212
+ const collection = await setUpCollection ( client ) ;
213
+ await using cs = collection . watch ( ) ;
214
+
215
+ setTimeout ( 1000 ) . then ( ( ) => collection . insertOne ( { name : 'bailey' } ) ) ;
216
+ await cs . next ( ) ;
217
+ await cs . close ( ) ;
218
+
219
+ return 'not error' ;
220
+ } ) ( ) ;
221
+
222
+ expect ( expected ) . to . equal ( 'not error' ) ;
223
+ } )
68
224
} ) ;
69
225
70
226
describe ( 'GridFSDownloadStream' , function ( ) {
@@ -78,5 +234,76 @@ describe('explicit resource management feature integration tests', function () {
78
234
79
235
await using downloadStream = bucket . openDownloadStreamByName ( 'foo.txt' ) ;
80
236
} )
237
+
238
+ it ( 'always cleans up the stream, regardless of thrown errors' , async function ( ) {
239
+ const error = await ( async ( ) => {
240
+ await using client = new MongoClient ( process . env . MONGODB_URI ! ) ;
241
+ await client . connect ( ) ;
242
+
243
+ const bucket = new GridFSBucket ( client . db ( 'foo' ) ) ;
244
+ const uploadStream = bucket . openUploadStream ( 'foo.txt' )
245
+ await pipeline ( Readable . from ( "AAAAAAA" . split ( '' ) ) , uploadStream ) ;
246
+
247
+ await using downloadStream = bucket . openDownloadStreamByName ( 'foo.txt' ) ;
248
+
249
+ throw new Error ( 'error thrown' ) ;
250
+ } ) ( ) . catch ( e => e ) ;
251
+
252
+ expect ( error ) . to . match ( / e r r o r t h r o w n / ) ;
253
+ } ) ;
254
+
255
+ it ( 'works if stream is explicitly closed' , async function ( ) {
256
+ const expected = await ( async ( ) => {
257
+ await using client = new MongoClient ( process . env . MONGODB_URI ! ) ;
258
+ await client . connect ( ) ;
259
+
260
+ const bucket = new GridFSBucket ( client . db ( 'foo' ) ) ;
261
+ const uploadStream = bucket . openUploadStream ( 'foo.txt' )
262
+ await pipeline ( Readable . from ( "AAAAAAA" . split ( '' ) ) , uploadStream ) ;
263
+
264
+ await using downloadStream = bucket . openDownloadStreamByName ( 'foo.txt' ) ;
265
+
266
+ await downloadStream . abort ( ) ;
267
+
268
+ return 'not error' ;
269
+ } ) ( ) ;
270
+
271
+ expect ( expected ) . to . equal ( 'not error' ) ;
272
+ } )
273
+
274
+ it ( 'throws premature close error if explicitly destroyed early' , async function ( ) {
275
+ // Gridfs streams inherit their _destroy() and Symbol.asyncDispose implementations from
276
+ // Nodejs' readable implementation. This behavior matches the behavior for other readable streams
277
+ // (see the below test).
278
+ const expected = await ( async ( ) => {
279
+ await using client = new MongoClient ( process . env . MONGODB_URI ! ) ;
280
+ await client . connect ( ) ;
281
+
282
+ const bucket = new GridFSBucket ( client . db ( 'foo' ) ) ;
283
+ const uploadStream = bucket . openUploadStream ( 'foo.txt' )
284
+ await pipeline ( Readable . from ( "AAAAAAA" . split ( '' ) ) , uploadStream ) ;
285
+
286
+ await using downloadStream = bucket . openDownloadStreamByName ( 'foo.txt' ) ;
287
+
288
+ downloadStream . destroy ( ) ;
289
+
290
+ return 'not error' ;
291
+ } ) ( ) . catch ( e => e ) ;
292
+
293
+ expect ( expected ) . to . match ( / P r e m a t u r e c l o s e / ) ;
294
+ } )
295
+
296
+ it ( 'throws premature close error if explicitly destroyed early (builtin stream)' , async function ( ) {
297
+ // Gridfs streams inherit their _destroy() and Symbol.asyncDispose implementations from
298
+ // Nodejs' readable implementation. This behavior matches the behavior for other readable streams (ie - ReadFileStream)
299
+ const expected = await ( async ( ) => {
300
+ await using readStream = createReadStream ( join ( __dirname , 'main.test.ts' ) ) ;
301
+ readStream . destroy ( ) ;
302
+
303
+ return 'not error' ;
304
+ } ) ( ) . catch ( e => e ) ;
305
+
306
+ expect ( expected ) . to . match ( / P r e m a t u r e c l o s e / ) ;
307
+ } )
81
308
} ) ;
82
309
} )
0 commit comments