@@ -3,6 +3,8 @@ import 'dart:convert';
3
3
import 'package:checks/checks.dart' ;
4
4
import 'package:http/http.dart' as http;
5
5
import 'package:test/scaffolding.dart' ;
6
+ import 'package:zulip/api/backoff.dart' ;
7
+ import 'package:zulip/api/exception.dart' ;
6
8
import 'package:zulip/api/model/events.dart' ;
7
9
import 'package:zulip/api/model/model.dart' ;
8
10
import 'package:zulip/api/model/narrow.dart' ;
@@ -238,6 +240,40 @@ void main() {
238
240
..messages.length.equals (30 );
239
241
});
240
242
243
+ test ('fetchOlder nop during backoff' , () => awaitFakeAsync ((async ) async {
244
+ final olderMessages = List .generate (5 , (i) => eg.streamMessage ());
245
+ final initialMessages = List .generate (5 , (i) => eg.streamMessage ());
246
+ await prepare (narrow: const CombinedFeedNarrow ());
247
+ await prepareMessages (foundOldest: false , messages: initialMessages);
248
+ check (connection.takeRequests ()).single;
249
+
250
+ connection.prepare (httpStatus: 400 , json: {
251
+ 'result' : 'error' , 'code' : 'BAD_REQUEST' , 'msg' : 'Bad request' });
252
+ check (async .pendingTimers).isEmpty ();
253
+ await check (model.fetchOlder ()).throws <ZulipApiException >();
254
+ checkNotified (count: 2 );
255
+ check (model).fetchOlderCoolingDown.isTrue ();
256
+ check (connection.takeRequests ()).single;
257
+
258
+ await model.fetchOlder ();
259
+ checkNotNotified ();
260
+ check (model).fetchOlderCoolingDown.isTrue ();
261
+ check (model).fetchingOlder.isFalse ();
262
+ check (connection.lastRequest).isNull ();
263
+
264
+ // Wait long enough that a first backoff is sure to finish.
265
+ async .elapse (const Duration (seconds: 1 ));
266
+ check (model).fetchOlderCoolingDown.isFalse ();
267
+ checkNotifiedOnce ();
268
+ check (connection.lastRequest).isNull ();
269
+
270
+ connection.prepare (json: olderResult (
271
+ anchor: 1000 , foundOldest: false , messages: olderMessages).toJson ());
272
+ await model.fetchOlder ();
273
+ checkNotified (count: 2 );
274
+ check (connection.takeRequests ()).single;
275
+ }));
276
+
241
277
test ('fetchOlder handles servers not understanding includeAnchor' , () async {
242
278
const narrow = CombinedFeedNarrow ();
243
279
await prepare (narrow: narrow);
@@ -1020,6 +1056,70 @@ void main() {
1020
1056
checkNotNotified ();
1021
1057
}));
1022
1058
1059
+ test ('fetchOlder backoff A starts, _reset, move fetch finishes,'
1060
+ ' fetchOlder backoff B starts, fetchOlder backoff A ends' , () => awaitFakeAsync ((async ) async {
1061
+ addTearDown (() => BackoffMachine .debugDuration = null );
1062
+ await prepareNarrow (narrow, initialMessages);
1063
+
1064
+ connection.prepare (httpStatus: 400 , json: {
1065
+ 'result' : 'error' , 'code' : 'BAD_REQUEST' , 'msg' : 'Bad request' });
1066
+ BackoffMachine .debugDuration = const Duration (seconds: 1 );
1067
+ await check (model.fetchOlder ()).throws <ZulipApiException >();
1068
+ final backoffTimerA = async .pendingTimers.single;
1069
+ check (model).fetchOlderCoolingDown.isTrue ();
1070
+ check (model).fetched.isTrue ();
1071
+ checkHasMessages (initialMessages);
1072
+ checkNotified (count: 2 );
1073
+
1074
+ connection.prepare (json: newestResult (
1075
+ foundOldest: false ,
1076
+ messages: initialMessages + movedMessages,
1077
+ ).toJson ());
1078
+ await store.handleEvent (eg.updateMessageEventMoveTo (
1079
+ origTopic: movedMessages[0 ].topic,
1080
+ origStreamId: otherStream.streamId,
1081
+ newMessages: movedMessages,
1082
+ ));
1083
+ // Check that _reset was called.
1084
+ check (model).fetched.isFalse ();
1085
+ checkHasMessages ([]);
1086
+ checkNotifiedOnce ();
1087
+ check (model).fetchOlderCoolingDown.isFalse ();
1088
+ check (backoffTimerA.isActive).isTrue ();
1089
+
1090
+ async .elapse (Duration .zero);
1091
+ check (model).fetched.isTrue ();
1092
+ checkHasMessages (initialMessages + movedMessages);
1093
+ checkNotifiedOnce ();
1094
+ check (model).fetchOlderCoolingDown.isFalse ();
1095
+ check (backoffTimerA.isActive).isTrue ();
1096
+
1097
+ connection.prepare (httpStatus: 400 , json: {
1098
+ 'result' : 'error' , 'code' : 'BAD_REQUEST' , 'msg' : 'Bad request' });
1099
+ BackoffMachine .debugDuration = const Duration (seconds: 2 );
1100
+ await check (model.fetchOlder ()).throws <ZulipApiException >();
1101
+ final backoffTimerB = async .pendingTimers.last;
1102
+ check (model).fetchOlderCoolingDown.isTrue ();
1103
+ check (backoffTimerA.isActive).isTrue ();
1104
+ check (backoffTimerB.isActive).isTrue ();
1105
+ checkNotified (count: 2 );
1106
+
1107
+ // When `backoffTimerA` ends, `fetchOlderCoolingDown` remains `true`
1108
+ // because the backoff was from a previous generation.
1109
+ async .elapse (const Duration (seconds: 1 ));
1110
+ check (model).fetchOlderCoolingDown.isTrue ();
1111
+ check (backoffTimerA.isActive).isFalse ();
1112
+ check (backoffTimerB.isActive).isTrue ();
1113
+ checkNotNotified ();
1114
+
1115
+ // When `backoffTimerB` ends, `fetchOlderCoolingDown` gets reset.
1116
+ async .elapse (const Duration (seconds: 1 ));
1117
+ check (model).fetchOlderCoolingDown.isFalse ();
1118
+ check (backoffTimerA.isActive).isFalse ();
1119
+ check (backoffTimerB.isActive).isFalse ();
1120
+ checkNotifiedOnce ();
1121
+ }));
1122
+
1023
1123
test ('fetchInitial, _reset, initial fetch finishes, move fetch finishes' , () => awaitFakeAsync ((async ) async {
1024
1124
await prepareNarrow (narrow, null );
1025
1125
@@ -1750,10 +1850,15 @@ void checkInvariants(MessageListView model) {
1750
1850
check (model)
1751
1851
..messages.isEmpty ()
1752
1852
..haveOldest.isFalse ()
1753
- ..fetchingOlder.isFalse ();
1853
+ ..fetchingOlder.isFalse ()
1854
+ ..fetchOlderCoolingDown.isFalse ();
1754
1855
}
1755
1856
if (model.haveOldest) {
1756
1857
check (model).fetchingOlder.isFalse ();
1858
+ check (model).fetchOlderCoolingDown.isFalse ();
1859
+ }
1860
+ if (model.fetchingOlder) {
1861
+ check (model).fetchOlderCoolingDown.isFalse ();
1757
1862
}
1758
1863
1759
1864
for (final message in model.messages) {
@@ -1793,7 +1898,7 @@ void checkInvariants(MessageListView model) {
1793
1898
if (model.haveOldest) {
1794
1899
check (model.items[i++ ]).isA <MessageListHistoryStartItem >();
1795
1900
}
1796
- if (model.fetchingOlder) {
1901
+ if (model.fetchingOlder || model.fetchOlderCoolingDown ) {
1797
1902
check (model.items[i++ ]).isA <MessageListLoadingItem >();
1798
1903
}
1799
1904
for (int j = 0 ; j < model.messages.length; j++ ) {
@@ -1849,4 +1954,5 @@ extension MessageListViewChecks on Subject<MessageListView> {
1849
1954
Subject <bool > get fetched => has ((x) => x.fetched, 'fetched' );
1850
1955
Subject <bool > get haveOldest => has ((x) => x.haveOldest, 'haveOldest' );
1851
1956
Subject <bool > get fetchingOlder => has ((x) => x.fetchingOlder, 'fetchingOlder' );
1957
+ Subject <bool > get fetchOlderCoolingDown => has ((x) => x.fetchOlderCoolingDown, 'fetchOlderCoolingDown' );
1852
1958
}
0 commit comments