fix: handle workspace-switch unauthorized and rollback edge case
This commit is contained in:
@@ -64,6 +64,7 @@ class BoardsRepository(
|
|||||||
is BoardsApiResult.Failure -> return sessionResult
|
is BoardsApiResult.Failure -> return sessionResult
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val previousWorkspaceId = sessionStore.getWorkspaceId()?.takeIf { it.isNotBlank() }
|
||||||
sessionStore.saveWorkspaceId(normalizedWorkspaceId)
|
sessionStore.saveWorkspaceId(normalizedWorkspaceId)
|
||||||
val listBoardsResult = apiClient.listBoards(
|
val listBoardsResult = apiClient.listBoards(
|
||||||
baseUrl = session.baseUrl,
|
baseUrl = session.baseUrl,
|
||||||
@@ -72,7 +73,14 @@ class BoardsRepository(
|
|||||||
)
|
)
|
||||||
return when (listBoardsResult) {
|
return when (listBoardsResult) {
|
||||||
is BoardsApiResult.Success -> BoardsApiResult.Success(Unit)
|
is BoardsApiResult.Success -> BoardsApiResult.Success(Unit)
|
||||||
is BoardsApiResult.Failure -> listBoardsResult
|
is BoardsApiResult.Failure -> {
|
||||||
|
if (previousWorkspaceId != null) {
|
||||||
|
sessionStore.saveWorkspaceId(previousWorkspaceId)
|
||||||
|
} else {
|
||||||
|
sessionStore.clearWorkspaceId()
|
||||||
|
}
|
||||||
|
listBoardsResult
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -101,11 +101,15 @@ class BoardsViewModel(
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
if (result.message.isUnauthorizedFailureMessage()) {
|
||||||
|
_events.emit(BoardsUiEvent.ForceSignOut)
|
||||||
|
} else {
|
||||||
_events.emit(BoardsUiEvent.ShowServerError(result.message))
|
_events.emit(BoardsUiEvent.ShowServerError(result.message))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun refreshBoards() {
|
fun refreshBoards() {
|
||||||
fetchBoards(initial = false, refresh = true)
|
fetchBoards(initial = false, refresh = true)
|
||||||
@@ -288,3 +292,8 @@ class BoardsViewModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun String.isUnauthorizedFailureMessage(): Boolean {
|
||||||
|
val normalized = lowercase()
|
||||||
|
return "401" in normalized || "403" in normalized || "authentication" in normalized || "unauthorized" in normalized
|
||||||
|
}
|
||||||
|
|||||||
@@ -318,6 +318,54 @@ class BoardsViewModelTest {
|
|||||||
assertEquals("ws-1", sessionStore.getWorkspaceId())
|
assertEquals("ws-1", sessionStore.getWorkspaceId())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun workspaceSwitchUnauthorizedEmitsForceSignOut() = runTest {
|
||||||
|
val api = FakeBoardsApiClient().apply {
|
||||||
|
usersMeResult = BoardsApiResult.Success(DrawerProfile(displayName = "Alice", email = null))
|
||||||
|
workspacesResult = BoardsApiResult.Success(
|
||||||
|
listOf(
|
||||||
|
WorkspaceSummary("ws-1", "Main"),
|
||||||
|
WorkspaceSummary("ws-2", "Platform"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
listBoardsResults.addLast(BoardsApiResult.Failure("Server error: 401"))
|
||||||
|
}
|
||||||
|
val sessionStore = InMemorySessionStore("https://kan.bn/", workspaceId = "ws-1")
|
||||||
|
val viewModel = newViewModel(api, sessionStore = sessionStore)
|
||||||
|
viewModel.loadDrawerData()
|
||||||
|
advanceUntilIdle()
|
||||||
|
|
||||||
|
val eventDeferred = async { viewModel.events.first() }
|
||||||
|
viewModel.onWorkspaceSelected("ws-2")
|
||||||
|
advanceUntilIdle()
|
||||||
|
|
||||||
|
val event = eventDeferred.await()
|
||||||
|
assertTrue(event is BoardsUiEvent.ForceSignOut)
|
||||||
|
assertEquals("ws-1", sessionStore.getWorkspaceId())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun workspaceSwitchFailureWithNullPreviousRollsBackPersistedWorkspaceId() = runTest {
|
||||||
|
val api = FakeBoardsApiClient().apply {
|
||||||
|
usersMeResult = BoardsApiResult.Success(DrawerProfile(displayName = "Alice", email = null))
|
||||||
|
workspacesResult = BoardsApiResult.Failure("Server error: 500")
|
||||||
|
listBoardsResults.addLast(BoardsApiResult.Failure("Server error: 500"))
|
||||||
|
}
|
||||||
|
val sessionStore = InMemorySessionStore("https://kan.bn/", workspaceId = null)
|
||||||
|
val viewModel = newViewModel(api, sessionStore = sessionStore)
|
||||||
|
viewModel.loadDrawerData()
|
||||||
|
advanceUntilIdle()
|
||||||
|
|
||||||
|
assertEquals(null, viewModel.uiState.value.drawer.activeWorkspaceId)
|
||||||
|
assertEquals(null, sessionStore.getWorkspaceId())
|
||||||
|
|
||||||
|
viewModel.onWorkspaceSelected("ws-2")
|
||||||
|
advanceUntilIdle()
|
||||||
|
|
||||||
|
assertEquals(null, sessionStore.getWorkspaceId())
|
||||||
|
assertEquals(null, viewModel.uiState.value.drawer.activeWorkspaceId)
|
||||||
|
}
|
||||||
|
|
||||||
private fun newViewModel(
|
private fun newViewModel(
|
||||||
apiClient: FakeBoardsApiClient,
|
apiClient: FakeBoardsApiClient,
|
||||||
sessionStore: InMemorySessionStore = InMemorySessionStore("https://kan.bn/", workspaceId = "ws-1"),
|
sessionStore: InMemorySessionStore = InMemorySessionStore("https://kan.bn/", workspaceId = "ws-1"),
|
||||||
|
|||||||
Reference in New Issue
Block a user