Skip to content

Commit 8db5269

Browse files
committed
content test: Test auth header in RealmContentNetworkImage
This gives us our first concrete tests of application code that uses PerAccountStoreWidget, so #30 is complete. Fixes: #30
1 parent 3162387 commit 8db5269

File tree

1 file changed

+115
-7
lines changed

1 file changed

+115
-7
lines changed

test/widgets/content_test.dart

+115-7
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,124 @@
1+
import 'dart:async';
2+
import 'dart:io';
3+
14
import 'package:checks/checks.dart';
25
import 'package:flutter/material.dart';
36
import 'package:flutter_test/flutter_test.dart';
7+
import 'package:zulip/api/core.dart';
48
import 'package:zulip/widgets/content.dart';
9+
import 'package:zulip/widgets/store.dart';
10+
11+
import '../example_data.dart' as eg;
12+
import '../model/binding.dart';
513

614
void main() {
7-
testWidgets('Throws if no `PerAccountStoreWidget` ancestor', (WidgetTester tester) async {
8-
await tester.pumpWidget(
9-
const RealmContentNetworkImage('https://zulip.invalid/path/to/image.png', filterQuality: FilterQuality.medium));
10-
check(tester.takeException()).isA<AssertionError>();
15+
TestDataBinding.ensureInitialized();
16+
17+
group('RealmContentNetworkImage', () {
18+
final authHeaders = authHeader(email: eg.selfAccount.email, apiKey: eg.selfAccount.apiKey);
19+
20+
Future<String?> actualAuthHeader(WidgetTester tester, String src) async {
21+
final globalStore = TestDataBinding.instance.globalStore;
22+
addTearDown(TestDataBinding.instance.reset);
23+
await globalStore.add(eg.selfAccount, eg.initialSnapshot);
24+
25+
final httpClient = _FakeHttpClient();
26+
debugNetworkImageHttpClientProvider = () => httpClient;
27+
httpClient.request.response
28+
..statusCode = HttpStatus.ok
29+
..content = kSolidBlueAvatar;
30+
31+
await tester.pumpWidget(GlobalStoreWidget(
32+
child: PerAccountStoreWidget(accountId: eg.selfAccount.id,
33+
child: RealmContentNetworkImage(src))));
34+
await tester.pump();
35+
await tester.pump();
36+
37+
final headers = httpClient.request.headers.values;
38+
check(authHeaders.keys).deepEquals(['Authorization']);
39+
return headers['Authorization']?.single;
40+
}
41+
42+
testWidgets('includes auth header if `src` on-realm', (tester) async {
43+
check(await actualAuthHeader(tester, 'https://chat.example/image.png'))
44+
.isNotNull().equals(authHeaders['Authorization']!);
45+
debugNetworkImageHttpClientProvider = null;
46+
});
47+
48+
testWidgets('excludes auth header if `src` off-realm', (tester) async {
49+
check(await actualAuthHeader(tester, 'https://other.example/image.png'))
50+
.isNull();
51+
debugNetworkImageHttpClientProvider = null;
52+
});
53+
54+
testWidgets('throws if no `PerAccountStoreWidget` ancestor', (WidgetTester tester) async {
55+
await tester.pumpWidget(
56+
const RealmContentNetworkImage('https://zulip.invalid/path/to/image.png', filterQuality: FilterQuality.medium));
57+
check(tester.takeException()).isA<AssertionError>();
58+
});
1159
});
60+
}
61+
62+
class _FakeHttpClient extends Fake implements HttpClient {
63+
final _FakeHttpClientRequest request = _FakeHttpClientRequest();
64+
65+
@override
66+
Future<HttpClientRequest> getUrl(Uri url) async => request;
67+
}
68+
69+
class _FakeHttpClientRequest extends Fake implements HttpClientRequest {
70+
final _FakeHttpClientResponse response = _FakeHttpClientResponse();
1271

13-
// TODO(#30): Simulate a `PerAccountStoreWidget` ancestor, to use in more tests:
14-
// TODO: 'Includes auth header if `src` is on-realm'
15-
// TODO: 'Excludes auth header if `src` is off-realm'
72+
@override
73+
final _FakeHttpHeaders headers = _FakeHttpHeaders();
74+
75+
@override
76+
Future<HttpClientResponse> close() async => response;
1677
}
78+
79+
class _FakeHttpHeaders extends Fake implements HttpHeaders {
80+
final Map<String, List<String>> values = {};
81+
82+
@override
83+
void add(String name, Object value, {bool preserveHeaderCase = false}) {
84+
(values[name] ??= []).add(value.toString());
85+
}
86+
}
87+
88+
class _FakeHttpClientResponse extends Fake implements HttpClientResponse {
89+
@override
90+
int statusCode = HttpStatus.ok;
91+
92+
late List<int> content;
93+
94+
@override
95+
int get contentLength => content.length;
96+
97+
@override
98+
HttpClientResponseCompressionState get compressionState => HttpClientResponseCompressionState.notCompressed;
99+
100+
@override
101+
StreamSubscription<List<int>> listen(void Function(List<int> event)? onData, {Function? onError, void Function()? onDone, bool? cancelOnError}) {
102+
return Stream.value(content).listen(
103+
onData, onDone: onDone, onError: onError, cancelOnError: cancelOnError);
104+
}
105+
}
106+
107+
/// A 100x100 PNG image of solid Zulip blue, [kZulipBrandColor].
108+
// Made from the following SVG:
109+
// <svg xmlns="http://www.w3.org/2000/svg" width="1" height="1" viewBox="0 0 1 1">
110+
// <rect style="fill:#6492fe;fill-opacity:1" width="1" height="1" x="0" y="0" />
111+
// </svg>
112+
// with `inkscape tmp.svg -w 100 --export-png=tmp1.png`,
113+
// `zopflipng tmp1.png tmp.png`,
114+
// and `xxd -i tmp.png`.
115+
const List<int> kSolidBlueAvatar = [
116+
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d,
117+
0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x64,
118+
0x01, 0x03, 0x00, 0x00, 0x00, 0x4a, 0x2c, 0x07, 0x17, 0x00, 0x00, 0x00,
119+
0x03, 0x50, 0x4c, 0x54, 0x45, 0x64, 0x92, 0xfe, 0xf1, 0xd6, 0x69, 0xa5,
120+
0x00, 0x00, 0x00, 0x13, 0x49, 0x44, 0x41, 0x54, 0x78, 0x01, 0x63, 0xa0,
121+
0x2b, 0x18, 0x05, 0xa3, 0x60, 0x14, 0x8c, 0x82, 0x51, 0x00, 0x00, 0x05,
122+
0x78, 0x00, 0x01, 0x1e, 0xcd, 0x28, 0xcd, 0x00, 0x00, 0x00, 0x00, 0x49,
123+
0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82,
124+
];

0 commit comments

Comments
 (0)