fix: align createCard due-date contract with api client ownership
This commit is contained in:
@@ -4,6 +4,7 @@ import java.io.IOException
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.URL
|
||||
import java.time.Instant
|
||||
import java.time.LocalDate
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -79,7 +80,7 @@ interface KanbnApiClient {
|
||||
listPublicId: String,
|
||||
title: String,
|
||||
description: String?,
|
||||
dueDate: String?,
|
||||
dueDate: LocalDate?,
|
||||
tagPublicIds: List<String>,
|
||||
): BoardsApiResult<CreatedEntityRef> {
|
||||
return BoardsApiResult.Failure("Card creation is not implemented.")
|
||||
@@ -300,16 +301,18 @@ class HttpKanbnApiClient : KanbnApiClient {
|
||||
appendIndex: Int,
|
||||
): BoardsApiResult<CreatedEntityRef> {
|
||||
return withContext(Dispatchers.IO) {
|
||||
val payload = JSONObject()
|
||||
.put("boardPublicId", boardPublicId)
|
||||
.put("name", title)
|
||||
.put("index", appendIndex)
|
||||
val payload =
|
||||
"{" +
|
||||
"\"boardPublicId\":\"${jsonEscape(boardPublicId)}\"," +
|
||||
"\"name\":\"${jsonEscape(title)}\"," +
|
||||
"\"index\":$appendIndex" +
|
||||
"}"
|
||||
request(
|
||||
baseUrl = baseUrl,
|
||||
path = "/api/v1/lists",
|
||||
method = "POST",
|
||||
apiKey = apiKey,
|
||||
body = payload.toString(),
|
||||
body = payload,
|
||||
) { code, body ->
|
||||
if (code in 200..299) {
|
||||
parseCreatedEntityRef(body)
|
||||
@@ -328,23 +331,29 @@ class HttpKanbnApiClient : KanbnApiClient {
|
||||
listPublicId: String,
|
||||
title: String,
|
||||
description: String?,
|
||||
dueDate: String?,
|
||||
dueDate: LocalDate?,
|
||||
tagPublicIds: List<String>,
|
||||
): BoardsApiResult<CreatedEntityRef> {
|
||||
return withContext(Dispatchers.IO) {
|
||||
val payload = JSONObject()
|
||||
.put("listPublicId", listPublicId)
|
||||
.put("title", title)
|
||||
.put("description", description ?: "")
|
||||
.put("dueDate", dueDate)
|
||||
.put("index", 0)
|
||||
.put("labelPublicIds", JSONArray(tagPublicIds))
|
||||
val payloadFields = mutableListOf(
|
||||
"\"listPublicId\":\"${jsonEscape(listPublicId)}\"",
|
||||
"\"title\":\"${jsonEscape(title)}\"",
|
||||
"\"index\":0",
|
||||
"\"labelPublicIds\":${jsonStringArray(tagPublicIds)}",
|
||||
)
|
||||
if (description != null) {
|
||||
payloadFields += "\"description\":\"${jsonEscape(description)}\""
|
||||
}
|
||||
if (dueDate != null) {
|
||||
payloadFields += "\"dueDate\":\"${dueDate}T00:00:00Z\""
|
||||
}
|
||||
val payload = "{${payloadFields.joinToString(",")}}"
|
||||
request(
|
||||
baseUrl = baseUrl,
|
||||
path = "/api/v1/cards",
|
||||
method = "POST",
|
||||
apiKey = apiKey,
|
||||
body = payload.toString(),
|
||||
body = payload,
|
||||
) { code, body ->
|
||||
if (code in 200..299) {
|
||||
parseCreatedEntityRef(body)
|
||||
@@ -890,6 +899,10 @@ class HttpKanbnApiClient : KanbnApiClient {
|
||||
return builder.toString()
|
||||
}
|
||||
|
||||
private fun jsonStringArray(values: List<String>): String {
|
||||
return values.joinToString(prefix = "[", postfix = "]", separator = ",") { "\"${jsonEscape(it)}\"" }
|
||||
}
|
||||
|
||||
private class MiniJsonParser(private val input: String) {
|
||||
private var index = 0
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package space.hackenslacker.kanbn4droid.app.boarddetail
|
||||
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.LabelDetail
|
||||
@@ -73,7 +74,7 @@ class BoardDetailRepository(
|
||||
listId: String,
|
||||
title: String,
|
||||
description: String?,
|
||||
dueDate: String?,
|
||||
dueDate: LocalDate?,
|
||||
tagIds: Collection<String>,
|
||||
): BoardsApiResult<CreatedEntityRef> {
|
||||
val normalizedListId = listId.trim()
|
||||
@@ -87,7 +88,6 @@ class BoardDetailRepository(
|
||||
}
|
||||
|
||||
val normalizedDescription = description?.trim()?.takeIf { it.isNotBlank() }
|
||||
val normalizedDueDate = dueDate?.trim()?.takeIf { it.isNotBlank() }
|
||||
val normalizedTagIds = tagIds
|
||||
.map { it.trim() }
|
||||
.filter { it.isNotBlank() }
|
||||
@@ -104,7 +104,7 @@ class BoardDetailRepository(
|
||||
listPublicId = normalizedListId,
|
||||
title = normalizedTitle,
|
||||
description = normalizedDescription,
|
||||
dueDate = normalizedDueDate,
|
||||
dueDate = dueDate,
|
||||
tagPublicIds = normalizedTagIds,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import java.net.InetSocketAddress
|
||||
import java.net.ServerSocket
|
||||
import java.net.Socket
|
||||
import java.time.Instant
|
||||
import java.time.LocalDate
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.TimeUnit
|
||||
@@ -21,6 +22,52 @@ import space.hackenslacker.kanbn4droid.app.boards.BoardsApiResult
|
||||
|
||||
class HttpKanbnApiClientBoardDetailParsingTest {
|
||||
|
||||
@Test
|
||||
fun createCardFormatsLocalDateAsUtcMidnightInPayload() = runTest {
|
||||
TestServer().use { server ->
|
||||
server.register(path = "/api/v1/cards", method = "POST", status = 200, responseBody = """{"publicId":"card-new"}""")
|
||||
|
||||
val result = HttpKanbnApiClient().createCard(
|
||||
baseUrl = server.baseUrl,
|
||||
apiKey = "api",
|
||||
listPublicId = "list-1",
|
||||
title = "Card",
|
||||
description = "Description",
|
||||
dueDate = LocalDate.of(2026, 3, 16),
|
||||
tagPublicIds = listOf("tag-1"),
|
||||
)
|
||||
|
||||
assertTrue(result is BoardsApiResult.Success<*>)
|
||||
val request = server.findRequest("POST", "/api/v1/cards")
|
||||
assertNotNull(request)
|
||||
assertTrue(request?.body?.contains("\"dueDate\":\"2026-03-16T00:00:00Z\"") == true)
|
||||
assertTrue(request?.body?.contains("\"description\":\"Description\"") == true)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun createCardOmitsNullDueDateAndDescriptionInPayload() = runTest {
|
||||
TestServer().use { server ->
|
||||
server.register(path = "/api/v1/cards", method = "POST", status = 200, responseBody = """{"publicId":"card-new"}""")
|
||||
|
||||
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 request = server.findRequest("POST", "/api/v1/cards")
|
||||
assertNotNull(request)
|
||||
assertTrue(request?.body?.contains("\"dueDate\"") == false)
|
||||
assertTrue(request?.body?.contains("\"description\"") == false)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getBoardDetailParsesWrappedPayloadWithDueDateVariants() = runTest {
|
||||
TestServer().use { server ->
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package space.hackenslacker.kanbn4droid.app.boarddetail
|
||||
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import java.time.LocalDate
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
@@ -216,7 +217,7 @@ class BoardDetailRepositoryTest {
|
||||
listId = " list-1 ",
|
||||
title = " Card title ",
|
||||
description = " Description ",
|
||||
dueDate = "2026-03-16T10:15:30Z",
|
||||
dueDate = LocalDate.of(2026, 3, 16),
|
||||
tagIds = listOf(" tag-1 ", "", "tag-2", "tag-1", " ", " tag-2"),
|
||||
)
|
||||
|
||||
@@ -224,7 +225,7 @@ class BoardDetailRepositoryTest {
|
||||
assertEquals("list-1", apiClient.lastCreateCardListPublicId)
|
||||
assertEquals("Card title", apiClient.lastCreateCardTitle)
|
||||
assertEquals("Description", apiClient.lastCreateCardDescription)
|
||||
assertEquals("2026-03-16T10:15:30Z", apiClient.lastCreateCardDueDate)
|
||||
assertEquals(LocalDate.of(2026, 3, 16), apiClient.lastCreateCardDueDate)
|
||||
assertEquals(listOf("tag-1", "tag-2"), apiClient.lastCreateCardTagPublicIds)
|
||||
}
|
||||
|
||||
@@ -509,7 +510,7 @@ class BoardDetailRepositoryTest {
|
||||
var lastCreateCardListPublicId: String? = null
|
||||
var lastCreateCardTitle: String? = null
|
||||
var lastCreateCardDescription: String? = null
|
||||
var lastCreateCardDueDate: String? = null
|
||||
var lastCreateCardDueDate: LocalDate? = null
|
||||
var lastCreateCardTagPublicIds: List<String> = emptyList()
|
||||
|
||||
override suspend fun healthCheck(baseUrl: String, apiKey: String): AuthResult = AuthResult.Success
|
||||
@@ -558,7 +559,7 @@ class BoardDetailRepositoryTest {
|
||||
listPublicId: String,
|
||||
title: String,
|
||||
description: String?,
|
||||
dueDate: String?,
|
||||
dueDate: LocalDate?,
|
||||
tagPublicIds: List<String>,
|
||||
): BoardsApiResult<CreatedEntityRef> {
|
||||
lastCreateCardListPublicId = listPublicId
|
||||
|
||||
Reference in New Issue
Block a user