fix: fail on malformed board detail responses
This commit is contained in:
@@ -227,7 +227,9 @@ class HttpKanbnApiClient : KanbnApiClient {
|
|||||||
apiKey = apiKey,
|
apiKey = apiKey,
|
||||||
) { code, body ->
|
) { code, body ->
|
||||||
if (code in 200..299) {
|
if (code in 200..299) {
|
||||||
BoardsApiResult.Success(parseBoardDetail(body, boardId))
|
parseBoardDetail(body, boardId)
|
||||||
|
?.let { BoardsApiResult.Success(it) }
|
||||||
|
?: BoardsApiResult.Failure("Malformed board detail response.")
|
||||||
} else {
|
} else {
|
||||||
BoardsApiResult.Failure(serverMessage(body, code))
|
BoardsApiResult.Failure(serverMessage(body, code))
|
||||||
}
|
}
|
||||||
@@ -482,9 +484,13 @@ class HttpKanbnApiClient : KanbnApiClient {
|
|||||||
return workspaces
|
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)
|
val root = parseJsonObject(body)
|
||||||
?: return BoardDetail(id = fallbackId, title = "Board", lists = emptyList())
|
?: return null
|
||||||
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
|
?: data
|
||||||
@@ -592,15 +598,21 @@ class HttpKanbnApiClient : KanbnApiClient {
|
|||||||
private fun jsonEscape(value: String): String {
|
private fun jsonEscape(value: String): String {
|
||||||
val builder = StringBuilder()
|
val builder = StringBuilder()
|
||||||
value.forEach { ch ->
|
value.forEach { ch ->
|
||||||
when (ch) {
|
if (ch.code in 0x00..0x1F) {
|
||||||
'\\' -> builder.append("\\\\")
|
when (ch) {
|
||||||
'"' -> builder.append("\\\"")
|
'\b' -> builder.append("\\b")
|
||||||
'\b' -> builder.append("\\b")
|
'\u000C' -> builder.append("\\f")
|
||||||
'\u000C' -> builder.append("\\f")
|
'\n' -> builder.append("\\n")
|
||||||
'\n' -> builder.append("\\n")
|
'\r' -> builder.append("\\r")
|
||||||
'\r' -> builder.append("\\r")
|
'\t' -> builder.append("\\t")
|
||||||
'\t' -> builder.append("\\t")
|
else -> builder.append("\\u%04x".format(ch.code))
|
||||||
else -> builder.append(ch)
|
}
|
||||||
|
} else {
|
||||||
|
when (ch) {
|
||||||
|
'\\' -> builder.append("\\\\")
|
||||||
|
'"' -> builder.append("\\\"")
|
||||||
|
else -> builder.append(ch)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return builder.toString()
|
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
|
@Test
|
||||||
fun requestMappingMatchesContractForBoardDetailAndMutations() = runTest {
|
fun requestMappingMatchesContractForBoardDetailAndMutations() = runTest {
|
||||||
TestServer().use { server ->
|
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
|
@Test
|
||||||
fun serverMessageIsPropagatedWithFallbackWhenMissing() = runTest {
|
fun serverMessageIsPropagatedWithFallbackWhenMissing() = runTest {
|
||||||
TestServer().use { server ->
|
TestServer().use { server ->
|
||||||
|
|||||||
Reference in New Issue
Block a user