@@ -80,13 +80,13 @@ describe('TransactionExecutor', () => {
80
80
81
81
it ( 'should stop retrying when time expires' , done => {
82
82
const executor = new TransactionExecutor ( ) ;
83
- let workInvocationCounter = 0 ;
83
+ const usedTransactions = [ ] ;
84
84
const realWork = transactionWork ( [ SERVICE_UNAVAILABLE , SESSION_EXPIRED , TRANSIENT_ERROR_1 , TRANSIENT_ERROR_2 ] , 42 ) ;
85
85
86
86
const result = executor . execute ( transactionCreator ( ) , tx => {
87
87
expect ( tx ) . toBeDefined ( ) ;
88
- workInvocationCounter ++ ;
89
- if ( workInvocationCounter === 3 ) {
88
+ usedTransactions . push ( tx ) ;
89
+ if ( usedTransactions . length === 3 ) {
90
90
const currentTime = Date . now ( ) ;
91
91
clock = lolex . install ( ) ;
92
92
clock . setSystemTime ( currentTime + 30001 ) ; // move `Date.now()` call forward by 30 seconds
@@ -95,7 +95,8 @@ describe('TransactionExecutor', () => {
95
95
} ) ;
96
96
97
97
result . catch ( error => {
98
- expect ( workInvocationCounter ) . toEqual ( 3 ) ;
98
+ expect ( usedTransactions . length ) . toEqual ( 3 ) ;
99
+ expectAllTransactionsToBeClosed ( usedTransactions ) ;
99
100
expect ( error . code ) . toEqual ( TRANSIENT_ERROR_1 ) ;
100
101
done ( ) ;
101
102
} ) ;
@@ -152,6 +153,14 @@ describe('TransactionExecutor', () => {
152
153
) ;
153
154
} ) ;
154
155
156
+ it ( 'should retry when transaction work throws and rollback fails' , done => {
157
+ testRetryWhenTransactionWorkThrowsAndRollbackFails (
158
+ [ SERVICE_UNAVAILABLE , TRANSIENT_ERROR_2 , SESSION_EXPIRED , SESSION_EXPIRED ] ,
159
+ [ SESSION_EXPIRED , TRANSIENT_ERROR_1 ] ,
160
+ done
161
+ ) ;
162
+ } ) ;
163
+
155
164
it ( 'should cancel in-flight timeouts when closed' , done => {
156
165
const executor = new TransactionExecutor ( ) ;
157
166
// do not execute setTimeout callbacks
@@ -190,16 +199,16 @@ describe('TransactionExecutor', () => {
190
199
function testRetryWhenTransactionCreatorFails ( errorCodes , done ) {
191
200
const executor = new TransactionExecutor ( ) ;
192
201
const transactionCreator = throwingTransactionCreator ( errorCodes , new FakeTransaction ( ) ) ;
193
- let workInvocationCounter = 0 ;
202
+ const usedTransactions = [ ] ;
194
203
195
204
const result = executor . execute ( transactionCreator , tx => {
196
205
expect ( tx ) . toBeDefined ( ) ;
197
- workInvocationCounter ++ ;
206
+ usedTransactions . push ( tx ) ;
198
207
return Promise . resolve ( 42 ) ;
199
208
} ) ;
200
209
201
210
result . then ( value => {
202
- expect ( workInvocationCounter ) . toEqual ( 1 ) ;
211
+ expect ( usedTransactions . length ) . toEqual ( 1 ) ;
203
212
expect ( value ) . toEqual ( 42 ) ;
204
213
verifyRetryDelays ( fakeSetTimeout , errorCodes . length ) ;
205
214
done ( ) ;
@@ -208,18 +217,19 @@ describe('TransactionExecutor', () => {
208
217
209
218
function testRetryWhenTransactionWorkReturnsRejectedPromise ( errorCodes , done ) {
210
219
const executor = new TransactionExecutor ( ) ;
211
- let workInvocationCounter = 0 ;
220
+ const usedTransactions = [ ] ;
212
221
const realWork = transactionWork ( errorCodes , 42 ) ;
213
222
214
223
const result = executor . execute ( transactionCreator ( ) , tx => {
215
224
expect ( tx ) . toBeDefined ( ) ;
216
- workInvocationCounter ++ ;
225
+ usedTransactions . push ( tx ) ;
217
226
return realWork ( ) ;
218
227
} ) ;
219
228
220
229
result . then ( value => {
221
230
// work should have failed 'failures.length' times and succeeded 1 time
222
- expect ( workInvocationCounter ) . toEqual ( errorCodes . length + 1 ) ;
231
+ expect ( usedTransactions . length ) . toEqual ( errorCodes . length + 1 ) ;
232
+ expectAllTransactionsToBeClosed ( usedTransactions ) ;
223
233
expect ( value ) . toEqual ( 42 ) ;
224
234
verifyRetryDelays ( fakeSetTimeout , errorCodes . length ) ;
225
235
done ( ) ;
@@ -228,18 +238,19 @@ describe('TransactionExecutor', () => {
228
238
229
239
function testRetryWhenTransactionCommitReturnsRejectedPromise ( errorCodes , done ) {
230
240
const executor = new TransactionExecutor ( ) ;
231
- let workInvocationCounter = 0 ;
241
+ const usedTransactions = [ ] ;
232
242
const realWork = ( ) => Promise . resolve ( 4242 ) ;
233
243
234
244
const result = executor . execute ( transactionCreator ( errorCodes ) , tx => {
235
245
expect ( tx ) . toBeDefined ( ) ;
236
- workInvocationCounter ++ ;
246
+ usedTransactions . push ( tx ) ;
237
247
return realWork ( ) ;
238
248
} ) ;
239
249
240
250
result . then ( value => {
241
251
// work should have failed 'failures.length' times and succeeded 1 time
242
- expect ( workInvocationCounter ) . toEqual ( errorCodes . length + 1 ) ;
252
+ expect ( usedTransactions . length ) . toEqual ( errorCodes . length + 1 ) ;
253
+ expectAllTransactionsToBeClosed ( usedTransactions ) ;
243
254
expect ( value ) . toEqual ( 4242 ) ;
244
255
verifyRetryDelays ( fakeSetTimeout , errorCodes . length ) ;
245
256
done ( ) ;
@@ -248,37 +259,60 @@ describe('TransactionExecutor', () => {
248
259
249
260
function testRetryWhenTransactionWorkThrows ( errorCodes , done ) {
250
261
const executor = new TransactionExecutor ( ) ;
251
- let workInvocationCounter = 0 ;
262
+ const usedTransactions = [ ] ;
252
263
const realWork = throwingTransactionWork ( errorCodes , 42 ) ;
253
264
254
265
const result = executor . execute ( transactionCreator ( ) , tx => {
255
266
expect ( tx ) . toBeDefined ( ) ;
256
- workInvocationCounter ++ ;
267
+ usedTransactions . push ( tx ) ;
257
268
return realWork ( ) ;
258
269
} ) ;
259
270
260
271
result . then ( value => {
261
272
// work should have failed 'failures.length' times and succeeded 1 time
262
- expect ( workInvocationCounter ) . toEqual ( errorCodes . length + 1 ) ;
273
+ expect ( usedTransactions . length ) . toEqual ( errorCodes . length + 1 ) ;
274
+ expectAllTransactionsToBeClosed ( usedTransactions ) ;
263
275
expect ( value ) . toEqual ( 42 ) ;
264
276
verifyRetryDelays ( fakeSetTimeout , errorCodes . length ) ;
265
277
done ( ) ;
266
278
} ) ;
267
279
}
268
280
281
+ function testRetryWhenTransactionWorkThrowsAndRollbackFails ( txWorkErrorCodes , rollbackErrorCodes , done ) {
282
+ const executor = new TransactionExecutor ( ) ;
283
+ const usedTransactions = [ ] ;
284
+ const realWork = throwingTransactionWork ( txWorkErrorCodes , 424242 ) ;
285
+
286
+ const result = executor . execute ( transactionCreator ( [ ] , rollbackErrorCodes ) , tx => {
287
+ expect ( tx ) . toBeDefined ( ) ;
288
+ usedTransactions . push ( tx ) ;
289
+ return realWork ( ) ;
290
+ } ) ;
291
+
292
+ result . then ( value => {
293
+ // work should have failed 'failures.length' times and succeeded 1 time
294
+ expect ( usedTransactions . length ) . toEqual ( txWorkErrorCodes . length + 1 ) ;
295
+ expectAllTransactionsToBeClosed ( usedTransactions ) ;
296
+ expect ( value ) . toEqual ( 424242 ) ;
297
+ verifyRetryDelays ( fakeSetTimeout , txWorkErrorCodes . length ) ;
298
+ done ( ) ;
299
+ } ) ;
300
+ }
301
+
269
302
function testNoRetryOnUnknownError ( errorCodes , expectedWorkInvocationCount , done ) {
270
303
const executor = new TransactionExecutor ( ) ;
271
- let workInvocationCounter = 0 ;
304
+ const usedTransactions = [ ] ;
272
305
const realWork = transactionWork ( errorCodes , 42 ) ;
273
306
274
307
const result = executor . execute ( transactionCreator ( ) , tx => {
275
308
expect ( tx ) . toBeDefined ( ) ;
276
- workInvocationCounter ++ ;
309
+ usedTransactions . push ( tx ) ;
277
310
return realWork ( ) ;
278
311
} ) ;
279
312
280
313
result . catch ( error => {
281
- expect ( workInvocationCounter ) . toEqual ( expectedWorkInvocationCount ) ;
314
+ expect ( usedTransactions . length ) . toEqual ( expectedWorkInvocationCount ) ;
315
+ expectAllTransactionsToBeClosed ( usedTransactions ) ;
282
316
if ( errorCodes . length === 1 ) {
283
317
expect ( error . code ) . toEqual ( errorCodes [ 0 ] ) ;
284
318
} else {
@@ -290,9 +324,10 @@ describe('TransactionExecutor', () => {
290
324
291
325
} ) ;
292
326
293
- function transactionCreator ( commitErrorCodes ) {
294
- const remainingErrorCodes = ( commitErrorCodes || [ ] ) . slice ( ) . reverse ( ) ;
295
- return ( ) => new FakeTransaction ( remainingErrorCodes . pop ( ) ) ;
327
+ function transactionCreator ( commitErrorCodes , rollbackErrorCodes ) {
328
+ const remainingCommitErrorCodes = ( commitErrorCodes || [ ] ) . slice ( ) . reverse ( ) ;
329
+ const remainingRollbackErrorCodes = ( rollbackErrorCodes || [ ] ) . slice ( ) . reverse ( ) ;
330
+ return ( ) => new FakeTransaction ( remainingCommitErrorCodes . pop ( ) , remainingRollbackErrorCodes . pop ( ) ) ;
296
331
}
297
332
298
333
function throwingTransactionCreator ( errorCodes , result ) {
@@ -348,20 +383,35 @@ function verifyRetryDelays(fakeSetTimeout, expectedInvocationCount) {
348
383
} ) ;
349
384
}
350
385
386
+ function expectAllTransactionsToBeClosed ( transactions ) {
387
+ transactions . forEach ( tx => expect ( tx . isOpen ( ) ) . toBeFalsy ( ) ) ;
388
+ }
389
+
351
390
class FakeTransaction {
352
391
353
- constructor ( commitErrorCode ) {
392
+ constructor ( commitErrorCode , rollbackErrorCode ) {
354
393
this . _commitErrorCode = commitErrorCode ;
394
+ this . _rollbackErrorCode = rollbackErrorCode ;
395
+ this . _open = true ;
355
396
}
356
397
357
398
isOpen ( ) {
358
- return true ;
399
+ return this . _open ;
359
400
}
360
401
361
402
commit ( ) {
403
+ this . _open = false ;
362
404
if ( this . _commitErrorCode ) {
363
405
return Promise . reject ( error ( this . _commitErrorCode ) ) ;
364
406
}
365
407
return Promise . resolve ( ) ;
366
408
}
409
+
410
+ rollback ( ) {
411
+ this . _open = false ;
412
+ if ( this . _rollbackErrorCode ) {
413
+ return Promise . reject ( error ( this . _rollbackErrorCode ) ) ;
414
+ }
415
+ return Promise . resolve ( ) ;
416
+ }
367
417
}
0 commit comments