PlayFramework FakeRequest returns 400 error - scala

In routes:
POST /login controllers.ApplicationCtrl.login()
In Controller:
def login = Action(parse.json) { implicit request => {
val email = (request.body \ "email").as[String]
val password = (request.body \ "password").as[String]
Ok(Json.toJson(
Map("status" -> "OK",
"message" -> "%s created".format(email))
))
}
In tests
"login" in new WithApplication{
val request = route( FakeRequest(
Helpers.POST,
controllers.routes.ApplicationCtrl.login.url,
FakeHeaders(Seq(CONTENT_TYPE -> Seq("application/json"))),
""" {"email" : "bob#mail.com", "password" : "secret"} """
)).get
status(request) must equalTo(OK)
}
When I test using command line:
curl --header "Content-type: application/json" --request POST --data '{"email" : "bob#mail.com", "password" : "secret"}' http://localhost:9000/login
It gets desirable response.
{"status":"OK","message":"bob#mail.com created"}
But the test returns 400 error.
What's wrong?
(command line test wins by simplicity and understandability)

What's happening here is that Play sets the content type of the request according to the type of the body. You're using a string body so that the content type header you're setting is later overridden by text/plain; charset=utf-8.
Because you're explicitly parsing the body as Json the body parser will return a bad request 403 if the content type is not either text/json or application/json.
The best thing to do in your case is to use a Json body, i.e:
"login" in new WithApplication {
val request = route( FakeRequest(
POST,
controllers.portal.routes.Portal.test.url,
FakeHeaders(Seq.empty),
play.api.libs.json.Json.obj("email" -> "bob#mail.com", "password" -> "secret")
)).get
status(request) must equalTo(OK)
}
Note that you can make that a bit more succinct by letting an alternate FakeRequest constructor infer the method and URL of your action from the call:
val request = route(FakeRequest(controllers.portal.routes.Portal.test)
.withBody(Json.obj("email" -> "bob#mail.com", "password" -> "secret"))).get
Data types you can use as the body parameter and their content type mapping:
JsValue -> application/json
NodeSeq -> text/xml
String -> text/plain
Map[String, Seq[String]] -> application/x-www-form-urlencoded
Array[Byte] -> nothing
There's also the option of using the tolerantJson as a body parser to skip checking the content type completely.

Related

Invalid Json: No content to map due to end-of-input when using play body parser

I'm trying to test a controller method that attempts to parse JSON sent in the request:
def addRoomToProfileForTime = Action.async(parse.json[AddRoomToProfileForTimeRequest]) { request =>
profileService.addRoomToProfileForTime(request.body.roomId, request.body.profileId, request.body.timeRange).value.map {
case Xor.Right(_) => Ok
case Xor.Left(err) => BadRequest(Json.toJson(err))
}
}
This is the case class that represents the request:
final case class AddRoomToProfileForTimeRequest(
roomId: Int,
profileId: Int,
timeRange: TimeRange
)
implicit val addRoomToProfileForTimeRequestFormat:Format[AddRoomToProfileForTimeRequest] = Json.format
This code works as expected with I make a request like so:
curl -H "Content-Type: application/json" -X POST -d '{"roomId":3,"profileId":1,"timeRange":{"id":1,"fromTime":"2000-01-01T01:01","toTime":"2000-01-01T02:01"}}' http://localhost:9000/api/profiles/addRoomToProfileForTime
But I'm trying to write a test for this method (note that I am using macwire for dependency injection, hence cannot use WithApplication:
"add a room to profile for time" in new TestContext {
val roomId = 1
val profileId = 1
val from = "2000-01-01T01:01"
val to = "2000-01-01T02:01"
val requestJson = Json.obj(
"roomId" -> roomId,
"profileId" -> profileId,
"timeRange" -> Json.obj(
"id" -> 1,
"fromTime" -> from,
"toTime" -> to
)
)
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
val fakeReq = FakeRequest(Helpers.POST, "api/profiles/addRoomToProfileForTime")
.withHeaders(CONTENT_TYPE -> "application/json")
.withJsonBody(requestJson)
val result = profileController.addRoomToProfileForTime()(fakeReq).run
val content = contentAsString(result)
println(content)
status(result) must equalTo(OK)
}
However, this test fails with a Bad Request from Play:
<body>
<h1>Bad Request</h1>
<p id="detail">
For request 'POST api/profiles/addRoomToProfileForTime' [Invalid Json: No content to map due to end-of-input at [Source: akka.util.ByteIterator$ByteArrayIterator$$anon$1#37d14073; line: 1, column: 0]]
</p>
</body>
If I parse the JSON with request.body.asJson the method behaves as expected. It's only using the body parser method above that I get this error.
The short answer is: on your FakeRequest in your controller test use the withBody method instead of withJsonBody.
I had this issue as well, and I embarrassingly spent hours on it until I figured it out. The long answer is that FakeRequest's withJsonBody returns a FakeRequest[AnyContentAsJson], and since your controller is expecting a JsValue (not an AnyContentAsJson), when you call apply() on your action it fails to match this apply method, which is the one you want:
def apply(request: Request[A]): Future[Result]
and instead hits this apply method:
def apply(rh: RequestHeader): Accumulator[ByteString, Result]
and thus since you're not then passing any Bytes to the Accumulator, you get the unexpected end-of-input error message you're getting.
Another reason can be not setting Content-Length in Postman application. By mistake I had disabled it, and forgot to enable it. .

Fail to post submittion of form-data

Im trying to perform a POST call in Play that submit a form data with email and password text.
This is what I've tried so far but this does not compiles:
def ws: WSClient
ws.url(railsLoginApi).withHeaders("Content-type" -> "application/json").post(Form("email" -> "xxx", "pass" -> "xxx"))
But I get an error in Form("email" -> "xxx", "pass" -> "xxx") saying:
unspecified value parameters. erro: seq[FormError] value:
Option[NotInferedT]
Does someone knows how to perform this in play using Scala?
As the url is accepting the form data. Content type should be application/x-www-form-urlencoded and body format will be like MyVariableOne=ValueOne&MyVariableTwo=ValueTwo
ws.url(railsLoginApi)
.withHeaders("Content-type" -> "application/x-www-form-urlencoded")
.post[String](Map("email" -> "xxx", "pass" -> "xxx").map { case (k, v) => s"$k=$v"}.mkString("&"))

Playframework Scala Transform Request from Json to x-www-form-urlencoded

Hi I am new to Scala and Playframework. I am getting an ajax request that is json format and I need to make another request to another server with x-www-form-urlencoded format.
I have this code in the controller
def getToken = Action.async(parse.json) { request =>
WS.url("https://api.xxxx.com/v1/yyyy")
.withHeaders(
"accept" -> "application/json",
"content-type" -> "application/x-www-form-urlencoded",
"Authorization" -> "Auth %s".format(apiKey)
).post(request.body) map { response =>
Logger.info("response from get user: " + Json.prettyPrint(response.json))
Ok("ok")
}
}
I tried different ways but I can't get this working. Maybe I should do a formatter in the model. Which would be the best way to cast the request json to a request x-www-form-urlencoded?
thank you
Short Answer
You just need to pass a Map[String, Seq[String]] to the post method.
If you have some keys and values you can easily construct such a Map.
For instance:
WS.url(url).post(Map("key1" -> Seq("value1"), "key2" -> Seq("value2")))
Details
As mentioned in Form Submission section of Play WS docs, if you want to submit a form you have to pass a Map[String, Seq[String]] to the post method.
What you are passing here, instead, is a JsValue (because the request.body is of type JsValue because of the body parser type which is parser.json). So first you have to extract the set of key/value pairs from the json object and then construct a Map[String, Seq[String]] from it and pass it to post method.
For example if you are sure that the json object (extracted from the request body by body parser) is a JSON Object (not an Array or String or a Numeric Value) you can construct the needed Map easily (just for the first level key/value pairs):
def getToken = Action.async(parse.json) { request =>
import play.api.libs.json.JsObject
val json = request.body.as[JsObject]
val formParamsMap = json.value.mapValues(_.asOpt[String].toSeq)
WS.url("https://api.xxxx.com/v1/yyyy")
.withHeaders(
"accept" -> "application/json",
"content-type" -> "application/x-www-form-urlencoded",
"Authorization" -> "Auth %s".format(apiKey)
)
.post(formParamsMap) map { response =>
Ok("ok")
}
}

Play framework Ws: how create request without encode?

In this moment, I do integration of our project on Play framework and gis service.
Gis service works with http get queries.
Here the right example of get point request:
http://myGisServer.ru/myservice?token=XYZ%3D&query=[{"id":"123","objectIds":["5"]}]
Where token is token form auth service, and this must be encoded for http request.
And query is not encoded json with specified parameters.
I create this request with ws:
def getPoint = Action.async{
val data = Json.obj(
"id" -> "123",
"objectIds" -> Json.arr("5")
)
val ws = WS.url("http://myGisServer.ru/myservice").withQueryString(
"token" -> currentToken,
"query" -> data.toString()
)
val futureResponse: Future[WSResponse] = ws.get()
futureResponse.map(response => {
Ok(response.json)
})
}
But that doesn't work because ws encode all request, including json string.
How can I exclude json from encoding, or create other query without encoding?

Found "play.api.libs.iteratee.Iteratee[Array[Byte],play.api.mvc.Result]" required "play.api.mvc.Result" error

Using Play 2.1-RC1 I can't write simple test.
Here's the action code:
def echoTestTagFromXml = Action(parse.xml) { request =>
(request.body \ "test" headOption).map(_.text).map { test =>
Ok(views.xml.testTag(test))
}.getOrElse {
BadRequest("Missing parameter [name]")
}
}
Here's testing code:
"Test Tag Xml Echo" in {
running(FakeApplication()) {
val req = new FakeRequest(POST, controllers.routes.SimpleResultsController.echoTestTagFromXml().url, FakeHeaders(), Xml("<test>gg</test>"))
val result = controllers.SimpleResultsController.echoTestTagFromXml()(req)
status(result) must equalTo(OK)
}
}
Test gives the error:
[error] found : play.api.libs.iteratee.Iteratee[Array[Byte],play.api.mvc.Result]
[error] required: play.api.mvc.Result
From Google I know problem is in BodyParser. But I have no idea(after API investigation) how to make the code working.
The following modified testing code should work, but I think there is a bug at the moment when trying to pass a body into a FakeRequest, somewhat a hangover with the Functional tests now deprecated 'routeAndCall' function. The body is always empty.
"Test Tag Xml Echo" in {
running(FakeApplication()) {
val req = FakeRequest(POST, controllers.routes.SimpleResultsController.echoTestTagFromXml().url, FakeHeaders(), Xml("<test>gg</test>"))
.withHeaders(CONTENT_TYPE -> "text/xml")
val result = await(controllers.SimpleResultsController.echoTestTagFromXml()(req).run)
contentAsString(result) must equalTo("gg")
status(result) must equalTo(OK)
}
}
I have a similar issue with passing Json into the body, but tried to get this working for your body parser (note the differences). Also, please set the content-type header.
You can however use the 'route' function instead:
"Test Tag Xml Echo Route" in {
running(FakeApplication()) {
val result = route(FakeRequest(POST, "/SimpleResultsController").withHeaders(CONTENT_TYPE -> "text/xml"), Xml("<test>gg</test>")).get
contentAsString(result) must equalTo("gg")
status(result) must equalTo(OK)
}
}
This seems to work for me and you should be able to copy/paste this solution.
If you don't want to repeat your route as a string then you can use the reverse routes as you did before: controllers.routes.SimpleResultsController.echoTestTagFromXml().url