From 8763271056c64cf0a41759f7ca8c7447ca7488c7 Mon Sep 17 00:00:00 2001 From: M57 <41645758+Hy4ri@users.noreply.github.com> Date: Sat, 20 Jun 2026 17:32:24 +0300 Subject: [PATCH] fix(#101): collect events once in init to prevent duplicate collectors on reconnect (#169) --- .../hermescontrol/ui/chat/ChatViewModel.kt | 11 +++---- .../ui/chat/ChatViewModelTest.kt | 33 +++++++++++++++++++ 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/m57/hermescontrol/ui/chat/ChatViewModel.kt b/app/src/main/java/com/m57/hermescontrol/ui/chat/ChatViewModel.kt index 5f16ff9c..928c0f84 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/chat/ChatViewModel.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/chat/ChatViewModel.kt @@ -113,6 +113,11 @@ class ChatViewModel( init { connectWebSocket() + viewModelScope.launch { + wsClient.events.collect { event -> + handleWsEvent(event) + } + } if (startCleanup) { startPendingRequestCleanup() } @@ -128,12 +133,6 @@ class ChatViewModel( viewModelScope.launch(Dispatchers.IO) { wsClient.connect() } - - viewModelScope.launch { - wsClient.events.collect { event -> - handleWsEvent(event) - } - } } // ── WS Event Handling ──────────────────────────────────────────────── diff --git a/app/src/test/java/com/m57/hermescontrol/ui/chat/ChatViewModelTest.kt b/app/src/test/java/com/m57/hermescontrol/ui/chat/ChatViewModelTest.kt index 4f26d780..14772454 100644 --- a/app/src/test/java/com/m57/hermescontrol/ui/chat/ChatViewModelTest.kt +++ b/app/src/test/java/com/m57/hermescontrol/ui/chat/ChatViewModelTest.kt @@ -657,4 +657,37 @@ class ChatViewModelTest { assertNull(state.streamingMessage) assertNull(state.clarifyRequest) } + + @Test + fun testReconnectDoesNotDuplicateEventCollection() = + runTest { + val viewModel = ChatViewModel(app, startCleanup = false) + advanceUntilIdle() + + // Setup active session + var createReqId = "" + every { HermesWsClient.send(WsMethods.SESSION_CREATE, any(), any()) } answers { + createReqId = "create-req-reconnect-test" + val onSent = arg<((String) -> Unit)?>(2) + onSent?.invoke(createReqId) + createReqId + } + mockEventsFlow.emit(WsEvent.GatewayReady(null)) + advanceUntilIdle() + mockEventsFlow.emit(WsEvent.RpcResult(createReqId, mapOf("session_id" to "session-123"))) + advanceUntilIdle() + + // Trigger reconnect + viewModel.reconnect() + advanceUntilIdle() + + // Emit a message token + mockEventsFlow.emit(WsEvent.MessageStart("session-123")) + mockEventsFlow.emit(WsEvent.MessageToken("Hello", "session-123")) + advanceUntilIdle() + + val state = viewModel.uiState.value + // Content should be "Hello", not "HelloHello" (no duplicate collectors running) + assertEquals("Hello", state.streamingMessage?.content) + } }