feat: add card detail repository with session-aware operations
This commit is contained in:
@@ -144,7 +144,8 @@ Kanbn4Droid is an unofficial app to connect to and manipulate data stored in sel
|
|||||||
- The modal dialog has "Add comment" as a title.
|
- The modal dialog has "Add comment" as a title.
|
||||||
- The modal dialog has an editable markdown-enabled text field for the comment.
|
- The modal dialog has an editable markdown-enabled text field for the comment.
|
||||||
- The modal dialog has two buttons at the bottom on the right side for "Cancel" and "Add" respectively.
|
- The modal dialog has two buttons at the bottom on the right side for "Cancel" and "Add" respectively.
|
||||||
- Current status: full card detail is still pending. Tapping a board-detail card currently navigates to `CardDetailPlaceholderActivity` with card id/title extras.
|
- Current status: full card detail UI is still pending. Tapping a board-detail card currently navigates to `CardDetailPlaceholderActivity` with card id/title extras.
|
||||||
|
- Card detail data operations now include a dedicated `CardDetailRepository` with session-aware failure mapping (`Missing session. Please sign in again.` and typed session-expired failures for auth errors), field normalization for title/description/due date updates, activity listing capped to newest 10, and add-comment refresh behavior.
|
||||||
|
|
||||||
**Settings view**
|
**Settings view**
|
||||||
- The view shows a list of settings that can be changed by the user. The following settings are available:
|
- The view shows a list of settings that can be changed by the user. The following settings are available:
|
||||||
|
|||||||
@@ -0,0 +1,245 @@
|
|||||||
|
package space.hackenslacker.kanbn4droid.app.carddetail
|
||||||
|
|
||||||
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import java.time.LocalDate
|
||||||
|
import space.hackenslacker.kanbn4droid.app.auth.ApiKeyStore
|
||||||
|
import space.hackenslacker.kanbn4droid.app.auth.KanbnApiClient
|
||||||
|
import space.hackenslacker.kanbn4droid.app.auth.SessionStore
|
||||||
|
import space.hackenslacker.kanbn4droid.app.boards.BoardsApiResult
|
||||||
|
|
||||||
|
class CardDetailRepository(
|
||||||
|
private val sessionStore: SessionStore,
|
||||||
|
private val apiKeyStore: ApiKeyStore,
|
||||||
|
private val apiClient: KanbnApiClient,
|
||||||
|
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO,
|
||||||
|
) {
|
||||||
|
|
||||||
|
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."
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed interface Result<out T> {
|
||||||
|
data class Success<T>(val value: T) : Result<T>
|
||||||
|
|
||||||
|
sealed interface Failure : Result<Nothing> {
|
||||||
|
val message: String
|
||||||
|
|
||||||
|
data class Generic(override val message: String) : Failure
|
||||||
|
data class SessionExpired(override val message: String = SESSION_EXPIRED_MESSAGE) : Failure
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun updateTitle(cardId: String, title: String): Result<Unit> {
|
||||||
|
val normalizedCardId = cardId.trim()
|
||||||
|
if (normalizedCardId.isBlank()) {
|
||||||
|
return Result.Failure.Generic("Card id is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
val normalizedTitle = title.trim()
|
||||||
|
if (normalizedTitle.isBlank()) {
|
||||||
|
return Result.Failure.Generic("Card title is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
val session = when (val sessionResult = session()) {
|
||||||
|
is Result.Success -> sessionResult.value
|
||||||
|
is Result.Failure -> return sessionResult
|
||||||
|
}
|
||||||
|
|
||||||
|
val detail = when (
|
||||||
|
val detailResult = apiClient.getCardDetail(
|
||||||
|
baseUrl = session.baseUrl,
|
||||||
|
apiKey = session.apiKey,
|
||||||
|
cardId = normalizedCardId,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
is BoardsApiResult.Success -> detailResult.value
|
||||||
|
is BoardsApiResult.Failure -> return mapFailure(detailResult.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
return when (
|
||||||
|
val updateResult = apiClient.updateCard(
|
||||||
|
baseUrl = session.baseUrl,
|
||||||
|
apiKey = session.apiKey,
|
||||||
|
cardId = normalizedCardId,
|
||||||
|
title = normalizedTitle,
|
||||||
|
description = detail.description,
|
||||||
|
dueDate = detail.dueDate,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
is BoardsApiResult.Success -> Result.Success(Unit)
|
||||||
|
is BoardsApiResult.Failure -> mapFailure(updateResult.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun updateDescription(cardId: String, description: String?): Result<Unit> {
|
||||||
|
val normalizedCardId = cardId.trim()
|
||||||
|
if (normalizedCardId.isBlank()) {
|
||||||
|
return Result.Failure.Generic("Card id is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
val normalizedDescription = description?.trim()?.takeIf { it.isNotBlank() }
|
||||||
|
|
||||||
|
val session = when (val sessionResult = session()) {
|
||||||
|
is Result.Success -> sessionResult.value
|
||||||
|
is Result.Failure -> return sessionResult
|
||||||
|
}
|
||||||
|
|
||||||
|
val detail = when (
|
||||||
|
val detailResult = apiClient.getCardDetail(
|
||||||
|
baseUrl = session.baseUrl,
|
||||||
|
apiKey = session.apiKey,
|
||||||
|
cardId = normalizedCardId,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
is BoardsApiResult.Success -> detailResult.value
|
||||||
|
is BoardsApiResult.Failure -> return mapFailure(detailResult.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
return when (
|
||||||
|
val updateResult = apiClient.updateCard(
|
||||||
|
baseUrl = session.baseUrl,
|
||||||
|
apiKey = session.apiKey,
|
||||||
|
cardId = normalizedCardId,
|
||||||
|
title = detail.title,
|
||||||
|
description = normalizedDescription ?: "",
|
||||||
|
dueDate = detail.dueDate,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
is BoardsApiResult.Success -> Result.Success(Unit)
|
||||||
|
is BoardsApiResult.Failure -> mapFailure(updateResult.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun updateDueDate(cardId: String, dueDate: LocalDate?): Result<Unit> {
|
||||||
|
val normalizedCardId = cardId.trim()
|
||||||
|
if (normalizedCardId.isBlank()) {
|
||||||
|
return Result.Failure.Generic("Card id is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
val session = when (val sessionResult = session()) {
|
||||||
|
is Result.Success -> sessionResult.value
|
||||||
|
is Result.Failure -> return sessionResult
|
||||||
|
}
|
||||||
|
|
||||||
|
val detail = when (
|
||||||
|
val detailResult = apiClient.getCardDetail(
|
||||||
|
baseUrl = session.baseUrl,
|
||||||
|
apiKey = session.apiKey,
|
||||||
|
cardId = normalizedCardId,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
is BoardsApiResult.Success -> detailResult.value
|
||||||
|
is BoardsApiResult.Failure -> return mapFailure(detailResult.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
return when (
|
||||||
|
val updateResult = apiClient.updateCard(
|
||||||
|
baseUrl = session.baseUrl,
|
||||||
|
apiKey = session.apiKey,
|
||||||
|
cardId = normalizedCardId,
|
||||||
|
title = detail.title,
|
||||||
|
description = detail.description,
|
||||||
|
dueDate = dueDate,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
is BoardsApiResult.Success -> Result.Success(Unit)
|
||||||
|
is BoardsApiResult.Failure -> mapFailure(updateResult.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun listActivities(cardId: String): Result<List<CardActivity>> {
|
||||||
|
val normalizedCardId = cardId.trim()
|
||||||
|
if (normalizedCardId.isBlank()) {
|
||||||
|
return Result.Failure.Generic("Card id is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
val session = when (val sessionResult = session()) {
|
||||||
|
is Result.Success -> sessionResult.value
|
||||||
|
is Result.Failure -> return sessionResult
|
||||||
|
}
|
||||||
|
|
||||||
|
return when (
|
||||||
|
val activitiesResult = apiClient.listCardActivities(
|
||||||
|
baseUrl = session.baseUrl,
|
||||||
|
apiKey = session.apiKey,
|
||||||
|
cardId = normalizedCardId,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
is BoardsApiResult.Success -> Result.Success(
|
||||||
|
activitiesResult.value
|
||||||
|
.sortedByDescending { it.createdAtEpochMillis }
|
||||||
|
.take(10),
|
||||||
|
)
|
||||||
|
|
||||||
|
is BoardsApiResult.Failure -> mapFailure(activitiesResult.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun addComment(cardId: String, comment: String): Result<List<CardActivity>> {
|
||||||
|
val normalizedCardId = cardId.trim()
|
||||||
|
if (normalizedCardId.isBlank()) {
|
||||||
|
return Result.Failure.Generic("Card id is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
val normalizedComment = comment.trim()
|
||||||
|
if (normalizedComment.isBlank()) {
|
||||||
|
return Result.Failure.Generic("Comment is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
val session = when (val sessionResult = session()) {
|
||||||
|
is Result.Success -> sessionResult.value
|
||||||
|
is Result.Failure -> return sessionResult
|
||||||
|
}
|
||||||
|
|
||||||
|
when (
|
||||||
|
val addCommentResult = apiClient.addCardComment(
|
||||||
|
baseUrl = session.baseUrl,
|
||||||
|
apiKey = session.apiKey,
|
||||||
|
cardId = normalizedCardId,
|
||||||
|
comment = normalizedComment,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
is BoardsApiResult.Success -> Unit
|
||||||
|
is BoardsApiResult.Failure -> return mapFailure(addCommentResult.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
return listActivities(normalizedCardId)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun session(): Result<SessionSnapshot> {
|
||||||
|
val baseUrl = sessionStore.getBaseUrl()?.takeIf { it.isNotBlank() }
|
||||||
|
?: return Result.Failure.SessionExpired(MISSING_SESSION_MESSAGE)
|
||||||
|
val apiKey = withContext(ioDispatcher) {
|
||||||
|
apiKeyStore.getApiKey(baseUrl)
|
||||||
|
}.getOrNull()?.takeIf { it.isNotBlank() }
|
||||||
|
?: return Result.Failure.SessionExpired(MISSING_SESSION_MESSAGE)
|
||||||
|
|
||||||
|
return Result.Success(SessionSnapshot(baseUrl = baseUrl, apiKey = apiKey))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun mapFailure(message: String): Result.Failure {
|
||||||
|
val normalizedMessage = message.trim().ifBlank { "Unknown error" }
|
||||||
|
return if (isAuthFailure(normalizedMessage)) {
|
||||||
|
Result.Failure.SessionExpired()
|
||||||
|
} else {
|
||||||
|
Result.Failure.Generic(normalizedMessage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class SessionSnapshot(
|
||||||
|
val baseUrl: String,
|
||||||
|
val apiKey: String,
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,352 @@
|
|||||||
|
package space.hackenslacker.kanbn4droid.app.carddetail
|
||||||
|
|
||||||
|
import java.time.LocalDate
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Assert.assertTrue
|
||||||
|
import org.junit.Test
|
||||||
|
import space.hackenslacker.kanbn4droid.app.auth.ApiKeyStore
|
||||||
|
import space.hackenslacker.kanbn4droid.app.auth.AuthResult
|
||||||
|
import space.hackenslacker.kanbn4droid.app.auth.KanbnApiClient
|
||||||
|
import space.hackenslacker.kanbn4droid.app.auth.LabelDetail
|
||||||
|
import space.hackenslacker.kanbn4droid.app.auth.SessionStore
|
||||||
|
import space.hackenslacker.kanbn4droid.app.boarddetail.BoardDetail
|
||||||
|
import space.hackenslacker.kanbn4droid.app.boarddetail.CreatedEntityRef
|
||||||
|
import space.hackenslacker.kanbn4droid.app.boards.BoardSummary
|
||||||
|
import space.hackenslacker.kanbn4droid.app.boards.BoardTemplate
|
||||||
|
import space.hackenslacker.kanbn4droid.app.boards.BoardsApiResult
|
||||||
|
import space.hackenslacker.kanbn4droid.app.boards.WorkspaceSummary
|
||||||
|
|
||||||
|
class CardDetailRepositoryTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun missingSession_returnsSessionExpiredFailure() = runTest {
|
||||||
|
val repository = createRepository(
|
||||||
|
sessionStore = InMemorySessionStore(baseUrl = null),
|
||||||
|
)
|
||||||
|
|
||||||
|
val result = repository.listActivities("card-1")
|
||||||
|
|
||||||
|
assertTrue(result is CardDetailRepository.Result.Failure.SessionExpired)
|
||||||
|
assertEquals(
|
||||||
|
CardDetailRepository.MISSING_SESSION_MESSAGE,
|
||||||
|
(result as CardDetailRepository.Result.Failure.SessionExpired).message,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun updateDescription_blankMapsToNull_andPreservesFailureMessage() = runTest {
|
||||||
|
val apiClient = FakeCardDetailApiClient().apply {
|
||||||
|
updateCardResult = BoardsApiResult.Failure("Card is archived")
|
||||||
|
}
|
||||||
|
val repository = createRepository(apiClient = apiClient)
|
||||||
|
|
||||||
|
val result = repository.updateDescription(cardId = "card-1", description = " ")
|
||||||
|
|
||||||
|
assertEquals(null, apiClient.lastUpdatedDescriptionNormalized)
|
||||||
|
assertTrue(result is CardDetailRepository.Result.Failure.Generic)
|
||||||
|
assertEquals("Card is archived", (result as CardDetailRepository.Result.Failure.Generic).message)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun updateTitle_authFailureMapsToSessionExpired() = runTest {
|
||||||
|
val apiClient = FakeCardDetailApiClient().apply {
|
||||||
|
updateCardResult = BoardsApiResult.Failure("Server error: 401")
|
||||||
|
}
|
||||||
|
val repository = createRepository(apiClient = apiClient)
|
||||||
|
|
||||||
|
val result = repository.updateTitle(cardId = "card-1", title = " Updated title ")
|
||||||
|
|
||||||
|
assertTrue(result is CardDetailRepository.Result.Failure.SessionExpired)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun listActivities_returnsNewestFirstTopTen() = runTest {
|
||||||
|
val apiClient = FakeCardDetailApiClient().apply {
|
||||||
|
listActivitiesResult = BoardsApiResult.Success((1..12).map { index ->
|
||||||
|
CardActivity(
|
||||||
|
id = "a-$index",
|
||||||
|
type = "comment",
|
||||||
|
text = "Activity $index",
|
||||||
|
createdAtEpochMillis = index.toLong(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
val repository = createRepository(apiClient = apiClient)
|
||||||
|
|
||||||
|
val result = repository.listActivities("card-1")
|
||||||
|
|
||||||
|
assertTrue(result is CardDetailRepository.Result.Success)
|
||||||
|
val activities = (result as CardDetailRepository.Result.Success<List<CardActivity>>).value
|
||||||
|
assertEquals(10, activities.size)
|
||||||
|
assertEquals("a-12", activities[0].id)
|
||||||
|
assertEquals("a-3", activities[9].id)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun listActivities_authFailureMapsToSessionExpired() = runTest {
|
||||||
|
val apiClient = FakeCardDetailApiClient().apply {
|
||||||
|
listActivitiesResult = BoardsApiResult.Failure("Server error: 403")
|
||||||
|
}
|
||||||
|
val repository = createRepository(apiClient = apiClient)
|
||||||
|
|
||||||
|
val result = repository.listActivities("card-1")
|
||||||
|
|
||||||
|
assertTrue(result is CardDetailRepository.Result.Failure.SessionExpired)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun addComment_success_refreshesActivities() = runTest {
|
||||||
|
val apiClient = FakeCardDetailApiClient().apply {
|
||||||
|
addCommentResult = BoardsApiResult.Success(Unit)
|
||||||
|
listActivitiesResult = BoardsApiResult.Success(
|
||||||
|
listOf(
|
||||||
|
CardActivity(
|
||||||
|
id = "a-1",
|
||||||
|
type = "comment",
|
||||||
|
text = "hello",
|
||||||
|
createdAtEpochMillis = 1L,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val repository = createRepository(apiClient = apiClient)
|
||||||
|
|
||||||
|
val result = repository.addComment(cardId = "card-1", comment = " hello ")
|
||||||
|
|
||||||
|
assertTrue(result is CardDetailRepository.Result.Success)
|
||||||
|
assertEquals(1, apiClient.addCommentCalls)
|
||||||
|
assertEquals(1, apiClient.listActivitiesCalls)
|
||||||
|
assertEquals("hello", apiClient.lastComment)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun addComment_authFailureMapsToSessionExpired() = runTest {
|
||||||
|
val apiClient = FakeCardDetailApiClient().apply {
|
||||||
|
addCommentResult = BoardsApiResult.Failure("Authentication failed. Check your API key.")
|
||||||
|
}
|
||||||
|
val repository = createRepository(apiClient = apiClient)
|
||||||
|
|
||||||
|
val result = repository.addComment(cardId = "card-1", comment = "A")
|
||||||
|
|
||||||
|
assertTrue(result is CardDetailRepository.Result.Failure.SessionExpired)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun updateDueDate_dateOnlyInputNormalizesToUtcMidnightPayloadContract() = runTest {
|
||||||
|
val apiClient = FakeCardDetailApiClient()
|
||||||
|
val repository = createRepository(apiClient = apiClient)
|
||||||
|
|
||||||
|
val result = repository.updateDueDate(cardId = "card-1", dueDate = LocalDate.parse("2026-03-16"))
|
||||||
|
|
||||||
|
assertTrue(result is CardDetailRepository.Result.Success)
|
||||||
|
assertEquals(LocalDate.of(2026, 3, 16), apiClient.lastUpdatedDueDate)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createRepository(
|
||||||
|
sessionStore: InMemorySessionStore = InMemorySessionStore(baseUrl = "https://kan.bn/"),
|
||||||
|
apiKeyStore: InMemoryApiKeyStore = InMemoryApiKeyStore("api-key"),
|
||||||
|
apiClient: FakeCardDetailApiClient = FakeCardDetailApiClient(),
|
||||||
|
): CardDetailRepository {
|
||||||
|
return CardDetailRepository(
|
||||||
|
sessionStore = sessionStore,
|
||||||
|
apiKeyStore = apiKeyStore,
|
||||||
|
apiClient = apiClient,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private class InMemorySessionStore(
|
||||||
|
private var baseUrl: String?,
|
||||||
|
private var workspaceId: String? = null,
|
||||||
|
) : SessionStore {
|
||||||
|
override fun getBaseUrl(): String? = baseUrl
|
||||||
|
|
||||||
|
override fun saveBaseUrl(url: String) {
|
||||||
|
baseUrl = url
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getWorkspaceId(): String? = workspaceId
|
||||||
|
|
||||||
|
override fun saveWorkspaceId(workspaceId: String) {
|
||||||
|
this.workspaceId = workspaceId
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun clearBaseUrl() {
|
||||||
|
baseUrl = null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun clearWorkspaceId() {
|
||||||
|
workspaceId = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class InMemoryApiKeyStore(private var apiKey: String?) : ApiKeyStore {
|
||||||
|
override suspend fun saveApiKey(baseUrl: String, apiKey: String): Result<Unit> {
|
||||||
|
this.apiKey = apiKey
|
||||||
|
return Result.success(Unit)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getApiKey(baseUrl: String): Result<String?> = Result.success(apiKey)
|
||||||
|
|
||||||
|
override suspend fun invalidateApiKey(baseUrl: String): Result<Unit> {
|
||||||
|
apiKey = null
|
||||||
|
return Result.success(Unit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class FakeCardDetailApiClient : KanbnApiClient {
|
||||||
|
var cardDetailResult: BoardsApiResult<CardDetail> = BoardsApiResult.Success(
|
||||||
|
CardDetail(
|
||||||
|
id = "card-1",
|
||||||
|
title = "Current title",
|
||||||
|
description = "Current description",
|
||||||
|
dueDate = LocalDate.of(2026, 3, 1),
|
||||||
|
listPublicId = "list-1",
|
||||||
|
index = 0,
|
||||||
|
tags = emptyList(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
var updateCardResult: BoardsApiResult<Unit> = BoardsApiResult.Success(Unit)
|
||||||
|
var listActivitiesResult: BoardsApiResult<List<CardActivity>> = BoardsApiResult.Success(emptyList())
|
||||||
|
var addCommentResult: BoardsApiResult<Unit> = BoardsApiResult.Success(Unit)
|
||||||
|
|
||||||
|
var lastUpdatedTitle: String? = null
|
||||||
|
var lastUpdatedDescription: String? = null
|
||||||
|
var lastUpdatedDescriptionNormalized: String? = null
|
||||||
|
var lastUpdatedDueDate: LocalDate? = null
|
||||||
|
var addCommentCalls: Int = 0
|
||||||
|
var listActivitiesCalls: Int = 0
|
||||||
|
var lastComment: String? = null
|
||||||
|
|
||||||
|
override suspend fun healthCheck(baseUrl: String, apiKey: String): AuthResult = AuthResult.Success
|
||||||
|
|
||||||
|
override suspend fun getCardDetail(baseUrl: String, apiKey: String, cardId: String): BoardsApiResult<CardDetail> {
|
||||||
|
return cardDetailResult
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun updateCard(
|
||||||
|
baseUrl: String,
|
||||||
|
apiKey: String,
|
||||||
|
cardId: String,
|
||||||
|
title: String,
|
||||||
|
description: String,
|
||||||
|
dueDate: LocalDate?,
|
||||||
|
): BoardsApiResult<Unit> {
|
||||||
|
lastUpdatedTitle = title
|
||||||
|
lastUpdatedDescription = description
|
||||||
|
lastUpdatedDescriptionNormalized = description.takeIf { it.isNotBlank() }
|
||||||
|
lastUpdatedDueDate = dueDate
|
||||||
|
return updateCardResult
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun listCardActivities(
|
||||||
|
baseUrl: String,
|
||||||
|
apiKey: String,
|
||||||
|
cardId: String,
|
||||||
|
): BoardsApiResult<List<CardActivity>> {
|
||||||
|
listActivitiesCalls += 1
|
||||||
|
return listActivitiesResult
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun addCardComment(
|
||||||
|
baseUrl: String,
|
||||||
|
apiKey: String,
|
||||||
|
cardId: String,
|
||||||
|
comment: String,
|
||||||
|
): BoardsApiResult<Unit> {
|
||||||
|
addCommentCalls += 1
|
||||||
|
lastComment = comment
|
||||||
|
return addCommentResult
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun listWorkspaces(baseUrl: String, apiKey: String): BoardsApiResult<List<WorkspaceSummary>> {
|
||||||
|
return BoardsApiResult.Success(emptyList())
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun listBoards(
|
||||||
|
baseUrl: String,
|
||||||
|
apiKey: String,
|
||||||
|
workspaceId: String,
|
||||||
|
): BoardsApiResult<List<BoardSummary>> {
|
||||||
|
return BoardsApiResult.Success(emptyList())
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun listBoardTemplates(
|
||||||
|
baseUrl: String,
|
||||||
|
apiKey: String,
|
||||||
|
workspaceId: String,
|
||||||
|
): BoardsApiResult<List<BoardTemplate>> {
|
||||||
|
return BoardsApiResult.Success(emptyList())
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun createBoard(
|
||||||
|
baseUrl: String,
|
||||||
|
apiKey: String,
|
||||||
|
workspaceId: String,
|
||||||
|
name: String,
|
||||||
|
templateId: String?,
|
||||||
|
): BoardsApiResult<BoardSummary> {
|
||||||
|
return BoardsApiResult.Success(BoardSummary("board-1", name))
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun deleteBoard(baseUrl: String, apiKey: String, boardId: String): BoardsApiResult<Unit> {
|
||||||
|
return BoardsApiResult.Success(Unit)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getBoardDetail(baseUrl: String, apiKey: String, boardId: String): BoardsApiResult<BoardDetail> {
|
||||||
|
return BoardsApiResult.Success(BoardDetail(id = boardId, title = "Board", lists = emptyList()))
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun renameList(
|
||||||
|
baseUrl: String,
|
||||||
|
apiKey: String,
|
||||||
|
listId: String,
|
||||||
|
newTitle: String,
|
||||||
|
): BoardsApiResult<Unit> {
|
||||||
|
return BoardsApiResult.Success(Unit)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun createList(
|
||||||
|
baseUrl: String,
|
||||||
|
apiKey: String,
|
||||||
|
boardPublicId: String,
|
||||||
|
title: String,
|
||||||
|
appendIndex: Int,
|
||||||
|
): BoardsApiResult<CreatedEntityRef> {
|
||||||
|
return BoardsApiResult.Success(CreatedEntityRef("list-1"))
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun createCard(
|
||||||
|
baseUrl: String,
|
||||||
|
apiKey: String,
|
||||||
|
listPublicId: String,
|
||||||
|
title: String,
|
||||||
|
description: String?,
|
||||||
|
dueDate: LocalDate?,
|
||||||
|
tagPublicIds: List<String>,
|
||||||
|
): BoardsApiResult<CreatedEntityRef> {
|
||||||
|
return BoardsApiResult.Success(CreatedEntityRef("card-1"))
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun moveCard(
|
||||||
|
baseUrl: String,
|
||||||
|
apiKey: String,
|
||||||
|
cardId: String,
|
||||||
|
targetListId: String,
|
||||||
|
): BoardsApiResult<Unit> {
|
||||||
|
return BoardsApiResult.Success(Unit)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun deleteCard(baseUrl: String, apiKey: String, cardId: String): BoardsApiResult<Unit> {
|
||||||
|
return BoardsApiResult.Success(Unit)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getLabelByPublicId(
|
||||||
|
baseUrl: String,
|
||||||
|
apiKey: String,
|
||||||
|
labelId: String,
|
||||||
|
): BoardsApiResult<LabelDetail> {
|
||||||
|
return BoardsApiResult.Success(LabelDetail(labelId, "#000000"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user