fix: make card comment refresh failures non-fatal
This commit is contained in:
@@ -19,6 +19,7 @@ class CardDetailRepository(
|
|||||||
companion object {
|
companion object {
|
||||||
const val MISSING_SESSION_MESSAGE = "Missing session. Please sign in again."
|
const val MISSING_SESSION_MESSAGE = "Missing session. Please sign in again."
|
||||||
private const val SESSION_EXPIRED_MESSAGE = "Session expired. 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> {
|
sealed interface Result<out T> {
|
||||||
@@ -206,7 +207,11 @@ class CardDetailRepository(
|
|||||||
is BoardsApiResult.Failure -> return mapFailure(addCommentResult.message)
|
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> {
|
private suspend fun session(): Result<SessionSnapshot> {
|
||||||
@@ -231,11 +236,18 @@ class CardDetailRepository(
|
|||||||
|
|
||||||
private fun isAuthFailure(message: String): Boolean {
|
private fun isAuthFailure(message: String): Boolean {
|
||||||
val lower = message.lowercase()
|
val lower = message.lowercase()
|
||||||
return lower.contains("authentication failed") ||
|
val hasAuthToken =
|
||||||
lower.contains("server error: 401") ||
|
lower.contains("authentication failed") ||
|
||||||
lower.contains("server error: 403") ||
|
lower.contains("unauthorized") ||
|
||||||
lower.contains(" 401") ||
|
lower.contains("forbidden")
|
||||||
lower.contains(" 403")
|
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(
|
private data class SessionSnapshot(
|
||||||
|
|||||||
@@ -120,6 +120,36 @@ class CardDetailRepositoryTest {
|
|||||||
assertEquals("hello", apiClient.lastComment)
|
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
|
@Test
|
||||||
fun addComment_authFailureMapsToSessionExpired() = runTest {
|
fun addComment_authFailureMapsToSessionExpired() = runTest {
|
||||||
val apiClient = FakeCardDetailApiClient().apply {
|
val apiClient = FakeCardDetailApiClient().apply {
|
||||||
@@ -132,6 +162,31 @@ class CardDetailRepositoryTest {
|
|||||||
assertTrue(result is CardDetailRepository.Result.Failure.SessionExpired)
|
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
|
@Test
|
||||||
fun updateDueDate_dateOnlyInputNormalizesToUtcMidnightPayloadContract() = runTest {
|
fun updateDueDate_dateOnlyInputNormalizesToUtcMidnightPayloadContract() = runTest {
|
||||||
val apiClient = FakeCardDetailApiClient()
|
val apiClient = FakeCardDetailApiClient()
|
||||||
|
|||||||
Reference in New Issue
Block a user