feat(settings): restore last session on startup (#1102) (#1105)

This commit is contained in:
M57 authored and GitHub committed 2026-09-13 16:45:04 +03:00
1 parent 7e8af8db01
commit 4246ae196d
16 files changed
+234 -4

No files matched your search

@@ -90,6 +90,10 @@ class ChatUpdateCommandTest {
every { AuthManager.isTypingEffectEnabled() } returns true
every { AuthManager.getTypingEffectDelayMs() } returns 30
every { AuthManager.isAutoReconnect() } returns false
every { AuthManager.isRestoreLastSession() } returns false
every { AuthManager.getLastOpenedSessionId() } returns null
every { AuthManager.setLastOpenedSessionId(any()) } returns Unit
every { AuthManager.clearLastOpenedSessionId() } returns Unit
every { HermesWsClient.events } returns mockEventsFlow
every { HermesWsClient.connectionStatus } returns mockConnectionStatus
every { HermesWsClient.connect() } answers {
@@ -164,6 +164,10 @@ class ChatViewModelTest {
every { AuthManager.isTypingEffectEnabled() } returns true
every { AuthManager.getTypingEffectDelayMs() } returns 30
every { AuthManager.isAutoReconnect() } returns false
every { AuthManager.isRestoreLastSession() } returns false
every { AuthManager.getLastOpenedSessionId() } returns null
every { AuthManager.setLastOpenedSessionId(any()) } returns Unit
every { AuthManager.clearLastOpenedSessionId() } returns Unit
every { HermesWsClient.events } returns mockEventsFlow
every { HermesWsClient.connectionStatus } returns mockConnectionStatus
every { HermesWsClient.connect() } answers {
@@ -5083,4 +5087,75 @@ class ChatViewModelTest {
assertTrue(updated.logs.any { it.text.contains("Stopped subagent") })
io.mockk.verify { HermesWsClient.sendRedirect(sessionId, "/stop sub-123", any()) }
}
@Test
fun testHandleGatewayReady_withRestoreLastSessionEnabled_resumesStoredSession() =
runTest {
every { AuthManager.isRestoreLastSession() } returns true
every { AuthManager.getLastOpenedSessionId() } returns "session-stored-999"
val resumeParamsSlot = slot<Map<String, Any>>()
every {
HermesWsClient.send(WsMethods.SESSION_RESUME, capture(resumeParamsSlot), any())
} answers {
"req-resume-test"
}
val viewModel = createViewModel()
advanceUntilIdle()
mockConnectionStatus.value = ConnectionStatus.CONNECTED
mockEventsFlow.emit(WsEvent.GatewayReady(null))
advanceUntilIdle()
io.mockk.verify {
HermesWsClient.send(
WsMethods.SESSION_RESUME,
any(),
any(),
)
}
assertEquals("session-stored-999", resumeParamsSlot.captured["session_id"])
}
@Test
fun testHandleGatewayReady_withRestoreLastSessionDisabled_createsNewSession() =
runTest {
every { AuthManager.isRestoreLastSession() } returns false
every { AuthManager.getLastOpenedSessionId() } returns "session-stored-999"
val viewModel = createViewModel()
advanceUntilIdle()
mockConnectionStatus.value = ConnectionStatus.CONNECTED
mockEventsFlow.emit(WsEvent.GatewayReady(null))
advanceUntilIdle()
io.mockk.verify {
HermesWsClient.send(
WsMethods.SESSION_CREATE,
any(),
any(),
)
}
io.mockk.verify(exactly = 0) {
HermesWsClient.send(
WsMethods.SESSION_RESUME,
any(),
any(),
)
}
}
@Test
fun testSwitchSession_updatesLastOpenedSessionId() =
runTest {
val (viewModel, _) = createViewModelWithSession()
advanceUntilIdle()
viewModel.switchSession("session-other-456")
advanceUntilIdle()
io.mockk.verify { AuthManager.setLastOpenedSessionId("session-other-456") }
}
}
@@ -95,6 +95,10 @@ class SlashCommandDispatchRpcTest {
every { AuthManager.isTypingEffectEnabled() } returns true
every { AuthManager.getTypingEffectDelayMs() } returns 30
every { AuthManager.isAutoReconnect() } returns false
every { AuthManager.isRestoreLastSession() } returns false
every { AuthManager.getLastOpenedSessionId() } returns null
every { AuthManager.setLastOpenedSessionId(any()) } returns Unit
every { AuthManager.clearLastOpenedSessionId() } returns Unit
every { HermesWsClient.events } returns mockEventsFlow
every { HermesWsClient.connectionStatus } returns mockConnectionStatus
every { HermesWsClient.connect() } answers {
@@ -63,6 +63,10 @@ class SettingsViewModelTest {
every { AuthManager.getAppLanguage() } returns "system"
every { AuthManager.getToken() } returns ""
every { AuthManager.isAutoReconnect() } returns true
every { AuthManager.isRestoreLastSession() } returns false
every { AuthManager.setRestoreLastSession(any()) } returns Unit
every { AuthManager.clearLastOpenedSessionIdsForConnection(any()) } returns Unit
every { AuthManager.clearLastOpenedSessionId() } returns Unit
every { AuthManager.getThemePreference() } returns ThemePreference.SYSTEM
every { AuthManager.isUseDynamicColors() } returns true
every { AuthManager.getThemePreset() } returns ThemePreset.DEFAULT
@@ -253,4 +257,26 @@ class SettingsViewModelTest {
verify { AuthManager.setSelectedProfileId(any()) }
assertEquals(true, viewModel.uiState.value.navigateToLogin)
}
@Test
fun testRestoreLastSession_loadsAndUpdatesState() {
every { AuthManager.isRestoreLastSession() } returns true
val viewModel = createViewModel()
assertEquals(true, viewModel.uiState.value.restoreLastSession)
viewModel.onRestoreLastSessionChange(false)
assertEquals(false, viewModel.uiState.value.restoreLastSession)
verify { AuthManager.setRestoreLastSession(false) }
}
@Test
fun testDeleteProfile_clearsStoredSessionIdsForConnection() {
val viewModel = createViewModel()
viewModel.deleteProfile("prof-1")
testDispatcher.scheduler.advanceUntilIdle()
verify { AuthManager.clearLastOpenedSessionIdsForConnection("prof-1") }
}
}