From 35c608b2bf4c72a9c64e3b3f3d4b1b6a37ea8f2b Mon Sep 17 00:00:00 2001 From: M57 Date: Sun, 21 Jun 2026 16:03:36 +0300 Subject: [PATCH 1/3] fix(#201): fix platforms endpoint response shape mismatch GET /api/messaging/platforms returns a JSON object with a platforms array inside, but the app expected a bare array. Wraps the response in a new MessagingPlatformResponse model. - Adds MessagingPlatformResponse.kt wrapper model - Updates HermesApiService.kt return type + adds profile query param - Updates ChannelsViewModel.kt to unwrap .platforms - Updates E2eIntegrationTest.kt mock to match new response shape --- .../data/model/MessagingPlatformResponse.kt | 7 +++++++ .../m57/hermescontrol/data/remote/HermesApiService.kt | 4 ++-- .../m57/hermescontrol/ui/channels/ChannelsViewModel.kt | 2 +- .../java/com/m57/hermescontrol/E2eIntegrationTest.kt | 10 +++++++++- 4 files changed, 19 insertions(+), 4 deletions(-) create mode 100644 app/src/main/java/com/m57/hermescontrol/data/model/MessagingPlatformResponse.kt diff --git a/app/src/main/java/com/m57/hermescontrol/data/model/MessagingPlatformResponse.kt b/app/src/main/java/com/m57/hermescontrol/data/model/MessagingPlatformResponse.kt new file mode 100644 index 00000000..5580ae56 --- /dev/null +++ b/app/src/main/java/com/m57/hermescontrol/data/model/MessagingPlatformResponse.kt @@ -0,0 +1,7 @@ +package com.m57.hermescontrol.data.model + +data class MessagingPlatformResponse( + val env_path: String, + val gateway_start_command: String, + val platforms: List, +) diff --git a/app/src/main/java/com/m57/hermescontrol/data/remote/HermesApiService.kt b/app/src/main/java/com/m57/hermescontrol/data/remote/HermesApiService.kt index 5e9e335f..6d5a0e39 100644 --- a/app/src/main/java/com/m57/hermescontrol/data/remote/HermesApiService.kt +++ b/app/src/main/java/com/m57/hermescontrol/data/remote/HermesApiService.kt @@ -16,7 +16,7 @@ import com.m57.hermescontrol.data.model.KanbanTask import com.m57.hermescontrol.data.model.LogResponse import com.m57.hermescontrol.data.model.McpServerToggleRequest import com.m57.hermescontrol.data.model.McpServersResponse -import com.m57.hermescontrol.data.model.MessagingPlatform +import com.m57.hermescontrol.data.model.MessagingPlatformResponse import com.m57.hermescontrol.data.model.MessagingPlatformUpdate import com.m57.hermescontrol.data.model.ModelOptionsResponse import com.m57.hermescontrol.data.model.PairingApproveRequest @@ -253,7 +253,7 @@ interface HermesApiService { ): Response @GET("api/messaging/platforms") - suspend fun getMessagingPlatforms(): Response> + suspend fun getMessagingPlatforms(@Query("profile") profile: String? = null): Response @PUT("api/messaging/platforms/{platform_id}") suspend fun configurePlatform( diff --git a/app/src/main/java/com/m57/hermescontrol/ui/channels/ChannelsViewModel.kt b/app/src/main/java/com/m57/hermescontrol/ui/channels/ChannelsViewModel.kt index 18bb9938..90f3ba50 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/channels/ChannelsViewModel.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/channels/ChannelsViewModel.kt @@ -35,7 +35,7 @@ class ChannelsViewModel : ViewModel() { } when (result) { is NetworkResult.Success -> { - _uiState.update { it.copy(isLoading = false, platforms = result.data.orEmpty()) } + _uiState.update { it.copy(isLoading = false, platforms = result.data?.platforms.orEmpty()) } } is NetworkResult.Failure -> { diff --git a/app/src/test/java/com/m57/hermescontrol/E2eIntegrationTest.kt b/app/src/test/java/com/m57/hermescontrol/E2eIntegrationTest.kt index c77fe129..5dd78581 100644 --- a/app/src/test/java/com/m57/hermescontrol/E2eIntegrationTest.kt +++ b/app/src/test/java/com/m57/hermescontrol/E2eIntegrationTest.kt @@ -14,6 +14,7 @@ import com.m57.hermescontrol.data.model.KanbanColumn import com.m57.hermescontrol.data.model.KanbanTask import com.m57.hermescontrol.data.model.LogResponse import com.m57.hermescontrol.data.model.MessagingPlatform +import com.m57.hermescontrol.data.model.MessagingPlatformResponse import com.m57.hermescontrol.data.model.ModelOptionsResponse import com.m57.hermescontrol.data.model.ModelProvider import com.m57.hermescontrol.data.model.PluginInfo @@ -895,7 +896,14 @@ class E2eIntegrationTest { fun testChannelsConfiguration_success() = runTest { val platform = MessagingPlatform("telegram", "Telegram", false, false, emptyList()) - coEvery { mockApiService.getMessagingPlatforms() } returns Response.success(listOf(platform)) + coEvery { mockApiService.getMessagingPlatforms() } returns + Response.success( + MessagingPlatformResponse( + env_path = "/tmp/.env", + gateway_start_command = "hermes gateway start", + platforms = listOf(platform), + ), + ) coEvery { mockApiService.configurePlatform("telegram", any()) } returns Response.success(Unit) val viewModel = ChannelsViewModel() From d4eb36f032f92822f084faf0543825e7a6d64813 Mon Sep 17 00:00:00 2001 From: M57 Date: Sun, 21 Jun 2026 16:12:35 +0300 Subject: [PATCH 2/3] fix: ktlint parameter-list-wrapping on getMessagingPlatforms --- .../com/m57/hermescontrol/data/remote/HermesApiService.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/m57/hermescontrol/data/remote/HermesApiService.kt b/app/src/main/java/com/m57/hermescontrol/data/remote/HermesApiService.kt index 6d5a0e39..2c165659 100644 --- a/app/src/main/java/com/m57/hermescontrol/data/remote/HermesApiService.kt +++ b/app/src/main/java/com/m57/hermescontrol/data/remote/HermesApiService.kt @@ -253,7 +253,9 @@ interface HermesApiService { ): Response @GET("api/messaging/platforms") - suspend fun getMessagingPlatforms(@Query("profile") profile: String? = null): Response + suspend fun getMessagingPlatforms( + @Query("profile") profile: String? = null, + ): Response @PUT("api/messaging/platforms/{platform_id}") suspend fun configurePlatform( From 262578528b8ac46b8acc33e774edbeb77ff4301a Mon Sep 17 00:00:00 2001 From: M57 Date: Sun, 21 Jun 2026 16:26:07 +0300 Subject: [PATCH 3/3] fix: stabilize flaky testAutoReconnect with polling assertion --- .../hermescontrol/data/ws/HermesWsClientTest.kt | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/app/src/test/java/com/m57/hermescontrol/data/ws/HermesWsClientTest.kt b/app/src/test/java/com/m57/hermescontrol/data/ws/HermesWsClientTest.kt index 45740e74..9bcf1acd 100644 --- a/app/src/test/java/com/m57/hermescontrol/data/ws/HermesWsClientTest.kt +++ b/app/src/test/java/com/m57/hermescontrol/data/ws/HermesWsClientTest.kt @@ -323,10 +323,20 @@ class HermesWsClientTest { serverSocket1?.close(1001, "Server shutting down") assertTrue("Client didn't detect disconnect", clientDisconnectedLatch.await(5, TimeUnit.SECONDS)) - assertEquals(ConnectionStatus.RECONNECTING, HermesWsClient.connectionStatus.value) + + // Poll for status to become RECONNECTING (async race mitigation — + // onClosed fires onDisconnected before updating _connectionStatus) + var status: ConnectionStatus + val deadline = System.currentTimeMillis() + 2000 + do { + status = HermesWsClient.connectionStatus.value + if (status == ConnectionStatus.RECONNECTING) break + Thread.sleep(50) + } while (System.currentTimeMillis() < deadline) + assertEquals(ConnectionStatus.RECONNECTING, status) // The client should now attempt to reconnect after initial backoff (1000ms) // Wait for the second connection to hit the server - assertTrue("Failed to reconnect", connect2Latch.await(5, TimeUnit.SECONDS)) + assertTrue("Failed to reconnect", connect2Latch.await(6, TimeUnit.SECONDS)) } }