fix: support plain data board wrapper parsing

This commit is contained in:
2026-03-16 00:25:24 -04:00
parent a2a54523ef
commit 9602b7959f
2 changed files with 100 additions and 0 deletions

View File

@@ -487,6 +487,7 @@ class HttpKanbnApiClient : KanbnApiClient {
?: return BoardDetail(id = fallbackId, title = "Board", lists = emptyList()) ?: return BoardDetail(id = fallbackId, title = "Board", lists = emptyList())
val data = root["data"] as? Map<*, *> val data = root["data"] as? Map<*, *>
val board = (data?.get("board") as? Map<*, *>) val board = (data?.get("board") as? Map<*, *>)
?: data
?: (root["board"] as? Map<*, *>) ?: (root["board"] as? Map<*, *>)
?: root ?: root

View File

@@ -141,6 +141,51 @@ class HttpKanbnApiClientBoardDetailParsingTest {
} }
} }
@Test
fun getBoardDetailParsesPlainDataWrapperWithoutBoardKey() = runTest {
TestServer().use { server ->
server.register(
path = "/api/v1/boards/data-only",
status = 200,
responseBody =
"""
{
"data": {
"id": "data-only",
"name": "Wrapped board",
"lists": [
{
"public_id": "list-9",
"name": "Queue",
"cards": [
{
"publicId": "card-99",
"name": "Card in data wrapper",
"labels": [
{"public_id": "tag-77", "title": "Infra", "color": "#ABCDEF"}
]
}
]
}
]
}
}
""".trimIndent(),
)
val result = HttpKanbnApiClient().getBoardDetail(server.baseUrl, "key", "data-only")
assertTrue(result is BoardsApiResult.Success<*>)
val detail = (result as BoardsApiResult.Success<*>).value as BoardDetail
assertEquals("data-only", detail.id)
assertEquals("Wrapped board", detail.title)
assertEquals(1, detail.lists.size)
assertEquals("list-9", detail.lists[0].id)
assertEquals("card-99", detail.lists[0].cards[0].id)
assertEquals("tag-77", detail.lists[0].cards[0].tags[0].id)
}
}
@Test @Test
fun requestMappingMatchesContractForBoardDetailAndMutations() = runTest { fun requestMappingMatchesContractForBoardDetailAndMutations() = runTest {
TestServer().use { server -> TestServer().use { server ->
@@ -209,6 +254,56 @@ class HttpKanbnApiClientBoardDetailParsingTest {
} }
} }
@Test
fun moveCardFailureUsesServerMessageAndFallback() = runTest {
TestServer().use { server ->
server.register(
path = "/api/v1/cards/c-msg",
status = 409,
responseBody = """{"error":"Card cannot be moved"}""",
)
server.register(
path = "/api/v1/cards/c-fallback",
status = 500,
responseBody = "{}",
)
val client = HttpKanbnApiClient()
val messageResult = client.moveCard(server.baseUrl, "api", "c-msg", "l-1")
val fallbackResult = client.moveCard(server.baseUrl, "api", "c-fallback", "l-1")
assertTrue(messageResult is BoardsApiResult.Failure)
assertEquals("Card cannot be moved", (messageResult as BoardsApiResult.Failure).message)
assertTrue(fallbackResult is BoardsApiResult.Failure)
assertEquals("Server error: 500", (fallbackResult as BoardsApiResult.Failure).message)
}
}
@Test
fun deleteCardFailureUsesServerMessageAndFallback() = runTest {
TestServer().use { server ->
server.register(
path = "/api/v1/cards/c-del-msg",
status = 403,
responseBody = """{"detail":"No permission to delete card"}""",
)
server.register(
path = "/api/v1/cards/c-del-fallback",
status = 502,
responseBody = "[]",
)
val client = HttpKanbnApiClient()
val messageResult = client.deleteCard(server.baseUrl, "api", "c-del-msg")
val fallbackResult = client.deleteCard(server.baseUrl, "api", "c-del-fallback")
assertTrue(messageResult is BoardsApiResult.Failure)
assertEquals("No permission to delete card", (messageResult as BoardsApiResult.Failure).message)
assertTrue(fallbackResult is BoardsApiResult.Failure)
assertEquals("Server error: 502", (fallbackResult as BoardsApiResult.Failure).message)
}
}
private data class CapturedRequest( private data class CapturedRequest(
val method: String, val method: String,
val path: String, val path: String,
@@ -316,7 +411,11 @@ class HttpKanbnApiClientBoardDetailParsingTest {
val reason = when (status) { val reason = when (status) {
200 -> "OK" 200 -> "OK"
400 -> "Bad Request" 400 -> "Bad Request"
403 -> "Forbidden"
409 -> "Conflict"
404 -> "Not Found" 404 -> "Not Found"
500 -> "Internal Server Error"
502 -> "Bad Gateway"
503 -> "Service Unavailable" 503 -> "Service Unavailable"
else -> "Error" else -> "Error"
} }