From 473986fe645a20486daf162d1f17fea893071cfa Mon Sep 17 00:00:00 2001 From: npub1ux8n2yfs8qfvgd75s7kyhar2mztac355v6vmrz4juc9l3msw4pgstums9e Date: Tue, 14 Jul 2026 11:55:10 -0700 Subject: [PATCH] test(mobile): cover auth disconnect deduplication Assert that a stream closure during NIP-42 authentication produces exactly one disconnect callback. Co-authored-by: npub1ux8n2yfs8qfvgd75s7kyhar2mztac355v6vmrz4juc9l3msw4pgstums9e Signed-off-by: npub1ux8n2yfs8qfvgd75s7kyhar2mztac355v6vmrz4juc9l3msw4pgstums9e --- .../test/shared/relay/relay_socket_test.dart | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/mobile/test/shared/relay/relay_socket_test.dart b/mobile/test/shared/relay/relay_socket_test.dart index 5546a822b..e807c9bb1 100644 --- a/mobile/test/shared/relay/relay_socket_test.dart +++ b/mobile/test/shared/relay/relay_socket_test.dart @@ -27,6 +27,30 @@ void main() { expect(channel.closeCount, 1); }); + test('auth-phase stream closure reports one disconnection', () async { + final channel = _ReadyWebSocketChannel(); + final disconnections = []; + final socket = RelaySocket( + wsUrl: 'wss://relay.example', + nsec: null, + onMessage: (_) {}, + onConnected: () => fail('socket must not connect'), + onDisconnected: disconnections.add, + channelFactory: (_) => channel, + ); + + final connecting = socket.connect(); + await Future.delayed(Duration.zero); + expect(socket.state, SocketState.authenticating); + + await channel.closeStream(); + await connecting; + + expect(disconnections, hasLength(1)); + expect(disconnections.single, isA()); + expect(socket.state, SocketState.disconnected); + }); + test('hung handshake times out, closes, and reports disconnection', () async { final channel = _HungWebSocketChannel(); final disconnected = Completer(); @@ -67,6 +91,8 @@ class _ReadyWebSocketChannel implements WebSocketChannel { @override Stream get stream => _controller.stream; + Future closeStream() => _controller.close(); + @override late final WebSocketSink sink = _RecordingWebSocketSink( _controller.sink,