fix: make card comment refresh failures non-fatal

This commit is contained in:
2026-03-16 20:41:07 -04:00
parent 82a3d59105
commit f85586ddc7
2 changed files with 73 additions and 6 deletions

View File

@@ -19,6 +19,7 @@ class CardDetailRepository(
companion object {
const val MISSING_SESSION_MESSAGE = "Missing session. Please sign in again."
private const val SESSION_EXPIRED_MESSAGE = "Session expired. Please sign in again."
private val AUTH_STATUS_CODE_REGEX = Regex("\\b(401|403)\\b")
}
sealed interface Result<out T> {
@@ -206,7 +207,11 @@ class CardDetailRepository(
is BoardsApiResult.Failure -> return mapFailure(addCommentResult.message)
}
return listActivities(normalizedCardId)
return when (val refreshResult = listActivities(normalizedCardId)) {
is Result.Success -> refreshResult
is Result.Failure.SessionExpired -> refreshResult
is Result.Failure.Generic -> Result.Success(emptyList())
}
}
private suspend fun session(): Result<SessionSnapshot> {
@@ -231,11 +236,18 @@ class CardDetailRepository(
private fun isAuthFailure(message: String): Boolean {
val lower = message.lowercase()
return lower.contains("authentication failed") ||
lower.contains("server error: 401") ||
lower.contains("server error: 403") ||
lower.contains(" 401") ||
lower.contains(" 403")
val hasAuthToken =
lower.contains("authentication failed") ||
lower.contains("unauthorized") ||
lower.contains("forbidden")
val hasAuthStatusCode = AUTH_STATUS_CODE_REGEX.containsMatchIn(lower) &&
(
lower.contains("server error") ||
lower.contains("http") ||
lower.contains("status") ||
lower.contains("code")
)
return hasAuthToken || hasAuthStatusCode
}
private data class SessionSnapshot(

View File

@@ -120,6 +120,36 @@ class CardDetailRepositoryTest {
assertEquals("hello", apiClient.lastComment)
}
@Test
fun addComment_refreshGenericFailure_returnsSuccessWithEmptyActivities() = runTest {
val apiClient = FakeCardDetailApiClient().apply {
addCommentResult = BoardsApiResult.Success(Unit)
listActivitiesResult = BoardsApiResult.Failure("Server temporarily unavailable")
}
val repository = createRepository(apiClient = apiClient)
val result = repository.addComment(cardId = "card-1", comment = "hello")
assertTrue(result is CardDetailRepository.Result.Success)
val activities = (result as CardDetailRepository.Result.Success<List<CardActivity>>).value
assertTrue(activities.isEmpty())
assertEquals(1, apiClient.addCommentCalls)
assertEquals(1, apiClient.listActivitiesCalls)
}
@Test
fun addComment_refreshAuthFailure_mapsToSessionExpired() = runTest {
val apiClient = FakeCardDetailApiClient().apply {
addCommentResult = BoardsApiResult.Success(Unit)
listActivitiesResult = BoardsApiResult.Failure("Server error: 403")
}
val repository = createRepository(apiClient = apiClient)
val result = repository.addComment(cardId = "card-1", comment = "hello")
assertTrue(result is CardDetailRepository.Result.Failure.SessionExpired)
}
@Test
fun addComment_authFailureMapsToSessionExpired() = runTest {
val apiClient = FakeCardDetailApiClient().apply {
@@ -132,6 +162,31 @@ class CardDetailRepositoryTest {
assertTrue(result is CardDetailRepository.Result.Failure.SessionExpired)
}
@Test
fun listActivities_nonAuthNumericMessage_remainsGenericFailure() = runTest {
val apiClient = FakeCardDetailApiClient().apply {
listActivitiesResult = BoardsApiResult.Failure("Server error: 1401")
}
val repository = createRepository(apiClient = apiClient)
val result = repository.listActivities("card-1")
assertTrue(result is CardDetailRepository.Result.Failure.Generic)
assertEquals("Server error: 1401", (result as CardDetailRepository.Result.Failure.Generic).message)
}
@Test
fun listActivities_forbiddenMessage_mapsToSessionExpired() = runTest {
val apiClient = FakeCardDetailApiClient().apply {
listActivitiesResult = BoardsApiResult.Failure("Forbidden")
}
val repository = createRepository(apiClient = apiClient)
val result = repository.listActivities("card-1")
assertTrue(result is CardDetailRepository.Result.Failure.SessionExpired)
}
@Test
fun updateDueDate_dateOnlyInputNormalizesToUtcMidnightPayloadContract() = runTest {
val apiClient = FakeCardDetailApiClient()