From 2e97ee9a8431537be1b45475f6c1e9637a853f7f Mon Sep 17 00:00:00 2001 From: M57 <41645758+Hy4ri@users.noreply.github.com> Date: Wed, 22 Jul 2026 15:51:07 +0300 Subject: [PATCH] refactor: remove verified dead code across data, UI, and resources (#680) * refactor: remove unused dead code across data, UI, and resources * fix: resolve dangling references left by dead-code removal Restore empty object Motion (braces required) and remove the 12 stale test usages that still called the now-deleted production symbols: - ServerStoreTest: removeServer/switchToServer tests - ServerEndpointTest: isCleartext assertions - ConnectViewModelTest: onHostChange/onPortChange tests + 3 dropped R.string refs - SettingsViewModelTest: renameProfile tests + clearNavigateToLogin - E2eIntegrationTest: previewSkill tests + onHostChange/onPortChange calls Verified locally: compileDebugUnitTestKotlin + 48 tests pass. * fix: remove unused imports flagged by ktlint Drops 4 no-longer-referenced imports left by the dead-code removal: - HermesApiService: SkillScanResponse (scanHubSkill removed) - ServerEndpointTest: assertFalse, assertTrue (isCleartext assertions dropped) - SettingsViewModelTest: assertFalse (rename tests removed) --------- Co-authored-by: M57 --- .../data/config/ServerCollectionOps.kt | 8 --- .../hermescontrol/data/config/ServerStore.kt | 3 - .../hermescontrol/data/local/AuthManager.kt | 12 ---- .../hermescontrol/data/model/BillingModels.kt | 10 --- .../hermescontrol/data/remote/AuthPayloads.kt | 13 ---- .../data/remote/CookieSerialization.kt | 7 -- .../data/remote/HermesApiService.kt | 9 --- .../data/remote/ServerEndpoint.kt | 3 - .../m57/hermescontrol/data/ws/WsMethods.kt | 1 - .../java/com/m57/hermescontrol/theme/Color.kt | 9 --- .../com/m57/hermescontrol/theme/Motion.kt | 67 +------------------ .../ui/achievements/AchievementsViewModel.kt | 18 ----- .../ui/billing/BillingViewModel.kt | 2 - .../ui/channels/ChannelsViewModel.kt | 8 --- .../m57/hermescontrol/ui/chat/ChatBubble.kt | 1 - .../hermescontrol/ui/chat/ChatViewModel.kt | 4 -- .../ui/connect/ConnectViewModel.kt | 14 ---- .../ui/sessions/SessionsViewModel.kt | 33 --------- .../ui/settings/SettingsViewModel.kt | 20 ------ .../ui/skills/SkillsViewModel.kt | 48 ------------- .../ui/system/SystemViewModel.kt | 19 ------ app/src/main/res/values-ko/strings.xml | 32 --------- app/src/main/res/values/strings.xml | 34 ---------- .../m57/hermescontrol/E2eIntegrationTest.kt | 46 ------------- .../data/config/ServerStoreTest.kt | 23 ------- .../data/local/AuthManagerTest.kt | 41 +++--------- .../data/remote/ServerEndpointTest.kt | 5 -- .../ui/connect/ConnectViewModelTest.kt | 23 ------- .../ui/settings/SettingsViewModelTest.kt | 47 ------------- 29 files changed, 10 insertions(+), 550 deletions(-) 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 9083c240..27179fec 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 @@ -8,14 +8,6 @@ fun ServerStoreState.addOrReplaceServer(profile: ConnectionProfile): ServerStore return copy(connectionProfiles = updated) } -fun ServerStoreState.removeServer(id: String): ServerStoreState { - val updated = connectionProfiles.filter { it.id != id } - val newSelected = if (selectedProfileId == id) null else selectedProfileId - return copy(connectionProfiles = updated, selectedProfileId = newSelected) -} - -fun ServerStoreState.switchToServer(id: String?): ServerStoreState = copy(selectedProfileId = id) - fun ServerStoreState.selfHealed(): ServerStoreState { val hasActive = connectionProfiles.any { it.id == selectedProfileId } val newSelected = if (selectedProfileId != null && !hasActive) null else selectedProfileId diff --git a/app/src/main/java/com/m57/hermescontrol/data/config/ServerStore.kt b/app/src/main/java/com/m57/hermescontrol/data/config/ServerStore.kt index 1645783c..a82b51de 100644 --- a/app/src/main/java/com/m57/hermescontrol/data/config/ServerStore.kt +++ b/app/src/main/java/com/m57/hermescontrol/data/config/ServerStore.kt @@ -5,7 +5,6 @@ import androidx.datastore.core.Serializer import com.m57.hermescontrol.data.remote.ServerEndpoint import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow @@ -22,7 +21,6 @@ class ServerStore( ) { private val _stateFlow: MutableStateFlow val stateFlow: StateFlow - val state: Flow init { val initial = @@ -35,7 +33,6 @@ class ServerStore( } _stateFlow = MutableStateFlow(initial) stateFlow = _stateFlow.asStateFlow() - state = stateFlow } fun getLatestState(): ServerStoreState = _stateFlow.value diff --git a/app/src/main/java/com/m57/hermescontrol/data/local/AuthManager.kt b/app/src/main/java/com/m57/hermescontrol/data/local/AuthManager.kt index 646cdf6d..efc01323 100644 --- a/app/src/main/java/com/m57/hermescontrol/data/local/AuthManager.kt +++ b/app/src/main/java/com/m57/hermescontrol/data/local/AuthManager.kt @@ -450,18 +450,6 @@ object AuthManager { fun getPort(): Int = serverStore.getLatestState().resolvedPort - fun setHost(host: String) { - val endpoint = endpoint() - val normalizedHost = host.trim().removePrefix("[").removeSuffix("]") - setBaseUrl(endpoint.baseUrl.newBuilder().host(normalizedHost).build().toString()) - } - - fun setPort(port: Int) { - require(port in 1..65535) { "Port is out of range" } - val endpoint = endpoint() - setBaseUrl(endpoint.baseUrl.newBuilder().port(port).build().toString()) - } - // ── Auto-reconnect ─────────────────────────────────────────────────── fun isAutoReconnect(): Boolean = serverStore.getLatestState().autoReconnect diff --git a/app/src/main/java/com/m57/hermescontrol/data/model/BillingModels.kt b/app/src/main/java/com/m57/hermescontrol/data/model/BillingModels.kt index 5259e485..466dbf32 100644 --- a/app/src/main/java/com/m57/hermescontrol/data/model/BillingModels.kt +++ b/app/src/main/java/com/m57/hermescontrol/data/model/BillingModels.kt @@ -125,11 +125,6 @@ data class UsageBar( // ── subscription.preview ────────────────────────────────────────────────── -@Serializable -data class SubscriptionPreviewRequest( - val subscription_type_id: String, -) - @Serializable data class SubscriptionPreviewResponse( val ok: Boolean? = null, @@ -184,11 +179,6 @@ data class SubscriptionResumeResponse( // ── subscription.upgrade ────────────────────────────────────────────────── -@Serializable -data class SubscriptionUpgradeRequest( - val subscription_type_id: String, -) - @Serializable data class SubscriptionUpgradeResponse( val ok: Boolean? = null, diff --git a/app/src/main/java/com/m57/hermescontrol/data/remote/AuthPayloads.kt b/app/src/main/java/com/m57/hermescontrol/data/remote/AuthPayloads.kt index 9fafab38..036f4ddf 100644 --- a/app/src/main/java/com/m57/hermescontrol/data/remote/AuthPayloads.kt +++ b/app/src/main/java/com/m57/hermescontrol/data/remote/AuthPayloads.kt @@ -1,7 +1,6 @@ package com.m57.hermescontrol.data.remote import kotlinx.serialization.Serializable -import kotlinx.serialization.decodeFromString import kotlinx.serialization.encodeToString @Serializable @@ -12,11 +11,6 @@ private data class PasswordLoginPayload( val next: String, ) -@Serializable -private data class WebSocketTicketPayload( - val ticket: String, -) - /** JSON serialization for authentication requests and responses. */ object AuthPayloads { fun passwordLogin( @@ -31,11 +25,4 @@ object AuthPayloads { next = "", ), ) - - fun webSocketTicket(body: String): String? = - runCatching { - OkHttpProvider.json.decodeFromString(body) - .ticket - .takeIf { it.isNotBlank() } - }.getOrNull() } diff --git a/app/src/main/java/com/m57/hermescontrol/data/remote/CookieSerialization.kt b/app/src/main/java/com/m57/hermescontrol/data/remote/CookieSerialization.kt index e86ea814..bc7605d6 100644 --- a/app/src/main/java/com/m57/hermescontrol/data/remote/CookieSerialization.kt +++ b/app/src/main/java/com/m57/hermescontrol/data/remote/CookieSerialization.kt @@ -77,13 +77,6 @@ fun wrapSessionCookie(rawValue: String): Cookie? { }.getOrNull() } -/** - * Build a permissive [Cookie] from a Set-Cookie `hermes_session_at=...` - * header captured during login. Used by [PersistentCookieJar] when the server - * does not echo an explicit domain (common for same-host dashboard logins). - */ -fun sessionCookieFromValue(value: String): Cookie? = wrapSessionCookie(value) - const val SESSION_COOKIE_NAME = "hermes_session_at" /** 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 57496808..7b254659 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 @@ -76,7 +76,6 @@ import com.m57.hermescontrol.data.model.SkillContentResponse import com.m57.hermescontrol.data.model.SkillHubInstallRequest import com.m57.hermescontrol.data.model.SkillHubSearchResponse import com.m57.hermescontrol.data.model.SkillHubUninstallRequest -import com.m57.hermescontrol.data.model.SkillScanResponse import com.m57.hermescontrol.data.model.StatusResponse import com.m57.hermescontrol.data.model.SystemStatsResponse import com.m57.hermescontrol.data.model.TelegramOnboardingApplyRequest @@ -211,14 +210,6 @@ interface HermesApiService { @Query("identifier") identifier: String, ): Response - @GET("api/skills/hub/scan") - suspend fun scanHubSkill( - @Query("identifier") identifier: String, - ): Response - - @GET("api/skills/hub/sources") - suspend fun getHubSources(): Response> - @POST("api/skills/hub/install") suspend fun installHubSkill( @Body body: SkillHubInstallRequest, diff --git a/app/src/main/java/com/m57/hermescontrol/data/remote/ServerEndpoint.kt b/app/src/main/java/com/m57/hermescontrol/data/remote/ServerEndpoint.kt index c01e9806..8efeade1 100644 --- a/app/src/main/java/com/m57/hermescontrol/data/remote/ServerEndpoint.kt +++ b/app/src/main/java/com/m57/hermescontrol/data/remote/ServerEndpoint.kt @@ -21,9 +21,6 @@ class ServerEndpoint private constructor( val baseUrl: HttpUrl, val securityWarning: String?, ) { - val isCleartext: Boolean - get() = baseUrl.scheme == "http" - fun resolve(relativePath: String): HttpUrl { val path = relativePath.trim().trimStart('/') require(path.isNotEmpty()) { "Endpoint path must not be blank" } diff --git a/app/src/main/java/com/m57/hermescontrol/data/ws/WsMethods.kt b/app/src/main/java/com/m57/hermescontrol/data/ws/WsMethods.kt index 47597720..1327d224 100644 --- a/app/src/main/java/com/m57/hermescontrol/data/ws/WsMethods.kt +++ b/app/src/main/java/com/m57/hermescontrol/data/ws/WsMethods.kt @@ -24,7 +24,6 @@ object WsMethods { // ── Commands catalog ────────────────────────────────────────────────── const val COMMANDS_CATALOG = "commands.catalog" const val COMMAND_DISPATCH = "command.dispatch" - const val COMMAND_RESOLVE = "command.resolve" const val SLASH_EXEC = "slash.exec" const val CONFIG_SET = "config.set" diff --git a/app/src/main/java/com/m57/hermescontrol/theme/Color.kt b/app/src/main/java/com/m57/hermescontrol/theme/Color.kt index beaf3daa..aaf13edb 100644 --- a/app/src/main/java/com/m57/hermescontrol/theme/Color.kt +++ b/app/src/main/java/com/m57/hermescontrol/theme/Color.kt @@ -77,21 +77,12 @@ val StatusGreyLight = Color(0xFFF5F5F5) // ── Chat-specific colours (always brand-defined for consistency) ─────── -val UserBubble = HermesPurple -val UserBubbleDark = HermesPurpleDark val AssistantBubble = Color(0xFF1C1C26) val AssistantBubbleLight = Color(0xFFEDEAF4) val SystemMessageColor = Color(0xFF8B879A) val ToolChipColor = Color(0xFF262633) val ToolChipColorLight = Color(0xFFE6E3EE) -// ── Skeleton shimmer colours ─────────────────────────────────────────── - -val SkeletonBase = Color(0xFF1C1C26) -val SkeletonHighlight = Color(0xFF2F2F3D) -val SkeletonBaseLight = Color(0xFFE6E3EE) -val SkeletonHighlightLight = Color(0xFFEFEDF4) - // ── Syntax highlighting tokens (code blocks) ───────────────────────────── // VS Code Dark+ palette — good general readability across all presets. // These are consumed exclusively by CodeBlockCard (MessageCards.kt). diff --git a/app/src/main/java/com/m57/hermescontrol/theme/Motion.kt b/app/src/main/java/com/m57/hermescontrol/theme/Motion.kt index 7ac0b8f5..a4fd3a00 100644 --- a/app/src/main/java/com/m57/hermescontrol/theme/Motion.kt +++ b/app/src/main/java/com/m57/hermescontrol/theme/Motion.kt @@ -1,78 +1,13 @@ package com.m57.hermescontrol.theme -import androidx.compose.animation.core.CubicBezierEasing -import androidx.compose.animation.core.Easing -import androidx.compose.animation.core.FastOutSlowInEasing -import androidx.compose.animation.core.Spring -import androidx.compose.animation.core.SpringSpec -import androidx.compose.animation.core.spring import androidx.compose.runtime.staticCompositionLocalOf /** * Motion tokens — duration, easing, and spring specs. * - * Design intent (ui-ux-pro-max §7): - * - Micro-interactions: 150–300 ms - * - Complex transitions: ≤ 400 ms - * - Never exceed 500 ms - * - Enter: ease-out (decelerated), Exit: ease-in (accelerated) - * - Exit shorter than enter (~60–70 %) - * - Spring physics for interactive elements - * - All animations must be interruptible - * * Access via `LocalMotion.current` in composables. */ -object Motion { - // ── Duration tokens (ms) ───────────────────────────────────────── - - /** Instant — state changes with no perceptible delay. */ - const val INSTANT = 0 - - /** Fast — micro-interactions, ripples, opacity toggles. */ - const val FAST = 150 - - /** Normal — standard UI transitions, sheet slides, expansion. */ - const val NORMAL = 250 - - /** Slow — complex multi-element transitions, screen navigation. */ - const val SLOW = 400 - - // ── Easing curves ──────────────────────────────────────────────── - - /** Emphasized decelerate — for ENTERING elements (ease-out). */ - val emphasizedDecelerate: Easing = - CubicBezierEasing(0.05f, 0.7f, 0.1f, 1.0f) - - /** Emphasized accelerate — for EXITING elements (ease-in). */ - val emphasizedAccelerate: Easing = - CubicBezierEasing(0.3f, 0.0f, 0.8f, 0.15f) - - /** Standard — default M3 easing for neutral transitions. */ - val standard: Easing = FastOutSlowInEasing - - // ── Spring specs ───────────────────────────────────────────────── - - /** Bouncy spring for interactive elements (press, release, drag). */ - fun bouncySpring(): SpringSpec = - spring( - dampingRatio = Spring.DampingRatioMediumBouncy, - stiffness = Spring.StiffnessMedium, - ) - - /** Snappy spring for state toggles (switch, selection). */ - fun snappySpring(): SpringSpec = - spring( - dampingRatio = Spring.DampingRatioNoBouncy, - stiffness = Spring.StiffnessHigh, - ) - - /** Gentle spring for layout animations (expand, collapse). */ - fun gentleSpring(): SpringSpec = - spring( - dampingRatio = Spring.DampingRatioLowBouncy, - stiffness = Spring.StiffnessLow, - ) -} +object Motion val MotionDefaults = Motion diff --git a/app/src/main/java/com/m57/hermescontrol/ui/achievements/AchievementsViewModel.kt b/app/src/main/java/com/m57/hermescontrol/ui/achievements/AchievementsViewModel.kt index 0cd9b472..a014b9e5 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/achievements/AchievementsViewModel.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/achievements/AchievementsViewModel.kt @@ -86,24 +86,6 @@ class AchievementsViewModel : ViewModel(), ToastHost { ) } - fun loadScanStatus() { - safeLaunchLoad( - apiCall = { safeApiCall { api.getAchievementScanStatus() } }, - onStart = { /* silent background refresh */ }, - onSuccess = { status -> - _uiState.update { - it.copy( - scanState = status.state, - scanLastError = status.lastError, - scanLastDurationMs = status.lastDurationMs, - scanRunCount = status.runCount, - ) - } - }, - onError = { /* silent — scan runs independently */ }, - ) - } - fun loadRecentUnlocks() { safeLaunchLoad( apiCall = { safeApiCall { api.getRecentUnlocks() } }, diff --git a/app/src/main/java/com/m57/hermescontrol/ui/billing/BillingViewModel.kt b/app/src/main/java/com/m57/hermescontrol/ui/billing/BillingViewModel.kt index ee1bf14a..c1f0d459 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/billing/BillingViewModel.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/billing/BillingViewModel.kt @@ -260,6 +260,4 @@ class BillingViewModel : ViewModel() { } } } - - fun clearActionMessage() = _uiState.update { it.copy(actionMessage = null) } } 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 a230d548..89121941 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 @@ -260,10 +260,6 @@ class ChannelsViewModel : ) } - fun dismissRestartNeeded() { - _uiState.update { it.copy(restartNeeded = false) } - } - // ── Telegram onboarding ──────────────────────────────────────────────── fun startTelegramOnboarding() { @@ -502,10 +498,6 @@ class ChannelsViewModel : } } - fun dismissOnboardingError() { - _uiState.update { it.copy(onboardingError = null) } - } - override fun clearToast() { _uiState.update { it.copy(toastMessage = null) } } diff --git a/app/src/main/java/com/m57/hermescontrol/ui/chat/ChatBubble.kt b/app/src/main/java/com/m57/hermescontrol/ui/chat/ChatBubble.kt index 41d643d8..48f65762 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/chat/ChatBubble.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/chat/ChatBubble.kt @@ -91,7 +91,6 @@ import com.m57.hermescontrol.theme.LocalHermesStatusColors import com.m57.hermescontrol.theme.SystemMessageColor import com.m57.hermescontrol.theme.ToolChipColor import com.m57.hermescontrol.theme.ToolChipColorLight -import com.m57.hermescontrol.theme.UserBubble import com.m57.hermescontrol.theme.onColorFor import kotlinx.coroutines.delay import kotlinx.coroutines.launch 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 d2f92d7e..019b7baf 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 @@ -1607,10 +1607,6 @@ class ChatViewModel( // ── UI actions ─────────────────────────────────────────────────────── - fun toggleSessionPicker() { - _uiState.update { it.copy(showSessionPicker = !it.showSessionPicker) } - } - /** * Dismiss the active clarify prompt and reject it (tell the agent no answer * was given). diff --git a/app/src/main/java/com/m57/hermescontrol/ui/connect/ConnectViewModel.kt b/app/src/main/java/com/m57/hermescontrol/ui/connect/ConnectViewModel.kt index c1da25f5..89ff8ee7 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/connect/ConnectViewModel.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/connect/ConnectViewModel.kt @@ -96,20 +96,6 @@ class ConnectViewModel( _uiState.update { it.copy(baseUrl = trimmed, transportWarning = warning, errorMessage = null) } } - fun onHostChange(value: String) { - val endpoint = AuthManager.endpoint() - val next = endpoint.baseUrl.newBuilder().host(value.trim()).build().toString() - onBaseUrlChange(next) - } - - fun onPortChange(value: String) { - val portText = value.filter { c -> c.isDigit() } - val port = portText.toIntOrNull() ?: return - val endpoint = AuthManager.endpoint() - val next = endpoint.baseUrl.newBuilder().port(port).build().toString() - onBaseUrlChange(next) - } - fun connect() { val state = _uiState.value if (state.token.isBlank()) { diff --git a/app/src/main/java/com/m57/hermescontrol/ui/sessions/SessionsViewModel.kt b/app/src/main/java/com/m57/hermescontrol/ui/sessions/SessionsViewModel.kt index 58ee02cd..a8b915b1 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/sessions/SessionsViewModel.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/sessions/SessionsViewModel.kt @@ -234,10 +234,6 @@ class SessionsViewModel : ViewModel(), ToastHost { } } - fun exitSelecting() { - _uiState.update { it.copy(isSelecting = false, selectedIds = emptySet()) } - } - fun toggleSessionSelection(id: String) { _uiState.update { val updated = it.selectedIds.toMutableSet() @@ -258,14 +254,6 @@ class SessionsViewModel : ViewModel(), ToastHost { // ── Rename ─────────────────────────────────────────────────────────── - fun startRenaming(sessionId: String) { - _uiState.update { it.copy(renamingSessionId = sessionId) } - } - - fun cancelRenaming() { - _uiState.update { it.copy(renamingSessionId = null) } - } - fun renameSession( sessionId: String, newTitle: String, @@ -308,27 +296,6 @@ class SessionsViewModel : ViewModel(), ToastHost { } } - // ── Copy prompt ────────────────────────────────────────────────────── - - fun copySessionPrompt(sessionId: String) { - viewModelScope.launch { - val result = - safeApiCall { - ApiClient.hermesApi.getSessionPrompt(sessionId) - } - when (result) { - is NetworkResult.Success -> { - val promptText = result.data.prompt ?: "No prompt available" - _uiState.update { it.copy(toastMessage = promptText) } - } - - is NetworkResult.Failure -> { - _uiState.update { it.copy(toastMessage = "Failed to get prompt: ${result.error.message}") } - } - } - } - } - // ── Delete (single) ────────────────────────────────────────────────── fun requestDeleteSession(sessionId: String) { diff --git a/app/src/main/java/com/m57/hermescontrol/ui/settings/SettingsViewModel.kt b/app/src/main/java/com/m57/hermescontrol/ui/settings/SettingsViewModel.kt index 33ef6173..59fdedfd 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/settings/SettingsViewModel.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/settings/SettingsViewModel.kt @@ -108,22 +108,6 @@ class SettingsViewModel( ApiClient.rebuild() } - fun onRenameProfileNameChange(value: String) { - _uiState.update { it.copy(renameProfileName = value, isSaved = false) } - } - - fun renameProfile() { - val currentId = _uiState.value.selectedProfileId ?: return - val newName = _uiState.value.renameProfileName.trim() - if (newName.isBlank()) return - val updatedProfiles = - AuthManager.getConnectionProfiles().map { - if (it.id == currentId) it.copy(name = newName) else it - } - AuthManager.saveConnectionProfiles(updatedProfiles) - viewModelScope.launch(ioDispatcher) { loadSettings() } - } - fun deleteProfile(profileId: String) { val updatedProfiles = AuthManager.getConnectionProfiles().filter { it.id != profileId } AuthManager.saveConnectionProfiles(updatedProfiles) @@ -235,10 +219,6 @@ class SettingsViewModel( ApiClient.rebuild() } - fun clearNavigateToLogin() { - _uiState.update { it.copy(navigateToLogin = false) } - } - // ── Delete confirmation ────────────────────────────────────────────── fun requestDeleteProfile(profileId: String) { diff --git a/app/src/main/java/com/m57/hermescontrol/ui/skills/SkillsViewModel.kt b/app/src/main/java/com/m57/hermescontrol/ui/skills/SkillsViewModel.kt index 410d8083..13b743a6 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/skills/SkillsViewModel.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/skills/SkillsViewModel.kt @@ -205,17 +205,6 @@ class SkillsViewModel( } } - fun clearHubPreview() { - _uiState.update { - it.copy( - hubPreviewIdentifier = null, - hubPreviewContent = null, - isHubPreviewing = false, - hubPreviewError = null, - ) - } - } - // ── Hub install ───────────────────────────────────────────────────────── fun installSkill(identifier: String) { @@ -293,43 +282,6 @@ class SkillsViewModel( } } - // ── SKILL.md preview ──────────────────────────────────────────────────── - - fun previewSkill(skillName: String) { - _uiState.update { - it.copy( - previewSkillName = skillName, - previewSkillContent = null, - isLoadingPreview = true, - ) - } - viewModelScope.launch { - val result = - withContext(Dispatchers.IO) { - safeApiCall { ApiClient.hermesApi.getSkillContent(skillName) } - } - when (result) { - is NetworkResult.Success -> { - _uiState.update { - it.copy( - isLoadingPreview = false, - previewSkillContent = result.data.content, - ) - } - } - - is NetworkResult.Failure -> { - _uiState.update { - it.copy( - isLoadingPreview = false, - toastMessage = "Failed to load preview: ${result.error.message}", - ) - } - } - } - } - } - fun clearPreview() { _uiState.update { it.copy( diff --git a/app/src/main/java/com/m57/hermescontrol/ui/system/SystemViewModel.kt b/app/src/main/java/com/m57/hermescontrol/ui/system/SystemViewModel.kt index a538e4a2..3579ac5c 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/system/SystemViewModel.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/system/SystemViewModel.kt @@ -700,23 +700,4 @@ class SystemViewModel : override fun clearToast() { _uiState.update { it.copy(toastMessage = null) } } - - fun clearTransientState() { - actionPollingJob?.cancel() - _uiState.update { - it.copy( - errorMessage = null, - toastMessage = null, - activeAction = null, - actionLog = null, - isLoading = false, - addingCred = false, - creatingHook = false, - sharing = false, - checkingUpdate = false, - updateConfirmOpen = false, - hookModalOpen = false, - ) - } - } } diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index c74409ad..ab6f1b53 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -3,13 +3,9 @@ 전송 - 음성 입력 듣는 중… 음성 입력을 위해 마이크 권한이 필요합니다 이 기기에서 음성 인식을 사용할 수 없습니다 - 확인 필요 - 옵션을 탭하면 입력창에 복사되고, 길게 누르면 바로 전송됩니다. - 응답 전송 닫기 Sudo 비밀번호 필요 @@ -43,13 +39,8 @@ 문제가 발생했습니다 무료 사용량 - 재개 취소 갱신: %1$s - 알 수 없음 - 활성 - 취소됨 - 일시중지됨 이 서버에서는 결제를 사용할 수 없습니다 (지원되지 않는 메서드) 활성 요금제 없음 — 무료 등급입니다. 요금제를 보려면 Nous Portal에 로그인하세요. @@ -120,11 +111,6 @@ 연결 끊김 네트워크 없음 세션 만료 - 생각 중… - 생각 중: %1$s… - 추론 - 추론 펼치기 - 추론 접기 연결되지 않음… 응답 대기 중… 메시지 입력… @@ -139,7 +125,6 @@ 편집 - 파일 첨부 첨부 제거 @@ -147,8 +132,6 @@ 연결 - 호스트 - 포트 인증 토큰 동작 자동 재연결 @@ -199,8 +182,6 @@ 토큰이 필요합니다 - 호스트가 필요합니다 - 포트는 1~65535 사이여야 합니다 잘못된 토큰 (401 Unauthorized) 접근 거부 (403 Forbidden) 서버가 HTTP %1$d 반환 @@ -208,19 +189,13 @@ 연결 거부됨 – Hermes가 실행 중인가요? 호스트를 찾을 수 없음 연결 실패: %1$s - 잘못된 페어링 문자열 — host, port 또는 token 누락 - 잘못된 페어링 문자열 — URL 또는 Base64 JSON이어야 함 - 페어링 문자열 파싱 실패: %1$s - hermes://… 또는 Base64 설정 붙여넣기 연결 프로필 저장 프로필 이름 예: 집 헤르메스, 사무실 서버 수동 입력 숨기기 수동으로 입력 API 토큰 - 127.0.0.1 - 9119 연결 @@ -640,16 +615,12 @@ 인증 로그인 Hermes 대시보드에 연결 - 호스트 - 포트 세션 토큰 사용자명 비밀번호 연결 대시보드 확인 중… 대시보드 설정 확인 중… - 호스트가 필요합니다 - 잘못된 포트 (1~65535) 세션 토큰이 필요합니다 사용자명이 필요합니다 비밀번호가 필요합니다 @@ -663,9 +634,6 @@ 인증 로그인 - 모델 제공자 확인 중… - 채널 로딩 중… - OAuth 제공자 로딩 중… OAuth 제공자가 없습니다 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2d7e3199..88eb9f0e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -3,13 +3,9 @@ Send - Voice input Listening… Microphone permission is required for voice input Speech recognition is not available on this device - Clarification Needed - Tap an option to copy to input, or long-press to send directly. - Response Send Dismiss Sudo Password Required @@ -42,11 +38,9 @@ Retry search Create your first job Add a server - Create subscription Connect a Telegram channel Configure model Start a new chat - Install from URL Explore Skills or Cron jobs Clear search Cancel @@ -56,13 +50,8 @@ Unknown error Free Usage - Resume Cancel Renews: %1$s - Unknown - Active - Cancelled - Paused Billing is unavailable on this server (method not supported) No active plan — you\'re on the free tier. Log in to Nous Portal to view your plan. @@ -133,11 +122,6 @@ Disconnected No network Session expired - Thinking\u2026 - Thinking: %1$s\u2026 - Reasoning - Expand reasoning - Collapse reasoning Not connected\u2026 Waiting for response\u2026 Queued \u2014 sends when agent is free\u2026 @@ -153,7 +137,6 @@ Edit - Attach file Remove attachment @@ -161,8 +144,6 @@ Connection - Host - Port Auth Token Behavior Auto-Reconnect @@ -229,8 +210,6 @@ Token is required - Host is required - Port must be between 1 and 65535 Invalid token (401 Unauthorized) Access denied (403 Forbidden) Server returned HTTP %1$d @@ -238,19 +217,13 @@ Connection refused – is Hermes running? Could not resolve host Connection failed: %1$s - Malformed pairing string — missing host, port, or token - Malformed pairing string — expected URL or Base64-encoded JSON - Failed to parse pairing string: %1$s - Paste hermes://… or Base64 config Save connection profile Profile Name e.g. Local Hermit, Remote Server Hide manual entry Enter manually instead API token - 127.0.0.1 - 9119 Server URL https://host:9119/ or http://127.0.0.1:9119/ Enter a valid server URL (https:// or http://) @@ -788,16 +761,12 @@ Auth Login Connect to your Hermes dashboard Server URL - Host - Port Session Token Username Password Connect Probing dashboard… Checking dashboard configuration… - Host is required - Invalid port (1–65535) Session token is required Username is required Password is required @@ -813,9 +782,6 @@ Auth Login - Fetching model providers… - Loading channels… - Loading OAuth providers… No OAuth providers diff --git a/app/src/test/java/com/m57/hermescontrol/E2eIntegrationTest.kt b/app/src/test/java/com/m57/hermescontrol/E2eIntegrationTest.kt index 08d9a76f..b4751a86 100644 --- a/app/src/test/java/com/m57/hermescontrol/E2eIntegrationTest.kt +++ b/app/src/test/java/com/m57/hermescontrol/E2eIntegrationTest.kt @@ -38,7 +38,6 @@ import com.m57.hermescontrol.data.model.ProfilesResponse import com.m57.hermescontrol.data.model.SessionInfo import com.m57.hermescontrol.data.model.SessionListResponse import com.m57.hermescontrol.data.model.Skill -import com.m57.hermescontrol.data.model.SkillContentResponse import com.m57.hermescontrol.data.model.SkillHubSearchResponse import com.m57.hermescontrol.data.model.StatusResponse import com.m57.hermescontrol.data.model.SystemStatsResponse @@ -100,8 +99,6 @@ class E2eIntegrationTest { every { AuthManager.getPort() } returns 9119 every { AuthManager.getBaseUrl() } returns "https://127.0.0.1:9119/" every { AuthManager.setToken(any()) } returns Unit - every { AuthManager.setHost(any()) } returns Unit - every { AuthManager.setPort(any()) } returns Unit every { AuthManager.setBaseUrl(any()) } returns Unit every { AuthManager.endpoint() @@ -471,47 +468,6 @@ class E2eIntegrationTest { // ── Skills Preview ──────────────────────────────────────────────────── - @Test - fun testSkillsPreview_success() = - runTest { - val content = SkillContentResponse("my-skill", "# My Skill\n\nDo stuff") - coEvery { mockApiService.getSkillContent(any()) } returns - Response.success(content) - - val viewModel = SkillsViewModel(mockApp) - viewModel.previewSkill("my-skill") - - advanceUntilIdle() - assertFalse(viewModel.uiState.value.isLoadingPreview) - assertEquals( - "my-skill", - viewModel.uiState.value.previewSkillName, - ) - assertEquals( - content.content, - viewModel.uiState.value.previewSkillContent, - ) - } - - @Test - fun testSkillsPreview_error() = - runTest { - coEvery { mockApiService.getSkillContent(any()) } returns - createErrorResponse(500) - - val viewModel = SkillsViewModel(mockApp) - viewModel.previewSkill("failing-skill") - - advanceUntilIdle() - assertFalse(viewModel.uiState.value.isLoadingPreview) - assertNull(viewModel.uiState.value.previewSkillContent) - assertNotNull(viewModel.uiState.value.toastMessage) - assertTrue( - viewModel.uiState.value.toastMessage!! - .contains("HTTP 500"), - ) - } - // Cron Jobs Screen: @Test fun testCronJobsListing_success() = @@ -1031,8 +987,6 @@ class E2eIntegrationTest { val connectViewModel = ConnectViewModel(mockApp) connectViewModel.onTokenChange("valid-token") - connectViewModel.onHostChange("127.0.0.1") - connectViewModel.onPortChange("9119") connectViewModel.connect() advanceUntilIdle() 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 0bd06b2f..f88d94c8 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 @@ -35,29 +35,6 @@ class ServerStoreTest { assertEquals("10.0.0.2", finalState.connectionProfiles[0].host) } - @Test - fun testPureOps_removeServer() { - val profile1 = ConnectionProfile(id = "1", name = "Test1", host = "10.0.0.1", port = 8080) - val profile2 = ConnectionProfile(id = "2", name = "Test2", host = "10.0.0.2", port = 8081) - val state = - ServerStoreState( - connectionProfiles = listOf(profile1, profile2), - selectedProfileId = "2", - ) - - val newState = state.removeServer("2") - assertEquals(1, newState.connectionProfiles.size) - assertEquals("1", newState.connectionProfiles[0].id) - assertNull(newState.selectedProfileId) - } - - @Test - fun testPureOps_switchToServer() { - val state = ServerStoreState(selectedProfileId = null) - val newState = state.switchToServer("prof-1") - assertEquals("prof-1", newState.selectedProfileId) - } - @Test fun testPureOps_selfHealed() { // Test orphaned selected profile ID diff --git a/app/src/test/java/com/m57/hermescontrol/data/local/AuthManagerTest.kt b/app/src/test/java/com/m57/hermescontrol/data/local/AuthManagerTest.kt index cf829dbf..655e4b1e 100644 --- a/app/src/test/java/com/m57/hermescontrol/data/local/AuthManagerTest.kt +++ b/app/src/test/java/com/m57/hermescontrol/data/local/AuthManagerTest.kt @@ -130,14 +130,9 @@ class AuthManagerTest { } @Test - fun testGetAndSetHost() { - AuthManager.setHost("10.0.0.1") + fun testGetHostAndPortFromBaseUrl() { + AuthManager.setBaseUrl("https://10.0.0.1:9090/") assertEquals("10.0.0.1", AuthManager.getHost()) - } - - @Test - fun testGetAndSetPort() { - AuthManager.setPort(9090) assertEquals(9090, AuthManager.getPort()) } @@ -149,10 +144,7 @@ class AuthManagerTest { @Test fun testBaseUrl() { - AuthManager.setHost("hermes.local") - AuthManager.setPort(1234) - // setHost/setPort derive from the canonical base URL (default is https), - // so the result keeps the https scheme and swaps host/port. + AuthManager.setBaseUrl("https://hermes.local:1234/") assertEquals("https://hermes.local:1234/", AuthManager.getBaseUrl()) } @@ -181,8 +173,7 @@ class AuthManagerTest { AuthManager.ensureDefaultProfile() every { mockPrefs.getString("token_${AuthManager.DEFAULT_PROFILE_ID}", null) } returns "token123" AuthManager.setSelectedProfileId(AuthManager.DEFAULT_PROFILE_ID) - AuthManager.setHost("hermes.local") - AuthManager.setPort(1234) + AuthManager.setBaseUrl("https://hermes.local:1234/") // Scheme tracks the canonical base URL (default https) → wss. assertEquals("wss://hermes.local:1234/api/ws?token=token123", AuthManager.wsUrl()) } @@ -192,8 +183,7 @@ class AuthManagerTest { AuthManager.ensureDefaultProfile() every { mockPrefs.getString("token_${AuthManager.DEFAULT_PROFILE_ID}", null) } returns null AuthManager.setSelectedProfileId(AuthManager.DEFAULT_PROFILE_ID) - AuthManager.setHost("hermes.local") - AuthManager.setPort(1234) + AuthManager.setBaseUrl("https://hermes.local:1234/") assertEquals("wss://hermes.local:1234/api/ws?token=", AuthManager.wsUrl()) } @@ -265,34 +255,21 @@ class AuthManagerTest { AuthManager.ensureDefaultProfile() AuthManager.setSelectedProfileId("nonexistent") AuthManager.saveConnectionProfiles(emptyList()) - AuthManager.setHost("192.168.1.1") + AuthManager.setBaseUrl("http://192.168.1.1:9119/") assertEquals("192.168.1.1", AuthManager.getHost()) } @Test - fun testSetHost_updatesProfileWhenSelected() { + fun testSetBaseUrl_updatesProfileWhenSelected() { val profile = ConnectionProfile(id = "prof-1", name = "Home", baseUrl = "http://10.0.0.1:9119/") AuthManager.saveConnectionProfiles(listOf(profile)) AuthManager.setSelectedProfileId("prof-1") - AuthManager.setHost("10.0.0.99") + AuthManager.setBaseUrl("http://10.0.0.99:9999/") val updated = AuthManager.getConnectionProfiles().first() - // host/port are now canonicalized into the base URL (profile was http) - assertEquals("http://10.0.0.99:9119/", updated.resolvedBaseUrl) - } - - @Test - fun testSetPort_updatesProfileWhenSelected() { - val profile = ConnectionProfile(id = "prof-1", name = "Home", baseUrl = "http://10.0.0.1:9119/") - AuthManager.saveConnectionProfiles(listOf(profile)) - AuthManager.setSelectedProfileId("prof-1") - - AuthManager.setPort(9999) - - val updated = AuthManager.getConnectionProfiles().first() - assertEquals("http://10.0.0.1:9999/", updated.resolvedBaseUrl) + assertEquals("http://10.0.0.99:9999/", updated.resolvedBaseUrl) } @Test diff --git a/app/src/test/java/com/m57/hermescontrol/data/remote/ServerEndpointTest.kt b/app/src/test/java/com/m57/hermescontrol/data/remote/ServerEndpointTest.kt index dc88c434..cc95023f 100644 --- a/app/src/test/java/com/m57/hermescontrol/data/remote/ServerEndpointTest.kt +++ b/app/src/test/java/com/m57/hermescontrol/data/remote/ServerEndpointTest.kt @@ -1,9 +1,7 @@ package com.m57.hermescontrol.data.remote import org.junit.Assert.assertEquals -import org.junit.Assert.assertFalse import org.junit.Assert.assertNull -import org.junit.Assert.assertTrue import org.junit.Assert.fail import org.junit.Test @@ -13,7 +11,6 @@ class ServerEndpointTest { val ep = ServerEndpoint.parse("https://example.com/hermes", CleartextPolicy.ALLOW_WITH_WARNING) assertEquals("https://example.com/hermes/", ep.baseUrl.toString()) assertNull(ep.securityWarning) - assertFalse(ep.isCleartext) } @Test @@ -21,7 +18,6 @@ class ServerEndpointTest { val ep = ServerEndpoint.parse("http://127.0.0.1:9119/", CleartextPolicy.ALLOW_WITH_WARNING) assertEquals("http://127.0.0.1:9119/", ep.baseUrl.toString()) assertEquals(ServerEndpoint.CLEARTEXT_WARNING, ep.securityWarning) - assertTrue(ep.isCleartext) } @Test @@ -130,7 +126,6 @@ class ServerEndpointTest { fun `fromLegacy preserves http host and port`() { val ep = ServerEndpoint.fromLegacy("10.0.0.1", 8080) assertEquals("http://10.0.0.1:8080/", ep.baseUrl.toString()) - assertTrue(ep.isCleartext) } @Test diff --git a/app/src/test/java/com/m57/hermescontrol/ui/connect/ConnectViewModelTest.kt b/app/src/test/java/com/m57/hermescontrol/ui/connect/ConnectViewModelTest.kt index df1ecdd9..543008ac 100644 --- a/app/src/test/java/com/m57/hermescontrol/ui/connect/ConnectViewModelTest.kt +++ b/app/src/test/java/com/m57/hermescontrol/ui/connect/ConnectViewModelTest.kt @@ -79,13 +79,6 @@ class ConnectViewModelTest { every { mockApp.getString(R.string.connect_error_refused) } returns "Connection refused – is Hermes running?" every { mockApp.getString(R.string.connect_error_timeout) } returns "Connection timed out" every { mockApp.getString(R.string.connect_error_connection_failed) } returns "Connection failed: %1\$s" - every { - mockApp.getString(R.string.connect_error_malformed) - } returns "Malformed pairing string — expected URL or Base64-encoded JSON" - every { - mockApp.getString(R.string.connect_error_missing_fields) - } returns "Malformed pairing string — missing host, port, or token" - every { mockApp.getString(R.string.connect_error_parse_failed) } returns "Failed to parse pairing string: %1\$s" } @After @@ -117,22 +110,6 @@ class ConnectViewModelTest { assertNull(viewModel.uiState.value.errorMessage) } - @Test - fun testOnHostChange() { - val viewModel = ConnectViewModel(mockApp) - viewModel.onHostChange(" 192.168.1.50 ") - assertEquals("https://192.168.1.50:9119/", viewModel.uiState.value.baseUrl) - assertNull(viewModel.uiState.value.errorMessage) - } - - @Test - fun testOnPortChange() { - val viewModel = ConnectViewModel(mockApp) - viewModel.onPortChange("90abc90") - assertEquals("https://127.0.0.1:9090/", viewModel.uiState.value.baseUrl) - assertNull(viewModel.uiState.value.errorMessage) - } - @Test fun testConnect_blankToken_showsError() { val viewModel = ConnectViewModel(mockApp) diff --git a/app/src/test/java/com/m57/hermescontrol/ui/settings/SettingsViewModelTest.kt b/app/src/test/java/com/m57/hermescontrol/ui/settings/SettingsViewModelTest.kt index a88b5406..6f9c5caf 100644 --- a/app/src/test/java/com/m57/hermescontrol/ui/settings/SettingsViewModelTest.kt +++ b/app/src/test/java/com/m57/hermescontrol/ui/settings/SettingsViewModelTest.kt @@ -16,7 +16,6 @@ import kotlinx.coroutines.test.resetMain import kotlinx.coroutines.test.setMain import org.junit.After import org.junit.Assert.assertEquals -import org.junit.Assert.assertFalse import org.junit.Before import org.junit.Test @@ -67,8 +66,6 @@ class SettingsViewModelTest { every { AuthManager.getConnectionProfiles() } returns emptyList() every { AuthManager.getSelectedProfileId() } answers { storedSelectedProfileId } every { AuthManager.baseUrl() } returns "http://127.0.0.1:9119/" - every { AuthManager.setHost(any()) } returns Unit - every { AuthManager.setPort(any()) } returns Unit every { AuthManager.setBaseUrl(any()) } returns Unit every { AuthManager.setToken(any()) } returns Unit every { AuthManager.setAutoReconnect(any()) } returns Unit @@ -146,47 +143,6 @@ class SettingsViewModelTest { assertEquals(AuthManager.DEFAULT_PROFILE_ID, viewModel.uiState.value.selectedProfileId) } - @Test - fun testOnRenameProfileNameChange_updatesState() { - val viewModel = createViewModel() - viewModel.onRenameProfileNameChange("New Name") - assertEquals("New Name", viewModel.uiState.value.renameProfileName) - assertFalse(viewModel.uiState.value.isSaved) - } - - @Test - fun testRenameProfile_updatesProfileName() { - every { AuthManager.getConnectionProfiles() } returns testProfiles - every { AuthManager.getSelectedProfileId() } returns "prof-1" - - val viewModel = createViewModel() - viewModel.onRenameProfileNameChange("Renamed Work") - viewModel.renameProfile() - - verify { AuthManager.saveConnectionProfiles(any()) } - } - - @Test - fun testRenameProfile_withBlankName_doesNothing() { - every { AuthManager.getConnectionProfiles() } returns testProfiles - every { AuthManager.getSelectedProfileId() } returns "prof-1" - - val viewModel = createViewModel() - viewModel.onRenameProfileNameChange(" ") - viewModel.renameProfile() - - verify(exactly = 0) { AuthManager.saveConnectionProfiles(any()) } - } - - @Test - fun testRenameProfile_noSelectedProfile_doesNothing() { - val viewModel = createViewModel() - viewModel.onRenameProfileNameChange("New Name") - viewModel.renameProfile() - - verify(exactly = 0) { AuthManager.saveConnectionProfiles(any()) } - } - @Test fun testDeleteProfile_removesProfileAndToken() { every { AuthManager.getConnectionProfiles() } returns testProfiles @@ -233,8 +189,5 @@ class SettingsViewModelTest { verify { AuthManager.saveConnectionProfiles(any()) } verify { AuthManager.setSelectedProfileId(any()) } assertEquals(true, viewModel.uiState.value.navigateToLogin) - - viewModel.clearNavigateToLogin() - assertEquals(false, viewModel.uiState.value.navigateToLogin) } }