-
Notifications
You must be signed in to change notification settings - Fork 362
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Create a fake WebSocket implementation (#1200)
* Create a fake WebSocket implementation * Better example * Update pkgs/web_socket/lib/testing.dart Co-authored-by: Nate Bosch <[email protected]> --------- Co-authored-by: Nate Bosch <[email protected]>
- Loading branch information
1 parent
4d2f9f9
commit 5c01453
Showing
5 changed files
with
189 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file | ||
// for details. All rights reserved. Use of this source code is governed by a | ||
// BSD-style license that can be found in the LICENSE file. | ||
|
||
import 'dart:async'; | ||
import 'dart:typed_data'; | ||
|
||
import '../web_socket.dart'; | ||
import 'utils.dart'; | ||
import 'web_socket.dart'; | ||
|
||
class FakeWebSocket implements WebSocket { | ||
late FakeWebSocket _other; | ||
|
||
final String _protocol; | ||
final _events = StreamController<WebSocketEvent>(); | ||
|
||
FakeWebSocket(this._protocol); | ||
|
||
@override | ||
Future<void> close([int? code, String? reason]) async { | ||
if (_events.isClosed) { | ||
throw WebSocketConnectionClosed(); | ||
} | ||
|
||
checkCloseCode(code); | ||
checkCloseReason(reason); | ||
|
||
unawaited(_events.close()); | ||
if (!_other._events.isClosed) { | ||
_other._events.add(CloseReceived(code ?? 1005, reason ?? '')); | ||
unawaited(_other._events.close()); | ||
} | ||
} | ||
|
||
@override | ||
Stream<WebSocketEvent> get events => _events.stream; | ||
|
||
@override | ||
String get protocol => _protocol; | ||
|
||
@override | ||
void sendBytes(Uint8List b) { | ||
if (_events.isClosed) { | ||
throw WebSocketConnectionClosed(); | ||
} | ||
if (_other._events.isClosed) return; | ||
_other._events.add(BinaryDataReceived(b)); | ||
} | ||
|
||
@override | ||
void sendText(String s) { | ||
if (_events.isClosed) { | ||
throw WebSocketConnectionClosed(); | ||
} | ||
if (_other._events.isClosed) return; | ||
_other._events.add(TextDataReceived(s)); | ||
} | ||
} | ||
|
||
/// Create a pair of fake [WebSocket]s that are connected to each other. | ||
/// | ||
/// Sending a message on one [WebSocket] will result in that same message being | ||
/// received by the other. | ||
/// | ||
/// This can be useful in constructing tests. | ||
/// | ||
/// For example: | ||
/// | ||
/// ```dart | ||
/// import 'dart:async'; | ||
/// | ||
/// import 'package:test/test.dart'; | ||
/// import 'package:web_socket/src/web_socket.dart'; | ||
/// import 'package:web_socket/testing.dart'; | ||
/// import 'package:web_socket/web_socket.dart'; | ||
/// | ||
/// Future<void> fakeTimeServer(WebSocket webSocket, String time) async { | ||
/// await webSocket.events.forEach((event) { | ||
/// switch (event) { | ||
/// case TextDataReceived(): | ||
/// case BinaryDataReceived(): | ||
/// webSocket.sendText(time); | ||
/// case CloseReceived(): | ||
/// } | ||
/// }); | ||
/// } | ||
/// | ||
/// Future<DateTime> getTime(WebSocket webSocket) async { | ||
/// webSocket.sendText(''); | ||
/// final time = switch (await webSocket.events.first) { | ||
/// TextDataReceived(:final text) => DateTime.parse(text), | ||
/// _ => throw Exception('unexpected response') | ||
/// }; | ||
/// await webSocket.close(); | ||
/// return time; | ||
/// } | ||
/// | ||
/// void main() async { | ||
/// late WebSocket client; | ||
/// late WebSocket server; | ||
/// | ||
/// setUp(() { | ||
/// (client, server) = fakes(); | ||
/// }); | ||
/// | ||
/// test('test valid time', () async { | ||
/// unawaited(fakeTimeServer(server, '2024-05-15T01:18:10.456Z')); | ||
/// expect( | ||
/// await getTime(client), | ||
/// DateTime.parse('2024-05-15T01:18:10.456Z')); | ||
/// }); | ||
/// } | ||
/// ``` | ||
(WebSocket, WebSocket) fakes({String protocol = ''}) { | ||
final peer1 = FakeWebSocket(protocol); | ||
final peer2 = FakeWebSocket(protocol); | ||
|
||
peer1._other = peer2; | ||
peer2._other = peer1; | ||
|
||
return (peer1, peer2); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export 'src/fake_web_socket.dart' show fakes; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file | ||
// for details. All rights reserved. Use of this source code is governed by a | ||
// BSD-style license that can be found in the LICENSE file. | ||
|
||
import 'package:web_socket/src/fake_web_socket.dart'; | ||
import 'package:web_socket/web_socket.dart'; | ||
import 'package:web_socket_conformance_tests/web_socket_conformance_tests.dart'; | ||
|
||
/// Forward data received from [from] to [to]. | ||
void proxy(WebSocket from, WebSocket to) { | ||
from.events.listen((event) { | ||
try { | ||
switch (event) { | ||
case TextDataReceived(:final text): | ||
to.sendText(text); | ||
case BinaryDataReceived(:final data): | ||
to.sendBytes(data); | ||
case CloseReceived(:var code, :final reason): | ||
if (code != null && (code < 3000 || code > 4999)) { | ||
code = null; | ||
} | ||
to.close(code, reason); | ||
} | ||
} on WebSocketConnectionClosed { | ||
// `to` may have been closed locally so ignore failures to forward the | ||
// data. | ||
} | ||
}); | ||
} | ||
|
||
/// Create a bidirectional proxy relationship between [a] and [b]. | ||
/// | ||
/// That means that events received by [a] will be forwarded to [b] and | ||
/// vise-versa. | ||
void bidirectionalProxy(WebSocket a, WebSocket b) { | ||
proxy(a, b); | ||
proxy(b, a); | ||
} | ||
|
||
void main() { | ||
// In order to use `testAll`, we need to provide a method that will connect | ||
// to a real WebSocket server. | ||
// | ||
// The approach is to connect to the server with a real WebSocket and forward | ||
// the data received by that data to one of the fakes. | ||
// | ||
// Like: | ||
// | ||
// 'hello' sendText('hello') TextDataReceived('hello') | ||
// [Server] -> [realClient] -> [FakeServer] -> [fakeClient] | ||
Future<WebSocket> connect(Uri url, {Iterable<String>? protocols}) async { | ||
final realClient = await WebSocket.connect(url, protocols: protocols); | ||
final (fakeServer, fakeClient) = fakes(protocol: realClient.protocol); | ||
bidirectionalProxy(realClient, fakeServer); | ||
return fakeClient; | ||
} | ||
|
||
testAll(connect); | ||
} |