fix: fail on malformed board detail responses
This commit is contained in:
@@ -227,7 +227,9 @@ class HttpKanbnApiClient : KanbnApiClient {
|
||||
apiKey = apiKey,
|
||||
) { code, body ->
|
||||
if (code in 200..299) {
|
||||
BoardsApiResult.Success(parseBoardDetail(body, boardId))
|
||||
parseBoardDetail(body, boardId)
|
||||
?.let { BoardsApiResult.Success(it) }
|
||||
?: BoardsApiResult.Failure("Malformed board detail response.")
|
||||
} else {
|
||||
BoardsApiResult.Failure(serverMessage(body, code))
|
||||
}
|
||||
@@ -482,9 +484,13 @@ class HttpKanbnApiClient : KanbnApiClient {
|
||||
return workspaces
|
||||
}
|
||||
|
||||
private fun parseBoardDetail(body: String, fallbackId: String): BoardDetail {
|
||||
private fun parseBoardDetail(body: String, fallbackId: String): BoardDetail? {
|
||||
if (body.isBlank()) {
|
||||
return BoardDetail(id = fallbackId, title = "Board", lists = emptyList())
|
||||
}
|
||||
|
||||
val root = parseJsonObject(body)
|
||||
?: return BoardDetail(id = fallbackId, title = "Board", lists = emptyList())
|
||||
?: return null
|
||||
val data = root["data"] as? Map<*, *>
|
||||
val board = (data?.get("board") as? Map<*, *>)
|
||||
?: data
|
||||
@@ -592,15 +598,21 @@ class HttpKanbnApiClient : KanbnApiClient {
|
||||
private fun jsonEscape(value: String): String {
|
||||
val builder = StringBuilder()
|
||||
value.forEach { ch ->
|
||||
when (ch) {
|
||||
'\\' -> builder.append("\\\\")
|
||||
'"' -> builder.append("\\\"")
|
||||
'\b' -> builder.append("\\b")
|
||||
'\u000C' -> builder.append("\\f")
|
||||
'\n' -> builder.append("\\n")
|
||||
'\r' -> builder.append("\\r")
|
||||
'\t' -> builder.append("\\t")
|
||||
else -> builder.append(ch)
|
||||
if (ch.code in 0x00..0x1F) {
|
||||
when (ch) {
|
||||
'\b' -> builder.append("\\b")
|
||||
'\u000C' -> builder.append("\\f")
|
||||
'\n' -> builder.append("\\n")
|
||||
'\r' -> builder.append("\\r")
|
||||
'\t' -> builder.append("\\t")
|
||||
else -> builder.append("\\u%04x".format(ch.code))
|
||||
}
|
||||
} else {
|
||||
when (ch) {
|
||||
'\\' -> builder.append("\\\\")
|
||||
'"' -> builder.append("\\\"")
|
||||
else -> builder.append(ch)
|
||||
}
|
||||
}
|
||||
}
|
||||
return builder.toString()
|
||||
|
||||
@@ -186,6 +186,25 @@ class HttpKanbnApiClientBoardDetailParsingTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getBoardDetailFailsOnMalformedJsonPayload() = runTest {
|
||||
TestServer().use { server ->
|
||||
server.register(
|
||||
path = "/api/v1/boards/malformed",
|
||||
status = 200,
|
||||
responseBody = "{\"data\": {\"id\": \"broken\"",
|
||||
)
|
||||
|
||||
val result = HttpKanbnApiClient().getBoardDetail(server.baseUrl, "key", "malformed")
|
||||
|
||||
assertTrue(result is BoardsApiResult.Failure)
|
||||
assertEquals(
|
||||
"Malformed board detail response.",
|
||||
(result as BoardsApiResult.Failure).message,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun requestMappingMatchesContractForBoardDetailAndMutations() = runTest {
|
||||
TestServer().use { server ->
|
||||
@@ -229,6 +248,24 @@ class HttpKanbnApiClientBoardDetailParsingTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun renameListEscapesControlCharactersInRequestBody() = runTest {
|
||||
TestServer().use { server ->
|
||||
server.register(path = "/api/v1/lists/l-esc", status = 200, responseBody = "{}")
|
||||
|
||||
val raw = "name\u0000line\u001f\n\tend"
|
||||
val result = HttpKanbnApiClient().renameList(server.baseUrl, "api", "l-esc", raw)
|
||||
|
||||
assertTrue(result is BoardsApiResult.Success<*>)
|
||||
val request = server.findRequest("PATCH", "/api/v1/lists/l-esc")
|
||||
assertNotNull(request)
|
||||
assertEquals(
|
||||
"{\"name\":\"name\\u0000line\\u001f\\n\\tend\"}",
|
||||
request?.body,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun serverMessageIsPropagatedWithFallbackWhenMissing() = runTest {
|
||||
TestServer().use { server ->
|
||||
|
||||
Reference in New Issue
Block a user