@@ -8,6 +8,7 @@ import 'package:zulip/api/model/events.dart';
8
8
import 'package:zulip/api/model/model.dart' ;
9
9
import 'package:zulip/api/route/events.dart' ;
10
10
import 'package:zulip/api/route/messages.dart' ;
11
+ import 'package:zulip/log.dart' ;
11
12
import 'package:zulip/model/store.dart' ;
12
13
import 'package:zulip/notifications/receive.dart' ;
13
14
@@ -391,6 +392,20 @@ void main() {
391
392
check (store.userSettings! .twentyFourHourTime).isTrue ();
392
393
}));
393
394
395
+ String ? lastReportedError;
396
+ String ? takeLastReportedError () {
397
+ final result = lastReportedError;
398
+ lastReportedError = null ;
399
+ return result;
400
+ }
401
+
402
+ Future <void > logAndReportErrorToUserBriefly (String ? message, {
403
+ String ? details,
404
+ }) async {
405
+ if (message == null ) return ;
406
+ lastReportedError = '$message \n $details ' ;
407
+ }
408
+
394
409
test ('handles expired queue' , () => awaitFakeAsync ((async ) async {
395
410
await prepareStore ();
396
411
updateMachine.debugPauseLoop ();
@@ -428,19 +443,44 @@ void main() {
428
443
}));
429
444
430
445
group ('retries on errors' , () {
431
- void checkRetry (void Function () prepareError) {
446
+ void checkRetry (void Function () prepareError, {
447
+ int expectedFailureCountNotifyThreshold = 0 ,
448
+ }) {
449
+ reportErrorToUserBriefly = logAndReportErrorToUserBriefly;
450
+ addTearDown (() => reportErrorToUserBriefly = defaultReportErrorToUserBriefly);
451
+
452
+ final expectedErrorMessage =
453
+ 'Error connecting to Zulip. Retrying…\n '
454
+ 'Error connecting to Zulip at ${eg .realmUrl .origin }. Will retry' ;
455
+
432
456
awaitFakeAsync ((async ) async {
433
457
await prepareStore (lastEventId: 1 );
434
458
updateMachine.debugPauseLoop ();
435
459
updateMachine.poll ();
436
460
check (async .pendingTimers).length.equals (0 );
437
461
438
- // Make the request, inducing an error in it.
439
- prepareError ();
440
- updateMachine.debugAdvanceLoop ();
441
- async .elapse (Duration .zero);
442
- checkLastRequest (lastEventId: 1 );
443
- check (store).isLoading.isTrue ();
462
+ // Need to add 1 to the upperbound for that one additional request
463
+ // to trigger error reporting.
464
+ for (int i = 0 ; i < expectedFailureCountNotifyThreshold + 1 ; i++ ) {
465
+ // Make the request, inducing an error in it.
466
+ prepareError ();
467
+ if (i > 0 ) {
468
+ // End polling backoff from the previous iteration.
469
+ async .flushTimers ();
470
+ }
471
+ updateMachine.debugAdvanceLoop ();
472
+ check (lastReportedError).isNull ();
473
+ async .elapse (Duration .zero);
474
+ if (i < expectedFailureCountNotifyThreshold) {
475
+ // The error message should not appear until the `updateMachine`
476
+ // has retried the given number of times.
477
+ check (takeLastReportedError ()).isNull ();
478
+ } else {
479
+ check (takeLastReportedError ()).isNotNull ().contains (expectedErrorMessage);
480
+ }
481
+ checkLastRequest (lastEventId: 1 );
482
+ check (store).isLoading.isTrue ();
483
+ }
444
484
445
485
// Polling doesn't resume immediately; there's a timer.
446
486
check (async .pendingTimers).length.equals (1 );
@@ -461,11 +501,13 @@ void main() {
461
501
}
462
502
463
503
test ('Server5xxException' , () {
464
- checkRetry (() => connection.prepare (httpStatus: 500 , body: 'splat' ));
504
+ checkRetry (() => connection.prepare (httpStatus: 500 , body: 'splat' ),
505
+ expectedFailureCountNotifyThreshold: 5 );
465
506
});
466
507
467
508
test ('NetworkException' , () {
468
- checkRetry (() => connection.prepare (exception: Exception ("failed" )));
509
+ checkRetry (() => connection.prepare (exception: Exception ("failed" )),
510
+ expectedFailureCountNotifyThreshold: 5 );
469
511
});
470
512
471
513
test ('ZulipApiException' , () {
0 commit comments