Play Framework & Scala: Problems with FakeRequest & JSON POST in unit test - json

I have spent too much time trying to debug the following issue but I am not sure where the problem is occurring.
Issue: Getting 400 Bad Request, Invalid Json as response with following exception:
com.fasterxml.jackson.databind.JsonMappingException: No content to map due to end-of-input
at [Source: [B#6ee503c9; line: 1, column: 1]
at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:164) ~[jackson-databind.jar:2.2.2]
at com.fasterxml.jackson.databind.ObjectMapper._initForReading(ObjectMapper.java:2931) ~[jackson-databind.jar:2.2.2]
at com.fasterxml.jackson.databind.ObjectMapper._readValue(ObjectMapper.java:2846) ~[jackson-databind.jar:2.2.2]
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:1569) ~[jackson-databind.jar:2.2.2]
at play.api.libs.json.JacksonJson$.parseJsValue(JsValue.scala:480) ~[play-json_2.10.jar:2.2.0]
at play.api.libs.json.Json$.parse(Json.scala:27) ~[play-json_2.10.jar:2.2.0]
Method to be tested in my controller:
def createArticle(id: String) =
Action.async(parse.json) { implicit request =>
(request.body \ "content").asOpt[String].map {
............
............
}.getOrElse(BadRequest("Invalid request body"))
}
Corresponding Unit Test:
"create article" in {
running(FakeApplication()) {
val postJson = Json.obj("content" -> "article content")
val result = resource.createArticle(ARTICE_ID)(FakeRequest(POST, controllers.routes.ArticleResource.create(ARTICLE_ID).url).withJsonBody(postJson).withHeaders(CONTENT_TYPE -> "application/json").run
status(result) must equalTo OK
}
}
I read the discussion here but none of the suggestions there helped.

I had similar problems, but didn't solve them so far (elegantly, anyway..). As my environment was java instead of scala, I can just give a hunch. I think that it's possible that when you send post it's done asynchronously (Action.async at your create article method), so you'd possibly need to wait at test code result before trying to see if it's OK.

I had a similar problem and the solution is pointed here. In my case it was because I using response.asJson() twice, and as described by #jroper
Considering that the body of an HTTP response is a stream, and not
something that you want to necessarily buffer in memory, then it makes
sense that accessing the body twice (regardless of what format you are
accessing the body in) would not be supported.

Try instantiating your request inside the method definition.
This is how it's working for me (example using POST with JSON body and result as JSON):
"process" should {
"should be valid" in {
val request = FakeRequest(POST, "/").withJsonBody(Json.parse(""" {
"id":1,
"name":"prod 1",
"price":55.55
}"""))
val result: Future[Result] = controller.process.apply(request)
val json = contentAsJson(result)
status(result) must be(CREATED)
(json \ "id").as[Int] mustBe 1
// .. more assertions and rest of code ...
Here the code deals with a JsValue and query it's nodes to check if the returned values are matching the desired mock data output

Related

Scala Play Json implicit writes type mismatch

I am new to Scala and Play, and I ask for help with this simple example. I tried to search for solution by myself, but I did not succeed.
I am trying to do the example from from Mastering Play Framework for Scala book, the one about extending Json parser (Pages 29-30).
The environment I use is:
Scala: 2.11.7
Play: 2.5.8
Activator: 1.3.10
The code is:
case class Subscription(emailId: String, interval: Long)
In controller:
import play.api.libs.json.Json
import play.api.libs.json.JsValue
import play.api.libs.json.Writes
.....
val parseAsSubscription = parse.using {
request =>
parse.json.map {
body =>
val emailId:String = (body \ "emailId").as[String]
val fromDate:Long = (body \ "fromDate").as[Long]
Subscription(emailId, fromDate)
}
}
implicit val subWrites:Writes[Subscription] = Json.writes[Subscription]
def getSub = Action(parseAsSubscription) {
request =>
val subscription: Subscription = request.body
Ok(Json.toJson(Subscription))
}
The line: Ok(Json.toJson(Subscription)) gives an error
No Json serializer found for type models.Subscription.type. Try to
implement an implicit Writes or Format for this type.
This is odd, because Writes object is defined one row above. Thus, I tried to pass it to toJson method explicitly:
Ok(Json.toJson(Subscription)(subWrites))
It gave me a different error, which partially explained why existing Writes object did not suit:
type mismatch;
found:
play.api.libs.json.Writes[models.Subscription]
required:
play.api.libs.json.Writes[models.Subscription.type]
However, I don't understand the nature of this error and what models.Subscription.type is .
I used to do a similar thing in a different example, and it worked just fine.
Any help will be appreciated.
You're trying to serialize the type Subscription, rather than the request body, which you stored as the value subscription. Try replacing the last line with Ok(Json.toJson(subscription)).

Decode gzipped JSON in Akka HTTP

I have an endpoint we can call /test that internally fetches data from a 3rd party API and then wants to do some transformation before returning a response. Where I am hung up is this 3rd party API is returning gzipped JSON and I can't decode it (yet). I found the decodeRequest directive but it seems I have to use this in my routing and I am a level deeper here. I have an internal method I call once I receive a GET to my endpoint /test which is named do3rdPartyAPIRequest where I build up an HttpRequest and pass to Http().singleRequest() so then in return I have a Future[HttpResponse] which is where I think I want to be but I am stuck here.
With some local APIs I built and consumed in a similar fashion I didn't encode my responses so typically with a Future[HttpResponse] I check the response status and go into conversion to JSON via Unmarshal but this one needs an extra step as far as I know before transforming to JSON. I realize this question is very similar to this one however that is spray specific and I haven't been able to translate this answer into current akka http
Finally figured this out - this might not be the absolute best to get a bytestring from response but it works .. Turns out you can use Gzip class
and you have two options
Gzip.decode
Gzip.decoderFlow
Here are my examples in case this helps you:
def getMyDomainObject(resp: HttpResponse):Future[MyDomain] = {
for {
byteString <- resp.entity.dataBytes.runFold(ByteString(""))(_ ++ _)
decompressedBytes <- Gzip.decode(byteString)
result <- Unmarshal(decompressedBytes).to[MyDomain]
} yield result
}
def getMyDomainObjectVersion2(resp:HttpResponse):Future[MyDomain] = {
resp.entity.dataBytes
.via(Gzip.decoderFlow)
.runWith(Sink.head)
.flatMap(Unmarshal(_).to[MyDomain])
}
You would expect that compressed content would be managed by akka-http by default, but it only provides PredefinedFromEntityUnmarshallers and inside entity there is not information about Content-encoding header.
To solve this you have to implement your own Unmarshaller and to have it in scope
Example:
implicit val gzipMessageUnmarshaller = Unmarshaller(ec => {
(msg: HttpMessage) => {
val `content-encoding` = msg.getHeader("Content-Encoding")
if (`content-encoding`.isPresent && `content-encoding`.get().value() == "gzip") {
val decompressedResponse = msg.entity.transformDataBytes(Gzip.decoderFlow)
Unmarshal(decompressedResponse).to[String]
} else {
Unmarshal(msg).to[String]
}
}
})

Unable to return a json inside Future[JsValue] from a WebSocket in Play 2.4

I have implemented Play framework's WebSocket so as to perform server communication using a WebSocket instead of Http. I have created a function as WebSocket.using[JsValue]. My json response is stored inside a Future[JsValue] variable and I am trying to fetch and return the json value from within Future[JsValue] variable. However I have been unable to return the json data from the Future[JsValue] variable. When I tried creating the WebSocket function as WebSocket.using[Future[JsValue]], in this case I was unable to create a json FrameFormatter for it.
def socketTest = WebSocket.using[JsValue] { request =>
val in = Iteratee.ignore[JsValue]
val out = Enumerator[JsValue](
Json.toJson(futureJsonVariable)
).andThen(Enumerator.eof)
(in, out)
}
futureJsonVariable is a variable of type Future[JsValue] In the above code the error at runtime is No Json serializer found for type scala.concurrent.Future[play.api.libs.json.JsValue]. Try to implement an implicit Writes or Format for this type. How can I return a json using a WebSocket method in Scala ? How can it be achieved using an Actor class instance ? If anyone knows best available online tutorials for WebSocket in Play framework. Any help is appreciated.
Use tryAccept to return either the result of the future when it is redeemed, or an error:
def socketTest = WebSocket.tryAccept[JsValue] { request =>
futureJsonVariable.map { json =>
val in = Iteratee.ignore[JsValue]
val out = Enumerator(json).andThen(Enumerator.eof)
Right((in, out))
} recover {
case err => Left(InternalServerError(err.getMessage))
}
}
This is similar to using but returns a Future[Either[Result, (Iteratee[A, _], Enumerator[A])]]. The Either[Result, ...] allows you to handle the case where something unexpected occurs calculating the future value A by providing a play.api.mvc.Result in the Left branch. The corollary is that you need to also wrap the "happy path" (where nothing goes wrong) in Right, in this case the iteratee/enumerator tuple you'd ordinarily return from using.
You can do something similar with the tryAcceptWithActor function.

problems with handeling Json request in play

I'm trying to make server which can handle HTTP requests with Json. Sorry if I haver. I'm just still new to all this.
So, I made function which takes JsValue and working with it.
def find(info: JsValue) = {
val req = Search.makeRequest("person",info)
val result = DB.withConnection { implicit c =>
SQL(req).as(person *)
}
Json.toJson(result)
}
Then I do something like this:
val test = Json.parse("""{"name":"John"}""")
Person.find(test)
It works fine. But then I try to call this function with HTTP request:
routes file:
GET /findperson controllers.PersonController.findPerson(info: String)
controller:
def findPerson(info: String) = Action {
Ok(Person.find(Json.parse(info)))
}
actual request:
http://localhost:9000/findperson?info="""{"name":"John"}"""
I get:
Exception: Malformed JSON: Got a sequence of JsValue outside an array or an object.
Can someone tell me how do it right? Please.
Although I agree with #Ryan that it is an unusual thing to want to do, I think the problem you are having is actually as the message says, "Malformed JSON". Remember that your url parameter is not source code, it's a simple string. There's no need to escape the quotation marks. So try using the url:
http://localhost:9000/findperson?info={"name":"John"}

How to report parsing errors when using JSON.parseFull with Scala

When my app is fed syntactically incorrect JSON I want to be able to report the error to the user with some useful detail that will allow the problem area to be located.
So in this example j will be None because of the trailing comma after "File1". Is there a way to obtain details of last parse error?
val badSyntax = """
{
"menu1": {
"id": "file1",
"value": "File1",
},
"menu2": {
"id": "file2",
"value": "File2",
}
}"""
val j = JSON.parseFull(badSyntax)
When you get a parse error, use JSON.lastNoSuccess to get the last error. It is of type JSON.NoSuccess of which thare are two subclasses, JSON.Error and JSON.Failure, both containing a msg: String member detailing the error.
Note that JSON.lastNoSuccess is not thread safe (it is a mere global variable) and is now deprecated (bound to disappear in scala 2.11)
UPDATE: Apparently, I was wrong about it not being thread-safe: it was indeed not thread-safe before scala 2.10, but now lastNoSuccess is backed by a thread-local variable (and is thus safe to use in a multi-threaded context).
After seing this, you'd be forgiven to think that as long as you read right after a parsing failure in the same thread as the one that was used to do the parsing (the thread where you called parseFull), then everything will work as expected. Unfortunately, during this refactor they also changed how they use lastNoSuccess internally inside Parsers.phrase (which is called by JSON.parseFull.
See https://github.com/scala/scala/commit/1a4faa92faaf495f75a6dd2c58376f6bb3fbf44c
Since this refactor, lastNoSuccess is reset to None at the end of Parsers.phrase. This is no problem in parsers in general, as lastNoSuccess is used as a temporary value that is returned as the result of Parsers.phrase anyway.
The problem here is that we don't call Parsers.phrase, but JSON.parseFull, which drops any error info (see case None => None inside method JSON.parseRaw at https://github.com/scala/scala/blob/v2.10.0/src/library/scala/util/parsing/json/JSON.scala).
The fact that JSON.parseFull drops any error info could easily be circumvented prior to scala 2.10 by directly reading JSON.lastNoSuccess as I advised before, but now that this value is reset at the end of Parsers.phrase, there is not much you can do to get the error information out of JSON.
Any solution? Yes. What you can do is to create your very own version of JSON that will not drop the error information:
import util.parsing.json._
object MyJSON extends Parser {
def parseRaw(input : String) : Either[NoSuccess, JSONType] = {
phrase(root)(new lexical.Scanner(input)) match {
case Success(result, _) => Right(result)
case ns: NoSuccess => Left(ns)
}
}
def parseFull(input: String): Either[NoSuccess, Any] = {
parseRaw(input).right.map(resolveType)
}
def resolveType(input: Any): Any = input match {
case JSONObject(data) => data.transform {
case (k,v) => resolveType(v)
}
case JSONArray(data) => data.map(resolveType)
case x => x
}
}
I just changed Option to Either as the result type, so that I can return parsing errors as an Left. Some test in the REPL:
scala> MyJSON.parseFull("[1,2,3]")
res11: Either[MyJSON.NoSuccess,Any] = Right(List(1.0, 2.0, 3.0))
scala> MyJSON.parseFull("[1,2,3")
res12: Either[MyJSON.NoSuccess,Any] =
Left([1.7] failure: end of input
[1,2,3
^)