@@ -50,7 +50,11 @@ import {
50
50
import { MAX_REQUEST_RETRIES } from '../src' ;
51
51
52
52
import api = google . firestore . v1 ;
53
- import { MAX_PENDING_OPS , REFERENCE_NAME_MIN_ID } from '../src/recursive-delete' ;
53
+ import {
54
+ RECURSIVE_DELETE_MAX_PENDING_OPS ,
55
+ REFERENCE_NAME_MIN_ID ,
56
+ } from '../src/recursive-delete' ;
57
+ import { Deferred } from '../src/util' ;
54
58
55
59
const PROJECT_ID = 'test-project' ;
56
60
const DATABASE_ROOT = `projects/${ PROJECT_ID } /databases/(default)` ;
@@ -140,7 +144,7 @@ describe('recursiveDelete() method:', () => {
140
144
'LESS_THAN' ,
141
145
endAt ( 'root' )
142
146
) ,
143
- limit ( MAX_PENDING_OPS )
147
+ limit ( RECURSIVE_DELETE_MAX_PENDING_OPS )
144
148
) ;
145
149
return stream ( ) ;
146
150
} ,
@@ -165,7 +169,7 @@ describe('recursiveDelete() method:', () => {
165
169
'LESS_THAN' ,
166
170
endAt ( 'root/doc/nestedCol' )
167
171
) ,
168
- limit ( MAX_PENDING_OPS )
172
+ limit ( RECURSIVE_DELETE_MAX_PENDING_OPS )
169
173
) ;
170
174
return stream ( ) ;
171
175
} ,
@@ -184,7 +188,7 @@ describe('recursiveDelete() method:', () => {
184
188
'root/doc' ,
185
189
select ( '__name__' ) ,
186
190
allDescendants ( /* kindless= */ true ) ,
187
- limit ( MAX_PENDING_OPS )
191
+ limit ( RECURSIVE_DELETE_MAX_PENDING_OPS )
188
192
) ;
189
193
return stream ( ) ;
190
194
} ,
@@ -222,7 +226,7 @@ describe('recursiveDelete() method:', () => {
222
226
'LESS_THAN' ,
223
227
endAt ( 'root' )
224
228
) ,
225
- limit ( MAX_PENDING_OPS )
229
+ limit ( RECURSIVE_DELETE_MAX_PENDING_OPS )
226
230
) ;
227
231
return stream ( ) ;
228
232
}
@@ -235,8 +239,32 @@ describe('recursiveDelete() method:', () => {
235
239
} ) ;
236
240
237
241
it ( 'creates a second query with the correct startAfter' , async ( ) => {
238
- const firstStream = Array . from ( Array ( MAX_PENDING_OPS ) . keys ( ) ) . map (
239
- ( _ , i ) => result ( 'doc' + i )
242
+ // This test checks that the second query is created with the correct
243
+ // startAfter() once the RecursiveDelete instance is below the
244
+ // MIN_PENDING_OPS threshold to send the next batch. Use lower limits
245
+ // than the actual RecursiveDelete class in order to make this test run fast.
246
+ const maxPendingOps = 100 ;
247
+ const minPendingOps = 11 ;
248
+ const maxBatchSize = 10 ;
249
+ const cutoff = maxPendingOps - minPendingOps ;
250
+ let numDeletesBuffered = 0 ;
251
+
252
+ // This deferred promise is used to delay the BatchWriteResponses from
253
+ // returning in order to create the situation where the number of pending
254
+ // operations is less than `minPendingOps`.
255
+ const bufferDeferred = new Deferred < void > ( ) ;
256
+
257
+ // This deferred completes when the second query is run.
258
+ const secondQueryDeferred = new Deferred < void > ( ) ;
259
+
260
+ const nLengthArray = ( n : number ) : number [ ] => Array . from ( Array ( n ) . keys ( ) ) ;
261
+
262
+ const firstStream = nLengthArray ( maxPendingOps ) . map ( ( _ , i ) =>
263
+ result ( 'doc' + i )
264
+ ) ;
265
+
266
+ const batchWriteResponse = mergeResponses (
267
+ nLengthArray ( maxBatchSize ) . map ( ( ) => successResponse ( 1 ) )
240
268
) ;
241
269
242
270
// Use an array to store that the queryEquals() method succeeded, since
@@ -257,7 +285,7 @@ describe('recursiveDelete() method:', () => {
257
285
'LESS_THAN' ,
258
286
endAt ( 'root' )
259
287
) ,
260
- limit ( MAX_PENDING_OPS )
288
+ limit ( maxPendingOps )
261
289
) ;
262
290
called . push ( 1 ) ;
263
291
return stream ( ...firstStream ) ;
@@ -279,34 +307,52 @@ describe('recursiveDelete() method:', () => {
279
307
referenceValue :
280
308
`projects/${ PROJECT_ID } /databases/(default)/` +
281
309
'documents/collectionId/doc' +
282
- ( MAX_PENDING_OPS - 1 ) ,
310
+ ( maxPendingOps - 1 ) ,
283
311
} ) ,
284
- limit ( MAX_PENDING_OPS )
312
+ limit ( maxPendingOps )
285
313
) ;
286
314
called . push ( 2 ) ;
315
+ secondQueryDeferred . resolve ( ) ;
287
316
return stream ( ) ;
288
317
} else {
289
318
called . push ( 3 ) ;
290
319
return stream ( ) ;
291
320
}
292
321
} ,
293
322
batchWrite : ( ) => {
294
- const responses = mergeResponses (
295
- Array . from ( Array ( 500 ) . keys ( ) ) . map ( ( ) => successResponse ( 1 ) )
296
- ) ;
297
- return response ( {
298
- writeResults : responses . writeResults ,
299
- status : responses . status ,
323
+ const returnedResponse = response ( {
324
+ writeResults : batchWriteResponse . writeResults ,
325
+ status : batchWriteResponse . status ,
300
326
} ) ;
327
+ if ( numDeletesBuffered < cutoff ) {
328
+ numDeletesBuffered += batchWriteResponse . writeResults ! . length ;
329
+
330
+ // By waiting for `bufferFuture` to complete, we can guarantee that
331
+ // the writes complete after all documents are streamed. Without
332
+ // this future, the test can race and complete the writes before
333
+ // the stream is finished, which is a different scenario this test
334
+ // is not for.
335
+ return bufferDeferred . promise . then ( ( ) => returnedResponse ) ;
336
+ } else {
337
+ // Once there are `cutoff` pending deletes, completing the future
338
+ // allows enough responses to be returned such that the number of
339
+ // pending deletes should be less than `minPendingOps`. This allows
340
+ // us to test that the second query is made.
341
+ bufferDeferred . resolve ( ) ;
342
+ return secondQueryDeferred . promise . then ( ( ) => returnedResponse ) ;
343
+ }
301
344
} ,
302
345
} ;
303
346
const firestore = await createInstance ( overrides ) ;
304
347
305
- // Use a custom batch size with BulkWriter to simplify the dummy
306
- // batchWrite() response logic.
307
348
const bulkWriter = firestore . bulkWriter ( ) ;
308
- bulkWriter . _maxBatchSize = 500 ;
309
- await firestore . recursiveDelete ( firestore . collection ( 'root' ) , bulkWriter ) ;
349
+ bulkWriter . _maxBatchSize = maxBatchSize ;
350
+ await firestore . _recursiveDelete (
351
+ firestore . collection ( 'root' ) ,
352
+ maxPendingOps ,
353
+ minPendingOps ,
354
+ bulkWriter
355
+ ) ;
310
356
expect ( called ) . to . deep . equal ( [ 1 , 2 ] ) ;
311
357
} ) ;
312
358
} ) ;
0 commit comments