Skip to content

Commit 98374b5

Browse files
rajveermalviyagnprice
authored andcommitted
notif: Ensure fetchBitmap succeeds only on HTTP 200 status
1 parent 3b10a4a commit 98374b5

File tree

2 files changed

+77
-18
lines changed

2 files changed

+77
-18
lines changed

lib/notifications/display.dart

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import 'dart:convert';
2+
import 'dart:io';
23

34
import 'package:http/http.dart' as http;
45
import 'package:collection/collection.dart';
@@ -279,10 +280,12 @@ class NotificationDisplayManager {
279280
try {
280281
// TODO timeout to prevent waiting indefinitely
281282
final resp = await http.get(url);
282-
return resp.bodyBytes;
283+
if (resp.statusCode == HttpStatus.ok) {
284+
return resp.bodyBytes;
285+
}
283286
} catch (e) {
284287
// TODO(log)
285-
return null;
286288
}
289+
return null;
287290
}
288291
}

test/notifications/display_test.dart

Lines changed: 72 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import 'dart:convert';
2+
import 'dart:io';
23
import 'dart:typed_data';
34

45
import 'package:checks/checks.dart';
@@ -8,6 +9,8 @@ import 'package:firebase_messaging/firebase_messaging.dart';
89
import 'package:flutter/widgets.dart';
910
import 'package:flutter_local_notifications/flutter_local_notifications.dart' hide Message, Person;
1011
import 'package:flutter_test/flutter_test.dart';
12+
import 'package:http/http.dart' as http;
13+
import 'package:http/testing.dart' as http_testing;
1114
import 'package:zulip/api/model/model.dart';
1215
import 'package:zulip/api/notifications.dart';
1316
import 'package:zulip/host/android_notifications.dart';
@@ -25,6 +28,7 @@ import 'package:zulip/widgets/theme.dart';
2528
import '../fake_async.dart';
2629
import '../model/binding.dart';
2730
import '../example_data.dart' as eg;
31+
import '../test_images.dart';
2832
import '../test_navigation.dart';
2933
import '../widgets/message_list_checks.dart';
3034
import '../widgets/page_checks.dart';
@@ -74,6 +78,24 @@ void main() {
7478
TestZulipBinding.ensureInitialized();
7579
final zulipLocalizations = GlobalLocalizations.zulipLocalizations;
7680

81+
http.Client makeFakeHttpClient({http.Response? response, Exception? exception}) {
82+
return http_testing.MockClient((request) async {
83+
assert((response != null) ^ (exception != null));
84+
if (exception != null) throw exception;
85+
return response!; // TODO return 404 on non avatar urls
86+
});
87+
}
88+
89+
final fakeHttpClientGivingSuccess = makeFakeHttpClient(
90+
response: http.Response.bytes(kSolidBlueAvatar, HttpStatus.ok));
91+
92+
T runWithHttpClient<T>(
93+
T Function() callback, {
94+
http.Client Function()? httpClientFactory,
95+
}) {
96+
return http.runWithClient(callback, httpClientFactory ?? () => fakeHttpClientGivingSuccess);
97+
}
98+
7799
Future<void> init() async {
78100
addTearDown(testBinding.reset);
79101
testBinding.firebaseMessagingInitialToken = '012abc';
@@ -102,6 +124,7 @@ void main() {
102124
required String expectedTitle,
103125
required String expectedTagComponent,
104126
required bool expectedIsGroupConversation,
127+
List<int>? expectedIconBitmap = kSolidBlueAvatar,
105128
}) {
106129
final expectedTag = '${data.realmUri}|${data.userId}|$expectedTagComponent';
107130
final expectedGroupKey = '${data.realmUri}|${data.userId}';
@@ -123,7 +146,8 @@ void main() {
123146
..text.equals(messageData.content)
124147
..timestampMs.equals(messageData.time * 1000)
125148
..person.which((it) => it.isNotNull()
126-
..iconBitmap.which((it) => isLast ? it.isNotNull() : it.isNull())
149+
..iconBitmap.which((it) => (isLast && expectedIconBitmap != null)
150+
? it.isNotNull().deepEquals(expectedIconBitmap) : it.isNull())
127151
..key.equals(expectedSenderKey)
128152
..name.equals(messageData.senderFullName));
129153
});
@@ -209,17 +233,17 @@ void main() {
209233
async.flushMicrotasks();
210234
}
211235

212-
test('stream message', () => awaitFakeAsync((async) async {
236+
test('stream message', () => runWithHttpClient(() => awaitFakeAsync((async) async {
213237
await init();
214238
final stream = eg.stream();
215239
final message = eg.streamMessage(stream: stream);
216240
await checkNotifications(async, messageFcmMessage(message, streamName: stream.name),
217241
expectedIsGroupConversation: true,
218242
expectedTitle: '#${stream.name} > ${message.topic}',
219243
expectedTagComponent: 'stream:${message.streamId}:${message.topic}');
220-
}));
244+
})));
221245

222-
test('stream message: multiple messages, same topic', () => awaitFakeAsync((async) async {
246+
test('stream message: multiple messages, same topic', () => runWithHttpClient(() => awaitFakeAsync((async) async {
223247
await init();
224248
final stream = eg.stream();
225249
const topic = 'topic 1';
@@ -253,54 +277,86 @@ void main() {
253277
expectedIsGroupConversation: true,
254278
expectedTitle: expectedTitle,
255279
expectedTagComponent: expectedTagComponent);
256-
}));
280+
})));
257281

258-
test('stream message: stream name omitted', () => awaitFakeAsync((async) async {
282+
test('stream message: stream name omitted', () => runWithHttpClient(() => awaitFakeAsync((async) async {
259283
await init();
260284
final stream = eg.stream();
261285
final message = eg.streamMessage(stream: stream);
262286
await checkNotifications(async, messageFcmMessage(message, streamName: null),
263287
expectedIsGroupConversation: true,
264288
expectedTitle: '#(unknown channel) > ${message.topic}',
265289
expectedTagComponent: 'stream:${message.streamId}:${message.topic}');
266-
}));
290+
})));
267291

268-
test('group DM: 3 users', () => awaitFakeAsync((async) async {
292+
test('group DM: 3 users', () => runWithHttpClient(() => awaitFakeAsync((async) async {
269293
await init();
270294
final message = eg.dmMessage(from: eg.thirdUser, to: [eg.otherUser, eg.selfUser]);
271295
await checkNotifications(async, messageFcmMessage(message),
272296
expectedIsGroupConversation: true,
273297
expectedTitle: "${eg.thirdUser.fullName} to you and 1 other",
274298
expectedTagComponent: 'dm:${message.allRecipientIds.join(",")}');
275-
}));
299+
})));
276300

277-
test('group DM: more than 3 users', () => awaitFakeAsync((async) async {
301+
test('group DM: more than 3 users', () => runWithHttpClient(() => awaitFakeAsync((async) async {
278302
await init();
279303
final message = eg.dmMessage(from: eg.thirdUser,
280304
to: [eg.otherUser, eg.selfUser, eg.fourthUser]);
281305
await checkNotifications(async, messageFcmMessage(message),
282306
expectedIsGroupConversation: true,
283307
expectedTitle: "${eg.thirdUser.fullName} to you and 2 others",
284308
expectedTagComponent: 'dm:${message.allRecipientIds.join(",")}');
285-
}));
309+
})));
286310

287-
test('1:1 DM', () => awaitFakeAsync((async) async {
311+
test('1:1 DM', () => runWithHttpClient(() => awaitFakeAsync((async) async {
288312
await init();
289313
final message = eg.dmMessage(from: eg.otherUser, to: [eg.selfUser]);
290314
await checkNotifications(async, messageFcmMessage(message),
291315
expectedIsGroupConversation: false,
292316
expectedTitle: eg.otherUser.fullName,
293317
expectedTagComponent: 'dm:${message.allRecipientIds.join(",")}');
294-
}));
295-
296-
test('self-DM', () => awaitFakeAsync((async) async {
318+
})));
319+
320+
test('1:1 DM: sender avatar loading fails, remote error', () => runWithHttpClient(
321+
() => awaitFakeAsync((async) async {
322+
await init();
323+
final message = eg.dmMessage(from: eg.otherUser, to: [eg.selfUser]);
324+
final data = messageFcmMessage(message);
325+
await receiveFcmMessage(async, data);
326+
checkNotification(data,
327+
messageStyleMessages: [data],
328+
expectedIsGroupConversation: false,
329+
expectedTitle: eg.otherUser.fullName,
330+
expectedTagComponent: 'dm:${message.allRecipientIds.join(",")}',
331+
expectedIconBitmap: null); // Failed to fetch avatar photo
332+
}),
333+
httpClientFactory: () => makeFakeHttpClient(
334+
response: http.Response.bytes([], HttpStatus.internalServerError))));
335+
336+
test('1:1 DM: sender avatar loading fails, local error', () => runWithHttpClient(
337+
() => awaitFakeAsync((async) async {
338+
await init();
339+
final message = eg.dmMessage(from: eg.otherUser, to: [eg.selfUser]);
340+
final data = messageFcmMessage(message);
341+
await receiveFcmMessage(async, data);
342+
checkNotification(data,
343+
messageStyleMessages: [data],
344+
expectedIsGroupConversation: false,
345+
expectedTitle: eg.otherUser.fullName,
346+
expectedTagComponent: 'dm:${message.allRecipientIds.join(",")}',
347+
expectedIconBitmap: null); // Failed to fetch avatar photo
348+
}),
349+
httpClientFactory: () => makeFakeHttpClient(
350+
exception: http.ClientException('Network failure'))));
351+
352+
test('self-DM', () => runWithHttpClient(() => awaitFakeAsync((async) async {
297353
await init();
298354
final message = eg.dmMessage(from: eg.selfUser, to: []);
299355
await checkNotifications(async, messageFcmMessage(message),
300356
expectedIsGroupConversation: false,
301357
expectedTitle: eg.selfUser.fullName,
302358
expectedTagComponent: 'dm:${message.allRecipientIds.join(",")}');
303-
}));
359+
})));
304360
});
305361

306362
group('NotificationDisplayManager open', () {

0 commit comments

Comments
 (0)