Facing issue testing akka http cache - scala

I am using Akka HTTP cache to cache my result. But i am facing issue to test it.
class GoogleAnalyticsController #Inject()(cache: Cache[String, HttpResponse],
googleAnalyticsApi: GoogleAnalyticsTrait,
googleAnalyticsHelper: GoogleAnalyticsHelper)
(implicit system: ActorSystem, materializer: ActorMaterializer) {
def routes: Route =
post {
pathPrefix("pageviews") {
path("clients" / Segment) { accountsClientId =>
entity(as[GoogleAnalyticsMetricsRequest]) { googleAnalyticsMetricsRequest =>
val googleAnalyticsMetricsKey = "key"
complete(
cache.getOrLoad(googleAnalyticsMetricsKey, _ => getGoogleAnalyticsMetricsData(accountsClientId, googleAnalyticsMetricsRequest))
)
}
}
}
}
private def getGoogleAnalyticsMetricsData(accountsClientId: String,
request: GoogleAnalyticsMetricsRequest) = {
val payload = generate(request)
val response = googleAnalyticsApi.googleAnalyticsMetricResponseHandler(accountsClientId, payload) // response from another microservice
googleAnalyticsHelper.googleAnalyticsMetricResponseHandler(
googleAnalyticsMetricsRequest.metricName, response)
}
}
class GoogleAnalyticsHelper extends LoggingHelper {
def googleAnalyticsMetricResponseHandler(metricName: String, response: Either[Throwable, Long]): Future[HttpResponse] =
response.fold({ error =>
logger.error(s"An exception has occurred while getting $metricName from behavior service and error is ${error.getMessage}")
Marshal(FailureResponse(error.getMessage)).to[HttpResponse].map(httpResponse => httpResponse.copy(status = StatusCodes.InternalServerError))
}, value =>
Marshal(MetricResponse(metricName, value)).to[HttpResponse].map(httpResponse => httpResponse.copy(status = StatusCodes.OK))
)
}
Test case: Sharing only the relevant part
"get success metric response for " + pageviews + " metric of given accounts client id" in { fixture =>
import fixture._
val metricResponse = MetricResponse(pageviews, 1)
val eventualHttpResponse = Marshal(metricResponse).to[HttpResponse].map(httpResponse => httpResponse.copy(status = StatusCodes.OK))
when(cache.getOrLoad(anyString, any[String => Future[HttpResponse]].apply)).thenReturn(eventualHttpResponse)
when(googleAnalyticsApi.getDataFromGoogleAnalytics(accountsClientId, generate(GoogleAnalyticsRequest(startDate, endDate, pageviews))))
.thenReturn(ApiResult[Long](Some("1"), None))
when(googleAnalyticsHelper.googleAnalyticsMetricResponseHandler(pageviews, Right(1))).thenReturn(eventualHttpResponse)
Post(s"/pageviews/clients/$accountsClientId").withEntity(requestEntity) ~>
googleAnalyticsController.routes ~> check {
status shouldEqual StatusCodes.OK
responseAs[String] shouldEqual generate(metricResponse)
}
}
By doing this, I am best to test if the cache has the key but not able to test if cache misses the hit. In code coverage, it misses following highlighted part
cache.getOrLoad(googleAnalyticsMetricsKey, _ =>
getGoogleAnalyticsMetricsData(accountsClientId,
googleAnalyticsMetricsRequest))
If there is a design issue, please feel free to guide me on how can I make my design testable.
Thanks in advance.

I think you don't need to mock the cache. You should create an actual object for cache instead of mocked one.
What you have done is, you have mocked the cache, in this case, the highlighted part will be not called as you are providing the mocked value. In the following stubbing, whenever cache.getOrLoad is found, eventualHttpResponse is returned:
when(cache.getOrLoad(anyString, any[String => Future[HttpResponse]].apply)).thenReturn(eventualHttpResponse)
and hence the function getGoogleAnalyticsMetricsData(accountsClientId, googleAnalyticsMetricsRequest) is never called.

Related

AskTimeoutException: Ask timed out on after [5000 ms]

I am trying to build a money transaction system using akka-http for REST API and akka actors for AccountActors.
post {
(path("accounts" / "move-money") & entity(as[MoveMoneyRequest])) { moveMoneyRequest =>
complete(
(bankActor ? moveMoneyRequest).map(x => MoveMoneyResponse("Money Transfer Successful!"))
)
}
}
The bankActor is created inside a main app
val bankActor = mainActorSystem.actorOf(Props(classOf[BankingActor], accountService), name = "bankActor")
Inside BankActor, we have:
def receive: Receive = LoggingReceive {
case req: MoveMoneyRequest =>
val fromAcc = createAccountActor(Some(req.fromAccount))
val toAcc = createAccountActor(Some(req.toAccount))
fromAcc ? DebitAccount(req.tranferAmount)
become(awaitFrom(fromAcc, toAcc, req.tranferAmount, sender))
}
private def createAccountActor(accountNum: Option[String]): ActorRef = {
actorOf(Props(classOf[AccountActor], accountNum, accountService))
}
Question: Now, for the first API call everytime, it's successful but seems the actor dies/shuts down and the ? (ask) does not find the actor as the message does not reach the receive method. Do I need to make the ask call different?
The correct directive to deal with futures is onComplete, for example
post {
(path("accounts" / "move-money") & entity(as[MoveMoneyRequest])) { moveMoneyRequest =>
val fut = (bankActor ? moveMoneyRequest).map(x => MoveMoneyResponse("Money Transfer Successful!"))
onComplete(fut){
case util.Success(_) => complete(StatusCodes.OK)
case util.Failure(ex) => complete(StatusCodes.InternalServerError)
}
}
}
More details in the docs.

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)

Handling Future[WSResponse] to find success or error state

In Scala I have a call to service in controller which is returning me Future[WSResponse]. I want to make sure service is returning valid result so send Ok(..) otherwise send BadRequest(...). I don't think I can use map. Any other suggestion?
def someWork = Action.async(parse.xml) { request =>
val result:Future[WSResponse] = someService.processData(request.body.toString())
//Need to send back Ok or BadRequest Message
}
EDIT
Solution from #alextsc is working fine. Now moving to test my existing test is failing. It is getting 400 instead of 200.
test("should post something") {
val requestBody = <value>{UUID.randomUUID}</value>
val mockResponse = mock[WSResponse]
val expectedResponse: Future[WSResponse] = Future.successful(mockResponse)
val request = FakeRequest(Helpers.POST, "/posthere").withXmlBody(requestBody)
when(mockResponse.body).thenReturn("SOME_RESPONSE")
when(someService.processData(any[String])).thenReturn(expectedResponse)
val response: Future[Result] = call(controller.someWork , request)
whenReady(response) { response =>
assert(response.header.status == 200)
}
}
You're on the right track and yes, you can use map.
Since you're using Action.async already and your service returns a future as it stands all you need to do is map that future to a Future[Result] so Play can handle it:
def someWork = Action.async(parse.xml) { request =>
someService.processData(request.body.toString()).map {
// Assuming status 200 (OK) is a valid result for you.
case resp : WSResponse if resp.getStatus == 200 => Ok(...)
case _ => BadRequest(...)
}
}
(I note that your service returns WSResponse (from the play ws java library) and not play.api.libs.ws.Response (the scala version of it), hence getStatus and not just status)

Adding headers to WSRequest in WS client from Play Framework

I have a service which requires authentication through headers. I have an existing java client which generates the values for me. I am trying to apply the headers to a ws request using a WSRequestHeaderFilter.
The code seems to run fine when I breakpoint and I can see the headers are applied. But in my Test Server (using the play SIRD router) the headers to not seem to be applied?
How can I make my required headers appear in the request using a filter or such method?
See the code below:
Filter:
class AuthenticatingFilter #Inject() (authHeaderGenerator: AuthHeaderGenerator) extends WSRequestFilter {
def apply(executor: WSRequestExecutor): WSRequestExecutor = {
new WSRequestExecutor {
override def execute(request: WSRequest): Future[WSResponse] = {
val headers = authHeaderGenerator.generateRequestHeaders(request.method, request.uri.toString, null).asScala.toList
executor.execute(request.withHeaders(headers:_*))
}
}
}
}
Usage in client:
//code omitted for brevity
def getStuff() = ws.url(s"${baseUrl}/authenticatedEndpoint").withRequestFilter(filter).get()
Test:
// code omitted for brevity
Server.withRouter() {
case GET(p"/authenticatedEndpoint") => Action {
request =>
request.headers.get(authHeader) match {
case Some(authHeaderValue) => Results.Ok(expectedResult)
case _ => Results.Forbidden
}
}
} {
implicit port =>
implicit val materializer = Play.current.materializer
WsTestClient.withClient {
client =>
val authenticatedClient: AuthenticatedClient = new AuthenticatedClient(client,filter)
val result: String = Await.result(authenticatedClient.getStuff(), Duration.Inf)
result must beEqualTo(expectedResult)
}
}
}
Thanks,
Ben
As it turned out this was a bug in play. I patched this and the change was merged into the master branch (https://github.com/playframework/playframework/pull/6077) so if you are having similar problems this should be fixed by upgrading to play 2.5.3 (when available)

Basic akka http client post not working

I am trying to make a basic http client posting some data to a REST API with Akka HTTP but I cannot make the following code work :
def sendData(e : GenericEvent): Future[Either[(String,StatusCode),(GenericEvent,StatusCode)]] = {
val request = Marshal(e).to[RequestEntity]
val responseFuture: Future[HttpResponse] = request map { req =>
Source.single(HttpRequest(method = HttpMethods.POST, uri = s"/data-ingest", headers = List(auth), entity = req))
.via(dataIngestFlow)
.runWith(Sink.head)
}
responseFuture.flatMap { response =>
response.status match {
case OK => Unmarshal(response.entity).to[GenericEvent].map(Right(_, response.status))
case BadRequest => Future.successful(Left(s"$e.data: incorrect data", response.status))
case _ => Unmarshal(response.entity).to[String].flatMap { entity =>
val error = s"generic event ingest failed with status code ${response.status} and entity $entity"
logger.error(error)
Future.failed(new IOException(error))
}
}
}
I got the following error
polymorphic expression cannot be instantiated to expected type;
[error] found :
[T]akka.stream.scaladsl.Sink[T,scala.concurrent.Future[T]]
[error] required:
akka.stream.Graph[akka.stream.SinkShape[akka.http.scaladsl.model.HttpResponse],akka.http.scaladsl.model.HttpResponse]
[error] .runWith(Sink.head)
Here is the code for the dataIngestFlow
val dataIngestFlow = Http().outgoingConnection(config.endpointUrl,config.endpointPort)
Here is the code on server side :
val routes = {
logRequestResult("akka-http-microservice") {
path("event-ingest") {
post {
entity(as[GenericEvent]) { eventIngest =>
log.info(s"Ingesting {} and publishing event to Kafka topic {}.", eventIngest.eventType,config.kafkaTopic)
kafka ! eventIngest
complete {
eventIngest
}
}~
entity(as[List[GenericEvent]]) { eventIngestList =>
eventIngestList.foreach{ eventIngest=>
log.info(s"Ingesting {} and publishing event List to Kafka topic {}.", eventIngest.eventType,config.kafkaTopic)
kafka ! eventIngest
}
complete {
eventIngestList
}
}
}
}
}
}
I tried another simple client, it builds well but the ingestion stop after 160 events, the server doesn't receive anymore events.
The first problem I see with your example is that RequestEntity does not have a map function. Therefore, the following line
val responseFuture: Future[HttpResponse] = request map { ...
should not compile.
Further, if request is actually a Future (which I infer from the map) then responseFuture is actually of type Future[Future[HttpResponse]] because the stream materializes into its own Future. To solve this problem you can use Future.flatMap instead of map. Namely:
val responseFuture: Future[HttpResponse] = request flatMap { req =>
This is the monadic bind operation within Futures.