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