Skip to content

Commit 5c75da6

Browse files
authored
Add tests for sending "cookie" and receiving "set-cookie" headers (#1113)
1 parent c2a6d64 commit 5c75da6

12 files changed

+331
-5
lines changed

pkgs/cronet_http/example/integration_test/client_test.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ Future<void> testConformance() async {
1313
() => testAll(
1414
CronetClient.defaultCronetEngine,
1515
canStreamRequestBody: false,
16+
canReceiveSetCookieHeaders: true,
17+
canSendCookieHeaders: true,
1618
));
1719

1820
group('from cronet engine', () {

pkgs/cupertino_http/example/integration_test/client_conformance_test.dart

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,19 @@ void main() {
1111
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
1212

1313
group('defaultSessionConfiguration', () {
14-
testAll(CupertinoClient.defaultSessionConfiguration);
14+
testAll(
15+
CupertinoClient.defaultSessionConfiguration,
16+
canReceiveSetCookieHeaders: true,
17+
canSendCookieHeaders: true,
18+
);
1519
});
1620
group('fromSessionConfiguration', () {
1721
final config = URLSessionConfiguration.ephemeralSessionConfiguration();
18-
testAll(() => CupertinoClient.fromSessionConfiguration(config),
19-
canWorkInIsolates: false);
22+
testAll(
23+
() => CupertinoClient.fromSessionConfiguration(config),
24+
canWorkInIsolates: false,
25+
canReceiveSetCookieHeaders: true,
26+
canSendCookieHeaders: true,
27+
);
2028
});
2129
}

pkgs/http/test/io/client_conformance_test.dart

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ import 'package:http_client_conformance_tests/http_client_conformance_tests.dart
1010
import 'package:test/test.dart';
1111

1212
void main() {
13-
testAll(IOClient.new, preservesMethodCase: false // https://dartbug.com/54187
14-
);
13+
testAll(
14+
IOClient.new, preservesMethodCase: false, // https://dartbug.com/54187
15+
canReceiveSetCookieHeaders: true,
16+
canSendCookieHeaders: true,
17+
);
1518
}

pkgs/http_client_conformance_tests/lib/http_client_conformance_tests.dart

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@ import 'src/multiple_clients_tests.dart';
1111
import 'src/redirect_tests.dart';
1212
import 'src/request_body_streamed_tests.dart';
1313
import 'src/request_body_tests.dart';
14+
import 'src/request_cookies_test.dart';
1415
import 'src/request_headers_tests.dart';
1516
import 'src/request_methods_tests.dart';
1617
import 'src/response_body_streamed_test.dart';
1718
import 'src/response_body_tests.dart';
19+
import 'src/response_cookies_test.dart';
1820
import 'src/response_headers_tests.dart';
1921
import 'src/response_status_line_tests.dart';
2022
import 'src/server_errors_test.dart';
@@ -27,10 +29,12 @@ export 'src/multiple_clients_tests.dart' show testMultipleClients;
2729
export 'src/redirect_tests.dart' show testRedirect;
2830
export 'src/request_body_streamed_tests.dart' show testRequestBodyStreamed;
2931
export 'src/request_body_tests.dart' show testRequestBody;
32+
export 'src/request_cookies_test.dart' show testRequestCookies;
3033
export 'src/request_headers_tests.dart' show testRequestHeaders;
3134
export 'src/request_methods_tests.dart' show testRequestMethods;
3235
export 'src/response_body_streamed_test.dart' show testResponseBodyStreamed;
3336
export 'src/response_body_tests.dart' show testResponseBody;
37+
export 'src/response_cookies_test.dart' show testResponseCookies;
3438
export 'src/response_headers_tests.dart' show testResponseHeaders;
3539
export 'src/response_status_line_tests.dart' show testResponseStatusLine;
3640
export 'src/server_errors_test.dart' show testServerErrors;
@@ -54,6 +58,12 @@ export 'src/server_errors_test.dart' show testServerErrors;
5458
/// If [preservesMethodCase] is `false` then tests that assume that the
5559
/// [Client] preserves custom request method casing will be skipped.
5660
///
61+
/// If [canSendCookieHeaders] is `false` then tests that require that "cookie"
62+
/// headers be sent by the client will not be run.
63+
///
64+
/// If [canReceiveSetCookieHeaders] is `false` then tests that require that
65+
/// "set-cookie" headers be received by the client will not be run.
66+
///
5767
/// The tests are run against a series of HTTP servers that are started by the
5868
/// tests. If the tests are run in the browser, then the test servers are
5969
/// started in another process. Otherwise, the test servers are run in-process.
@@ -64,6 +74,8 @@ void testAll(
6474
bool redirectAlwaysAllowed = false,
6575
bool canWorkInIsolates = true,
6676
bool preservesMethodCase = false,
77+
bool canSendCookieHeaders = false,
78+
bool canReceiveSetCookieHeaders = false,
6779
}) {
6880
testRequestBody(clientFactory());
6981
testRequestBodyStreamed(clientFactory(),
@@ -82,4 +94,8 @@ void testAll(
8294
testMultipleClients(clientFactory);
8395
testClose(clientFactory);
8496
testIsolate(clientFactory, canWorkInIsolates: canWorkInIsolates);
97+
testRequestCookies(clientFactory(),
98+
canSendCookieHeaders: canSendCookieHeaders);
99+
testResponseCookies(clientFactory(),
100+
canReceiveSetCookieHeaders: canReceiveSetCookieHeaders);
85101
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// Copyright (c) 2024, 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:convert';
7+
import 'dart:io';
8+
9+
import 'package:stream_channel/stream_channel.dart';
10+
11+
/// Starts an HTTP server that captures "cookie" headers.
12+
///
13+
/// Channel protocol:
14+
/// On Startup:
15+
/// - send port
16+
/// On Request Received:
17+
/// - send a list of header lines starting with "cookie:"
18+
/// When Receive Anything:
19+
/// - exit
20+
void hybridMain(StreamChannel<Object?> channel) async {
21+
late ServerSocket server;
22+
23+
server = (await ServerSocket.bind('localhost', 0))
24+
..listen((Socket socket) async {
25+
final request = utf8.decoder.bind(socket).transform(const LineSplitter());
26+
27+
final cookies = <String>[];
28+
request.listen((line) {
29+
if (line.toLowerCase().startsWith('cookie:')) {
30+
cookies.add(line);
31+
}
32+
33+
if (line.isEmpty) {
34+
// A blank line indicates the end of the headers.
35+
channel.sink.add(cookies);
36+
}
37+
});
38+
39+
socket.writeAll(
40+
[
41+
'HTTP/1.1 200 OK',
42+
'Access-Control-Allow-Origin: *',
43+
'Content-Length: 0',
44+
'\r\n', // Add \r\n at the end of this header section.
45+
],
46+
'\r\n', // Separate each field by \r\n.
47+
);
48+
await socket.close();
49+
});
50+
51+
channel.sink.add(server.port);
52+
await channel
53+
.stream.first; // Any writes indicates that the server should exit.
54+
unawaited(server.close());
55+
}

pkgs/http_client_conformance_tests/lib/src/request_cookies_server_vm.dart

Lines changed: 14 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkgs/http_client_conformance_tests/lib/src/request_cookies_server_web.dart

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Copyright (c) 2024, 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 'package:async/async.dart';
6+
import 'package:http/http.dart';
7+
import 'package:stream_channel/stream_channel.dart';
8+
import 'package:test/test.dart';
9+
10+
import 'request_cookies_server_vm.dart'
11+
if (dart.library.js_interop) 'request_cookies_server_web.dart';
12+
13+
// The an HTTP header into [name, value].
14+
final headerSplitter = RegExp(':[ \t]+');
15+
16+
/// Tests that the [Client] correctly sends "cookie" headers in the request.
17+
///
18+
/// If [canSendCookieHeaders] is `false` then tests that require that "cookie"
19+
/// headers be sent by the client will not be run.
20+
void testRequestCookies(Client client,
21+
{bool canSendCookieHeaders = false}) async {
22+
group('request cookies', () {
23+
late final String host;
24+
late final StreamChannel<Object?> httpServerChannel;
25+
late final StreamQueue<Object?> httpServerQueue;
26+
27+
setUpAll(() async {
28+
httpServerChannel = await startServer();
29+
httpServerQueue = StreamQueue(httpServerChannel.stream);
30+
host = 'localhost:${await httpServerQueue.nextAsInt}';
31+
});
32+
tearDownAll(() => httpServerChannel.sink.add(null));
33+
34+
test('one cookie', () async {
35+
await client
36+
.get(Uri.http(host, ''), headers: {'cookie': 'SID=298zf09hf012fh2'});
37+
38+
final cookies = (await httpServerQueue.next as List).cast<String>();
39+
expect(cookies, hasLength(1));
40+
final [header, value] = cookies[0].split(headerSplitter);
41+
expect(header.toLowerCase(), 'cookie');
42+
expect(value, 'SID=298zf09hf012fh2');
43+
}, skip: canSendCookieHeaders ? false : 'cannot send cookie headers');
44+
45+
test('multiple cookies semicolon separated', () async {
46+
await client.get(Uri.http(host, ''),
47+
headers: {'cookie': 'SID=298zf09hf012fh2; lang=en-US'});
48+
49+
final cookies = (await httpServerQueue.next as List).cast<String>();
50+
expect(cookies, hasLength(1));
51+
final [header, value] = cookies[0].split(headerSplitter);
52+
expect(header.toLowerCase(), 'cookie');
53+
expect(value, 'SID=298zf09hf012fh2; lang=en-US');
54+
}, skip: canSendCookieHeaders ? false : 'cannot send cookie headers');
55+
});
56+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Copyright (c) 2024, 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 returns a custom status line.
12+
///
13+
/// Channel protocol:
14+
/// On Startup:
15+
/// - send port
16+
/// On Request Received:
17+
/// - load response status line from channel
18+
/// - exit
19+
void hybridMain(StreamChannel<Object?> channel) async {
20+
late HttpServer server;
21+
final clientQueue = StreamQueue(channel.stream);
22+
23+
server = (await HttpServer.bind('localhost', 0))
24+
..listen((request) async {
25+
await request.drain<void>();
26+
final socket = await request.response.detachSocket(writeHeaders: false);
27+
28+
final headers = (await clientQueue.next) as List;
29+
socket.writeAll(
30+
[
31+
'HTTP/1.1 200 OK',
32+
'Access-Control-Allow-Origin: *',
33+
'Content-Length: 0',
34+
...headers,
35+
'\r\n', // Add \r\n at the end of this header section.
36+
],
37+
'\r\n', // Separate each field by \r\n.
38+
);
39+
await socket.close();
40+
unawaited(server.close());
41+
});
42+
43+
channel.sink.add(server.port);
44+
}

pkgs/http_client_conformance_tests/lib/src/response_cookies_server_vm.dart

Lines changed: 14 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)