Skip to content

Commit 5e64a9b

Browse files
notif: Ensure fetchBitmap succeeds only on HTTP 200 status
1 parent c89af39 commit 5e64a9b

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';
@@ -281,10 +282,12 @@ class NotificationDisplayManager {
281282
try {
282283
// TODO timeout to prevent waiting indefinitely
283284
final resp = await http.get(url);
284-
return resp.bodyBytes;
285+
if (resp.statusCode == HttpStatus.ok) {
286+
return resp.bodyBytes;
287+
}
285288
} catch (e) {
286289
// TODO(log)
287-
return null;
288290
}
291+
return null;
289292
}
290293
}

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/material.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';
@@ -79,6 +83,24 @@ void main() {
7983
TestZulipBinding.ensureInitialized();
8084
final zulipLocalizations = GlobalLocalizations.zulipLocalizations;
8185

86+
http.Client makeFakeHttpClient({http.Response? response, Exception? exception}) {
87+
return http_testing.MockClient((request) async {
88+
assert((response != null) ^ (exception != null));
89+
if (exception != null) throw exception;
90+
return response!; // TODO return 404 on non avatar urls
91+
});
92+
}
93+
94+
final fakeHttpClientGivingSuccess = makeFakeHttpClient(
95+
response: http.Response.bytes(kSolidBlueAvatar, HttpStatus.ok));
96+
97+
T runWithHttpClient<T>(
98+
T Function() callback, {
99+
http.Client Function()? httpClientFactory,
100+
}) {
101+
return http.runWithClient(callback, httpClientFactory ?? () => fakeHttpClientGivingSuccess);
102+
}
103+
82104
Future<void> init() async {
83105
addTearDown(testBinding.reset);
84106
testBinding.firebaseMessagingInitialToken = '012abc';
@@ -114,6 +136,7 @@ void main() {
114136
required String expectedTitle,
115137
required String expectedTagComponent,
116138
required bool expectedIsGroupConversation,
139+
List<int>? expectedIconBitmap = kSolidBlueAvatar,
117140
}) {
118141
final expectedTag = '${data.realmUri}|${data.userId}|$expectedTagComponent';
119142
final expectedGroupKey = '${data.realmUri}|${data.userId}';
@@ -135,7 +158,8 @@ void main() {
135158
..text.equals(messageData.content)
136159
..timestampMs.equals(messageData.time * 1000)
137160
..person.which((it) => it.isNotNull()
138-
..iconBitmap.which((it) => isLast ? it.isNotNull() : it.isNull())
161+
..iconBitmap.which((it) => (isLast && expectedIconBitmap != null)
162+
? it.isNotNull().deepEquals(expectedIconBitmap) : it.isNull())
139163
..key.equals(expectedSenderKey)
140164
..name.equals(messageData.senderFullName));
141165
});
@@ -221,17 +245,17 @@ void main() {
221245
async.flushMicrotasks();
222246
}
223247

224-
test('stream message', () => awaitFakeAsync((async) async {
248+
test('stream message', () => runWithHttpClient(() => awaitFakeAsync((async) async {
225249
await init();
226250
final stream = eg.stream();
227251
final message = eg.streamMessage(stream: stream);
228252
await checkNotifications(async, messageFcmMessage(message, streamName: stream.name),
229253
expectedIsGroupConversation: true,
230254
expectedTitle: '#${stream.name} > ${message.topic}',
231255
expectedTagComponent: 'stream:${message.streamId}:${message.topic}');
232-
}));
256+
})));
233257

234-
test('stream message: multiple messages, same topic', () => awaitFakeAsync((async) async {
258+
test('stream message: multiple messages, same topic', () => runWithHttpClient(() => awaitFakeAsync((async) async {
235259
await init();
236260
final stream = eg.stream();
237261
const topic = 'topic 1';
@@ -265,54 +289,86 @@ void main() {
265289
expectedIsGroupConversation: true,
266290
expectedTitle: expectedTitle,
267291
expectedTagComponent: expectedTagComponent);
268-
}));
292+
})));
269293

270-
test('stream message: stream name omitted', () => awaitFakeAsync((async) async {
294+
test('stream message: stream name omitted', () => runWithHttpClient(() => awaitFakeAsync((async) async {
271295
await init();
272296
final stream = eg.stream();
273297
final message = eg.streamMessage(stream: stream);
274298
await checkNotifications(async, messageFcmMessage(message, streamName: null),
275299
expectedIsGroupConversation: true,
276300
expectedTitle: '#(unknown channel) > ${message.topic}',
277301
expectedTagComponent: 'stream:${message.streamId}:${message.topic}');
278-
}));
302+
})));
279303

280-
test('group DM: 3 users', () => awaitFakeAsync((async) async {
304+
test('group DM: 3 users', () => runWithHttpClient(() => awaitFakeAsync((async) async {
281305
await init();
282306
final message = eg.dmMessage(from: eg.thirdUser, to: [eg.otherUser, eg.selfUser]);
283307
await checkNotifications(async, messageFcmMessage(message),
284308
expectedIsGroupConversation: true,
285309
expectedTitle: "${eg.thirdUser.fullName} to you and 1 other",
286310
expectedTagComponent: 'dm:${message.allRecipientIds.join(",")}');
287-
}));
311+
})));
288312

289-
test('group DM: more than 3 users', () => awaitFakeAsync((async) async {
313+
test('group DM: more than 3 users', () => runWithHttpClient(() => awaitFakeAsync((async) async {
290314
await init();
291315
final message = eg.dmMessage(from: eg.thirdUser,
292316
to: [eg.otherUser, eg.selfUser, eg.fourthUser]);
293317
await checkNotifications(async, messageFcmMessage(message),
294318
expectedIsGroupConversation: true,
295319
expectedTitle: "${eg.thirdUser.fullName} to you and 2 others",
296320
expectedTagComponent: 'dm:${message.allRecipientIds.join(",")}');
297-
}));
321+
})));
298322

299-
test('1:1 DM', () => awaitFakeAsync((async) async {
323+
test('1:1 DM', () => runWithHttpClient(() => awaitFakeAsync((async) async {
300324
await init();
301325
final message = eg.dmMessage(from: eg.otherUser, to: [eg.selfUser]);
302326
await checkNotifications(async, messageFcmMessage(message),
303327
expectedIsGroupConversation: false,
304328
expectedTitle: eg.otherUser.fullName,
305329
expectedTagComponent: 'dm:${message.allRecipientIds.join(",")}');
306-
}));
307-
308-
test('self-DM', () => awaitFakeAsync((async) async {
330+
})));
331+
332+
test('1:1 DM: sender avatar loading fails, remote error', () => runWithHttpClient(
333+
() => awaitFakeAsync((async) async {
334+
await init();
335+
final message = eg.dmMessage(from: eg.otherUser, to: [eg.selfUser]);
336+
final data = messageFcmMessage(message);
337+
await receiveFcmMessage(async, data);
338+
checkNotification(data,
339+
messageStyleMessages: [data],
340+
expectedIsGroupConversation: false,
341+
expectedTitle: eg.otherUser.fullName,
342+
expectedTagComponent: 'dm:${message.allRecipientIds.join(",")}',
343+
expectedIconBitmap: null); // Failed to fetch avatar photo
344+
}),
345+
httpClientFactory: () => makeFakeHttpClient(
346+
response: http.Response.bytes([], HttpStatus.internalServerError))));
347+
348+
test('1:1 DM: sender avatar loading fails, local error', () => runWithHttpClient(
349+
() => awaitFakeAsync((async) async {
350+
await init();
351+
final message = eg.dmMessage(from: eg.otherUser, to: [eg.selfUser]);
352+
final data = messageFcmMessage(message);
353+
await receiveFcmMessage(async, data);
354+
checkNotification(data,
355+
messageStyleMessages: [data],
356+
expectedIsGroupConversation: false,
357+
expectedTitle: eg.otherUser.fullName,
358+
expectedTagComponent: 'dm:${message.allRecipientIds.join(",")}',
359+
expectedIconBitmap: null); // Failed to fetch avatar photo
360+
}),
361+
httpClientFactory: () => makeFakeHttpClient(
362+
exception: http.ClientException('Network failure'))));
363+
364+
test('self-DM', () => runWithHttpClient(() => awaitFakeAsync((async) async {
309365
await init();
310366
final message = eg.dmMessage(from: eg.selfUser, to: []);
311367
await checkNotifications(async, messageFcmMessage(message),
312368
expectedIsGroupConversation: false,
313369
expectedTitle: eg.selfUser.fullName,
314370
expectedTagComponent: 'dm:${message.allRecipientIds.join(",")}');
315-
}));
371+
})));
316372
});
317373

318374
group('NotificationDisplayManager open', () {

0 commit comments

Comments
 (0)