Skip to content

Commit a479221

Browse files
authored
Add browser tests for the response body. (#712)
1 parent 0e20ff9 commit a479221

File tree

6 files changed

+158
-65
lines changed

6 files changed

+158
-65
lines changed

pkgs/http_client_conformance_tests/lib/http_client_conformance_tests.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,15 @@ import 'src/redirect_tests.dart';
88
import 'src/request_body_streamed_tests.dart';
99
import 'src/request_body_tests.dart';
1010
import 'src/request_headers_tests.dart';
11+
import 'src/response_body_streamed_test.dart';
1112
import 'src/response_body_tests.dart';
1213
import 'src/response_headers_tests.dart';
1314

1415
export 'src/redirect_tests.dart' show testRedirect;
1516
export 'src/request_body_streamed_tests.dart' show testRequestBodyStreamed;
1617
export 'src/request_body_tests.dart' show testRequestBody;
1718
export 'src/request_headers_tests.dart' show testRequestHeaders;
19+
export 'src/response_body_streamed_test.dart' show testResponseBodyStreamed;
1820
export 'src/response_body_tests.dart' show testResponseBody;
1921
export 'src/response_headers_tests.dart' show testResponseHeaders;
2022

@@ -37,6 +39,8 @@ void testAll(Client client,
3739
testRequestBody(client);
3840
testRequestBodyStreamed(client, canStreamRequestBody: canStreamRequestBody);
3941
testResponseBody(client, canStreamResponseBody: canStreamResponseBody);
42+
testResponseBodyStreamed(client,
43+
canStreamResponseBody: canStreamResponseBody);
4044
testRequestHeaders(client);
4145
testResponseHeaders(client);
4246
testRedirect(client, redirectAlwaysAllowed: redirectAlwaysAllowed);
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'dart:async';
6+
import 'dart:io';
7+
8+
import 'package:stream_channel/stream_channel.dart';
9+
10+
/// Starts an HTTP server that responds with "Hello World!"
11+
///
12+
/// Channel protocol:
13+
/// On Startup:
14+
/// - send port
15+
/// When Receive Anything:
16+
/// - exit
17+
void hybridMain(StreamChannel<Object?> channel) async {
18+
final server = (await HttpServer.bind('localhost', 0))
19+
..listen((request) async {
20+
await request.drain<void>();
21+
request.response.headers.set('Access-Control-Allow-Origin', '*');
22+
request.response.headers.set('Content-Type', 'text/plain');
23+
request.response.write('Hello World!');
24+
await request.response.close();
25+
});
26+
27+
channel.sink.add(server.port);
28+
await channel
29+
.stream.first; // Any writes indicates that the server should exit.
30+
unawaited(server.close());
31+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'dart:async';
6+
import 'dart:io';
7+
8+
import 'package:async/async.dart';
9+
import 'package:stream_channel/stream_channel.dart';
10+
11+
/// Starts an HTTP server that sends a stream of integers.
12+
///
13+
/// Channel protocol:
14+
/// On Startup:
15+
/// - send port
16+
/// When Receive Anything:
17+
/// - close current request
18+
/// - exit server
19+
void hybridMain(StreamChannel<Object?> channel) async {
20+
final channelQueue = StreamQueue(channel.stream);
21+
var serverWriting = true;
22+
23+
late HttpServer server;
24+
server = (await HttpServer.bind('localhost', 0))
25+
..listen((request) async {
26+
await request.drain<void>();
27+
request.response.headers.set('Access-Control-Allow-Origin', '*');
28+
request.response.headers.set('Content-Type', 'text/plain');
29+
serverWriting = true;
30+
for (var i = 0; serverWriting; ++i) {
31+
request.response.write('$i\n');
32+
await request.response.flush();
33+
// Let the event loop run.
34+
await Future(() {});
35+
}
36+
await request.response.close();
37+
unawaited(server.close());
38+
});
39+
40+
channel.sink.add(server.port);
41+
unawaited(channelQueue.next.then((value) => serverWriting = false));
42+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'dart:convert';
6+
7+
import 'package:async/async.dart';
8+
import 'package:http/http.dart';
9+
import 'package:stream_channel/stream_channel.dart';
10+
import 'package:test/test.dart';
11+
12+
/// Tests that the [Client] correctly implements HTTP responses with bodies of
13+
/// unbounded size.
14+
///
15+
/// If [canStreamResponseBody] is `false` then tests that assume that the
16+
/// [Client] supports receiving HTTP responses with unbounded body sizes will
17+
/// be skipped
18+
void testResponseBodyStreamed(Client client,
19+
{bool canStreamResponseBody = true}) async {
20+
group('streamed response body', () {
21+
late final String host;
22+
late final StreamChannel<Object?> httpServerChannel;
23+
late final StreamQueue<Object?> httpServerQueue;
24+
25+
setUpAll(() async {
26+
httpServerChannel =
27+
spawnHybridUri('../lib/src/response_body_streamed_server.dart');
28+
httpServerQueue = StreamQueue(httpServerChannel.stream);
29+
host = 'localhost:${await httpServerQueue.next}';
30+
});
31+
tearDownAll(() => httpServerChannel.sink.add(null));
32+
33+
test('large response streamed without content length', () async {
34+
// The server continuously streams data to the client until
35+
// instructed to stop.
36+
//
37+
// This ensures that the client supports streamed responses.
38+
39+
final request = Request('GET', Uri.http(host, ''));
40+
final response = await client.send(request);
41+
var lastReceived = 0;
42+
await const LineSplitter()
43+
.bind(const Utf8Decoder().bind(response.stream))
44+
.forEach((s) {
45+
lastReceived = int.parse(s.trim());
46+
if (lastReceived == 1000) {
47+
httpServerChannel.sink.add(true);
48+
}
49+
});
50+
expect(response.headers['content-type'], 'text/plain');
51+
expect(lastReceived, greaterThanOrEqualTo(1000));
52+
}, skip: canStreamResponseBody ? false : 'does not stream response bodies');
53+
});
54+
}

pkgs/http_client_conformance_tests/lib/src/response_body_tests.dart

Lines changed: 25 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,9 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

5-
import 'dart:async';
6-
import 'dart:convert';
7-
import 'dart:io';
8-
5+
import 'package:async/async.dart';
96
import 'package:http/http.dart';
7+
import 'package:stream_channel/stream_channel.dart';
108
import 'package:test/test.dart';
119

1210
/// Tests that the [Client] correctly implements HTTP responses with bodies.
@@ -17,78 +15,40 @@ import 'package:test/test.dart';
1715
void testResponseBody(Client client,
1816
{bool canStreamResponseBody = true}) async {
1917
group('response body', () {
18+
late final String host;
19+
late final StreamChannel<Object?> httpServerChannel;
20+
late final StreamQueue<Object?> httpServerQueue;
21+
const message = 'Hello World!';
22+
23+
setUpAll(() async {
24+
httpServerChannel =
25+
spawnHybridUri('../lib/src/response_body_server.dart');
26+
httpServerQueue = StreamQueue(httpServerChannel.stream);
27+
host = 'localhost:${await httpServerQueue.next}';
28+
});
29+
tearDownAll(() => httpServerChannel.sink.add(null));
30+
2031
test('small response with content length', () async {
21-
const message = 'Hello World!';
22-
final server = (await HttpServer.bind('localhost', 0))
23-
..listen((request) async {
24-
await request.drain<void>();
25-
request.response.headers.set('Content-Type', 'text/plain');
26-
request.response.write(message);
27-
await request.response.close();
28-
});
29-
final response =
30-
await client.get(Uri.http('localhost:${server.port}', ''));
32+
final response = await client.get(Uri.http(host, ''));
3133
expect(response.body, message);
3234
expect(response.bodyBytes, message.codeUnits);
3335
expect(response.contentLength, message.length);
3436
expect(response.headers['content-type'], 'text/plain');
35-
await server.close();
3637
});
3738

3839
test('small response streamed without content length', () async {
39-
const message = 'Hello World!';
40-
final server = (await HttpServer.bind('localhost', 0))
41-
..listen((request) async {
42-
await request.drain<void>();
43-
request.response.headers.set('Content-Type', 'text/plain');
44-
request.response.write(message);
45-
await request.response.close();
46-
});
47-
final request = Request('GET', Uri.http('localhost:${server.port}', ''));
40+
final request = Request('GET', Uri.http(host, ''));
4841
final response = await client.send(request);
4942
expect(await response.stream.bytesToString(), message);
50-
expect(response.contentLength, null);
43+
if (canStreamResponseBody) {
44+
expect(response.contentLength, null);
45+
} else {
46+
// If the response body is small then the Client can emulate a streamed
47+
// response without streaming. But `response.contentLength` may or
48+
// may not be set.
49+
expect(response.contentLength, isIn([null, 12]));
50+
}
5151
expect(response.headers['content-type'], 'text/plain');
52-
await server.close();
5352
});
54-
55-
test('large response streamed without content length', () async {
56-
// The server continuously streams data to the client until
57-
// instructed to stop (by setting `serverWriting` to `false`).
58-
// The client sets `serverWriting` to `false` after it has
59-
// already received some data.
60-
//
61-
// This ensures that the client supports streamed responses.
62-
var serverWriting = false;
63-
final server = (await HttpServer.bind('localhost', 0))
64-
..listen((request) async {
65-
await request.drain<void>();
66-
request.response.headers.set('Content-Type', 'text/plain');
67-
serverWriting = true;
68-
for (var i = 0; serverWriting; ++i) {
69-
request.response.write('$i\n');
70-
await request.response.flush();
71-
// Let the event loop run.
72-
await Future<void>.delayed(const Duration());
73-
}
74-
await request.response.close();
75-
});
76-
final request = Request('GET', Uri.http('localhost:${server.port}', ''));
77-
final response = await client.send(request);
78-
var lastReceived = 0;
79-
await const LineSplitter()
80-
.bind(const Utf8Decoder().bind(response.stream))
81-
.forEach((s) {
82-
lastReceived = int.parse(s.trim());
83-
if (lastReceived < 1000) {
84-
expect(serverWriting, true);
85-
} else {
86-
serverWriting = false;
87-
}
88-
});
89-
expect(response.headers['content-type'], 'text/plain');
90-
expect(lastReceived, greaterThanOrEqualTo(1000));
91-
await server.close();
92-
}, skip: canStreamResponseBody ? false : 'does not stream response bodies');
9353
});
9454
}

pkgs/http_client_conformance_tests/test/browser_client_test.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,7 @@ void main() {
1616
testRequestBody(client);
1717
testRedirect(client, redirectAlwaysAllowed: true);
1818
testRequestBodyStreamed(client, canStreamRequestBody: false);
19+
testResponseBody(client, canStreamResponseBody: false);
20+
testResponseBodyStreamed(client, canStreamResponseBody: false);
1921
});
2022
}

0 commit comments

Comments
 (0)