From dcf26f1a16d444fd2deb707feb9dfcbb4307249c Mon Sep 17 00:00:00 2001 From: M57 <41645758+Hy4ri@users.noreply.github.com> Date: Thu, 23 Jul 2026 13:04:10 +0300 Subject: [PATCH] Add comprehensive tests for ServerCollectionOps (#696) * Test ServerCollectionOps data manipulation utilities Added comprehensive unit tests for `ServerCollectionOps.kt` to cover all scenarios for `addOrUpdate`, `selfHealed`, and resolution properties (`resolvedBaseUrl`, `resolvedHost`, `resolvedPort`). Also updated the function name `addOrReplaceServer` to `addOrUpdate` to match the intended signature in the issue description, and updated affected call sites. * Fix ktlint formatting issues and ensure build succeeds Fixed ktlint errors in `ServerCollectionOpsTest.kt` by correctly formatting arguments and expressions to comply with project style guidelines. Ran `assembleDebug` and tests locally to verify build health. --- .../data/config/ServerCollectionOps.kt | 2 +- .../data/config/ServerCollectionOpsTest.kt | 177 ++++++++++++++++++ .../data/config/ServerStoreTest.kt | 6 +- 3 files changed, 181 insertions(+), 4 deletions(-) create mode 100644 app/src/test/java/com/m57/hermescontrol/data/config/ServerCollectionOpsTest.kt diff --git a/app/src/main/java/com/m57/hermescontrol/data/config/ServerCollectionOps.kt b/app/src/main/java/com/m57/hermescontrol/data/config/ServerCollectionOps.kt index 27179fec..4f67137d 100644 --- a/app/src/main/java/com/m57/hermescontrol/data/config/ServerCollectionOps.kt +++ b/app/src/main/java/com/m57/hermescontrol/data/config/ServerCollectionOps.kt @@ -3,7 +3,7 @@ package com.m57.hermescontrol.data.config import com.m57.hermescontrol.data.remote.CleartextPolicy import com.m57.hermescontrol.data.remote.ServerEndpoint -fun ServerStoreState.addOrReplaceServer(profile: ConnectionProfile): ServerStoreState { +fun ServerStoreState.addOrUpdate(profile: ConnectionProfile): ServerStoreState { val updated = connectionProfiles.filter { it.id != profile.id } + profile return copy(connectionProfiles = updated) } diff --git a/app/src/test/java/com/m57/hermescontrol/data/config/ServerCollectionOpsTest.kt b/app/src/test/java/com/m57/hermescontrol/data/config/ServerCollectionOpsTest.kt new file mode 100644 index 00000000..cc9ff636 --- /dev/null +++ b/app/src/test/java/com/m57/hermescontrol/data/config/ServerCollectionOpsTest.kt @@ -0,0 +1,177 @@ +package com.m57.hermescontrol.data.config + +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNull +import org.junit.Test + +class ServerCollectionOpsTest { + @Test + fun addOrUpdate_addsNewProfile() { + val initialState = + ServerStoreState( + connectionProfiles = + listOf( + ConnectionProfile(id = "1", name = "Profile 1", baseUrl = "http://192.168.1.1:9119"), + ), + ) + val newProfile = ConnectionProfile(id = "2", name = "Profile 2", baseUrl = "http://192.168.1.2:9119") + + val result = initialState.addOrUpdate(newProfile) + + assertEquals(2, result.connectionProfiles.size) + assertEquals(newProfile, result.connectionProfiles[1]) + } + + @Test + fun addOrUpdate_replacesExistingProfile() { + val initialState = + ServerStoreState( + connectionProfiles = + listOf( + ConnectionProfile(id = "1", name = "Profile 1", baseUrl = "http://192.168.1.1:9119"), + ConnectionProfile(id = "2", name = "Profile 2", baseUrl = "http://192.168.1.2:9119"), + ), + ) + val updatedProfile = + ConnectionProfile(id = "1", name = "Updated Profile 1", baseUrl = "http://192.168.1.100:9119") + + val result = initialState.addOrUpdate(updatedProfile) + + assertEquals(2, result.connectionProfiles.size) + // Should retain the order implicitly, or just check it exists + val replaced = result.connectionProfiles.find { it.id == "1" } + assertEquals("Updated Profile 1", replaced?.name) + assertEquals("http://192.168.1.100:9119", replaced?.baseUrl) + } + + @Test + fun selfHealed_keepsActiveProfile() { + val initialState = + ServerStoreState( + connectionProfiles = + listOf( + ConnectionProfile(id = "1", name = "Profile 1", baseUrl = "http://192.168.1.1:9119"), + ConnectionProfile(id = "2", name = "Profile 2", baseUrl = "http://192.168.1.2:9119"), + ), + selectedProfileId = "2", + ) + + val result = initialState.selfHealed() + + assertEquals("2", result.selectedProfileId) + } + + @Test + fun selfHealed_nullifiesMissingProfile() { + val initialState = + ServerStoreState( + connectionProfiles = + listOf( + ConnectionProfile(id = "1", name = "Profile 1", baseUrl = "http://192.168.1.1:9119"), + ), + selectedProfileId = "3", + ) + + val result = initialState.selfHealed() + + assertNull(result.selectedProfileId) + } + + @Test + fun selfHealed_handlesAlreadyNull() { + val initialState = + ServerStoreState( + connectionProfiles = + listOf( + ConnectionProfile(id = "1", name = "Profile 1", baseUrl = "http://192.168.1.1:9119"), + ), + selectedProfileId = null, + ) + + val result = initialState.selfHealed() + + assertNull(result.selectedProfileId) + } + + @Test + fun resolvedBaseUrl_usesSelectedProfileBaseUrl() { + val state = + ServerStoreState( + connectionProfiles = + listOf( + ConnectionProfile(id = "1", name = "Profile 1", baseUrl = "http://192.168.1.1:9119"), + ), + selectedProfileId = "1", + baseUrl = "http://top.level.base.url:9119", // Should be ignored + ) + + val result = state.resolvedBaseUrl + + assertEquals("http://192.168.1.1:9119/", result) + } + + @Test + fun resolvedBaseUrl_fallsBackToTopLevelBaseUrl() { + val state = + ServerStoreState( + connectionProfiles = + listOf( + ConnectionProfile(id = "1", name = "Profile 1", baseUrl = null), // Selected but no baseUrl + ), + selectedProfileId = "1", + baseUrl = "http://top.level.base.url:9119", // Should be used as fallback + ) + + val result = state.resolvedBaseUrl + + assertEquals("http://top.level.base.url:9119/", result) + } + + @Test + fun resolvedBaseUrl_fallsBackToLegacyLoopback() { + val state = + ServerStoreState( + connectionProfiles = emptyList(), // No selected profile + selectedProfileId = null, + baseUrl = null, // No top-level baseUrl + host = "127.0.0.1", + port = 9119, + ) + + val result = state.resolvedBaseUrl + + assertEquals("http://127.0.0.1:9119/", result) + } + + @Test + fun resolvedHost_extractsCorrectHost() { + val state = + ServerStoreState( + connectionProfiles = + listOf( + ConnectionProfile(id = "1", name = "Profile 1", baseUrl = "https://my.server.local:443"), + ), + selectedProfileId = "1", + ) + + val result = state.resolvedHost + + assertEquals("my.server.local", result) + } + + @Test + fun resolvedPort_extractsCorrectPort() { + val state = + ServerStoreState( + connectionProfiles = + listOf( + ConnectionProfile(id = "1", name = "Profile 1", baseUrl = "https://my.server.local:8443"), + ), + selectedProfileId = "1", + ) + + val result = state.resolvedPort + + assertEquals(8443, result) + } +} diff --git a/app/src/test/java/com/m57/hermescontrol/data/config/ServerStoreTest.kt b/app/src/test/java/com/m57/hermescontrol/data/config/ServerStoreTest.kt index f88d94c8..9441ef35 100644 --- a/app/src/test/java/com/m57/hermescontrol/data/config/ServerStoreTest.kt +++ b/app/src/test/java/com/m57/hermescontrol/data/config/ServerStoreTest.kt @@ -20,17 +20,17 @@ class ServerStoreTest { } @Test - fun testPureOps_addOrReplaceServer() { + fun testPureOps_addOrUpdate() { val state = ServerStoreState() val profile = ConnectionProfile(id = "1", name = "Test", host = "10.0.0.1", port = 8080) - val newState = state.addOrReplaceServer(profile) + val newState = state.addOrUpdate(profile) assertEquals(1, newState.connectionProfiles.size) assertEquals(profile, newState.connectionProfiles[0]) // Replace val updatedProfile = profile.copy(host = "10.0.0.2") - val finalState = newState.addOrReplaceServer(updatedProfile) + val finalState = newState.addOrUpdate(updatedProfile) assertEquals(1, finalState.connectionProfiles.size) assertEquals("10.0.0.2", finalState.connectionProfiles[0].host) }