Spray MultipartFormData Spec - scala

I have a spray endpoint which accepts a MultipartFormData like this:
trait ApiRouting extends Routing with ResultService with FormDataUnmarshallers {
override def route: Route =
path("test") {
post {
entity(as[MultipartFormData]) { formData =>
complete(handleRequest(formData))
}
}
}
}
This works fine when I post via postman. However, I am trying to write a spec that tests this endpoint and get this error:
java.lang.ClassCastException: spray.http.HttpEntity$Empty$ cannot be
cast to spray.http.HttpEntity$NonEmpty
This is what I have:
trait Context extends Scope with ApiRouting {}
"check post request" should {
"return response data for post request" in new Context {
val file = new File("test")
val httpEntity = HttpEntity(MediaTypes.`multipart/form-data`, HttpData(file)).asInstanceOf[HttpEntity.NonEmpty]
val formFile = FormFile("file", httpEntity)
val mfd = MultipartFormData(Seq(BodyPart(formFile, "file")))
Post("/test", mfd) ~> route ~> check {
status must_== StatusCodes.OK
contentType must_== `multipart/form-data`
}
}
}
Any ideas on how to test a spray multipart form data?

It's happening because your are passing zero-length file into HttpData. Try to refer to a real file.
Also, you can pass your File directly into BodyPart. It will looks like:
Post(Uri("/test"),
MultipartFormData(
Seq(BodyPart(file, "file", ContentType(MediaTypes.`application/xml`)))
)
)

Related

How to check the request body parameters type validation in akka http micro-services?

my Object is
case class Request(id:Int,name:String,phone:String)
my request in postman is
{
"id": "1205", **here i have changed the request body parameter type Int to String**
"name": "sekhar",
"phone":"1234567890"
}
how can I check the request parameter is valid or invalid when my request body field is the wrong data type
I have used
implicit def myRejectionHandler = RejectionHandler.newBuilder()
.handle {
case MissingQueryParamRejection(param) =>
println(" Test1 ")
val errorResponse = ErrorResponse(BadRequest.intValue, "Missing Parameter", s"The required $param was not found.")
var json:JsValue=Json.toJson(errorResponse)
complete(HttpResponse(BadRequest, entity = HttpEntity(ContentTypes.`application/json`, json.toString())))
}
.handle { case MissingFormFieldRejection(msg) =>
println(" Test2 ")
complete(BadRequest, msg)
}
.handle { case MalformedQueryParamRejection(msg,error,cause) =>
println(" Test3 ")
complete(BadRequest, msg)
}
.handleAll[MethodRejection] { methodRejections =>
val names = methodRejections.map(_.supported.name)
println(" Test4 ")
complete((MethodNotAllowed, s"Can't do that! Supported: ${names mkString " or "}!"))
}
.handleNotFound { complete((NotFound, "Not here!")) }
.result()
val routes: Route = handleRejections(myRejectionHandler) {
//Routes
}
Http().bindAndHandle(routes, "localhost", 8090)
it's, again and again, takes only handleAll[MethodRejection] when being changed the query params(for the false parameter too) on that time too.
If you are using Spray Json, then you might have created a format for your case class, it should look like this:
Assuming:
case class Request(id:Int, name:String, phone:String)
You should have a trait like:
import spray.json._
trait RequestJsonSupport extends DefaultJsonProtocol {
implicit val requestFormat = jsonFormat3(Request.apply)
}
And then extend it on your route class:
class MyRouteClass(...) extends RequestJsonSupport {...}
That way your Akka Http instance knows how to parse a Json input and convert it into your case class. Then you can worry about missing fields and such. Spray will take care of this.
For example, if you sent this:
{
"id": "1205",
"name": "sekhar",
"phone":"1234567890"
}
Spray will throw an:
The request content was malformed:
Expected Int as JsNumber, but got "1205"
Check out Spray Json repo here.

Akka-http logrequest not logging the request body

I am using akka-http and trying to log a request on a specific path using logrequest :
path(Segment / "account") { id =>
logRequest("users/account", Logging.InfoLevel) {
post {
entity(as[Account]) { account => ???
complete(HttpResponse(StatusCodes.NoContent))
}
}
}
however on my log I see something like
HttpRequest(HttpMethod(POST),https://localhost:9009/api/users/123/account,List(Host: localhost:9009, User-Agent: akka-http/10.0.6, Timeout-Access: <function1>),HttpEntity.Chunked(application/json),HttpProtocol(HTTP/1.1))
what I am looking for is the exact request including the body (json) as it was sent by the requestor.
The "HttpEntity.Chunked(application/json)" segment of the log is the output of HttpEntity.Chunked#toString. To get the entire request body, which is implemented as a stream, you need to call HttpEntity#toStrict to convert the Chunked request entity into a Strict request entity. You can make this call in a custom route:
def logRequestEntity(route: Route, level: LogLevel)
(implicit m: Materializer, ex: ExecutionContext) = {
def requestEntityLoggingFunction(loggingAdapter: LoggingAdapter)(req: HttpRequest): Unit = {
val timeout = 900.millis
val bodyAsBytes: Future[ByteString] = req.entity.toStrict(timeout).map(_.data)
val bodyAsString: Future[String] = bodyAsBytes.map(_.utf8String)
bodyAsString.onComplete {
case Success(body) =>
val logMsg = s"$req\nRequest body: $body"
loggingAdapter.log(level, logMsg)
case Failure(t) =>
val logMsg = s"Failed to get the body for: $req"
loggingAdapter.error(t, logMsg)
}
}
DebuggingDirectives.logRequest(LoggingMagnet(requestEntityLoggingFunction(_)))(route)
}
To use the above, pass your route to it:
val loggedRoute = logRequestEntity(route, Logging.InfoLevel)

spray.io debugging directives - converting rejection to StatusCodes

I am using logRequestResponse debugging directive in order to log every request/response failing through whole path tree. Log entries looks as follows:
2015-07-29 14:03:13,643 [INFO ] [DataImportServices-akka.actor.default-dispatcher-6] [akka.actor.ActorSystemImpl]ActorSystem(DataImportServices) - get-userr: Response for
Request : HttpRequest(POST,https://localhost:8080/city/v1/transaction/1234,List(Accept-Language: cs, Accept-Encoding: gzip,...
Response: Rejected(List(MalformedRequestContentRejection(Protocol message tag had invalid wire type.,...
My root route trait which assembles all partial routes to one look as follows:
trait RestAPI extends Directives {
this: ServiceActors with Core =>
private implicit val _ = system.dispatcher
val route: Route =
logRequestResponse("log-activity", Logging.InfoLevel) {
new CountryImportServiceApi().route ~
new CityImportServiceApi().route
}
}
And partial routes are defined as following:
class CinemaImportServiceApi()(implicit executionContext: ExecutionContext) extends Directives {
implicit val timeout = Timeout(15 seconds)
val route: Route = {
pathPrefix("city") {
pathPrefix("v1") {
path("transaction" / Segment ) {
(siteId: String, transactionId: String) =>
post {
authenticate(BasicAuth(cityUserPasswordAuthenticator _, realm = "bd city import api")) {
user =>
entity(as[CityTrans]) { e =>
complete {
StatusCodes.OK
}
}
}
}
}
}
}
}
Assembled routes are run via HttpServiceActor runRoute.
I would like to convert rejection to StatusCode and log that via logRequestResponse. Even though I write a custom function for logging I get rejection. What seems to me fishy is that since it is wrapped the whole route tree rejection is still not converted to HttpResponse. In tests we are sealing the route in order to convert Rejection to HttpResponse. Is there a way how to mark a route as a complete route hence actually seal it? Or am I missing some important concept here?
Thx
I would add something like the following:
} ~ // This is the closing brace of the pathPrefix("city"), just add the ~
pathPrefix("") {
complete {
(StatusCodes.OK, "Invalid Route")
}
}
of course, change the OK and the body to whatever you need. pathPrefix("") will match any path that was rejected by any previous routes.

Test REST API Controller in scala and play framework 2.1

Play Framework v 2.1 with scala, I am trying to test my ProductController with invalid call (missing parameters), I am suppose to get BadRequest response..
Controller code is return Ok(Json.toJson(some class..)) or BadRequest(Json.toJson(some class..)) incase something went wrong.
I Defined the test class:
class ProductApplicationTests extends PlaySpecification
with Mockito
with ProductServiceComponent
{
lazy val productService = mock[ProductService]
... other things
def app = FakeApplication(
withoutPlugins = Seq("com.typesafe.plugin.RedisPlugin"),
withGlobal = Some(
new GlobalSettings {
override def getControllerInstance[A](clazz: Class[A]) = clazz match {
case c if c.isAssignableFrom(classOf[ProductController]) => new ProductController(ProductApplicationTests.this).asInstanceOf[A]
case _ => super.getControllerInstance(clazz)
}
}
)
)
def mockStuff = {
productService.addProduct(any[AddProductRequest]) returns
DTOResponse(product.id.get)
productService.updateProduct(any[UpdateProductRequest]) returns
DTOResponse(product.id.get)
productService.deleteProduct(any[DeleteProductRequest]) returns
DTOResponse(product.id.get)
}
step(mockStuff)
"Application" should {
"Get product with no tenant Id" in {running(FakeApplication()) {
val req = FakeRequest(method = "POST", uri = routes.ProductController.getProducts("en_US", "1", "1").url,
headers = FakeHeaders(
Seq("Content-type"->Seq("application/json"))
),
body = None
)
val Some(result) = route(req)
status(result) must equalTo(400)
Problem:
I get error:
Cannot write an instance of None.type to HTTP response. Try to define a Writeable[None.type]
I have followed this post: Play 2 - Scala FakeRequest withJsonBody And i dont understand what im doing wrong..
When i send this code as test it works..
"Get product with valid parameters" in new WithApplication(app) {
val result = route(FakeRequest(GET, "/v1.0/products?lang=en_US&t=1&ids=1,2"))
result must beSome
status(result.get) must equalTo(OK)
contentType(result.get) must beSome.which(_ == "application/json")
contentAsString(result.get) must contain("products")
contentAsString(result.get) must contain("notFoundIds")
}
Thanks for any comment/answers..
By the way my global.scala looks like:
override def onBadRequest(request: RequestHeader, error: String) = {
var errorResponse:ErrorResponse[String] = ErrorResponse(ErrorCode.GeneralError, "Error processing request", 500)
errorResponse.addMessage(error)
Future.successful(BadRequest(Json.toJson(errorResponse)))
}
override def onError(request: RequestHeader, ex: Throwable) =
{
var errorResponse:ErrorResponse[String] = ErrorResponse(ErrorCode.GeneralError, "Error processing request", 500)
errorResponse.addMessage(ex.getMessage)
Future.successful(BadRequest(Json.toJson(errorResponse)))
}
And if i run Get in RestAPI test client i get:
{"code":100,"message":"Error processing request - Missing parameter: t"}
If you take a look at the source for FakeRequest, you'll notice that you need to set the body to AnyContentAsEmpty. On a sidenote, should that first test method be a POST? Your other examples seem to be GETs.
As for your second issue with running Get in RestAPI tet client, the error message seems pretty clear, you need to provide the parameter t as you did in the previous example val result = route(FakeRequest(GET, "/v1.0/products?lang=en_US&t=1&ids=1,2"))

How would you implement String to json object unmarshaller for parameter of url-encoded POST?

How would you implement String to json object unmarshaller for parameter of url-encoded POST ? I'm using version 1.2.
Here is what I want. Foursquare pushes url-encoded POST to my service. My route looks like this
path("handle_4sq_push") {
formFields("checkin".as[FsqCheckin], "user".as[String], "secret".as[String]) {
(checkin, user, secret) =>
complete {
StatusCodes.OK
}
}
}
I have json parser for FsqCheckin which is defined like this
implicit val fsqCheckinFormat = jsonFormat(FsqCheckin.apply, "id", "createdAt", "timeZoneOffset", "user", "venue")
So it's all good but it works only if parameters are form-encoded. Otherwise Spray says
There was a problem with the requests Content-Type:
Field 'checkin' can only be read from 'multipart/form-data' form content
So I thought I'd write unmarshaller. I wrote this
implicit def MyJsonUnmarshaller[T: RootJsonReader] =
Unmarshaller.delegate[String, T](ContentTypes.`*`) {
value => {
val json = JsonParser(value)
json.convertTo[T]
}
}
But if I bring it to scope of my route I get following compile error
too many arguments for method formFields: (fdm: spray.routing.directives.FieldDefMagnet)fdm.Out
formFields("checkin".as[FsqCheckin], "user".as[String], "secret".as[String]) {
^
It's the same error I have if I didn't have json parser for FsqCheckin in the scope.
How can I deal with this problem ?
Shockingly I figured it out myself. Here is working version of universal unmarshaller.
implicit def String2JsonParser[T: RootJsonReader] = new FromStringDeserializer[T] {
def apply(value: String) =
try
Right(JsonParser(value).convertTo[T])
catch {
case ex: Throwable => Left(spray.httpx.unmarshalling.MalformedContent(ex.getMessage))
}
}