@@ -10,6 +10,7 @@ import 'package:zulip/api/route/events.dart';
10
10
import 'package:zulip/api/route/messages.dart' ;
11
11
import 'package:zulip/model/message_list.dart' ;
12
12
import 'package:zulip/model/narrow.dart' ;
13
+ import 'package:zulip/log.dart' ;
13
14
import 'package:zulip/model/store.dart' ;
14
15
import 'package:zulip/notifications/receive.dart' ;
15
16
@@ -393,6 +394,20 @@ void main() {
393
394
check (store.userSettings! .twentyFourHourTime).isTrue ();
394
395
}));
395
396
397
+ String ? lastReportedError;
398
+ String ? takeLastReportedError () {
399
+ final result = lastReportedError;
400
+ lastReportedError = null ;
401
+ return result;
402
+ }
403
+
404
+ Future <void > logAndReportErrorToUserBriefly (String ? message, {
405
+ String ? details,
406
+ }) async {
407
+ if (message == null ) return ;
408
+ lastReportedError = '$message \n $details ' ;
409
+ }
410
+
396
411
test ('handles expired queue' , () => awaitFakeAsync ((async ) async {
397
412
await prepareStore ();
398
413
updateMachine.debugPauseLoop ();
@@ -456,19 +471,44 @@ void main() {
456
471
}));
457
472
458
473
group ('retries on errors' , () {
459
- void checkRetry (void Function () prepareError) {
474
+ void checkRetry (void Function () prepareError, {
475
+ int expectedFailureCountNotifyThreshold = 0 ,
476
+ }) {
477
+ reportErrorToUserBriefly = logAndReportErrorToUserBriefly;
478
+ addTearDown (() => reportErrorToUserBriefly = defaultReportErrorToUserBriefly);
479
+
480
+ final expectedErrorMessage =
481
+ 'Error connecting to Zulip. Retrying…\n '
482
+ 'Error connecting to Zulip at ${eg .realmUrl .origin }. Will retry' ;
483
+
460
484
awaitFakeAsync ((async ) async {
461
485
await prepareStore (lastEventId: 1 );
462
486
updateMachine.debugPauseLoop ();
463
487
updateMachine.poll ();
464
488
check (async .pendingTimers).length.equals (0 );
465
489
466
- // Make the request, inducing an error in it.
467
- prepareError ();
468
- updateMachine.debugAdvanceLoop ();
469
- async .elapse (Duration .zero);
470
- checkLastRequest (lastEventId: 1 );
471
- check (store).isLoading.isTrue ();
490
+ // Need to add 1 to the upperbound for that one additional request
491
+ // to trigger error reporting.
492
+ for (int i = 0 ; i < expectedFailureCountNotifyThreshold + 1 ; i++ ) {
493
+ // Make the request, inducing an error in it.
494
+ prepareError ();
495
+ if (i > 0 ) {
496
+ // End polling backoff from the previous iteration.
497
+ async .flushTimers ();
498
+ }
499
+ updateMachine.debugAdvanceLoop ();
500
+ check (lastReportedError).isNull ();
501
+ async .elapse (Duration .zero);
502
+ if (i < expectedFailureCountNotifyThreshold) {
503
+ // The error message should not appear until the `updateMachine`
504
+ // has retried the given number of times.
505
+ check (takeLastReportedError ()).isNull ();
506
+ } else {
507
+ check (takeLastReportedError ()).isNotNull ().contains (expectedErrorMessage);
508
+ }
509
+ checkLastRequest (lastEventId: 1 );
510
+ check (store).isLoading.isTrue ();
511
+ }
472
512
473
513
// Polling doesn't resume immediately; there's a timer.
474
514
check (async .pendingTimers).length.equals (1 );
@@ -489,11 +529,13 @@ void main() {
489
529
}
490
530
491
531
test ('Server5xxException' , () {
492
- checkRetry (() => connection.prepare (httpStatus: 500 , body: 'splat' ));
532
+ checkRetry (() => connection.prepare (httpStatus: 500 , body: 'splat' ),
533
+ expectedFailureCountNotifyThreshold: 5 );
493
534
});
494
535
495
536
test ('NetworkException' , () {
496
- checkRetry (() => connection.prepare (exception: Exception ("failed" )));
537
+ checkRetry (() => connection.prepare (exception: Exception ("failed" )),
538
+ expectedFailureCountNotifyThreshold: 5 );
497
539
});
498
540
499
541
test ('ZulipApiException' , () {
0 commit comments