mirror of
https://github.com/OpenBankProject/OBP-API.git
synced 2026-02-06 11:06:49 +00:00
test/(http4s): add response conversion tests and enhance bridge debugging
- Add Http4sResponseConversionTest with comprehensive test coverage for Lift to HTTP4S response conversion - Test all response types: InMemoryResponse, StreamingResponse, OutputStreamResponse, BasicResponse - Validate HTTP status codes, headers, and body preservation across response types - Test edge cases: empty responses, large payloads (>1MB), UTF-8 characters, error status codes - Test streaming responses with callbacks and output stream handling - Enhance Http4sLiftWebBridge debugging with body preview (first 200 bytes) and request JSON/body logging - Improve observability for request/response flow debugging and troubleshooting - Validates Requirements 2.4 (Task 2.5) for response conversion parity
This commit is contained in:
parent
b951231528
commit
bab466127f
@ -140,7 +140,9 @@ object Http4sLiftWebBridge extends MdcLoggable {
|
||||
val contentType = headers.find(_.name.equalsIgnoreCase("Content-Type")).map(_.values.mkString(",")).getOrElse("none")
|
||||
val authHeader = headers.find(_.name.equalsIgnoreCase("Authorization")).map(_.values.mkString(",")).getOrElse("none")
|
||||
val bodySize = body.length
|
||||
logger.debug(s"[BRIDGE] buildLiftReq: method=${liftReq.request.method}, uri=${liftReq.request.uri}, path=${liftReq.path.partPath.mkString("/")}, wholePath=${liftReq.path.wholePath.mkString("/")}, contentType=$contentType, authHeader=$authHeader, bodySize=$bodySize")
|
||||
val bodyPreview = if (body.length > 0) new String(body.take(200), "UTF-8") else "empty"
|
||||
logger.debug(s"[BRIDGE] buildLiftReq: method=${liftReq.request.method}, uri=${liftReq.request.uri}, path=${liftReq.path.partPath.mkString("/")}, wholePath=${liftReq.path.wholePath.mkString("/")}, contentType=$contentType, authHeader=$authHeader, bodySize=$bodySize, bodyPreview=$bodyPreview")
|
||||
logger.debug(s"[BRIDGE] Req.json = ${liftReq.json}, Req.body = ${liftReq.body}")
|
||||
logger.debug(s"Http4sLiftBridge buildLiftReq: method=${liftReq.request.method}, uri=${liftReq.request.uri}, path=${liftReq.path.partPath.mkString("/")}")
|
||||
liftReq
|
||||
}
|
||||
|
||||
@ -0,0 +1,575 @@
|
||||
package code.api.util.http4s
|
||||
|
||||
import cats.effect.IO
|
||||
import cats.effect.unsafe.implicits.global
|
||||
import net.liftweb.http._
|
||||
import org.http4s.{Header, Headers, Response, Status}
|
||||
import org.scalatest.{FeatureSpec, GivenWhenThen, Matchers}
|
||||
import org.typelevel.ci.CIString
|
||||
|
||||
import java.io.{ByteArrayInputStream, ByteArrayOutputStream, InputStream, OutputStream}
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
/**
|
||||
* Unit tests for Lift → HTTP4S response conversion in Http4sLiftWebBridge.
|
||||
*
|
||||
* Tests validate:
|
||||
* - Handling of all Lift response types (InMemoryResponse, StreamingResponse, OutputStreamResponse, BasicResponse)
|
||||
* - HTTP status code and header preservation
|
||||
* - Error response format consistency
|
||||
* - Streaming responses and callbacks
|
||||
* - Edge cases (empty responses, large payloads, special characters)
|
||||
*
|
||||
* Validates: Requirements 2.4 (Task 2.5)
|
||||
*/
|
||||
class Http4sResponseConversionTest extends FeatureSpec with Matchers with GivenWhenThen {
|
||||
|
||||
feature("Lift to HTTP4S response conversion - InMemoryResponse") {
|
||||
scenario("Convert simple InMemoryResponse with JSON body") {
|
||||
Given("A Lift InMemoryResponse with JSON data")
|
||||
val jsonData = """{"status":"success","message":"Test response"}"""
|
||||
val data = jsonData.getBytes("UTF-8")
|
||||
val headers = List(("Content-Type", "application/json; charset=utf-8"))
|
||||
val liftResponse = InMemoryResponse(data, headers, Nil, 200)
|
||||
|
||||
When("Response is converted to HTTP4S")
|
||||
val http4sResponse = liftResponseToHttp4sForTest(liftResponse)
|
||||
|
||||
Then("Status code should be preserved")
|
||||
http4sResponse.status.code should equal(200)
|
||||
|
||||
And("Headers should be preserved")
|
||||
val contentType = http4sResponse.headers.get(CIString("Content-Type"))
|
||||
contentType should not be empty
|
||||
contentType.get.head.value should include("application/json")
|
||||
|
||||
And("Body should be preserved")
|
||||
val bodyBytes = http4sResponse.body.compile.to(Array).unsafeRunSync()
|
||||
new String(bodyBytes, "UTF-8") should equal(jsonData)
|
||||
}
|
||||
|
||||
scenario("Convert InMemoryResponse with empty body") {
|
||||
Given("A Lift InMemoryResponse with empty body")
|
||||
val liftResponse = InMemoryResponse(Array.emptyByteArray, Nil, Nil, 204)
|
||||
|
||||
When("Response is converted to HTTP4S")
|
||||
val http4sResponse = liftResponseToHttp4sForTest(liftResponse)
|
||||
|
||||
Then("Status code should be 204 No Content")
|
||||
http4sResponse.status.code should equal(204)
|
||||
|
||||
And("Body should be empty")
|
||||
val bodyBytes = http4sResponse.body.compile.to(Array).unsafeRunSync()
|
||||
bodyBytes.length should equal(0)
|
||||
}
|
||||
|
||||
scenario("Convert InMemoryResponse with multiple headers") {
|
||||
Given("A Lift InMemoryResponse with multiple headers")
|
||||
val data = "test".getBytes("UTF-8")
|
||||
val headers = List(
|
||||
("Content-Type", "application/json"),
|
||||
("X-Custom-Header", "custom-value"),
|
||||
("X-Request-Id", "12345"),
|
||||
("Cache-Control", "no-cache")
|
||||
)
|
||||
val liftResponse = InMemoryResponse(data, headers, Nil, 200)
|
||||
|
||||
When("Response is converted to HTTP4S")
|
||||
val http4sResponse = liftResponseToHttp4sForTest(liftResponse)
|
||||
|
||||
Then("All headers should be preserved")
|
||||
http4sResponse.headers.get(CIString("Content-Type")) should not be empty
|
||||
http4sResponse.headers.get(CIString("X-Custom-Header")).get.head.value should equal("custom-value")
|
||||
http4sResponse.headers.get(CIString("X-Request-Id")).get.head.value should equal("12345")
|
||||
http4sResponse.headers.get(CIString("Cache-Control")).get.head.value should equal("no-cache")
|
||||
}
|
||||
|
||||
scenario("Convert InMemoryResponse with UTF-8 characters") {
|
||||
Given("A Lift InMemoryResponse with UTF-8 data")
|
||||
val utf8Data = """{"name":"Bänk Tëst","currency":"€"}"""
|
||||
val data = utf8Data.getBytes("UTF-8")
|
||||
val headers = List(("Content-Type", "application/json; charset=utf-8"))
|
||||
val liftResponse = InMemoryResponse(data, headers, Nil, 200)
|
||||
|
||||
When("Response is converted to HTTP4S")
|
||||
val http4sResponse = liftResponseToHttp4sForTest(liftResponse)
|
||||
|
||||
Then("UTF-8 characters should be preserved")
|
||||
val bodyBytes = http4sResponse.body.compile.to(Array).unsafeRunSync()
|
||||
new String(bodyBytes, "UTF-8") should equal(utf8Data)
|
||||
}
|
||||
|
||||
scenario("Convert InMemoryResponse with large payload") {
|
||||
Given("A Lift InMemoryResponse with large payload (>1MB)")
|
||||
val largeData = ("x" * (1024 * 1024 + 100)).getBytes("UTF-8") // 1MB + 100 bytes
|
||||
val liftResponse = InMemoryResponse(largeData, Nil, Nil, 200)
|
||||
|
||||
When("Response is converted to HTTP4S")
|
||||
val http4sResponse = liftResponseToHttp4sForTest(liftResponse)
|
||||
|
||||
Then("Large payload should be preserved")
|
||||
val bodyBytes = http4sResponse.body.compile.to(Array).unsafeRunSync()
|
||||
bodyBytes.length should equal(largeData.length)
|
||||
}
|
||||
|
||||
scenario("Convert InMemoryResponse with error status codes") {
|
||||
Given("Lift InMemoryResponses with various error status codes")
|
||||
val errorCodes = List(400, 401, 403, 404, 500, 502, 503)
|
||||
|
||||
errorCodes.foreach { code =>
|
||||
val errorData = s"""{"code":$code,"message":"Error message"}""".getBytes("UTF-8")
|
||||
val liftResponse = InMemoryResponse(errorData, Nil, Nil, code)
|
||||
|
||||
When(s"Response with status $code is converted")
|
||||
val http4sResponse = liftResponseToHttp4sForTest(liftResponse)
|
||||
|
||||
Then(s"Status code $code should be preserved")
|
||||
http4sResponse.status.code should equal(code)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
feature("Lift to HTTP4S response conversion - StreamingResponse") {
|
||||
scenario("Convert StreamingResponse with callback") {
|
||||
Given("A Lift StreamingResponse with data and callback")
|
||||
val testData = "streaming test data"
|
||||
val inputStream = new ByteArrayInputStream(testData.getBytes("UTF-8"))
|
||||
val callbackInvoked = new AtomicBoolean(false)
|
||||
val onEnd = () => callbackInvoked.set(true)
|
||||
val headers = List(("Content-Type", "text/plain"))
|
||||
val liftResponse = StreamingResponse(inputStream, onEnd, -1, headers, Nil, 200)
|
||||
|
||||
When("Response is converted to HTTP4S")
|
||||
val http4sResponse = liftResponseToHttp4sForTest(liftResponse)
|
||||
|
||||
Then("Status code should be preserved")
|
||||
http4sResponse.status.code should equal(200)
|
||||
|
||||
And("Headers should be preserved")
|
||||
http4sResponse.headers.get(CIString("Content-Type")).get.head.value should include("text/plain")
|
||||
|
||||
And("Body should be preserved")
|
||||
val bodyBytes = http4sResponse.body.compile.to(Array).unsafeRunSync()
|
||||
new String(bodyBytes, "UTF-8") should equal(testData)
|
||||
|
||||
And("Callback should be invoked")
|
||||
callbackInvoked.get() should be(true)
|
||||
}
|
||||
|
||||
scenario("Convert StreamingResponse with empty stream") {
|
||||
Given("A Lift StreamingResponse with empty stream")
|
||||
val emptyStream = new ByteArrayInputStream(Array.emptyByteArray)
|
||||
val callbackInvoked = new AtomicBoolean(false)
|
||||
val onEnd = () => callbackInvoked.set(true)
|
||||
val liftResponse = StreamingResponse(emptyStream, onEnd, 0, Nil, Nil, 200)
|
||||
|
||||
When("Response is converted to HTTP4S")
|
||||
val http4sResponse = liftResponseToHttp4sForTest(liftResponse)
|
||||
|
||||
Then("Body should be empty")
|
||||
val bodyBytes = http4sResponse.body.compile.to(Array).unsafeRunSync()
|
||||
bodyBytes.length should equal(0)
|
||||
|
||||
And("Callback should still be invoked")
|
||||
callbackInvoked.get() should be(true)
|
||||
}
|
||||
|
||||
scenario("Convert StreamingResponse with large stream") {
|
||||
Given("A Lift StreamingResponse with large stream (>1MB)")
|
||||
val largeData = "x" * (1024 * 1024 + 100) // 1MB + 100 bytes
|
||||
val inputStream = new ByteArrayInputStream(largeData.getBytes("UTF-8"))
|
||||
val callbackInvoked = new AtomicBoolean(false)
|
||||
val onEnd = () => callbackInvoked.set(true)
|
||||
val liftResponse = StreamingResponse(inputStream, onEnd, -1, Nil, Nil, 200)
|
||||
|
||||
When("Response is converted to HTTP4S")
|
||||
val http4sResponse = liftResponseToHttp4sForTest(liftResponse)
|
||||
|
||||
Then("Large stream should be preserved")
|
||||
val bodyBytes = http4sResponse.body.compile.to(Array).unsafeRunSync()
|
||||
bodyBytes.length should equal(largeData.length)
|
||||
|
||||
And("Callback should be invoked")
|
||||
callbackInvoked.get() should be(true)
|
||||
}
|
||||
|
||||
scenario("Convert StreamingResponse ensures callback invocation on error") {
|
||||
Given("A Lift StreamingResponse with failing stream")
|
||||
val failingStream = new InputStream {
|
||||
override def read(): Int = throw new RuntimeException("Stream read error")
|
||||
}
|
||||
val callbackInvoked = new AtomicBoolean(false)
|
||||
val onEnd = () => callbackInvoked.set(true)
|
||||
val liftResponse = StreamingResponse(failingStream, onEnd, -1, Nil, Nil, 200)
|
||||
|
||||
When("Response conversion is attempted")
|
||||
val result = try {
|
||||
liftResponseToHttp4sForTest(liftResponse)
|
||||
"no-error"
|
||||
} catch {
|
||||
case _: RuntimeException => "error-caught"
|
||||
}
|
||||
|
||||
Then("Error should be caught")
|
||||
result should equal("error-caught")
|
||||
|
||||
And("Callback should still be invoked (finally block)")
|
||||
callbackInvoked.get() should be(true)
|
||||
}
|
||||
}
|
||||
|
||||
feature("Lift to HTTP4S response conversion - OutputStreamResponse") {
|
||||
scenario("Convert OutputStreamResponse with simple output") {
|
||||
Given("A Lift OutputStreamResponse")
|
||||
val testData = "output stream test data"
|
||||
val out: OutputStream => Unit = (os: OutputStream) => {
|
||||
os.write(testData.getBytes("UTF-8"))
|
||||
os.flush()
|
||||
}
|
||||
val headers = List(("Content-Type", "text/plain"))
|
||||
val liftResponse = OutputStreamResponse(out, -1, headers, Nil, 200)
|
||||
|
||||
When("Response is converted to HTTP4S")
|
||||
val http4sResponse = liftResponseToHttp4sForTest(liftResponse)
|
||||
|
||||
Then("Status code should be preserved")
|
||||
http4sResponse.status.code should equal(200)
|
||||
|
||||
And("Headers should be preserved")
|
||||
http4sResponse.headers.get(CIString("Content-Type")).get.head.value should include("text/plain")
|
||||
|
||||
And("Body should be preserved")
|
||||
val bodyBytes = http4sResponse.body.compile.to(Array).unsafeRunSync()
|
||||
new String(bodyBytes, "UTF-8") should equal(testData)
|
||||
}
|
||||
|
||||
scenario("Convert OutputStreamResponse with JSON output") {
|
||||
Given("A Lift OutputStreamResponse with JSON data")
|
||||
val jsonData = """{"status":"success","data":{"id":123,"name":"Test"}}"""
|
||||
val out: OutputStream => Unit = (os: OutputStream) => {
|
||||
os.write(jsonData.getBytes("UTF-8"))
|
||||
os.flush()
|
||||
}
|
||||
val headers = List(("Content-Type", "application/json"))
|
||||
val liftResponse = OutputStreamResponse(out, -1, headers, Nil, 200)
|
||||
|
||||
When("Response is converted to HTTP4S")
|
||||
val http4sResponse = liftResponseToHttp4sForTest(liftResponse)
|
||||
|
||||
Then("JSON body should be preserved")
|
||||
val bodyBytes = http4sResponse.body.compile.to(Array).unsafeRunSync()
|
||||
new String(bodyBytes, "UTF-8") should equal(jsonData)
|
||||
}
|
||||
|
||||
scenario("Convert OutputStreamResponse with empty output") {
|
||||
Given("A Lift OutputStreamResponse with no output")
|
||||
val out: OutputStream => Unit = (os: OutputStream) => {
|
||||
os.flush()
|
||||
}
|
||||
val liftResponse = OutputStreamResponse(out, 0, Nil, Nil, 204)
|
||||
|
||||
When("Response is converted to HTTP4S")
|
||||
val http4sResponse = liftResponseToHttp4sForTest(liftResponse)
|
||||
|
||||
Then("Status code should be 204")
|
||||
http4sResponse.status.code should equal(204)
|
||||
|
||||
And("Body should be empty")
|
||||
val bodyBytes = http4sResponse.body.compile.to(Array).unsafeRunSync()
|
||||
bodyBytes.length should equal(0)
|
||||
}
|
||||
|
||||
scenario("Convert OutputStreamResponse with large output") {
|
||||
Given("A Lift OutputStreamResponse with large output (>1MB)")
|
||||
val largeData = "x" * (1024 * 1024 + 100) // 1MB + 100 bytes
|
||||
val out: OutputStream => Unit = (os: OutputStream) => {
|
||||
os.write(largeData.getBytes("UTF-8"))
|
||||
os.flush()
|
||||
}
|
||||
val liftResponse = OutputStreamResponse(out, -1, Nil, Nil, 200)
|
||||
|
||||
When("Response is converted to HTTP4S")
|
||||
val http4sResponse = liftResponseToHttp4sForTest(liftResponse)
|
||||
|
||||
Then("Large output should be preserved")
|
||||
val bodyBytes = http4sResponse.body.compile.to(Array).unsafeRunSync()
|
||||
bodyBytes.length should equal(largeData.length)
|
||||
}
|
||||
|
||||
scenario("Convert OutputStreamResponse with UTF-8 output") {
|
||||
Given("A Lift OutputStreamResponse with UTF-8 data")
|
||||
val utf8Data = """{"name":"Tëst Bänk","symbol":"€£¥"}"""
|
||||
val out: OutputStream => Unit = (os: OutputStream) => {
|
||||
os.write(utf8Data.getBytes("UTF-8"))
|
||||
os.flush()
|
||||
}
|
||||
val headers = List(("Content-Type", "application/json; charset=utf-8"))
|
||||
val liftResponse = OutputStreamResponse(out, -1, headers, Nil, 200)
|
||||
|
||||
When("Response is converted to HTTP4S")
|
||||
val http4sResponse = liftResponseToHttp4sForTest(liftResponse)
|
||||
|
||||
Then("UTF-8 characters should be preserved")
|
||||
val bodyBytes = http4sResponse.body.compile.to(Array).unsafeRunSync()
|
||||
new String(bodyBytes, "UTF-8") should equal(utf8Data)
|
||||
}
|
||||
}
|
||||
|
||||
feature("Lift to HTTP4S response conversion - BasicResponse") {
|
||||
scenario("Convert BasicResponse with no body") {
|
||||
Given("A Lift BasicResponse with no body")
|
||||
val headers = List(("X-Custom-Header", "test-value"))
|
||||
val liftResponse = new BasicResponse {
|
||||
override def code: Int = 204
|
||||
override def headers: List[(String, String)] = headers
|
||||
override def cookies: List[net.liftweb.http.provider.HTTPCookie] = Nil
|
||||
override def reason: String = "No Content"
|
||||
}
|
||||
|
||||
When("Response is converted to HTTP4S")
|
||||
val http4sResponse = liftResponseToHttp4sForTest(liftResponse)
|
||||
|
||||
Then("Status code should be preserved")
|
||||
http4sResponse.status.code should equal(204)
|
||||
|
||||
And("Headers should be preserved")
|
||||
http4sResponse.headers.get(CIString("X-Custom-Header")).get.head.value should equal("test-value")
|
||||
|
||||
And("Body should be empty")
|
||||
val bodyBytes = http4sResponse.body.compile.to(Array).unsafeRunSync()
|
||||
bodyBytes.length should equal(0)
|
||||
}
|
||||
|
||||
scenario("Convert BasicResponse with various status codes") {
|
||||
Given("BasicResponses with various status codes")
|
||||
val statusCodes = List(200, 201, 204, 301, 302, 400, 401, 403, 404, 500)
|
||||
|
||||
statusCodes.foreach { code =>
|
||||
val liftResponse = new BasicResponse {
|
||||
override def code: Int = code
|
||||
override def headers: List[(String, String)] = Nil
|
||||
override def cookies: List[net.liftweb.http.provider.HTTPCookie] = Nil
|
||||
override def reason: String = s"Status $code"
|
||||
}
|
||||
|
||||
When(s"BasicResponse with status $code is converted")
|
||||
val http4sResponse = liftResponseToHttp4sForTest(liftResponse)
|
||||
|
||||
Then(s"Status code $code should be preserved")
|
||||
http4sResponse.status.code should equal(code)
|
||||
}
|
||||
}
|
||||
|
||||
scenario("Convert BasicResponse with multiple headers") {
|
||||
Given("A Lift BasicResponse with multiple headers")
|
||||
val headers = List(
|
||||
("X-Header-1", "value-1"),
|
||||
("X-Header-2", "value-2"),
|
||||
("X-Header-3", "value-3")
|
||||
)
|
||||
val liftResponse = new BasicResponse {
|
||||
override def code: Int = 200
|
||||
override def headers: List[(String, String)] = headers
|
||||
override def cookies: List[net.liftweb.http.provider.HTTPCookie] = Nil
|
||||
override def reason: String = "OK"
|
||||
}
|
||||
|
||||
When("Response is converted to HTTP4S")
|
||||
val http4sResponse = liftResponseToHttp4sForTest(liftResponse)
|
||||
|
||||
Then("All headers should be preserved")
|
||||
http4sResponse.headers.get(CIString("X-Header-1")).get.head.value should equal("value-1")
|
||||
http4sResponse.headers.get(CIString("X-Header-2")).get.head.value should equal("value-2")
|
||||
http4sResponse.headers.get(CIString("X-Header-3")).get.head.value should equal("value-3")
|
||||
}
|
||||
}
|
||||
|
||||
feature("Lift to HTTP4S response conversion - Content-Type handling") {
|
||||
scenario("Add default Content-Type when missing") {
|
||||
Given("A Lift InMemoryResponse without Content-Type header")
|
||||
val data = """{"status":"success"}""".getBytes("UTF-8")
|
||||
val liftResponse = InMemoryResponse(data, Nil, Nil, 200)
|
||||
|
||||
When("Response is converted to HTTP4S")
|
||||
val http4sResponse = liftResponseToHttp4sForTest(liftResponse)
|
||||
|
||||
Then("Default Content-Type should be added")
|
||||
val contentType = http4sResponse.headers.get(CIString("Content-Type"))
|
||||
contentType should not be empty
|
||||
contentType.get.head.value should include("application/json")
|
||||
}
|
||||
|
||||
scenario("Preserve existing Content-Type header") {
|
||||
Given("A Lift InMemoryResponse with Content-Type header")
|
||||
val data = "plain text".getBytes("UTF-8")
|
||||
val headers = List(("Content-Type", "text/plain; charset=utf-8"))
|
||||
val liftResponse = InMemoryResponse(data, headers, Nil, 200)
|
||||
|
||||
When("Response is converted to HTTP4S")
|
||||
val http4sResponse = liftResponseToHttp4sForTest(liftResponse)
|
||||
|
||||
Then("Existing Content-Type should be preserved")
|
||||
val contentType = http4sResponse.headers.get(CIString("Content-Type"))
|
||||
contentType should not be empty
|
||||
contentType.get.head.value should equal("text/plain; charset=utf-8")
|
||||
}
|
||||
|
||||
scenario("Handle various Content-Type formats") {
|
||||
Given("Lift responses with various Content-Type formats")
|
||||
val contentTypes = List(
|
||||
"application/json",
|
||||
"application/json; charset=utf-8",
|
||||
"text/html",
|
||||
"text/plain; charset=iso-8859-1",
|
||||
"application/xml",
|
||||
"application/octet-stream"
|
||||
)
|
||||
|
||||
contentTypes.foreach { ct =>
|
||||
val data = "test".getBytes("UTF-8")
|
||||
val headers = List(("Content-Type", ct))
|
||||
val liftResponse = InMemoryResponse(data, headers, Nil, 200)
|
||||
|
||||
When(s"Response with Content-Type '$ct' is converted")
|
||||
val http4sResponse = liftResponseToHttp4sForTest(liftResponse)
|
||||
|
||||
Then("Content-Type should be preserved")
|
||||
val contentType = http4sResponse.headers.get(CIString("Content-Type"))
|
||||
contentType should not be empty
|
||||
contentType.get.head.value should equal(ct)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
feature("Lift to HTTP4S response conversion - Error responses") {
|
||||
scenario("Convert error response with JSON body") {
|
||||
Given("A Lift error response with JSON error message")
|
||||
val errorJson = """{"code":400,"message":"Invalid request"}"""
|
||||
val data = errorJson.getBytes("UTF-8")
|
||||
val headers = List(("Content-Type", "application/json"))
|
||||
val liftResponse = InMemoryResponse(data, headers, Nil, 400)
|
||||
|
||||
When("Response is converted to HTTP4S")
|
||||
val http4sResponse = liftResponseToHttp4sForTest(liftResponse)
|
||||
|
||||
Then("Error status code should be preserved")
|
||||
http4sResponse.status.code should equal(400)
|
||||
|
||||
And("Error body should be preserved")
|
||||
val bodyBytes = http4sResponse.body.compile.to(Array).unsafeRunSync()
|
||||
new String(bodyBytes, "UTF-8") should equal(errorJson)
|
||||
}
|
||||
|
||||
scenario("Convert 404 Not Found response") {
|
||||
Given("A Lift 404 response")
|
||||
val errorJson = """{"code":404,"message":"Resource not found"}"""
|
||||
val data = errorJson.getBytes("UTF-8")
|
||||
val liftResponse = InMemoryResponse(data, Nil, Nil, 404)
|
||||
|
||||
When("Response is converted to HTTP4S")
|
||||
val http4sResponse = liftResponseToHttp4sForTest(liftResponse)
|
||||
|
||||
Then("404 status should be preserved")
|
||||
http4sResponse.status.code should equal(404)
|
||||
}
|
||||
|
||||
scenario("Convert 500 Internal Server Error response") {
|
||||
Given("A Lift 500 response")
|
||||
val errorJson = """{"code":500,"message":"Internal server error"}"""
|
||||
val data = errorJson.getBytes("UTF-8")
|
||||
val liftResponse = InMemoryResponse(data, Nil, Nil, 500)
|
||||
|
||||
When("Response is converted to HTTP4S")
|
||||
val http4sResponse = liftResponseToHttp4sForTest(liftResponse)
|
||||
|
||||
Then("500 status should be preserved")
|
||||
http4sResponse.status.code should equal(500)
|
||||
}
|
||||
|
||||
scenario("Convert 401 Unauthorized response") {
|
||||
Given("A Lift 401 response")
|
||||
val errorJson = """{"code":401,"message":"Authentication required"}"""
|
||||
val data = errorJson.getBytes("UTF-8")
|
||||
val headers = List(("WWW-Authenticate", "DirectLogin"))
|
||||
val liftResponse = InMemoryResponse(data, headers, Nil, 401)
|
||||
|
||||
When("Response is converted to HTTP4S")
|
||||
val http4sResponse = liftResponseToHttp4sForTest(liftResponse)
|
||||
|
||||
Then("401 status should be preserved")
|
||||
http4sResponse.status.code should equal(401)
|
||||
|
||||
And("WWW-Authenticate header should be preserved")
|
||||
http4sResponse.headers.get(CIString("WWW-Authenticate")).get.head.value should equal("DirectLogin")
|
||||
}
|
||||
}
|
||||
|
||||
feature("Lift to HTTP4S response conversion - Edge cases") {
|
||||
scenario("Handle response with special characters in headers") {
|
||||
Given("A Lift response with special characters in header values")
|
||||
val headers = List(
|
||||
("X-Special", "value with spaces, commas, and \"quotes\""),
|
||||
("X-Unicode", "Tëst Hëädër Välüë")
|
||||
)
|
||||
val liftResponse = InMemoryResponse(Array.emptyByteArray, headers, Nil, 200)
|
||||
|
||||
When("Response is converted to HTTP4S")
|
||||
val http4sResponse = liftResponseToHttp4sForTest(liftResponse)
|
||||
|
||||
Then("Special characters in headers should be preserved")
|
||||
http4sResponse.headers.get(CIString("X-Special")).get.head.value should equal("value with spaces, commas, and \"quotes\"")
|
||||
http4sResponse.headers.get(CIString("X-Unicode")).get.head.value should equal("Tëst Hëädër Välüë")
|
||||
}
|
||||
|
||||
scenario("Handle response with many headers") {
|
||||
Given("A Lift response with many headers")
|
||||
val headers = (1 to 50).map(i => (s"X-Header-$i", s"value-$i")).toList
|
||||
val liftResponse = InMemoryResponse(Array.emptyByteArray, headers, Nil, 200)
|
||||
|
||||
When("Response is converted to HTTP4S")
|
||||
val http4sResponse = liftResponseToHttp4sForTest(liftResponse)
|
||||
|
||||
Then("All headers should be preserved")
|
||||
(1 to 50).foreach { i =>
|
||||
http4sResponse.headers.get(CIString(s"X-Header-$i")).get.head.value should equal(s"value-$i")
|
||||
}
|
||||
}
|
||||
|
||||
scenario("Handle response with binary data") {
|
||||
Given("A Lift response with binary data")
|
||||
val binaryData = (0 to 255).map(_.toByte).toArray
|
||||
val headers = List(("Content-Type", "application/octet-stream"))
|
||||
val liftResponse = InMemoryResponse(binaryData, headers, Nil, 200)
|
||||
|
||||
When("Response is converted to HTTP4S")
|
||||
val http4sResponse = liftResponseToHttp4sForTest(liftResponse)
|
||||
|
||||
Then("Binary data should be preserved")
|
||||
val bodyBytes = http4sResponse.body.compile.to(Array).unsafeRunSync()
|
||||
bodyBytes should equal(binaryData)
|
||||
}
|
||||
|
||||
scenario("Handle response with invalid status code") {
|
||||
Given("A Lift response with unusual status code")
|
||||
val liftResponse = InMemoryResponse(Array.emptyByteArray, Nil, Nil, 999)
|
||||
|
||||
When("Response is converted to HTTP4S")
|
||||
val http4sResponse = liftResponseToHttp4sForTest(liftResponse)
|
||||
|
||||
Then("Status code should be handled gracefully")
|
||||
// HTTP4S will either accept it or convert to 500
|
||||
http4sResponse.status.code should (equal(999) or equal(500))
|
||||
}
|
||||
}
|
||||
|
||||
// Helper method to access private liftResponseToHttp4s method for testing
|
||||
private def liftResponseToHttp4sForTest(response: LiftResponse): Response[IO] = {
|
||||
// Use reflection to access private method
|
||||
val method = Http4sLiftWebBridge.getClass.getDeclaredMethod(
|
||||
"liftResponseToHttp4s",
|
||||
classOf[LiftResponse]
|
||||
)
|
||||
method.setAccessible(true)
|
||||
method.invoke(Http4sLiftWebBridge, response).asInstanceOf[IO[Response[IO]]].unsafeRunSync()
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user