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