feat: implement board detail repository and mutation aggregation
This commit is contained in:
@@ -0,0 +1,197 @@
|
||||
package space.hackenslacker.kanbn4droid.app.boarddetail
|
||||
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
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 BoardDetailRepository(
|
||||
private val sessionStore: SessionStore,
|
||||
private val apiKeyStore: ApiKeyStore,
|
||||
private val apiClient: KanbnApiClient,
|
||||
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO,
|
||||
) {
|
||||
suspend fun getBoardDetail(boardId: String): BoardsApiResult<BoardDetail> {
|
||||
val normalizedBoardId = boardId.trim()
|
||||
if (normalizedBoardId.isBlank()) {
|
||||
return BoardsApiResult.Failure("Board id is required")
|
||||
}
|
||||
|
||||
val session = when (val sessionResult = session()) {
|
||||
is BoardsApiResult.Success -> sessionResult.value
|
||||
is BoardsApiResult.Failure -> return sessionResult
|
||||
}
|
||||
|
||||
return apiClient.getBoardDetail(
|
||||
baseUrl = session.baseUrl,
|
||||
apiKey = session.apiKey,
|
||||
boardId = normalizedBoardId,
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun renameList(listId: String, newTitle: String): BoardsApiResult<Unit> {
|
||||
val normalizedListId = listId.trim()
|
||||
if (normalizedListId.isBlank()) {
|
||||
return BoardsApiResult.Failure("List id is required")
|
||||
}
|
||||
|
||||
val normalizedTitle = newTitle.trim()
|
||||
if (normalizedTitle.isBlank()) {
|
||||
return BoardsApiResult.Failure("List title is required")
|
||||
}
|
||||
|
||||
val session = when (val sessionResult = session()) {
|
||||
is BoardsApiResult.Success -> sessionResult.value
|
||||
is BoardsApiResult.Failure -> return sessionResult
|
||||
}
|
||||
|
||||
return apiClient.renameList(
|
||||
baseUrl = session.baseUrl,
|
||||
apiKey = session.apiKey,
|
||||
listId = normalizedListId,
|
||||
newTitle = normalizedTitle,
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun moveCards(cardIds: Collection<String>, targetListId: String): CardBatchMutationResult {
|
||||
val normalizedTargetListId = targetListId.trim()
|
||||
if (normalizedTargetListId.isBlank()) {
|
||||
return CardBatchMutationResult.Failure("Target list id is required")
|
||||
}
|
||||
|
||||
val normalizedCardIds = normalizeCardIds(cardIds)
|
||||
if (normalizedCardIds.isEmpty()) {
|
||||
return CardBatchMutationResult.Failure("At least one card id is required")
|
||||
}
|
||||
|
||||
val session = when (val sessionResult = session()) {
|
||||
is BoardsApiResult.Success -> sessionResult.value
|
||||
is BoardsApiResult.Failure -> return CardBatchMutationResult.Failure(sessionResult.message)
|
||||
}
|
||||
|
||||
val failuresByCardId = linkedMapOf<String, String>()
|
||||
normalizedCardIds.forEach { cardId ->
|
||||
when (
|
||||
val result = apiClient.moveCard(
|
||||
baseUrl = session.baseUrl,
|
||||
apiKey = session.apiKey,
|
||||
cardId = cardId,
|
||||
targetListId = normalizedTargetListId,
|
||||
)
|
||||
) {
|
||||
is BoardsApiResult.Success -> Unit
|
||||
is BoardsApiResult.Failure -> failuresByCardId[cardId] = result.message
|
||||
}
|
||||
}
|
||||
|
||||
return aggregateBatchMutationResult(
|
||||
normalizedCardIds = normalizedCardIds,
|
||||
failuresByCardId = failuresByCardId,
|
||||
partialMessage = "Some cards could not be moved. Please try again.",
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun deleteCards(cardIds: Collection<String>): CardBatchMutationResult {
|
||||
val normalizedCardIds = normalizeCardIds(cardIds)
|
||||
if (normalizedCardIds.isEmpty()) {
|
||||
return CardBatchMutationResult.Failure("At least one card id is required")
|
||||
}
|
||||
|
||||
val session = when (val sessionResult = session()) {
|
||||
is BoardsApiResult.Success -> sessionResult.value
|
||||
is BoardsApiResult.Failure -> return CardBatchMutationResult.Failure(sessionResult.message)
|
||||
}
|
||||
|
||||
val failuresByCardId = linkedMapOf<String, String>()
|
||||
normalizedCardIds.forEach { cardId ->
|
||||
when (val result = apiClient.deleteCard(session.baseUrl, session.apiKey, cardId)) {
|
||||
is BoardsApiResult.Success -> Unit
|
||||
is BoardsApiResult.Failure -> failuresByCardId[cardId] = result.message
|
||||
}
|
||||
}
|
||||
|
||||
return aggregateBatchMutationResult(
|
||||
normalizedCardIds = normalizedCardIds,
|
||||
failuresByCardId = failuresByCardId,
|
||||
partialMessage = "Some cards could not be deleted. Please try again.",
|
||||
)
|
||||
}
|
||||
|
||||
private fun normalizeCardIds(cardIds: Collection<String>): List<String> {
|
||||
return cardIds.map { it.trim() }
|
||||
.filter { it.isNotBlank() }
|
||||
.distinct()
|
||||
}
|
||||
|
||||
private fun aggregateBatchMutationResult(
|
||||
normalizedCardIds: List<String>,
|
||||
failuresByCardId: Map<String, String>,
|
||||
partialMessage: String,
|
||||
): CardBatchMutationResult {
|
||||
if (failuresByCardId.isEmpty()) {
|
||||
return CardBatchMutationResult.Success
|
||||
}
|
||||
|
||||
if (failuresByCardId.size == normalizedCardIds.size) {
|
||||
val firstFailureMessage = normalizedCardIds
|
||||
.asSequence()
|
||||
.mapNotNull { failuresByCardId[it] }
|
||||
.firstOrNull()
|
||||
?.trim()
|
||||
.orEmpty()
|
||||
.ifBlank { "Unknown error" }
|
||||
return CardBatchMutationResult.Failure(firstFailureMessage)
|
||||
}
|
||||
|
||||
return CardBatchMutationResult.PartialSuccess(
|
||||
failedCardIds = failuresByCardId.keys.toSet(),
|
||||
message = partialMessage,
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun session(): BoardsApiResult<SessionSnapshot> {
|
||||
val baseUrl = sessionStore.getBaseUrl()?.takeIf { it.isNotBlank() }
|
||||
?: return BoardsApiResult.Failure("Missing session. Please sign in again.")
|
||||
val apiKey = withContext(ioDispatcher) {
|
||||
apiKeyStore.getApiKey(baseUrl)
|
||||
}.getOrNull()?.takeIf { it.isNotBlank() }
|
||||
?: return BoardsApiResult.Failure("Missing session. Please sign in again.")
|
||||
|
||||
val workspaceId = when (val workspaceResult = resolveWorkspaceId(baseUrl = baseUrl, apiKey = apiKey)) {
|
||||
is BoardsApiResult.Success -> workspaceResult.value
|
||||
is BoardsApiResult.Failure -> return workspaceResult
|
||||
}
|
||||
|
||||
return BoardsApiResult.Success(
|
||||
SessionSnapshot(baseUrl = baseUrl, apiKey = apiKey, workspaceId = workspaceId),
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun resolveWorkspaceId(baseUrl: String, apiKey: String): BoardsApiResult<String> {
|
||||
val storedWorkspaceId = sessionStore.getWorkspaceId()?.takeIf { it.isNotBlank() }
|
||||
if (storedWorkspaceId != null) {
|
||||
return BoardsApiResult.Success(storedWorkspaceId)
|
||||
}
|
||||
|
||||
return when (val workspacesResult = apiClient.listWorkspaces(baseUrl, apiKey)) {
|
||||
is BoardsApiResult.Success -> {
|
||||
val first = workspacesResult.value.firstOrNull()?.id
|
||||
?: return BoardsApiResult.Failure("No workspaces available for this account.")
|
||||
sessionStore.saveWorkspaceId(first)
|
||||
BoardsApiResult.Success(first)
|
||||
}
|
||||
|
||||
is BoardsApiResult.Failure -> workspacesResult
|
||||
}
|
||||
}
|
||||
|
||||
private data class SessionSnapshot(
|
||||
val baseUrl: String,
|
||||
val apiKey: String,
|
||||
@Suppress("unused")
|
||||
val workspaceId: String,
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,431 @@
|
||||
package space.hackenslacker.kanbn4droid.app.boarddetail
|
||||
|
||||
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.SessionStore
|
||||
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 BoardDetailRepositoryTest {
|
||||
|
||||
@Test
|
||||
fun getBoardDetailUsesStoredWorkspaceWhenPresent() = runTest {
|
||||
val apiClient = FakeBoardDetailApiClient().apply {
|
||||
boardDetailResult = BoardsApiResult.Success(sampleBoardDetail())
|
||||
}
|
||||
val sessionStore = InMemorySessionStore(baseUrl = "https://kan.bn/", workspaceId = "ws-stored")
|
||||
val repository = createRepository(sessionStore = sessionStore, apiClient = apiClient)
|
||||
|
||||
val result = repository.getBoardDetail("board-1")
|
||||
|
||||
assertTrue(result is BoardsApiResult.Success<*>)
|
||||
assertEquals(0, apiClient.listWorkspacesCalls)
|
||||
assertEquals("board-1", apiClient.lastBoardId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getBoardDetailFetchesAndPersistsWorkspaceWhenMissing() = runTest {
|
||||
val apiClient = FakeBoardDetailApiClient().apply {
|
||||
workspacesResult = BoardsApiResult.Success(listOf(WorkspaceSummary("ws-1", "Main")))
|
||||
boardDetailResult = BoardsApiResult.Success(sampleBoardDetail())
|
||||
}
|
||||
val sessionStore = InMemorySessionStore(baseUrl = "https://kan.bn/")
|
||||
val repository = createRepository(sessionStore = sessionStore, apiClient = apiClient)
|
||||
|
||||
val result = repository.getBoardDetail("board-1")
|
||||
|
||||
assertTrue(result is BoardsApiResult.Success<*>)
|
||||
assertEquals(1, apiClient.listWorkspacesCalls)
|
||||
assertEquals("ws-1", sessionStore.getWorkspaceId())
|
||||
assertEquals("board-1", apiClient.lastBoardId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getBoardDetailFailsWhenNoWorkspacesAvailable() = runTest {
|
||||
val apiClient = FakeBoardDetailApiClient().apply {
|
||||
workspacesResult = BoardsApiResult.Success(emptyList())
|
||||
}
|
||||
val repository = createRepository(
|
||||
sessionStore = InMemorySessionStore(baseUrl = "https://kan.bn/"),
|
||||
apiClient = apiClient,
|
||||
)
|
||||
|
||||
val result = repository.getBoardDetail("board-1")
|
||||
|
||||
assertTrue(result is BoardsApiResult.Failure)
|
||||
assertEquals("No workspaces available for this account.", (result as BoardsApiResult.Failure).message)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getBoardDetailPropagatesApiFailureMessageUnchanged() = runTest {
|
||||
val apiClient = FakeBoardDetailApiClient().apply {
|
||||
boardDetailResult = BoardsApiResult.Failure("Server says no")
|
||||
}
|
||||
val repository = createRepository(
|
||||
sessionStore = InMemorySessionStore(baseUrl = "https://kan.bn/", workspaceId = "ws-1"),
|
||||
apiClient = apiClient,
|
||||
)
|
||||
|
||||
val result = repository.getBoardDetail("board-1")
|
||||
|
||||
assertTrue(result is BoardsApiResult.Failure)
|
||||
assertEquals("Server says no", (result as BoardsApiResult.Failure).message)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun renameListPropagatesApiFailureMessageUnchanged() = runTest {
|
||||
val apiClient = FakeBoardDetailApiClient().apply {
|
||||
renameListResult = BoardsApiResult.Failure("List cannot be renamed")
|
||||
}
|
||||
val repository = createRepository(
|
||||
sessionStore = InMemorySessionStore(baseUrl = "https://kan.bn/", workspaceId = "ws-1"),
|
||||
apiClient = apiClient,
|
||||
)
|
||||
|
||||
val result = repository.renameList("list-1", "New title")
|
||||
|
||||
assertTrue(result is BoardsApiResult.Failure)
|
||||
assertEquals("List cannot be renamed", (result as BoardsApiResult.Failure).message)
|
||||
assertEquals("list-1", apiClient.lastListId)
|
||||
assertEquals("New title", apiClient.lastListTitle)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getBoardDetailValidatesBoardId() = runTest {
|
||||
val repository = createRepository()
|
||||
|
||||
val result = repository.getBoardDetail(" ")
|
||||
|
||||
assertTrue(result is BoardsApiResult.Failure)
|
||||
assertEquals("Board id is required", (result as BoardsApiResult.Failure).message)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun renameListValidatesListId() = runTest {
|
||||
val repository = createRepository()
|
||||
|
||||
val result = repository.renameList(" ", "Some title")
|
||||
|
||||
assertTrue(result is BoardsApiResult.Failure)
|
||||
assertEquals("List id is required", (result as BoardsApiResult.Failure).message)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun renameListValidatesTitle() = runTest {
|
||||
val repository = createRepository()
|
||||
|
||||
val result = repository.renameList("list-1", " ")
|
||||
|
||||
assertTrue(result is BoardsApiResult.Failure)
|
||||
assertEquals("List title is required", (result as BoardsApiResult.Failure).message)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun moveCardsValidatesTargetListId() = runTest {
|
||||
val repository = createRepository()
|
||||
|
||||
val result = repository.moveCards(cardIds = listOf("card-1"), targetListId = " ")
|
||||
|
||||
assertTrue(result is CardBatchMutationResult.Failure)
|
||||
assertEquals("Target list id is required", (result as CardBatchMutationResult.Failure).message)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun moveCardsValidatesCardIds() = runTest {
|
||||
val repository = createRepository()
|
||||
|
||||
val result = repository.moveCards(cardIds = listOf(" ", ""), targetListId = "list-2")
|
||||
|
||||
assertTrue(result is CardBatchMutationResult.Failure)
|
||||
assertEquals("At least one card id is required", (result as CardBatchMutationResult.Failure).message)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteCardsValidatesCardIds() = runTest {
|
||||
val repository = createRepository()
|
||||
|
||||
val result = repository.deleteCards(cardIds = listOf(" ", ""))
|
||||
|
||||
assertTrue(result is CardBatchMutationResult.Failure)
|
||||
assertEquals("At least one card id is required", (result as CardBatchMutationResult.Failure).message)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun moveCardsReturnsSuccessWhenAllMutationsSucceed() = runTest {
|
||||
val apiClient = FakeBoardDetailApiClient().apply {
|
||||
moveOutcomes = mapOf(
|
||||
"card-1" to BoardsApiResult.Success(Unit),
|
||||
"card-2" to BoardsApiResult.Success(Unit),
|
||||
)
|
||||
}
|
||||
val repository = createRepository(apiClient = apiClient)
|
||||
|
||||
val result = repository.moveCards(
|
||||
cardIds = listOf(" card-1 ", "", "card-1", "card-2"),
|
||||
targetListId = " list-target ",
|
||||
)
|
||||
|
||||
assertEquals(CardBatchMutationResult.Success, result)
|
||||
assertEquals(listOf("card-1", "card-2"), apiClient.movedCardIds)
|
||||
assertEquals("list-target", apiClient.lastMoveTargetListId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun moveCardsReturnsPartialSuccessWhenSomeMutationsFail() = runTest {
|
||||
val apiClient = FakeBoardDetailApiClient().apply {
|
||||
moveOutcomes = mapOf(
|
||||
"card-1" to BoardsApiResult.Success(Unit),
|
||||
"card-2" to BoardsApiResult.Failure("Cannot move"),
|
||||
"card-3" to BoardsApiResult.Success(Unit),
|
||||
)
|
||||
}
|
||||
val repository = createRepository(apiClient = apiClient)
|
||||
|
||||
val result = repository.moveCards(
|
||||
cardIds = listOf("card-1", " card-2 ", "card-3", "card-2"),
|
||||
targetListId = "list-target",
|
||||
)
|
||||
|
||||
assertTrue(result is CardBatchMutationResult.PartialSuccess)
|
||||
assertEquals(setOf("card-2"), (result as CardBatchMutationResult.PartialSuccess).failedCardIds)
|
||||
assertEquals("Some cards could not be moved. Please try again.", result.message)
|
||||
assertEquals(listOf("card-1", "card-2", "card-3"), apiClient.movedCardIds)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun moveCardsReturnsFailureWithFirstNormalizedMessageWhenAllFail() = runTest {
|
||||
val apiClient = FakeBoardDetailApiClient().apply {
|
||||
moveOutcomes = mapOf(
|
||||
"card-2" to BoardsApiResult.Failure(" "),
|
||||
"card-1" to BoardsApiResult.Failure("Second failure"),
|
||||
)
|
||||
}
|
||||
val repository = createRepository(apiClient = apiClient)
|
||||
|
||||
val result = repository.moveCards(
|
||||
cardIds = listOf(" card-2 ", "card-1", "card-2"),
|
||||
targetListId = "list-target",
|
||||
)
|
||||
|
||||
assertTrue(result is CardBatchMutationResult.Failure)
|
||||
assertEquals("Unknown error", (result as CardBatchMutationResult.Failure).message)
|
||||
assertEquals(listOf("card-2", "card-1"), apiClient.movedCardIds)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteCardsReturnsSuccessWhenAllMutationsSucceed() = runTest {
|
||||
val apiClient = FakeBoardDetailApiClient().apply {
|
||||
deleteOutcomes = mapOf(
|
||||
"card-1" to BoardsApiResult.Success(Unit),
|
||||
"card-2" to BoardsApiResult.Success(Unit),
|
||||
)
|
||||
}
|
||||
val repository = createRepository(apiClient = apiClient)
|
||||
|
||||
val result = repository.deleteCards(cardIds = listOf("card-1", " card-2 ", "card-1"))
|
||||
|
||||
assertEquals(CardBatchMutationResult.Success, result)
|
||||
assertEquals(listOf("card-1", "card-2"), apiClient.deletedCardIds)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteCardsReturnsPartialSuccessWhenSomeMutationsFail() = runTest {
|
||||
val apiClient = FakeBoardDetailApiClient().apply {
|
||||
deleteOutcomes = mapOf(
|
||||
"card-1" to BoardsApiResult.Success(Unit),
|
||||
"card-2" to BoardsApiResult.Failure("Cannot delete"),
|
||||
"card-3" to BoardsApiResult.Success(Unit),
|
||||
)
|
||||
}
|
||||
val repository = createRepository(apiClient = apiClient)
|
||||
|
||||
val result = repository.deleteCards(cardIds = listOf("card-1", " card-2 ", "card-3", "card-2"))
|
||||
|
||||
assertTrue(result is CardBatchMutationResult.PartialSuccess)
|
||||
assertEquals(setOf("card-2"), (result as CardBatchMutationResult.PartialSuccess).failedCardIds)
|
||||
assertEquals("Some cards could not be deleted. Please try again.", result.message)
|
||||
assertEquals(listOf("card-1", "card-2", "card-3"), apiClient.deletedCardIds)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteCardsReturnsFailureWithFirstNormalizedMessageWhenAllFail() = runTest {
|
||||
val apiClient = FakeBoardDetailApiClient().apply {
|
||||
deleteOutcomes = mapOf(
|
||||
"card-2" to BoardsApiResult.Failure("Delete failed first"),
|
||||
"card-1" to BoardsApiResult.Failure("Delete failed second"),
|
||||
)
|
||||
}
|
||||
val repository = createRepository(apiClient = apiClient)
|
||||
|
||||
val result = repository.deleteCards(cardIds = listOf(" card-2 ", "card-1", "card-2"))
|
||||
|
||||
assertTrue(result is CardBatchMutationResult.Failure)
|
||||
assertEquals("Delete failed first", (result as CardBatchMutationResult.Failure).message)
|
||||
assertEquals(listOf("card-2", "card-1"), apiClient.deletedCardIds)
|
||||
}
|
||||
|
||||
private fun createRepository(
|
||||
sessionStore: InMemorySessionStore = InMemorySessionStore(baseUrl = "https://kan.bn/", workspaceId = "ws-1"),
|
||||
apiClient: FakeBoardDetailApiClient = FakeBoardDetailApiClient(),
|
||||
apiKeyStore: InMemoryApiKeyStore = InMemoryApiKeyStore("api-key"),
|
||||
): BoardDetailRepository {
|
||||
return BoardDetailRepository(
|
||||
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 FakeBoardDetailApiClient : KanbnApiClient {
|
||||
var workspacesResult: BoardsApiResult<List<WorkspaceSummary>> =
|
||||
BoardsApiResult.Success(listOf(WorkspaceSummary("ws-1", "Main")))
|
||||
var boardDetailResult: BoardsApiResult<BoardDetail> = BoardsApiResult.Success(sampleBoardDetail())
|
||||
var renameListResult: BoardsApiResult<Unit> = BoardsApiResult.Success(Unit)
|
||||
var moveOutcomes: Map<String, BoardsApiResult<Unit>> = emptyMap()
|
||||
var deleteOutcomes: Map<String, BoardsApiResult<Unit>> = emptyMap()
|
||||
|
||||
var listWorkspacesCalls: Int = 0
|
||||
var lastBoardId: String? = null
|
||||
var lastListId: String? = null
|
||||
var lastListTitle: String? = null
|
||||
var movedCardIds: MutableList<String> = mutableListOf()
|
||||
var deletedCardIds: MutableList<String> = mutableListOf()
|
||||
var lastMoveTargetListId: String? = null
|
||||
|
||||
override suspend fun healthCheck(baseUrl: String, apiKey: String): AuthResult = AuthResult.Success
|
||||
|
||||
override suspend fun listWorkspaces(
|
||||
baseUrl: String,
|
||||
apiKey: String,
|
||||
): BoardsApiResult<List<WorkspaceSummary>> {
|
||||
listWorkspacesCalls += 1
|
||||
return workspacesResult
|
||||
}
|
||||
|
||||
override suspend fun getBoardDetail(
|
||||
baseUrl: String,
|
||||
apiKey: String,
|
||||
boardId: String,
|
||||
): BoardsApiResult<BoardDetail> {
|
||||
lastBoardId = boardId
|
||||
return boardDetailResult
|
||||
}
|
||||
|
||||
override suspend fun renameList(
|
||||
baseUrl: String,
|
||||
apiKey: String,
|
||||
listId: String,
|
||||
newTitle: String,
|
||||
): BoardsApiResult<Unit> {
|
||||
lastListId = listId
|
||||
lastListTitle = newTitle
|
||||
return renameListResult
|
||||
}
|
||||
|
||||
override suspend fun moveCard(
|
||||
baseUrl: String,
|
||||
apiKey: String,
|
||||
cardId: String,
|
||||
targetListId: String,
|
||||
): BoardsApiResult<Unit> {
|
||||
movedCardIds += cardId
|
||||
lastMoveTargetListId = targetListId
|
||||
return moveOutcomes[cardId] ?: BoardsApiResult.Success(Unit)
|
||||
}
|
||||
|
||||
override suspend fun deleteCard(
|
||||
baseUrl: String,
|
||||
apiKey: String,
|
||||
cardId: String,
|
||||
): BoardsApiResult<Unit> {
|
||||
deletedCardIds += cardId
|
||||
return deleteOutcomes[cardId] ?: BoardsApiResult.Success(Unit)
|
||||
}
|
||||
|
||||
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("new", name))
|
||||
}
|
||||
|
||||
override suspend fun deleteBoard(
|
||||
baseUrl: String,
|
||||
apiKey: String,
|
||||
boardId: String,
|
||||
): BoardsApiResult<Unit> {
|
||||
return BoardsApiResult.Success(Unit)
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
fun sampleBoardDetail(): BoardDetail {
|
||||
return BoardDetail(id = "board-1", title = "Board", lists = emptyList())
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user