Check string in gatling simulation - scala

Trying to check if a string exists on the body. Similar to checking status which is .check(status.is(200)). I want to check for string as well. tried .check(bodyString.is("greeting")) but got an error:
val scn = scenario("GreetingPages")
.during(testTimeSecs) {
exec(
http ("greeting")
.get("/greeting")
.check(status.is(200))
.check(bodyString.is("Greeting"))
).pause(minWaitMs,maxWaitMs)
.exec(
http("greeting1")
.get("/greeting1")
.check(status.is(200))
.check(bodyString.is("Greeting1"))
).pause(minWaitMs,maxWaitMs)
.exec(
http("Third page")
.get("/greeting2")
.check(status.is(200))
.check(bodyString.is("Greeting2"))
).pause(minWaitMs,maxWaitMs)
}
---- Errors --------------------------------------------------------------------
bodyString.find.is(Greeting), but actually found {"message":"G 9 (47.37%)
reeting"}
bodyString.find.is(Greeting1), but actually found {"message":" 5 (26.32%)
Greeting1"}
bodyString.find.is(Greeting2), but actually found {"message":" 5 (26.32%)
Greeting2"}

The reason is the bodyString return the full response body, as it described in the documentation.
You could use in matcher (you can see the implementation here - look for InMatcher[A], but it won't work because you need to switch expected.contains(actualValue) with expected.contains(expected)
I suggest you implement your own MyOwnInMatcher:
class MyOwnInMatcher[A](expected: Seq[A]) extends Matcher[A] {
def name: String = "customIn"
protected def doMatch(actual: Option[A]): Validation[Option[A]] = actual match {
case Some(actualValue) =>
if (expected.contains(actualValue))
actual.success
else
s"found $actualValue".failure
case _ => Validator.FoundNothingFailure
}
}
and use it:
.check(jsonPath("$.message").validate(customIn("Greeting")))), which checks if "Greeting" exists in the message attribute of the json response body.

Related

Gatling session - get attribute as Long

I am a new in Scala and got some problems with casting from String to Long. I try to get Gatling session value as Long in request. Before in exec() part, I try to set the userId value
def setUserId(): ChainBuilder = {
exec(session => session
.set("userId", Random.nextLong())
)
}
Next, in request creator I want to use it like that because I need a new userId every call:
object UserRequestCreator {
def sampleUserRequest(currency: String): Request = {
Data data = new Data()
data.setUserId("${userId}".toLong)
data.setCurrency(currency)
}
}
Test scenario:
exec(setUserId())
.exec(http("postUser")
.post(endpointUser).asXml
.headers(headers)
.body(StringBody(toXmlString(sampleUserRequest("EUR"), classOf[Request])))
.check(status.is(200))
but receive error:
java.lang.NumberFormatException: For input string: "${userId}"
How to fix that in Scala?
I also try Long.valueOf, JLong.parseLong("${userId"}, 16), Try(BigDecimal(...)) and more but nothing can help. I think the problem is with $ symbol, but I don't see any different way to get this value from the session. Maybe it is possible to store Long in the Gating session?
From the documentation and based on your current code, one way to do it is like that:
// with a function payload
http("name").post("/")
.body(StringBody(session => s"""{ "foo": "${session("dynamicValueKey").as[String]}" }"""))
Thus, in your case:
StringBody(session => toXmlString(sampleUserRequest(session)("EUR"), classOf[Request])
def sampleUserRequest(session: Session)(currency: String): Request = {
//...
data.setUserId(session("userId").as[Long])
}

Modify a JSON response to send back to the api in Gatling/Scala

I am doing some Gatling and since I never do scala, I am kind of lost.
I want to modify the JSON response from a JsonPath I receive before sending it back
My code look like this
.exec(
http("Get call")
.get("getEndpoint")
.check(jsonPath("$.value").saveAs("RESPONSE_DATA"))
)
.exec(
http("Post call")
.post("postEndpoint")
.header("content-type", "application/json")
.body(StringBody("${RESPONSE_DATA}"))
.asJson
)
For example, I want to change to the first name of the user receive in Json from the Get Call. I can't manage to find an answer to Gatling documentation
Thanks to Lars comment, I manage to find a solution. I was too focus on finding the specific method for Gatling that I forgot the basic way to do programming
Here the new code
.exec(
http("Get call")
.get("getEndpoint")
.check(jsonPath("$.value").saveAs("RESPONSE_DATA"))
)
.exec(session =>
{
// put body response into variable
val response = session("RESPONSE_DATA").as[String];
// generate random string as you convenience
val randomString = Random.alphanumeric.filter(_.isLetter).take(5).mkString;
// use replace method to modify your json (which is right now a string)
newResponse = response.replace(
"""specificKey":""",
"""specificKey":""" + randomString + "",
)
session
}.set("RESPONSE_DATA", newResponse)
// ^ really important to set the new value of session outside of brackets !!
)
.exec(
http("Post call")
.post("postEndpoint")
.header("content-type", "application/json")
.body(StringBody("${RESPONSE_DATA}"))
.asJson
)
Not the cleaner code I did, but it works.

Gatling request body as bytearray

val scn = scenario("gatling test").
feed(circularfeeder.circular)
.exec(http("request")
.post(endpoint)
.header("Content-Type", "application/json")
.header("Accept-Encoding", "charset=UTF-8")
.body(StringBody("""${request}"""))
.check(status.is(200))
The above code is used to send every circularfeeder data as a string to the body of the request. Suppose if i want to send as byte array instead of string in the body how do I modify the line .body(StringBody("""${request}"""))
The code .body(ByteArrayBody(getBytesData("""${request}"""))) does not work. Any advise?
Option 1
Call getBytes method on request string and pass that to ByteArrayBody
.body(ByteArrayBody { session =>
session("request")
.validate[String]
.map(s => s.getBytes(StandardCharsets.UTF_8))
})
Option 2
Convert the raw data you got from your feeder.
val circularfeeder = csv("myFile.csv").convert {
case ("request", string) => string.getBytes(StandardCharsets.UTF_8)
}
...
.body(ByteArrayBody("${request}"))

Stripe Webhook Verification Error with Play Framework

I am trying to set up stripe payment with a play framework application. I am having issues with setting up the webhook.
com.stripe.exception.SignatureVerificationException: No signatures found matching the expected signature for payload
This is the error I keep getting when I try and construct the event sent from stripe. I print out the values for the body and the signature and they look like they should be correct. This is the code I am using to collect the webhook.
def webhook = Action { implicit request: Request[AnyContent] =>
println(request.headers.toMap)
val bodyOp:Option[JsValue] = request.body.asJson
val sigOp:Option[String] = request.headers.get("Stripe-Signature")
var event: Event = null
if (bodyOp.isEmpty || sigOp.isEmpty) {
WebhookController.logger.write("EMPTY BODY OR SIG body-"+bodyOp+" sig-"+sigOp,Logger.RED)
BadRequest
} else {
val body = bodyOp.get.toString
val sig = sigOp.get
println(body)
println(sig)
try {
event = Webhook.constructEvent(body, sig, "whsec_5XwS8yCNOcq1CKfhh2Dtvm8RaoaE3p7b")
val eventType: String = event.getType
eventType match {
case "customer.subscription.deleted" => deleteCustomer(event)
case "invoice.payment.succeeded" => successPayment(event)
case "invoice.payment.failed" => failedPayment(event)
case _ => WebhookController.logger.write("UNKNOWN " + event, Logger.RED)
}
Ok("")
} catch {
case e: JsonSyntaxException =>
e.printStackTrace()
WebhookController.logger.write("ERROR" + e.getMessage +" "+exceptionToString(e), Logger.RED)
BadRequest
case e: SignatureVerificationException =>
e.printStackTrace()
WebhookController.logger.write("ERROR" + e.getMessage + " "+exceptionToString(e), Logger.RED)
WebhookController.logger.write("SIG ERROR header-"+e.getSigHeader+" status code-"+e.getStatusCode,Logger.RED )
BadRequest
}
}
}
Karllekko was on the right track. Play framework automatically parsed it as a json which caused the error. request.body.asText didn't work because the content-type header value was set to json. Tolarant Text would have worked except for stripe sends their webhooks with utf-8 and tolarant text doesn't parse with utf-8. So I ended up having to use a RawBuffer and turning that into a String (https://www.playframework.com/documentation/2.6.x/ScalaBodyParsers)
class WebhookController #Inject()(parsers: PlayBodyParsers) extends Controller() {
def webhook = Action(parsers.raw) { implicit request: Request[RawBuffer] =>
val bodyOp = request.body.asBytes()
val sigOp:Option[String] = request.headers.get("Stripe-Signature")
var event: Event = null
if (bodyOp.isEmpty || sigOp.isEmpty) {
BadRequest
} else {
val body = bodyOp.get.utf8String
val sig = sigOp.get
try {
event = Webhook.constructEvent(body, sig, secret)
...
...
You need to pass the raw body of the request exactly as received to constructEvent, not the JSON parsed version.
I believe that should be
event = Webhook.constructEvent(request.body.asText, sig, "whsec_5XwS8yCNOcq1CKfhh2Dtvm8RaoaE3p7b")
in this case?
You can have a look at the examples from Stripe as well[0]. But the core issue is that you must sign the exact string sent by Stripe, without any interference by your code or a framework in the middle.
[0]- https://github.com/stripe/stripe-java/blob/master/src/test/java/com/stripe/net/WebhookTest.java
zamsler's solution works for me. Thank you! Saved me a lot of time. For anyone who wants the Play 2.8 Java version:
#BodyParser.Of(BodyParser.Raw.class)
public Result webhook(Http.Request request) {
String payload = request.body().asBytes().utf8String();
String sigHeader = request.getHeaders().get("Stripe-Signature").get();
String endpointSecret = "xxxxxxxx";
try {
event = Webhook.constructEvent(payload, sigHeader, endpointSecret);
} catch (SignatureVerificationException e) {
e.printStackTrace();
// Invalid signature
return badRequest();
}
...
...
}

Custom spray.io directive to validate request header value

I am new to spray and I am trying to write a custom directive. I would like the directive to reject the request if the header value is not valid otherwise leave the request alone.
I've tried to absorb this page:
http://spray.io/documentation/1.1.2/spray-routing/key-concepts/directives/
Specifically, the part about the responder chain. I'm trying to create something at the level of the bar Directive in the illustration. I'm just not getting how to pass the context unchanged to the inner route.
My else block below is not correct but expresses what I am trying to do. I just can't figure out how to implement it.
Any help would be greatly appreciated.
trait ApiKeyDirective {
import spray.routing.directives.HeaderDirectives._
import spray.routing.directives.BasicDirectives._
def validateApiKey(): Directive1 = {
headerValueByName("api-key") {key =>
val valid = key == "123"
if (!valid) reject() else pass
}
}
}
object ApiKeyDirective extends ApiKeyDirective
You can combine
headerValueByName:
def headerValueByName(headerName: String): Directive1[String]
with validate:
def validate(check: ⇒ Boolean, errorMsg: String): Directive0
For example:
def validateApiKey(route: Route) =
headerValueByName("api-key") { key =>
validate(key == "123", "Invalid API key") {
route
}
}
or without validate:
def validateApiKey(route: Route) =
headerValueByName("api-key") { key =>
if (key == "123")
route
else
reject(ValidationRejection("Invalid API key"))
}
Usage:
lazy val route = ...
... ~
pathPrefix("test_directive") {
get {
validateApiKey {
complete("ok")
}
}
} ~
...
Test from cmd/shell:
# curl http://localhost:8080/test_directive
Request is missing required HTTP header 'api-key'
# curl http://localhost:8080/test_directive -H 'api-key: bad'
Invalid API key
# curl http://localhost:8080/test_directive -H 'api-key: 123'
"ok"
I'm just not getting how to pass the context unchanged to the inner
route.
Spray does that for you!
Your code is mostly correct, there are just 2 simple problems to fix!
Firstly, you need to flatMap headerValueByName("api-key") directive.
Secondly, the return type will be Directive0 because the directive won't provide any value.
So final code would look like this:
object ApiKeyDirective {
import spray.routing.Directives._
val validateApiKey: Directive0 =
headerValueByName("api-key").flatMap { key =>
val valid = key == "123"
if (!valid) reject() else pass
}
}
Also, I recommend you to add a custom rejection to reject() block so that API users will be informed when their api key is invalid.