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

View file

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