From 6d313fdf60554c987ea5fbb06324df66fc783385 Mon Sep 17 00:00:00 2001 From: Wally Hackenslacker Date: Mon, 16 Mar 2026 14:12:33 -0400 Subject: [PATCH] test: add create endpoint contract coverage for api client --- AGENTS.md | 1 + .../kanbn4droid/app/auth/KanbnApiClient.kt | 6 +- ...ttpKanbnApiClientBoardDetailParsingTest.kt | 60 ++++++++++++++++++- 3 files changed, 64 insertions(+), 3 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index e2a985e..5d0ba1b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -24,6 +24,7 @@ Kanbn4Droid is an unofficial app to connect to and manipulate data stored in sel - Baseline tests: - JVM unit tests for auth URL normalization and auth error mapping in `app/src/test/`. - JVM unit tests for boards repository and boards view model in `app/src/test/space/hackenslacker/kanbn4droid/app/boards/`. + - JVM API-client contract tests for board-detail parsing and create list/card endpoint payloads and id parsing in `app/src/test/java/space/hackenslacker/kanbn4droid/app/auth/HttpKanbnApiClientBoardDetailParsingTest.kt`. - Instrumentation tests for login and boards flows in `app/src/androidTest/`. ## Command-line workflow diff --git a/app/src/main/java/space/hackenslacker/kanbn4droid/app/auth/KanbnApiClient.kt b/app/src/main/java/space/hackenslacker/kanbn4droid/app/auth/KanbnApiClient.kt index 13e8adb..06c6977 100644 --- a/app/src/main/java/space/hackenslacker/kanbn4droid/app/auth/KanbnApiClient.kt +++ b/app/src/main/java/space/hackenslacker/kanbn4droid/app/auth/KanbnApiClient.kt @@ -778,7 +778,11 @@ class HttpKanbnApiClient : KanbnApiClient { ?: data } - else -> root + else -> { + (root["card"] as? Map<*, *>) + ?: (root["list"] as? Map<*, *>) + ?: root + } } val publicId = extractString(entity, "publicId", "public_id", "id") diff --git a/app/src/test/java/space/hackenslacker/kanbn4droid/app/auth/HttpKanbnApiClientBoardDetailParsingTest.kt b/app/src/test/java/space/hackenslacker/kanbn4droid/app/auth/HttpKanbnApiClientBoardDetailParsingTest.kt index 25892ca..c702ff3 100644 --- a/app/src/test/java/space/hackenslacker/kanbn4droid/app/auth/HttpKanbnApiClientBoardDetailParsingTest.kt +++ b/app/src/test/java/space/hackenslacker/kanbn4droid/app/auth/HttpKanbnApiClientBoardDetailParsingTest.kt @@ -18,12 +18,41 @@ import org.junit.Assert.assertNull import org.junit.Assert.assertTrue import org.junit.Test import space.hackenslacker.kanbn4droid.app.boarddetail.BoardDetail +import space.hackenslacker.kanbn4droid.app.boarddetail.CreatedEntityRef import space.hackenslacker.kanbn4droid.app.boards.BoardsApiResult class HttpKanbnApiClientBoardDetailParsingTest { @Test - fun createCardFormatsLocalDateAsUtcMidnightInPayload() = runTest { + fun createList_sendsAppendIndexPayload_andParsesPublicIdFallbacks() = runTest { + TestServer().use { server -> + server.register( + path = "/api/v1/lists", + method = "POST", + status = 200, + responseBody = """{"list":{"public_id":"list-new"}}""", + ) + + val result = HttpKanbnApiClient().createList( + baseUrl = server.baseUrl, + apiKey = "api", + boardPublicId = "board-1", + title = "Sprint", + appendIndex = 7, + ) + + assertTrue(result is BoardsApiResult.Success<*>) + val createdRef = (result as BoardsApiResult.Success<*>).value as CreatedEntityRef + assertEquals("list-new", createdRef.publicId) + + val request = server.findRequest("POST", "/api/v1/lists") + assertNotNull(request) + assertEquals("{\"boardPublicId\":\"board-1\",\"name\":\"Sprint\",\"index\":7}", request?.body) + } + } + + @Test + fun createCard_serializesLocalDateAsUtcMidnight() = runTest { TestServer().use { server -> server.register(path = "/api/v1/cards", method = "POST", status = 200, responseBody = """{"publicId":"card-new"}""") @@ -46,7 +75,7 @@ class HttpKanbnApiClientBoardDetailParsingTest { } @Test - fun createCardOmitsNullDueDateAndDescriptionInPayload() = runTest { + fun createCard_sendsTopIndex_andOmittedDueDateWhenNull() = runTest { TestServer().use { server -> server.register(path = "/api/v1/cards", method = "POST", status = 200, responseBody = """{"publicId":"card-new"}""") @@ -63,11 +92,38 @@ class HttpKanbnApiClientBoardDetailParsingTest { assertTrue(result is BoardsApiResult.Success<*>) val request = server.findRequest("POST", "/api/v1/cards") assertNotNull(request) + assertTrue(request?.body?.contains("\"index\":0") == true) assertTrue(request?.body?.contains("\"dueDate\"") == false) assertTrue(request?.body?.contains("\"description\"") == false) } } + @Test + fun createCard_returnsCreatedRefWithNullPublicIdWhenResponseHasNoId() = runTest { + TestServer().use { server -> + server.register( + path = "/api/v1/cards", + method = "POST", + status = 200, + responseBody = """{"card":{"title":"Card without id"}}""", + ) + + val result = HttpKanbnApiClient().createCard( + baseUrl = server.baseUrl, + apiKey = "api", + listPublicId = "list-1", + title = "Card", + description = null, + dueDate = null, + tagPublicIds = emptyList(), + ) + + assertTrue(result is BoardsApiResult.Success<*>) + val createdRef = (result as BoardsApiResult.Success<*>).value as CreatedEntityRef + assertNull(createdRef.publicId) + } + } + @Test fun getBoardDetailParsesWrappedPayloadWithDueDateVariants() = runTest { TestServer().use { server ->