From 926170babddbcb162812f2762709baca14227f18 Mon Sep 17 00:00:00 2001 From: Diane Huxley Date: Wed, 20 Mar 2024 12:21:27 -0700 Subject: [PATCH] #200 Restructure http endpoints according to latest design (#203) * Update client to new endpoints * Update existing endpoints in server * Implement getExchanges server endpoint * Pass filter to ExchangeApi.getExchanges * PUT * dry * PUT and other fix * Fix test --- .../tbdex/sdk/httpclient/TbdexHttpClient.kt | 73 +++++++------- .../sdk/httpclient/TbdexHttpClientTest.kt | 8 +- .../tbdex/sdk/httpserver/TbdexHttpServer.kt | 29 +++--- .../sdk/httpserver/handlers/GetExchange.kt | 95 +++++++++++++++++++ .../sdk/httpserver/handlers/GetExchanges.kt | 3 +- .../sdk/httpserver/handlers/SubmitClose.kt | 13 +-- .../sdk/httpserver/handlers/SubmitMessage.kt | 60 ++++++++++++ .../sdk/httpserver/handlers/SubmitOrder.kt | 13 +-- .../httpserver/handlers/CreateExchangeTest.kt | 10 +- .../httpserver/handlers/GetExchangeTest.kt | 95 +++++++++++++++++++ .../httpserver/handlers/GetExchangesTest.kt | 8 +- .../httpserver/handlers/SubmitCloseTest.kt | 12 +-- .../httpserver/handlers/SubmitMessageTest.kt | 52 ++++++++++ .../httpserver/handlers/SubmitOrderTest.kt | 14 +-- 14 files changed, 391 insertions(+), 94 deletions(-) create mode 100644 httpserver/src/main/kotlin/tbdex/sdk/httpserver/handlers/GetExchange.kt create mode 100644 httpserver/src/main/kotlin/tbdex/sdk/httpserver/handlers/SubmitMessage.kt create mode 100644 httpserver/src/test/kotlin/tbdex/sdk/httpserver/handlers/GetExchangeTest.kt create mode 100644 httpserver/src/test/kotlin/tbdex/sdk/httpserver/handlers/SubmitMessageTest.kt diff --git a/httpclient/src/main/kotlin/tbdex/sdk/httpclient/TbdexHttpClient.kt b/httpclient/src/main/kotlin/tbdex/sdk/httpclient/TbdexHttpClient.kt index 33dd44fb..995b21c8 100644 --- a/httpclient/src/main/kotlin/tbdex/sdk/httpclient/TbdexHttpClient.kt +++ b/httpclient/src/main/kotlin/tbdex/sdk/httpclient/TbdexHttpClient.kt @@ -72,18 +72,6 @@ object TbdexHttpClient { } } - private fun sendMessage(pfiDid: String, path: String, requestBody: RequestBody) { - - val pfiServiceEndpoint = getPfiServiceEndpoint(pfiDid) - val url = pfiServiceEndpoint + path - - val request = buildRequest(url, requestBody) - - println("Attempting to send rfq message to: ${request.url}") - - executeRequest(request) - } - /** * Send RFQ message to the PFI. * @@ -95,14 +83,10 @@ object TbdexHttpClient { validateMessage(rfq) val pfiDid = rfq.metadata.to - val exchangeId = rfq.metadata.exchangeId - - val path = "/exchanges/$exchangeId" - val body: RequestBody = Json.stringify(CreateExchangeRequest(rfq)) .toRequestBody(jsonMediaType) - this.sendMessage(pfiDid, path, body) + this.createExchange(pfiDid, body) } /** @@ -117,14 +101,27 @@ object TbdexHttpClient { validateMessage(rfq) val pfiDid = rfq.metadata.to - val exchangeId = rfq.metadata.exchangeId - - val path = "/exchanges/$exchangeId" - val body: RequestBody = Json.stringify(CreateExchangeRequest(rfq, replyTo)) .toRequestBody(jsonMediaType) - this.sendMessage(pfiDid, path, body) + this.createExchange(pfiDid, body) + } + + private fun createExchange(pfiDid: String, requestBody: RequestBody) { + val path = "/exchanges" + + val pfiServiceEndpoint = getPfiServiceEndpoint(pfiDid) + val url = pfiServiceEndpoint + path + + val request = Request.Builder() + .url(url) + .addHeader("Content-Type", JSON_HEADER) + .post(requestBody) + .build() + + println("Attempting to send rfq message to: ${request.url}") + + executeRequest(request) } /** @@ -139,12 +136,11 @@ object TbdexHttpClient { val pfiDid = order.metadata.to val exchangeId = order.metadata.exchangeId - val path = "/exchanges/$exchangeId/order" val body: RequestBody = Json.stringify(order) .toRequestBody(jsonMediaType) - this.sendMessage(pfiDid, path, body) + this.submitMessage(pfiDid, exchangeId, body) } /** @@ -159,12 +155,28 @@ object TbdexHttpClient { val pfiDid = close.metadata.to val exchangeId = close.metadata.exchangeId - val path = "/exchanges/$exchangeId/close" val body: RequestBody = Json.stringify(close) .toRequestBody(jsonMediaType) - this.sendMessage(pfiDid, path, body) + this.submitMessage(pfiDid, exchangeId, body) + } + + private fun submitMessage(pfiDid: String, exchangeId: String, requestBody: RequestBody) { + val path = "/exchanges/$exchangeId" + + val pfiServiceEndpoint = getPfiServiceEndpoint(pfiDid) + val url = pfiServiceEndpoint + path + + val request = Request.Builder() + .url(url) + .addHeader("Content-Type", JSON_HEADER) + .post(requestBody) + .build() + + println("Attempting to send message to exchange ${exchangeId} to: ${request.url}") + + executeRequest(request) } /** @@ -281,15 +293,6 @@ object TbdexHttpClient { message.verify() } - private fun buildRequest(url: String, body: RequestBody): Request { - val request = Request.Builder() - .url(url) - .addHeader("Content-Type", JSON_HEADER) - .post(body) - .build() - return request - } - private fun executeRequest(request: Request) { val response: Response = client.newCall(request).execute() if (!response.isSuccessful) { diff --git a/httpclient/src/test/kotlin/tbdex/sdk/httpclient/TbdexHttpClientTest.kt b/httpclient/src/test/kotlin/tbdex/sdk/httpclient/TbdexHttpClientTest.kt index 092136e0..310c1921 100644 --- a/httpclient/src/test/kotlin/tbdex/sdk/httpclient/TbdexHttpClientTest.kt +++ b/httpclient/src/test/kotlin/tbdex/sdk/httpclient/TbdexHttpClientTest.kt @@ -95,7 +95,7 @@ class TbdexHttpClientTest { assertDoesNotThrow { TbdexHttpClient.createExchange(rfq) } val request = server.takeRequest() - assertEquals(request.path, "/exchanges/${rfq.metadata.exchangeId}") + assertEquals(request.path, "/exchanges") assertEquals( Json.jsonMapper.readTree(request.body.readUtf8()), Json.jsonMapper.readTree(Json.stringify(mapOf("rfq" to rfq))) @@ -111,7 +111,7 @@ class TbdexHttpClientTest { assertDoesNotThrow { TbdexHttpClient.createExchange(rfq, replyTo) } val request = server.takeRequest() - assertEquals(request.path, "/exchanges/${rfq.metadata.exchangeId}") + assertEquals(request.path, "/exchanges") assertEquals( Json.jsonMapper.readTree(request.body.readUtf8()), Json.jsonMapper.readTree(Json.stringify(mapOf("rfq" to rfq, "replyTo" to replyTo))) @@ -195,7 +195,7 @@ class TbdexHttpClientTest { assertDoesNotThrow { TbdexHttpClient.submitOrder(order) } val request = server.takeRequest() - assertEquals(request.path, "/exchanges/${order.metadata.exchangeId}/order") + assertEquals(request.path, "/exchanges/${order.metadata.exchangeId}") assertEquals( Json.jsonMapper.readTree(request.body.readUtf8()), Json.jsonMapper.readTree(Json.stringify(order)) @@ -253,7 +253,7 @@ class TbdexHttpClientTest { assertDoesNotThrow { TbdexHttpClient.submitClose(close) } val request = server.takeRequest() - assertEquals(request.path, "/exchanges/${close.metadata.exchangeId}/close") + assertEquals(request.path, "/exchanges/${close.metadata.exchangeId}") assertEquals( Json.jsonMapper.readTree(request.body.readUtf8()), Json.jsonMapper.readTree(Json.stringify(close)) diff --git a/httpserver/src/main/kotlin/tbdex/sdk/httpserver/TbdexHttpServer.kt b/httpserver/src/main/kotlin/tbdex/sdk/httpserver/TbdexHttpServer.kt index 0e94fdcd..03b9e09f 100644 --- a/httpserver/src/main/kotlin/tbdex/sdk/httpserver/TbdexHttpServer.kt +++ b/httpserver/src/main/kotlin/tbdex/sdk/httpserver/TbdexHttpServer.kt @@ -14,12 +14,15 @@ import io.ktor.server.plugins.contentnegotiation.ContentNegotiation import io.ktor.server.response.respond import io.ktor.server.routing.get import io.ktor.server.routing.post +import io.ktor.server.routing.put import io.ktor.server.routing.route import io.ktor.server.routing.routing import tbdex.sdk.httpserver.handlers.createExchange +import tbdex.sdk.httpserver.handlers.getExchange import tbdex.sdk.httpserver.handlers.getExchanges import tbdex.sdk.httpserver.handlers.getOfferings import tbdex.sdk.httpserver.handlers.submitClose +import tbdex.sdk.httpserver.handlers.submitMessage import tbdex.sdk.httpserver.handlers.submitOrder import tbdex.sdk.httpserver.models.ExchangesApi import tbdex.sdk.httpserver.models.FakeExchangesApi @@ -101,7 +104,7 @@ class TbdexHttpServer(val config: TbdexHttpServerConfig) { } route("/exchanges") { - post("/{exchangeId}") { + post { createExchange( call = call, offeringsApi = offeringsApi, @@ -110,28 +113,30 @@ class TbdexHttpServer(val config: TbdexHttpServerConfig) { ) } - post("/{exchangeId}/order") { - submitOrder( + put("/{exchangeId}") { + submitMessage( call = call, exchangesApi = exchangesApi, callback = submitCallbacks.getOrDefault("order", null) ) } - post("/{exchangeId}/close") { - submitClose( - call = call, - exchangesApi = exchangesApi, - callback = submitCallbacks.getOrDefault("close", null) - ) - } - get { getExchanges( call, exchangesApi, getCallbacks.getOrDefault("exchanges", null), - pfiDid) + pfiDid + ) + } + + get("/{exchangeId}") { + getExchange( + call, + exchangesApi, + getCallbacks.getOrDefault("exchange", null), + pfiDid + ) } } diff --git a/httpserver/src/main/kotlin/tbdex/sdk/httpserver/handlers/GetExchange.kt b/httpserver/src/main/kotlin/tbdex/sdk/httpserver/handlers/GetExchange.kt new file mode 100644 index 00000000..2426f618 --- /dev/null +++ b/httpserver/src/main/kotlin/tbdex/sdk/httpserver/handlers/GetExchange.kt @@ -0,0 +1,95 @@ +package tbdex.sdk.httpserver.handlers + +import io.ktor.http.HttpHeaders +import io.ktor.http.HttpStatusCode +import io.ktor.server.application.ApplicationCall +import io.ktor.server.response.respond +import tbdex.sdk.httpclient.RequestToken +import tbdex.sdk.httpclient.models.ErrorDetail +import tbdex.sdk.httpserver.models.ErrorResponse +import tbdex.sdk.httpserver.models.ExchangesApi +import tbdex.sdk.httpserver.models.GetCallback +import tbdex.sdk.httpserver.models.GetExchangesFilter +import tbdex.sdk.protocol.models.Message + +/** + * Get exchanges response + * + * @property data list of exchanges (list of tbdex messages) + */ +class GetExchangeResponse( + val data: List? +) + +/** + * Get exchanges + * + * @param call Ktor server application call + * @param exchangesApi Exchanges API interface + * @param callback Callback function to be invoked + */ +@Suppress("SwallowedException") +suspend fun getExchange( + call: ApplicationCall, + exchangesApi: ExchangesApi, + callback: GetCallback?, + pfiDid: String +) { + val authzHeader = call.request.headers[HttpHeaders.Authorization] + if (authzHeader == null) { + call.respond( + HttpStatusCode.Unauthorized, + ErrorResponse( + errors = listOf( + ErrorDetail( + detail = "Authorization header required" + ) + ) + ) + ) + return + } + + val arr = authzHeader.split("Bearer ") + if (arr.size != 2) { + call.respond( + HttpStatusCode.Unauthorized, + ErrorResponse( + errors = listOf( + ErrorDetail( + detail = "Malformed Authorization header. Expected: Bearer TOKEN_HERE" + ) + ) + ) + ) + return + } + + val token = arr[1] + val requesterDid: String + try { + requesterDid = RequestToken.verify(token, pfiDid) + } catch (e: Exception) { + call.respond( + HttpStatusCode.Unauthorized, + ErrorResponse( + errors = listOf( + ErrorDetail( + detail = "Could not verify Authorization header." + ) + ) + ) + ) + return + } + + val exchanges = exchangesApi.getExchange(call.parameters["exchangeId"]!!) + + if (callback != null) { + // TODO: figure out what to do with callback result. should we pass through the exchanges we've fetched + // and allow the callback to modify what's returned? (issue #10) + val result = callback.invoke(call, GetExchangesFilter()) + } + + call.respond(HttpStatusCode.OK, GetExchangeResponse(data = exchanges)) +} \ No newline at end of file diff --git a/httpserver/src/main/kotlin/tbdex/sdk/httpserver/handlers/GetExchanges.kt b/httpserver/src/main/kotlin/tbdex/sdk/httpserver/handlers/GetExchanges.kt index 719f0b1c..e18d6641 100644 --- a/httpserver/src/main/kotlin/tbdex/sdk/httpserver/handlers/GetExchanges.kt +++ b/httpserver/src/main/kotlin/tbdex/sdk/httpserver/handlers/GetExchanges.kt @@ -83,7 +83,8 @@ suspend fun getExchanges( return } - val exchanges = exchangesApi.getExchanges() + val ids = call.request.queryParameters.getAll("id") ?: emptyList() + val exchanges = exchangesApi.getExchanges(GetExchangesFilter(ids, requesterDid)) if (callback != null) { // TODO: figure out what to do with callback result. should we pass through the exchanges we've fetched diff --git a/httpserver/src/main/kotlin/tbdex/sdk/httpserver/handlers/SubmitClose.kt b/httpserver/src/main/kotlin/tbdex/sdk/httpserver/handlers/SubmitClose.kt index 00437d4a..a0b3b06c 100644 --- a/httpserver/src/main/kotlin/tbdex/sdk/httpserver/handlers/SubmitClose.kt +++ b/httpserver/src/main/kotlin/tbdex/sdk/httpserver/handlers/SubmitClose.kt @@ -25,18 +25,9 @@ import tbdex.sdk.protocol.models.MessageKind suspend fun submitClose( call: ApplicationCall, exchangesApi: ExchangesApi, - callback: SubmitCallback? + callback: SubmitCallback?, + message: Close, ) { - val message: Close? - - try { - message = Message.parse(call.receiveText()) as Close - } catch (e: Exception) { - val errorDetail = ErrorDetail(detail = "Parsing of TBDex message failed: ${e.message}") - val errorResponse = ErrorResponse(listOf(errorDetail)) - call.respond(HttpStatusCode.BadRequest, errorResponse) - return - } val exchangeId = message.metadata.exchangeId.toString() val exchange: List diff --git a/httpserver/src/main/kotlin/tbdex/sdk/httpserver/handlers/SubmitMessage.kt b/httpserver/src/main/kotlin/tbdex/sdk/httpserver/handlers/SubmitMessage.kt new file mode 100644 index 00000000..423d3291 --- /dev/null +++ b/httpserver/src/main/kotlin/tbdex/sdk/httpserver/handlers/SubmitMessage.kt @@ -0,0 +1,60 @@ +package tbdex.sdk.httpserver.handlers + +import io.ktor.http.HttpStatusCode +import io.ktor.server.application.ApplicationCall +import io.ktor.server.request.receiveText +import io.ktor.server.response.respond +import tbdex.sdk.httpclient.models.ErrorDetail +import tbdex.sdk.httpserver.models.ErrorResponse +import tbdex.sdk.httpserver.models.ExchangesApi +import tbdex.sdk.httpserver.models.SubmitCallback +import tbdex.sdk.protocol.models.Close +import tbdex.sdk.protocol.models.Message +import tbdex.sdk.protocol.models.MessageKind +import tbdex.sdk.protocol.models.Order + +/** + * Handles the submission of a close message by parsing the incoming message, + * validating the submission, and executing the specified callback if provided. + * + * @param call The [ApplicationCall] instance representing the HTTP call. + * @param exchangesApi The [ExchangesApi] instance for interacting with TBDex exchanges. + * @param callback The optional callback function to be executed after a successful close submission. + */ +@Suppress("TooGenericExceptionCaught", "MaxLineLength", "SwallowedException") +suspend fun submitMessage( + call: ApplicationCall, + exchangesApi: ExchangesApi, + callback: SubmitCallback? +) { + val message: Message + + try { + message = Message.parse(call.receiveText()) + } catch (e: Exception) { + val errorDetail = ErrorDetail(detail = "Parsing of TBDex message failed: ${e.message}") + val errorResponse = ErrorResponse(listOf(errorDetail)) + call.respond(HttpStatusCode.BadRequest, errorResponse) + return + } + + if (message.metadata.exchangeId != call.parameters["exchangeId"]) { + val errorDetail = ErrorDetail(detail = "Exchange ID of message must match URL") + val errorResponse = ErrorResponse(listOf(errorDetail)) + call.respond(HttpStatusCode.BadRequest, errorResponse) + return + } + + when (message.metadata.kind) { + MessageKind.close -> { + return submitClose(call, exchangesApi, callback, message as Close) + } + MessageKind.order -> { + return submitOrder(call, exchangesApi, callback, message as Order) + } + else -> { + val errorDetail = ErrorDetail(detail = "Message must be a valid Order or Close message") + call.respond(HttpStatusCode.BadRequest, ErrorResponse(listOf(errorDetail))) + } + } +} \ No newline at end of file diff --git a/httpserver/src/main/kotlin/tbdex/sdk/httpserver/handlers/SubmitOrder.kt b/httpserver/src/main/kotlin/tbdex/sdk/httpserver/handlers/SubmitOrder.kt index 5e619c90..fec42ee6 100644 --- a/httpserver/src/main/kotlin/tbdex/sdk/httpserver/handlers/SubmitOrder.kt +++ b/httpserver/src/main/kotlin/tbdex/sdk/httpserver/handlers/SubmitOrder.kt @@ -26,18 +26,9 @@ import tbdex.sdk.protocol.models.Quote suspend fun submitOrder( call: ApplicationCall, exchangesApi: ExchangesApi, - callback: SubmitCallback? + callback: SubmitCallback?, + message: Order, ) { - val message: Order? - - try { - message = Message.parse(call.receiveText()) as Order - } catch (e: Exception) { - val errorDetail = ErrorDetail(detail = "Parsing of TBDex message failed: ${e.message}") - val errorResponse = ErrorResponse(listOf(errorDetail)) - call.respond(HttpStatusCode.BadRequest, errorResponse) - return - } val exchangeId = message.metadata.exchangeId val exchange: List diff --git a/httpserver/src/test/kotlin/tbdex/sdk/httpserver/handlers/CreateExchangeTest.kt b/httpserver/src/test/kotlin/tbdex/sdk/httpserver/handlers/CreateExchangeTest.kt index 87f77434..b305aac4 100644 --- a/httpserver/src/test/kotlin/tbdex/sdk/httpserver/handlers/CreateExchangeTest.kt +++ b/httpserver/src/test/kotlin/tbdex/sdk/httpserver/handlers/CreateExchangeTest.kt @@ -20,7 +20,7 @@ import kotlin.test.assertEquals class CreateExchangeTest : ServerTest() { @Test fun `returns BadRequest if no request body is provided`() = runBlocking { - val response = client.post("/exchanges/123") { + val response = client.post("/exchanges") { contentType(ContentType.Application.Json) } @@ -36,7 +36,7 @@ class CreateExchangeTest : ServerTest() { rfq.sign(aliceDid) exchangesApi.addMessage(rfq) - val response = client.post("/exchanges/123") { + val response = client.post("/exchanges") { contentType(ContentType.Application.Json) setBody(CreateExchangeRequest(rfq)) } @@ -52,7 +52,7 @@ class CreateExchangeTest : ServerTest() { val rfq = createRfq(null, listOf("foo")) rfq.sign(aliceDid) - val response = client.post("/exchanges/123") { + val response = client.post("/exchanges") { contentType(ContentType.Application.Json) setBody(CreateExchangeRequest(rfq)) } @@ -68,7 +68,7 @@ class CreateExchangeTest : ServerTest() { val rfq = createRfq() rfq.sign(aliceDid) - val response = client.post("/exchanges/123") { + val response = client.post("/exchanges") { contentType(ContentType.Application.Json) setBody(CreateExchangeRequest(rfq, "foo")) } @@ -84,7 +84,7 @@ class CreateExchangeTest : ServerTest() { val rfq = createRfq(offeringsApi.getOffering("123")) rfq.sign(aliceDid) - val response = client.post("/exchanges/123") { + val response = client.post("/exchanges") { contentType(ContentType.Application.Json) setBody(CreateExchangeRequest(rfq, "http://localhost:9000/callback")) } diff --git a/httpserver/src/test/kotlin/tbdex/sdk/httpserver/handlers/GetExchangeTest.kt b/httpserver/src/test/kotlin/tbdex/sdk/httpserver/handlers/GetExchangeTest.kt new file mode 100644 index 00000000..ab311bcf --- /dev/null +++ b/httpserver/src/test/kotlin/tbdex/sdk/httpserver/handlers/GetExchangeTest.kt @@ -0,0 +1,95 @@ +package tbdex.sdk.httpserver.handlers + +import ServerTest +import TestData +import com.fasterxml.jackson.annotation.JsonInclude +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule +import com.fasterxml.jackson.module.kotlin.registerKotlinModule +import io.ktor.client.plugins.auth.Auth +import io.ktor.client.plugins.auth.providers.bearer +import io.ktor.client.plugins.contentnegotiation.ContentNegotiation +import io.ktor.client.request.bearerAuth +import io.ktor.client.request.get +import io.ktor.client.statement.bodyAsText +import io.ktor.http.HttpStatusCode +import io.ktor.serialization.jackson.jackson +import kotlinx.coroutines.runBlocking +import org.junit.jupiter.api.BeforeEach +import tbdex.sdk.httpclient.RequestToken +import tbdex.sdk.httpclient.models.ErrorResponse +import tbdex.sdk.protocol.models.Message +import tbdex.sdk.protocol.models.Rfq +import tbdex.sdk.protocol.serialization.Json +import kotlin.test.Test +import kotlin.test.assertContains +import kotlin.test.assertEquals + +class GetExchangeTest : ServerTest() { + + @BeforeEach + fun `setup client`() { + client.config { + install(ContentNegotiation) { + jackson { + registerModule(JavaTimeModule()) + registerKotlinModule() + .setSerializationInclusion(JsonInclude.Include.NON_NULL) + findAndRegisterModules() + } + } + install(Auth) { + bearer { + sendWithoutRequest { true } + } + } + } + } + + @Test + fun `returns 401 if no Bearer token is present`() = runBlocking { + + val response = client.get("/exchanges/123") + + assertEquals(HttpStatusCode.Unauthorized, response.status) + + val errorResponse = Json.jsonMapper.readValue(response.bodyAsText(), ErrorResponse::class.java) + assertContains(errorResponse.errors.first().detail, "Authorization header required") + } + + @Test + fun `returns 401 if malformed Bearer token is present`() = runBlocking { + + val response = client.get("/exchanges/123") { + bearerAuth("Bearer abc123") + } + + assertEquals(HttpStatusCode.Unauthorized, response.status) + + val errorResponse = Json.jsonMapper.readValue(response.bodyAsText(), ErrorResponse::class.java) + assertContains(errorResponse.errors.first().detail, "Malformed Authorization header.") + } + + @Test + fun `returns 200 if exchanges are found`() = runBlocking { + val rfq = TestData.createRfq() + rfq.sign(TestData.aliceDid) + + exchangesApi.addMessage(rfq) + + val response = client.get("/exchanges/${rfq.metadata.exchangeId}") { + bearerAuth(RequestToken.generate(TestData.aliceDid, TestData.pfiDid.uri)) + } + + assertEquals(HttpStatusCode.OK, response.status) + + val responseString = response.bodyAsText() + val jsonNode = Json.jsonMapper.readTree(responseString) + val exchange = jsonNode.get("data").elements().asSequence() + .map { + val string = it.toString() + Message.parse(string) + }.toList() + + assertEquals((exchange[0] as Rfq).metadata.from, TestData.aliceDid.uri) + } +} \ No newline at end of file diff --git a/httpserver/src/test/kotlin/tbdex/sdk/httpserver/handlers/GetExchangesTest.kt b/httpserver/src/test/kotlin/tbdex/sdk/httpserver/handlers/GetExchangesTest.kt index efd3b103..3e109a8d 100644 --- a/httpserver/src/test/kotlin/tbdex/sdk/httpserver/handlers/GetExchangesTest.kt +++ b/httpserver/src/test/kotlin/tbdex/sdk/httpserver/handlers/GetExchangesTest.kt @@ -79,7 +79,7 @@ class GetExchangesTest : ServerTest() { exchangesApi.addMessage(rfq) exchangesApi.addMessage(quote) - val response = client.get("/exchanges") { + val response = client.get("/exchanges?id=${rfq.metadata.exchangeId}&id=${quote.metadata.exchangeId}") { bearerAuth(RequestToken.generate(TestData.aliceDid, TestData.pfiDid.uri)) } @@ -96,6 +96,10 @@ class GetExchangesTest : ServerTest() { } .toList() - assertEquals((exchanges[0][0] as Rfq).metadata.from, TestData.aliceDid.uri) + assertEquals(exchanges.size, 2) + assertEquals(exchanges[0].size, 1) + + assertEquals(exchanges[0][0].metadata.id, rfq.metadata.id) + assertEquals(exchanges[1][0].metadata.id, quote.metadata.id) } } \ No newline at end of file diff --git a/httpserver/src/test/kotlin/tbdex/sdk/httpserver/handlers/SubmitCloseTest.kt b/httpserver/src/test/kotlin/tbdex/sdk/httpserver/handlers/SubmitCloseTest.kt index bbe95bbc..f83c73b5 100644 --- a/httpserver/src/test/kotlin/tbdex/sdk/httpserver/handlers/SubmitCloseTest.kt +++ b/httpserver/src/test/kotlin/tbdex/sdk/httpserver/handlers/SubmitCloseTest.kt @@ -7,7 +7,7 @@ import TestData.createRfq import assertk.assertThat import assertk.assertions.contains import de.fxlae.typeid.TypeId -import io.ktor.client.request.post +import io.ktor.client.request.put import io.ktor.client.request.setBody import io.ktor.client.statement.bodyAsText import io.ktor.http.ContentType @@ -25,7 +25,7 @@ import kotlin.test.assertEquals class SubmitCloseTest : ServerTest() { @Test fun `returns BadRequest if no request body is provided`() = runBlocking { - val response = client.post("/exchanges/123/close") { + val response = client.put("/exchanges/123") { contentType(ContentType.Application.Json) } @@ -44,7 +44,7 @@ class SubmitCloseTest : ServerTest() { val close = createClose(exchangeId = rfq.metadata.exchangeId, protocol = "2.0") close.sign(aliceDid) - val response = client.post("/exchanges/123/close") { + val response = client.put("/exchanges/${close.metadata.exchangeId}") { contentType(ContentType.Application.Json) setBody(close) } @@ -61,7 +61,7 @@ class SubmitCloseTest : ServerTest() { close.sign(aliceDid) exchangesApi.addMessage(close) - val response = client.post("/exchanges/123/close") { + val response = client.put("/exchanges/${close.metadata.exchangeId}") { contentType(ContentType.Application.Json) setBody(close) } @@ -80,7 +80,7 @@ class SubmitCloseTest : ServerTest() { val close = createClose(TypeId.generate(MessageKind.rfq.name).toString()) close.sign(aliceDid) - val response = client.post("/exchanges/123/close") { + val response = client.put("/exchanges/${close.metadata.exchangeId}") { contentType(ContentType.Application.Json) setBody(close) } @@ -103,7 +103,7 @@ class SubmitCloseTest : ServerTest() { val close = createClose(rfq.metadata.exchangeId) close.sign(aliceDid) - val response = client.post("/exchanges/123/close") { + val response = client.put("/exchanges/${close.metadata.exchangeId}") { contentType(ContentType.Application.Json) setBody(close) } diff --git a/httpserver/src/test/kotlin/tbdex/sdk/httpserver/handlers/SubmitMessageTest.kt b/httpserver/src/test/kotlin/tbdex/sdk/httpserver/handlers/SubmitMessageTest.kt new file mode 100644 index 00000000..89732b43 --- /dev/null +++ b/httpserver/src/test/kotlin/tbdex/sdk/httpserver/handlers/SubmitMessageTest.kt @@ -0,0 +1,52 @@ +package tbdex.sdk.httpserver.handlers; + +import ServerTest +import TestData.aliceDid +import TestData.createOrder +import TestData.createQuote +import TestData.pfiDid +import de.fxlae.typeid.TypeId +import io.ktor.client.request.put +import io.ktor.client.request.setBody +import io.ktor.client.statement.bodyAsText +import io.ktor.http.ContentType +import io.ktor.http.HttpStatusCode +import io.ktor.http.contentType +import kotlinx.coroutines.runBlocking +import org.junit.jupiter.api.Test +import tbdex.sdk.httpclient.models.ErrorResponse +import tbdex.sdk.protocol.serialization.Json +import kotlin.test.assertContains +import kotlin.test.assertEquals + +class SubmitMessageTest : ServerTest() { + @Test + fun `returns BadRequest if exchangeId of message does not match URL`() = runBlocking { + val order = createOrder(TypeId.generate("rfq").toString()) + order.sign(aliceDid) + val response = client.put("/exchanges/1234") { + contentType(ContentType.Application.Json) + setBody(order) + } + + val errorResponse = Json.jsonMapper.readValue(response.bodyAsText(), ErrorResponse::class.java) + + assertEquals(HttpStatusCode.BadRequest, response.status) + assertContains(errorResponse.errors.first().detail, "Exchange ID of message must match URL") + } + + @Test + fun `returns BadRequest if message is not a valid Order or Close`() = runBlocking { + val quote = createQuote(TypeId.generate("rfq").toString()) + quote.sign(pfiDid) + val response = client.put("/exchanges/${quote.metadata.exchangeId}") { + contentType(ContentType.Application.Json) + setBody(quote) + } + + val errorResponse = Json.jsonMapper.readValue(response.bodyAsText(), ErrorResponse::class.java) + + assertEquals(HttpStatusCode.BadRequest, response.status) + assertContains(errorResponse.errors.first().detail, "Message must be a valid Order or Close message") + } +} \ No newline at end of file diff --git a/httpserver/src/test/kotlin/tbdex/sdk/httpserver/handlers/SubmitOrderTest.kt b/httpserver/src/test/kotlin/tbdex/sdk/httpserver/handlers/SubmitOrderTest.kt index e31856e7..84d20fc6 100644 --- a/httpserver/src/test/kotlin/tbdex/sdk/httpserver/handlers/SubmitOrderTest.kt +++ b/httpserver/src/test/kotlin/tbdex/sdk/httpserver/handlers/SubmitOrderTest.kt @@ -9,7 +9,7 @@ import TestData.pfiDid import assertk.assertThat import assertk.assertions.contains import de.fxlae.typeid.TypeId -import io.ktor.client.request.post +import io.ktor.client.request.put import io.ktor.client.request.setBody import io.ktor.client.statement.bodyAsText import io.ktor.http.ContentType @@ -26,7 +26,7 @@ import kotlin.test.assertEquals class SubmitOrderTest : ServerTest() { @Test fun `returns BadRequest if no request body is provided`() = runBlocking { - val response = client.post("/exchanges/123/order") { + val response = client.put("/exchanges/123") { contentType(ContentType.Application.Json) } @@ -41,7 +41,7 @@ class SubmitOrderTest : ServerTest() { val order = createOrder(TypeId.generate("rfq").toString()) order.sign(aliceDid) - val response = client.post("/exchanges/123/order") { + val response = client.put("/exchanges/${order.metadata.exchangeId}") { contentType(ContentType.Application.Json) setBody(order) } @@ -61,7 +61,7 @@ class SubmitOrderTest : ServerTest() { val order = createOrder(rfq.metadata.exchangeId) order.sign(aliceDid) - val response = client.post("/exchanges/${rfq.metadata.exchangeId}/order") { + val response = client.put("/exchanges/${rfq.metadata.exchangeId}") { contentType(ContentType.Application.Json) setBody(order) } @@ -84,7 +84,7 @@ class SubmitOrderTest : ServerTest() { val order = createOrder(exchangeId = rfq.metadata.exchangeId, protocol = "2.0") order.sign(aliceDid) - val response = client.post("/exchanges/123/order") { + val response = client.put("/exchanges/${order.metadata.exchangeId}") { contentType(ContentType.Application.Json) setBody(order) } @@ -104,7 +104,7 @@ class SubmitOrderTest : ServerTest() { val order = createOrder(quote.metadata.exchangeId) order.sign(aliceDid) - val response = client.post("/exchanges/${quote.metadata.exchangeId}/order") { + val response = client.put("/exchanges/${quote.metadata.exchangeId}") { contentType(ContentType.Application.Json) setBody(order) } @@ -128,7 +128,7 @@ class SubmitOrderTest : ServerTest() { val order = createOrder(quote.metadata.exchangeId) order.sign(aliceDid) - val response = client.post("/exchanges/${rfq.metadata.exchangeId}/order") { + val response = client.put("/exchanges/${rfq.metadata.exchangeId}") { contentType(ContentType.Application.Json) setBody(order) }