Why won't gatling post an auth token from a POST return body to a GET header - scala

I tried all the suggestions I found from other examples on this site and I still cannot get my Gatling test to pass the authentication token from a POST to the following GETs. The token is taken from the return body of the POST and passed in as a header to the GET
Initially the login was in BaseSimulationTest but I put it in GetDealsTests for troubleshooting
Steps I have tried:
I added this:
//println(" Token value" + session("accessToken").as[String])
And I was able to see that I got a string back in the terminal that seemed to indicate accessToken was a null value
I tried declaring var accessValue and var accessToken both in the method and globally. No change.
I tried checking the header value on the GET after passing the token in but .check(header.is(expected = ${accessToken})) seems to just error out
I have put the login POST and GET in the same method, different methods etc
I have tried passing in the username and password from .formParam instead of in the body of the request statement
Do I need to do the headers as a map? Is this a scope thing? Do I need to declare variables differently? The tests are running with an "expected 200 but received 401" type result. I think the POST doesn't even see the token being passed to it.
class GetDealsTests extends BaseSimulationTest {
def logIn2() = {
exec(http("Log in")
.post(getUrl() + "login")
.header("Content-Type", "application/json")
.body(ElFileBody("bodies/Login.json")).asJson
.check(status.is(200))
.check(jsonPath("$.access_token").saveAs("accessToken")))
exec(
session=>{
accessValue = session("accessToken").as[String]
session
})
}
def getAllDeals() = {
exec(_.set("accessToken", accessValue))
.exec(
http("Get all deals")
.get("deals/all")
.header("Authorization", "Bearer" + "${accessToken}")
.check(status.is(200))
)
}
val scnGetAllDeals = scenario("Get All Deals endpoint tests")
.forever() {
exec(logIn2())
exec(getAllDeals())
}
setUp(
scnGetAllDeals.inject(
nothingFor(5 seconds),
atOnceUsers(users))
).protocols(httpConf.inferHtmlResources())
.maxDuration(FiniteDuration(duration.toLong, MINUTES))
}
I looked at the following: Gatling won't save access token, Gatling Scala:Unable to send auth token to the method using session variable, Gatling - Setting Authorization header as part of request and don't see what I'm doing wrong

Lots of things:
Both logIn2 and getAllDeals are missing dots to properly chain the different method calls. As an example, here's what your logIn2 is actually doing (properly formatting helps):
def logIn2() = {
val discardedResult = exec(http("Log in")
.post(getUrl() + "login")
.header("Content-Type", "application/json")
.body(ElFileBody("bodies/Login.json")).asJson
.check(status.is(200))
.check(jsonPath("$.access_token").saveAs("accessToken")))
return exec(
session => {
accessValue = session("accessToken").as[String]
session
})
}
You probably shouldn't be using a global var. You don't need it and such construct is not threadsafe, so under load, virtual users will be updating and reading from this reference concurrently and result will be a mess.
You're missing a space in your Authorization header, between Bearer and the actual value.
asJson is merely a shortcut for .header("Content-Type", "application/json"), so you setting this header twice.
class GetDealsTests extends BaseSimulationTest {
val logIn2 =
exec(http("Log in")
.post(getUrl() + "login")
.body(ElFileBody("bodies/Login.json")).asJson
.check(status.is(200))
.check(jsonPath("$.access_token").saveAs("accessToken")))
val getAllDeals =
exec(
http("Get all deals")
.get("deals/all")
.header("Authorization", "Bearer ${accessToken}")
.check(status.is(200))
)
val scnGetAllDeals = scenario("Get All Deals endpoint tests")
.forever {
exec(logIn2)
.exec(getAllDeals)
}
setUp(
scnGetAllDeals.inject(
nothingFor(5 seconds),
atOnceUsers(users))
).protocols(httpConf.inferHtmlResources())
.maxDuration(FiniteDuration(duration.toLong, MINUTES))
}

Related

Gatling error : Failed to build request: No attribute named 'jwtoken' is defined

I have this error when running my script in scala: "Failed to build request: No attribute named 'jwtoken' is defined " , i extract the value of jwtoken then i put it in a variable, then i want to read it in an other request, i have put 2 objects because i want run two scenario's :
class PPT_GET extends Simulation
{ val httpProtocol = http
.baseUrl("") // Here is the root for all relative URLs
val acteurscsv = csv("./src/test/resources/Data/ksp-acteurs.csv").circular
val jwt = "123"
object TokenGen {
val tokenGen = exec(http("access_token")
.post("https://URL1/token")
.header("Content-Type", "application/x-www-form-urlencoded")
.formParam("grant_type", value = "client_credentials")
.formParam("client_id", value = "PRTAPIPPD")
.formParam("client_secret", value = "xxxxxx")
.check(status is 200, jsonPath("$.access_token").saveAs("atoken")))
.exec(http("jwt_token")
.get("https://bsc-ppd.web.bpifrance.fr/mga/sps/apiauthsvc/policy/AT4JWT")
.header("Authorization", "Bearer ${atoken}")
.header("CorrelationID", "someID")
.header("UsercallerSecret", "user1")
.header("UsercallerID", "X1")
.header("Accept", "application/json")
.check(status is 200, jsonPath("$.JWTFull").saveAs("jwtoken"))) //here i extract the variable jwtoken
}
object Acteurs {
val acteurs =
pause(80)
.feed(acteurscsv)
.exec(http("ksp-acteurs")
.get("https://URL1/ksp-acteurs/${ACTCODE}")
.header("Authorization", "Bearer ${jwtoken}" ) // here where i have the erreor
.check(status is 200))
}
i have put 2 objects because i want run two scenario's
This is not how things work. A scenario is a virtual user journey and data is saved in each virtual user's memory space called Session.
So you can't save data with a check in one scenario and expect to find it in another one, because those scenarios would be executed by different virtual users.
You have to chain your requests in the same scenario, eg:
val scn = scenario("foo").exec(TokenGen.tokenGen, Acteurs.acteurs)
Then, if your goal is to generate one single token for your test, you can use a global reference and sequential scenarios:
var token: String = null
val generateToken = scenario("loadToken")
.exec(TokenGen.tokenGen)
.exec { session =>
token = session("jwtoken").as[String]
session
}
object Acteurs {
val acteurs =
feed(acteurscsv)
.exec(http("ksp-acteurs")
.get("https://URL1/ksp-acteurs/${ACTCODE}")
.header("Authorization", _ => s"Bearer $token" )
.check(status is 200))
}
setUp(
generateToken.inject(atOnceUsers(1))
.andThen(
scenario("acteurs").exec(Acteurs.acteurs).inject(???)
)
)

how can i have two barer token in header using akka http

i have an app in akka http i am using its header directive for using accessToken as barer token everything is working fine , now i want to have two barer token instead of one, first should be accessToken and second should be a refreshToken
here is my working code for one barer token
private def bearerToken: Directive1[Option[String]] =
for {
authBearerHeader <- optionalHeaderValueByType(classOf[Authorization]).map(extractBearerToken)
xAuthCookie <- optionalCookie("X-Authorization-Token").map(_.map(_.value))
} yield authBearerHeader.orElse(xAuthCookie)
private def extractBearerToken(authHeader: Option[Authorization]): Option[String] =
authHeader.collect {
case Authorization(OAuth2BearerToken(token)) => token
}
a route defined in my main controller
def accessProtectedResource: server.Route =
path("access-protected-resource") {
get {
bearerToken { accessToken =>
accessToken match {
case Some(token) =>
case Failure(e)=>failWith(ex)
}
}
}
and from postman i added barer token in Authorization tab of postman
can anyone please guide what changes i need to make if i need to pass two barer token (accessToken and refreshToken)

How to Save a Response Body and Use It Throughout the Gatling Execution

I am using 2 API calls in my Gatling simulation. One API returns the authentication token for the second, so I need to call the token generation API call only once during the execution and use it's generated token for the second API throughout the execution. But this works only for the first cycle of execution and the token that I have saved is not getting used for the remaining executions.
object KeycloakToken extends CMS {
def request(conf: ExecutionConfig): HttpRequestBuilder = {
http("Get Auth token from Keycloak")
.post(s"${conf.authUrl}/token")
.body(StringBody(""" {
"realm": "realm",
"clientId": "clientId",
"clientSecret": "clientSecret",
"grantType": "urn:ietf:params:oauth:grant-type:token-exchange",
"userName" : "userName"
} """)).asJson
.check(jsonPath("$.access_token").saveAs("token"))
}
}
object getOffers extends CMS {
def request(conf: ExecutionConfig): HttpRequestBuilder = {
http("getOffers")
.post(s"$context")
.body(StringBody(""" Body """)).asJson
.headers(headers)
.header("Authorization", s => s"Bearer ${s.attributes("token")}")
.check(jsonPath("$.data.offers.offers[0].id").exists)
}
}
execute.apply(
scenario("service")
.exec(session => session.set("counter" , myGlobalVar.getAndIncrement))
.doIf(session => session("counter").validate[Int].map(i => i == 0)) {
exec(KeycloakToken.request(conf)) //--call only once
}
.exec(getOffers.request(conf))
)
A gatling session object is unique to each user. So when you set your counter session variable as the first step of your scenario then use a doIf to only get the token if counter==0 only the first user to execute will ever try to get the token.
Since the session is unique to that user, none of the other users will have a value for token in their session object.
What you're trying to do seems to be a pretty common issue - make a single request to get some kind of data, then have that data shared among all the users. eg: here
Note that it looks like this kind of scenario will be easier once gatling 3.4

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.

How to use a single OAuth2.0 token for Multiple Virtual Users in a Gatling load test

I need to load test an API that requires an OAuth2.0 token via Gatling (of which I'm a complete novice!) but would like each virtual user to use the same token. I'm retrieving the token ok (I think) and putting it in a variable called 'access' but I keep getting 'no attribute named 'access' is defined' when the test itself starts.
My token retrieval looks like the following(along with httpConf, used below):
class MySimulation extends Simulation {
val httpConf = http
.baseUrl("https://MyBaseUrl.Com/")
.acceptHeader("application/json")
.doNotTrackHeader("1")
.acceptLanguageHeader("en-UK,en;q=0.5")
.acceptEncodingHeader("gzip, deflate")
.userAgentHeader("Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0")
.shareConnections
val header = Map("Content-Type" -> """application/x-www-form-urlencoded""")
al auth = scenario("Retrieve Token")
.exec(http("POST OAuth Req")
.post("https://SomeTokenUrl")
.formParam("resource", "someresource")
.formParam("grant_type", "somegranttype")
.formParam("client_secret", "someclientsecret")
.formParam("client_id", "someclientid")
.headers(header).check(status.is(200)).check(jsonPath("$.access_token").find.saveAs("access")))
I then tried setting up the load test as (Note: I did initially put 'Map', rather than the mutable variant, but read somewhere the default was immutable, and wondered if this was why the header couldn't update):
val headers_10 = scala.collection.mutable.Map("Content-Type" -> "application/json; charset=ISO-8859-1", "Authorization" -> "Bearer ${access}")
val scn = scenario("MyService Gatling test run")
.exec(http("")
.post("Myservice/api")
.headers(headers_10.toMap)
.body(StringBody("""{"SomeProperty": "Some Value"}"""))
.asJson
.check(status.is(200)))
setUp(
auth.inject(constantUsersPerSec(1) during (2 seconds)),
scn.inject(nothingFor(2 seconds),
constantUsersPerSec(10) during (10 seconds)
).protocols(httpConf))
.assertions(global.responseTime.max.lt(500))
.assertions(forAll.failedRequests.percent.lte(1))
.assertions(global.responseTime.mean.lte(100))
The idea was that the token retrieval would complete prior to the load test kicking in and the 'access' variable would then be used by the load test scenario, but it gives the following result:
ERROR : Failed to build request: No attribute named 'access' is defined
I've reached the end of my tether with it. I'm guessing it might be something to do with scopes, and perhaps the variable doesn't carry over to the load test scenario, but I've seen examples elsewhere that seem to recommend exactly that set up, so I don't know whether these other examples are partially complete or what.
Today I implemented this scenario for my project. Please see the code below and it will work for you as well.
Note: I have written this code with my API's required params. You can modify this code as per your requirement. For now, this code is written in a single class. I have implemented this code in a proper format as well with the use of different classes and property files. I will post that one as well.
For a single class, the code goes as follows:
`
package aapi
import io.gatling.core.Predef._
import io.gatling.http.Predef._
class manifestSimulation extends Simulation {
private var token = ""
object authAvi{
// This is the request(API) which we are going to use for generating the auth token 1 time and then will feed this token into subsequent request.
var postBody = "{\"username\":\"devusername\”,\”password\”:\”devpassword”}”
val auth = scenario("Retrieve our auth Token which will be used in the subsequent request“)
.exec(
http("POST OAuth Req")
.post(“User Post URL“)
.body(StringBody(postBody))
.header("ClientId", “test”)
.header("DSN", “devDB”)
.header("accept", "application/json")
.header("Accept-Language", "en-us")
.header("Content-Type", "application/json")
.check(status.is(200))
.check(jsonPath("$.token")
.saveAs("token")))
.exitHereIfFailed
.exec{session => { token = session("token").as[String]
session}}
}
object manifest {
// This is the request(API) which we are going to hit multiple times using the token which we generated from the previous auth API
var manifestHeaders = Map("ClientId" -> “test”, "DSN" -> "devDB", "Token" -> "${token}")
val manifestMethod = exec(session => session.set("token", token))
.exec(http("Manifest Details")
.get(“Your get URL“)
.headers(manifestHeaders)
.check(status.is(200))
)
}
val scn = scenario(“**********This is your actual load test*******************”)
.exec(manifest.manifestMethod)
setUp(
authAvi.auth.inject(constantUsersPerSec(1) during (1 seconds)), // fire 1 requests per second for 1 second to retrieve token
scn.inject(nothingFor(4 seconds), // waits 4 seconds as a margin to process token and this time varies for every user
constantUsersPerSec(5) during (5 seconds))) // fire 5 requests per second for 5 seconds which will result in 25 (5*5) requests and overall 26 requests when the report gets generated (because we have 1 request for auth token and 25 requests of our intended API (25+1 = 26)
}`
Sessions are per user and no session data is shared between users. So while you have 1 user running your 'auth' scenario and saving the token, it is two different users that run 'scn' and they don't have access to the session values of the auth user.
It's not recommended practice, but you can solve this by pushing the auth token into a regular scala var and the setting this in the auth scenario and reading it in the main scenario - you just need to be sure that auth always completes before you inject any other users.
var token: String = ""
then in the auth scenario, have a step at the end such as
.exec(session => {
token = session("access").as[String]
session
})
then at the start of the scn scenario have a step to set the session variable
.exec(session.set("access", token))
I've used this pattern in the past and it works, but I'm sure there are nicer ways to do it
#Tarun,
When I did it, I had the 'exec' in my scenario, rather than the set up, and used the following syntax:
val dataToUse = feed(testData)
.exec(session => session.set("access", token))
.exec(http("")
.post("*the_URL_to_send_to)*")
.headers(headers_10.toMap)
.body(RawFileBody("${filePath}")).asJson
.check(status.is(200))
)
As mentioned in the comments in the previous discussion, this was because I was using a later version of gatling and the 'get' method was no longer part of the session api.
My full solution was as follows - note that are a number of things to look out for that might not apply to your solution. I used an object, as it just made things clearer in my mind for what I was trying to do! Also, some of the imports are probably redundant, as I included them as part of scattergun approach to finding something that worked!
Finally, I basically list the contents of a directory, and cycle through the files listed in it, using each one as a feeder. You look as if you're using a literal template of json, so probably don't need that, but I thought I would include it for completeness, as it's quite handy - if you change the format of your json, you don't need to mess around changing the template in the simulation, you just clear the directory and drop examples of the new format in there and away you go! :
package myTest
import io.gatling.core.Predef._
import io.gatling.http.Predef._
import scala.concurrent.duration._
import scala.collection.JavaConversions._
import java.io.File
import java.io.FileNotFoundException
class myTestSimulation extends Simulation {
val httpConf = http
.baseUrl("*your_base_URL*")
.acceptHeader("application/json") // Here are the common headers
.doNotTrackHeader("1")
.acceptLanguageHeader("en-UK,en;q=0.5")
.acceptEncodingHeader("gzip, deflate")
.userAgentHeader("Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0")
.shareConnections
val header = Map("Content-Type" -> """application/x-www-form-urlencoded""");
private var token = ""
val auth = scenario("Retrieve Token")
.exec(
http("POST OAuth Req")
.post("*URL_for_Token*")
.formParam("resource", "*your_resource_value*")
.formParam("grant_type", "*your_grant_type*")
.formParam("client_secret", "*your_client_secret_value*")
.formParam("client_id", "*your_client_id_value*")
.headers(header)
.check(status.is(200)).check(jsonPath("$.access_token").find.saveAs("access")))
.exec{session => { token = session("access").as[String]
session}}
object myTestObject {
var headers_10 = scala.collection.mutable.Map("Content-Type" -> "application/json; charset=ISO-8859-1", "Authorization" -> "Bearer ${access}")
val testData = Iterator.continually(
new File("*pathway_to_file*") match {
case d if d.isDirectory => d.listFiles.map(f => Map("filePath" -> f.getPath))
case _ => throw new FileNotFoundException("Samples path must point to directory")
}).flatten
val myTestObjectMethod = feed(testData)
.exec(session => session.set("access", token))
.exec(http("")
.post("*the_URL_to_send_to(don't_forget_that_base_URL_above_is_automatically_stuck_to_the_front_of_this!)*")
.headers(headers_10.toMap)
.body(RawFileBody("${filePath}")).asJson
.check(status.is(200))
)
}
val scn = scenario("my_actual_load_test")
.exec(myTestSimulation.myTestObject)
setUp(
auth.inject(constantUsersPerSec(1) during (1 seconds)), // fire 1 requests per second for 1 second to retrieve token
scn.inject(nothingFor(2 seconds), // waits 2 seconds as a margin to process token
constantUsersPerSec(50) during (300 seconds) // fire 50 requests per second for 300 seconds
).protocols(httpConf))
.assertions(global.responseTime.max.lt(500)) // set max acceptable response time
.assertions(forAll.failedRequests.percent.lte(1)) // less than 1% of tests should fail
.assertions(global.responseTime.mean.lte(100)) // set average response time
}
I mean, I've probably made a typo somewhere along the line as I removed the sensitive stuff from it, but hopefully that will do what you need.