@@ -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,41 @@ 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
+ check (model).messages.length.equals (10 );
276
+ }));
277
+
241
278
test ('fetchOlder handles servers not understanding includeAnchor' , () async {
242
279
const narrow = CombinedFeedNarrow ();
243
280
await prepare (narrow: narrow);
@@ -1020,6 +1057,70 @@ void main() {
1020
1057
checkNotNotified ();
1021
1058
}));
1022
1059
1060
+ test ('fetchOlder backoff A starts, _reset, move fetch finishes,'
1061
+ ' fetchOlder backoff B starts, fetchOlder backoff A ends' , () => awaitFakeAsync ((async ) async {
1062
+ addTearDown (() => BackoffMachine .debugDuration = null );
1063
+ await prepareNarrow (narrow, initialMessages);
1064
+
1065
+ connection.prepare (httpStatus: 400 , json: {
1066
+ 'result' : 'error' , 'code' : 'BAD_REQUEST' , 'msg' : 'Bad request' });
1067
+ BackoffMachine .debugDuration = const Duration (seconds: 1 );
1068
+ await check (model.fetchOlder ()).throws <ZulipApiException >();
1069
+ final backoffTimerA = async .pendingTimers.single;
1070
+ check (model).fetchOlderCoolingDown.isTrue ();
1071
+ check (model).fetched.isTrue ();
1072
+ checkHasMessages (initialMessages);
1073
+ checkNotified (count: 2 );
1074
+
1075
+ connection.prepare (json: newestResult (
1076
+ foundOldest: false ,
1077
+ messages: initialMessages + movedMessages,
1078
+ ).toJson ());
1079
+ await store.handleEvent (eg.updateMessageEventMoveTo (
1080
+ origTopic: movedMessages[0 ].topic,
1081
+ origStreamId: otherStream.streamId,
1082
+ newMessages: movedMessages,
1083
+ ));
1084
+ // Check that _reset was called.
1085
+ check (model).fetched.isFalse ();
1086
+ checkHasMessages ([]);
1087
+ checkNotifiedOnce ();
1088
+ check (model).fetchOlderCoolingDown.isFalse ();
1089
+ check (backoffTimerA.isActive).isTrue ();
1090
+
1091
+ async .elapse (Duration .zero);
1092
+ check (model).fetched.isTrue ();
1093
+ checkHasMessages (initialMessages + movedMessages);
1094
+ checkNotifiedOnce ();
1095
+ check (model).fetchOlderCoolingDown.isFalse ();
1096
+ check (backoffTimerA.isActive).isTrue ();
1097
+
1098
+ connection.prepare (httpStatus: 400 , json: {
1099
+ 'result' : 'error' , 'code' : 'BAD_REQUEST' , 'msg' : 'Bad request' });
1100
+ BackoffMachine .debugDuration = const Duration (seconds: 2 );
1101
+ await check (model.fetchOlder ()).throws <ZulipApiException >();
1102
+ final backoffTimerB = async .pendingTimers.last;
1103
+ check (model).fetchOlderCoolingDown.isTrue ();
1104
+ check (backoffTimerA.isActive).isTrue ();
1105
+ check (backoffTimerB.isActive).isTrue ();
1106
+ checkNotified (count: 2 );
1107
+
1108
+ // When `backoffTimerA` ends, `fetchOlderCoolingDown` remains `true`
1109
+ // because the backoff was from a previous generation.
1110
+ async .elapse (const Duration (seconds: 1 ));
1111
+ check (model).fetchOlderCoolingDown.isTrue ();
1112
+ check (backoffTimerA.isActive).isFalse ();
1113
+ check (backoffTimerB.isActive).isTrue ();
1114
+ checkNotNotified ();
1115
+
1116
+ // When `backoffTimerB` ends, `fetchOlderCoolingDown` gets reset.
1117
+ async .elapse (const Duration (seconds: 1 ));
1118
+ check (model).fetchOlderCoolingDown.isFalse ();
1119
+ check (backoffTimerA.isActive).isFalse ();
1120
+ check (backoffTimerB.isActive).isFalse ();
1121
+ checkNotifiedOnce ();
1122
+ }));
1123
+
1023
1124
test ('fetchInitial, _reset, initial fetch finishes, move fetch finishes' , () => awaitFakeAsync ((async ) async {
1024
1125
await prepareNarrow (narrow, null );
1025
1126
@@ -1750,10 +1851,15 @@ void checkInvariants(MessageListView model) {
1750
1851
check (model)
1751
1852
..messages.isEmpty ()
1752
1853
..haveOldest.isFalse ()
1753
- ..fetchingOlder.isFalse ();
1854
+ ..fetchingOlder.isFalse ()
1855
+ ..fetchOlderCoolingDown.isFalse ();
1754
1856
}
1755
1857
if (model.haveOldest) {
1756
1858
check (model).fetchingOlder.isFalse ();
1859
+ check (model).fetchOlderCoolingDown.isFalse ();
1860
+ }
1861
+ if (model.fetchingOlder) {
1862
+ check (model).fetchOlderCoolingDown.isFalse ();
1757
1863
}
1758
1864
1759
1865
for (final message in model.messages) {
@@ -1793,7 +1899,7 @@ void checkInvariants(MessageListView model) {
1793
1899
if (model.haveOldest) {
1794
1900
check (model.items[i++ ]).isA <MessageListHistoryStartItem >();
1795
1901
}
1796
- if (model.fetchingOlder) {
1902
+ if (model.fetchingOlder || model.fetchOlderCoolingDown ) {
1797
1903
check (model.items[i++ ]).isA <MessageListLoadingItem >();
1798
1904
}
1799
1905
for (int j = 0 ; j < model.messages.length; j++ ) {
@@ -1849,4 +1955,5 @@ extension MessageListViewChecks on Subject<MessageListView> {
1849
1955
Subject <bool > get fetched => has ((x) => x.fetched, 'fetched' );
1850
1956
Subject <bool > get haveOldest => has ((x) => x.haveOldest, 'haveOldest' );
1851
1957
Subject <bool > get fetchingOlder => has ((x) => x.fetchingOlder, 'fetchingOlder' );
1958
+ Subject <bool > get fetchOlderCoolingDown => has ((x) => x.fetchOlderCoolingDown, 'fetchOlderCoolingDown' );
1852
1959
}
0 commit comments