fix(settings): stop double-invoking authRepository.logout on logout/delete

SettingsViewModel.logout() and deleteAccount() each called
authRepository.logout() directly before flipping the state flag that
drives the screen's LaunchedEffect -> onLogout callback ->
NavHostViewModel.logout(), which invokes authRepository.logout() again.
Each redundant call fires a POST /api/auth/logout that 401s (tokens
already gone) and re-runs session cleanup; with the planned full-wipe
change that duplication becomes actively harmful.

Collapse SettingsViewModel.logout() to a single state update, and drop
the direct authRepository.logout() call from the deleteAccount success
branch. NavHostViewModel.logout() remains the single source of truth
for the cleanup side of the flow.
This commit is contained in:
Garfie 2026-04-14 18:14:52 -05:00
parent 4814f8a5b5
commit ff25ef6f47
2 changed files with 6 additions and 17 deletions

View file

@ -23,7 +23,6 @@ import com.garfiec.librechat.feature.settings.util.PlatformCacheCleaner
import com.garfiec.librechat.feature.settings.viewmodel.delegate.SpeechSettingsContract
import com.garfiec.librechat.feature.settings.viewmodel.delegate.SpeechSettingsFactory
import com.google.common.truth.Truth.assertThat
import io.mockk.Ordering
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.every
@ -158,23 +157,20 @@ class SettingsViewModelTest {
}
@Test
fun `logout calls authRepository and sets isLoggedOut`() = runTest {
coEvery { authRepository.logout() } returns Result.Success(Unit)
fun `logout sets isLoggedOut flag without calling authRepository`() = runTest {
viewModel = createViewModel()
advanceUntilIdle()
viewModel.logout()
advanceUntilIdle()
coVerify { authRepository.logout() }
coVerify(exactly = 0) { authRepository.logout() }
assertThat(viewModel.uiState.value.isLoggedOut).isTrue()
}
@Test
fun `deleteAccount calls userRepository then authRepository`() = runTest {
fun `deleteAccount calls userRepository and sets isAccountDeleted without calling authRepository`() = runTest {
coEvery { userRepository.deleteUser() } returns Result.Success(Unit)
coEvery { authRepository.logout() } returns Result.Success(Unit)
viewModel = createViewModel()
advanceUntilIdle()
@ -182,10 +178,8 @@ class SettingsViewModelTest {
viewModel.deleteAccount()
advanceUntilIdle()
coVerify(ordering = Ordering.ORDERED) {
userRepository.deleteUser()
authRepository.logout()
}
coVerify { userRepository.deleteUser() }
coVerify(exactly = 0) { authRepository.logout() }
assertThat(viewModel.uiState.value.isAccountDeleted).isTrue()
}

View file

@ -585,11 +585,7 @@ class SettingsViewModel(
// ── Auth actions ───────────────────────────────────────────────
fun logout() {
viewModelScope.launch {
_uiState.update { it.copy(isLoading = true) }
authRepository.logout()
_uiState.update { it.copy(isLoading = false, isLoggedOut = true) }
}
_uiState.update { it.copy(isLoggedOut = true) }
}
fun deleteAccount(token: String? = null, backupCode: String? = null) {
@ -597,7 +593,6 @@ class SettingsViewModel(
_uiState.update { it.copy(isLoading = true) }
when (val result = userRepository.deleteUser(token = token, backupCode = backupCode)) {
is Result.Success -> {
authRepository.logout()
_uiState.update { it.copy(isLoading = false, isAccountDeleted = true, showDeleteAccountOtpDialog = false) }
}
is Result.Error -> {