I have the following scenario:
Given path /resource
And request data
When method POST
Then status 200
And match response contains resourceResponse
If resource adds a new field, how can I get that info without fail? I mean I can do * match response == resourceResponse but in such case test case fails.
Thanks.
Use match contains.
* def response1 = { foo: 'bar', hello: 'world' }
* def response2 = { foo: 'bar', hello: 'world', extra: 'blah' }
* match response2 contains response1
Related
I'm using the scala sttp client and in particular, using the Monix backend and I have the following code:
def httpTask(href: String): Task[HttpBinaryResponse] = {
val request = basicRequest
.get(uri"$href")
AsyncHttpClientMonixBackend
.resource()
.use { backend =>
request.send(backend).map {
response: Response[Either[String, String]] =>
println(s"For URL: $href >> Got response code: ${response.code}")
HttpBinaryResponse(
href,
response.code.isSuccess,
response.headers.map(elem => (elem.name, elem.value)))
}
}
}
I have a set of URL's that I run through this method:
hrefs.toSet.flatten.filter(_.startsWith("http")).map(httpTask) // Set[Task[HttpBinaryResponse]]
In my caller function, I then execute the task:
val taskSeq = appBindings.httpService.parse(parserFilter)
val chunks = taskSeq.sliding(30, 30).toSeq
val batchedTasks = chunks.map(chunk => Task.parSequence(chunk))
val allBatches = Task.sequence(batchedTasks).map(_.flatten)
I then pattern match on the materialized type and return, bit what I want here is not just a Success and a Failure, but I would like to have both the values at once:
allBatches.runToFuture.materialize.map {
case Success(elem) =>
Ok(
Json
.obj("status" -> "Ok", "URL size" -> s"${elem.size}")
).enableCors
case Failure(err) =>
Ok(
Json
.obj("status" -> "error", "message" -> s"${err.getMessage}")
).enableCors
}
I need to get a List of URL's with a corresponding Success or a Failure message. With this approach of what I have, I get only either one of those, which is obvious as I pattern match. How can I collect both Success and Failure?
Whenever I hit unknown route in my http4s application it returns 404 error page with Content-Type: text/plain and body:
Not found
How can I force it to always return body as JSON with Content-Type: application/json?
{"message": "Not found"}
I figured out that when I assembly httpApp I can map over it and "adjust" responses:
val httpApp = Router.publicRoutes[F].orNotFound.map(ErrorTranslator.handle)
where ErrorTranslator just detects responses with status code of client error and Content-Type which is not application/json and then just wraps body into JSON:
object ErrorTranslator {
val ContentType = "Content-Type"
val ApplicationJson = "application/json"
private def translate[F[_]: ConcurrentEffect: Sync](r: Response[F]): Response[F] =
r.headers.get(CaseInsensitiveString(ContentType)).map(_.value) match {
case Some(ApplicationJson) => r
case _ => r.withEntity(r.bodyAsText.map(ErrorView(_))) //wrap reponse body into enity
}
def handle[F[_]: ConcurrentEffect: Sync]: PartialFunction[Response[F], Response[F]] = {
case Status.ClientError(r) => translate(r)
case r => r
}
}
It works, but I wonder if there is maybe some less convoluted solution?
It would be also great if a solution could "translate" other errors, like 400 Bad request into JSON, similarily to presented code.
You can also make it with value and mapF function:
val jsonNotFound: Response[F] =
Response(
Status.NotFound,
body = Stream("""{"error": "Not found"}""").through(utf8Encode),
headers = Headers(`Content-Type`(MediaType.application.json) :: Nil)
)
val routes: HttpRoutes[F] = routes.someRoutes().mapF(_.getOrElse(jsonNotFound))
I suppose you have defined your routes in a similar fashion, then you can add a default case statement
HttpRoutes.of[IO] {
case GET -> Root / "api" =>
Ok()
case _ -> Root =>
// Your default route could be done like this
Ok(io.circe.parser.parse("""{"message": "Not Found"}"""))
}
I have two Akka HTTP services (Service 1 and Service 2). One needs to communicate with the other.
Service 2 provides a POST endpoint /lastname that takes in json payload like this {name: "doe"} and returns {"returnVal":false} or {"returnVal":true}
Question
How can Service 1 connect to it, send json payload, and fetch the boolean response?
I am trying below:
val responseFuture: Future[HttpResponse] = Http().singleRequest(
HttpRequest(
uri = s"$host:$port/my/endpoint")
entity = HttpEntity(MediaTypes.`application/json`, method = HttpMethods.POST)
)
but I can't figure out out to send json data {name: "doe"} along with the request.
for creating JSON you need to use json marshaller like upickle, spray-json. I use spray json for converting scala classes to json and vice versa. I assume Service2 take {"name": "paul"} as request and return {"exist": true} as response.
import spray.json.{DefaultJsonProtocol, RootJsonFormat}
case class NameExistsRequest(name: String)
case class NameExistsResponse(exist: Boolean)
object ServiceJsonProtocolNameExists extends DefaultJsonProtocol{
implicit val nameExistsRequestJF: RootJsonFormat[NameExistsRequest] = jsonFormat1(NameExistsRequest)
implicit val nameExistsResponseJF: RootJsonFormat[NameExistsResponse] = jsonFormat1(NameExistsResponse)
}
case object NameExistsError extends RuntimeException with NoStackTrace
case object InternalError extends RuntimeException with NoStackTrace
def sendNameExistsRequest(): Future[NameExistsResponse] = {
import ServiceJsonProtocolNameExists._
import spray.json._
val endpoint = "http://your-endpoint"
val request = NameExistsRequest("paul")
val httpRequest = HttpRequest(
method = HttpMethods.POST,
uri = Uri(endpoint),
headers = List.empty,
entity = HttpEntity(ContentTypes.`application/json`, request.toJson.toString)
)
Http().singleRequest(httpRequest).transformWith {
case Success(response) =>
response match {
case HttpResponse(StatusCodes.OK, _, entity, _) =>
Unmarshal(entity).to[String].map {content =>
content.parseJson.convertTo[NameExistsResponse]
}
case res =>
println("failure response: " + res)
Future.failed(NameExistsError)
}
case Failure(f) =>
println("failed to send request, caused by: " + f)
Future.failed(InternalError)
}
}
which NameExistsRequest represent your request and we convert it to json with toJson method and NameExistsResponse represent your response which returned by Service2. we use Unmarshall and convertTo, to convert json to scala class.
I want to get both body and headers from dispatch request. How to do this?
val response = Http(request OK as.String)
for (r <- response) yield {
println(r.toString) //prints body
// println(r.getHeaders) // ???? how to print headers here ????
}
We needed the response body of failed requests to an API, so we came up with this solution:
Define your own ApiHttpError class with code and body (for the body text):
case class ApiHttpError(code: Int, body: String)
extends Exception("Unexpected response status: %d".format(code))
Define OkWithBodyHandler similar to what is used in the source of displatch:
class OkWithBodyHandler[T](f: Response => T) extends AsyncCompletionHandler[T] {
def onCompleted(response: Response) = {
if (response.getStatusCode / 100 == 2) {
f(response)
} else {
throw ApiHttpError(response.getStatusCode, response.getResponseBody)
}
}
}
Now, near your call to the code that might throw and exception (calling API), add implicit override to the ToupleBuilder (again similar to the source code) and call OkWithBody on request:
class MyApiService {
implicit class MyRequestHandlerTupleBuilder(req: Req) {
def OKWithBody[T](f: Response => T) =
(req.toRequest, new OkWithBodyHandler(f))
}
def callApi(request: Req) = {
Http(request OKWithBody as.String).either
}
}
From now on, fetching either will give you the [Throwable, String] (using as.String), and the Throwable is our ApiHttpError with code and body.
Hope it helped.
From the Dispatch docs:
import dispatch._, Defaults._
val svc = url("http://api.hostip.info/country.php")
val country = Http(svc OK as.String)
The above defines and initiates a request to the given host where 2xx
responses are handled as a string. Since Dispatch is fully
asynchronous, country represents a future of the string rather than
the string itself.
(source)
In your example, the type of response is Future[String], because 'OK as.String' converts 2xx responses into strings and non-2xx responses into failed futures. If you remove 'OK as.String', you'll get a Future[com.ning.http.client.Response]. You can then use getResponseBody, getHeaders, etc. to inspect the Request object.
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"))