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

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(???)
)
)

Related

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

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))
}

Modify contentHeaders of Swagger Codegen methods in kotlin

I'm using swagger codegen for my REST API calls. For authentication purposes i need to send a session-token within the headers of every request. This is currently done, via APIClients' defaultHeaders
open class ApiClient(val baseUrl: String) {
companion object {
...
#JvmStatic
var defaultHeaders: Map<String, String> by ApplicationDelegates.setOnce(mapOf(ContentType to JsonMediaType, Accept to JsonMediaType))
...
}
}
The way swagger generates the code, these headers can only be modified once.
ApiClient.defaultHeaders += mapOf("Authorization" to userSession!!.idToken.jwtToken)
The problem with this is, that i cannot change the token (e.g. because another user logged in within the application lifetime). Looking deeper into the generated code, before each request is sent, a merge of both defaultHeaders and requestConfig.headers (=contentHeaders) is being made.
inline protected fun <reified T: Any?> request(requestConfig: RequestConfig, body : Any? = null): ApiInfrastructureResponse<T?> {
...
val headers = defaultHeaders + requestConfig.headers
...
}
The given RequestConfig object comes from every api call. However it is not possible to change these contentHeaders. Also they are empty by default.
fun someAPIRestCall(someParam: kotlin.String) : Unit {
val localVariableBody: kotlin.Any? = type
val localVariableQuery: MultiValueMap = mapOf()
val contentHeaders: kotlin.collections.Map<kotlin.String,kotlin.String> = mapOf() // THESE WILL BE MERGED WITH defaultHeaders
val acceptsHeaders: kotlin.collections.Map<kotlin.String,kotlin.String> = mapOf("Accept" to "application/json")
val localVariableHeaders: kotlin.collections.MutableMap<kotlin.String,kotlin.String> = mutableMapOf()
localVariableHeaders.putAll(contentHeaders)
localVariableHeaders.putAll(acceptsHeaders)
val localVariableConfig = RequestConfig(
RequestMethod.POST,
"someEndpointURL"),
query = localVariableQuery,
headers = localVariableHeaders // THESE WILL BE MERGED WITH defaultHeaders
)
val response = request<Unit>(
localVariableConfig,
localVariableBody
)
...
}
Is it possible to tell swagger-codegen to include some kind of parameter to the generated method signature to add values to those contentHeaders?
EDIT:
This is the current code-gen call within my gradle build chain
task generateSwagger(type: JavaExec) {
main = "-jar"
args "swagger-codegen-cli-2.4.7.jar", "generate", "-i", "./swagger_core.yml", "-l", "kotlin", "-o", "./tmp/RestApi", "--type-mappings", "number=kotlin.Long"
}
By now, i found a solution, that is more of a hack, but it works.
As i am using gradle to build the app, i introduced a task, that changes the generated swagger code, before it actually compiles.
task editAPISources {
def token = "Map<String, String> by ApplicationDelegates.setOnce(mapOf(ContentType to JsonMediaType, Accept to JsonMediaType))"
def value = "MutableMap<String, String> = mutableMapOf(ContentType to JsonMediaType, Accept to JsonMediaType)"
def file = new File("./app/tmp/RestApi/src/main/kotlin/io/swagger/client/infrastructure/ApiClient.kt")
def newConfig = file.text.replace(token, value)
file.write newConfig
}
The result is a now changeable header :=
#JvmStatic
var defaultHeaders: MutableMap<String, String> = mutableMapOf(ContentType to JsonMediaType, Accept to JsonMediaType)

Passing Values to Multiple Files in Gatling

I am designing a software test using Gatling for my company's web based application. After many iterations, I have come to a design that I believe will make it easy for testers at my company to easily change parameters as needed, but it is quite complex. The main file which contains the scenario, setup, and simulation calls is all contained in one file that then connects to a different file via methods for each of the other functions we are testing.
My problem is when logging into our API with a request, using one of the function files, it sends a response that includes a session token. It is important that I am able to pass the value of that token back to the main file, so that I can use it as a parameter in other function methods. I am having a problem passing the actual value of this token to the main file.
Here is the sample code for the method:
object BasicLogin {
def login ( hostparam:String, controlparam:String, usernameparam:String, passwordparam:String) : (ChainBuilder, String) = {
val user = usernameparam
val password = passwordparam
val host = hostparam
val controlid = controlparam
val logintype = "user"
val jsonrequest = """{"logintype":"""" + logintype + """",
"controlid":"""" + controlid + """",
"user":"""" + user + """",
"password":"""" + password + """",
"host":"""" + host + """"
}"""
val loginChain = exec(http("Login")
.post("sample.url.com")
.body(StringBody(jsonrequest))
.asJSON
.check(jsonPath("$..result").is("true"))
.check(jsonPath("$..session")
.find
.saveAs("currentSession")))
val accessToken: String = exec(session => {
val sessionid = session("currentSession").as[String]
println("token: " + sessionid)
session })
.toString
return (loginChain, accessToken)
}
}
Here is my code calling the method for login:
val host = "IPaddress"
val controlid = "IDcontroller"
val username = "JDoe"
val password = "TerriblePassword"
val result = BasicLogin.login(host, controlid, username, password)
val basiclogin:ChainBuilder = result._1
val currentSession:String = result._2
println("Session: " + currentSession)
println("Login: " + basiclogin)
And here is what I get in response:
Session: ChainBuilder(List(io.gatling.core.action.builder.SessionHookBuilder#6f70f32f))
Login: ChainBuilder(List(io.gatling.http.action.sync.HttpRequestActionBuilder#3dd69f5a))
I have searched in many different places, and have not found much in terms of similar issues. I know Gatling is run in multi-cast, and not sure if this is even possible. I have utilized the save as method, and tried calling the value using Gatling DSL, but no luck. I originally was trying to use just values without methods, but that failed miserably. I have also tried the set method and mapping, but neither of these worked either.
So now I am trying to get a value from the response, and return it back to the main file using the method. It runs, but it returns the chainbuilder.
Any advice?
Rob

Akka-http & scribe for linkedin API: set/get cookie without session (scala)

I am using akka-http for bulding a REST API. (I am new to build REST web services).
I don't know how I can get and set cookie without using a session. This cookie must contain encrypt token access. I don't use Play or spray.
My code for the moment is:
lazy val signin = path("signin") {
get {
/* create the OAuthService object with a callback URL*/
val service = buildService()
/* get the request token*/
val requestToken = service.getRequestToken
/* create the cookie */
val jwtCookieEncrypted = tokenUtil.createLinkedinTokenSecret(requestToken)
val cookie = HttpCookie("jwtTokenCookie", jwtCookieEncrypted)
/* making the user validate our requestToken by redirecting him to the following URL*/
val authURL = service.getAuthorizationUrl(requestToken)
redirect(authURL, StatusCodes.TemporaryRedirect)
}
}
lazy val callback = path("callback") {
// extract cookie with the jwtTokenCookie name
cookie("jwtTokenCookie") { cookiePair =>
complete(s"The logged in user is '${cookiePair.name}'")
}
get {
parameters('code, 'state) { (code, state) => // must come from cookie and not request parameters
/* create the OAuthService object with a callback URL*/
val service = buildService()
/* get the request token*/
val requestToken = new Token(code, state)
if(state == tokenUtil.decryptLinkedinToken(requestToken.getSecret).getOrElse("")) "continue" else "throw error"
val verifier = new Verifier(state)
/* get the access token
(need to exchange requestToken and verifier for an accessToken which is the one used to sign requests)*/
val accessToken = service.getAccessToken(requestToken, verifier)
logger.debug(accessToken.getRawResponse)
/* sign request*/
val ResourceUrl = Settings.LinkedIn.ResourceUrl
val request = new OAuthRequest(Verb.GET, ResourceUrl)
service.signRequest(accessToken, request)
val response = request.send
if (response.getCode == StatusCodes.OK.intValue) complete(response.getBody)
else complete(int2StatusCode(response.getCode))
}
}
}
signin ~ callback
Check the akka doc. In your response you can include the header. In your case, maybe with redirect it´s not so simple. But you could complete the signing request returning a 308 Http code with the Location Header pointing to your oauth2 Auth server.
Is it better ?
path("signin") {
get {
val service = buildService()
val requestToken = service.getRequestToken
val authURL = service.getAuthorizationUrl(requestToken)
val requestTokenCrypted = tokenUtil.createLinkedinToken(requestToken)
val cookie = HttpCookie("abcde", requestTokenCrypted.getSecret)
setCookie(cookie) {
complete(HttpResponse(
status = StatusCodes.TemporaryRedirect,
headers = List(Location(authURL))
))
}
}
}

Controller action returns "Invalid Json" when using a Fakerequest from a spec2 test

I am using playframework 2.6 and play-slick 0.8.0.
Action code:
def addCompany = Authenticated {
DBAction(parse.json) {
implicit rs => {
val newCompany = rs.request.body
val result = CompanyTable.insert(newCompany.as[Company])(rs.dbSession)
if(result > 0)
Ok("{\"id\":"+result+"}")
else
Ok("New company was not created.")
}
}
}
The Action is a composition of an Action that just checks for a valid session and the DBAction, which requires the request body to have a valid JSON object.
Test code:
"should create a Company from a Json request" in new InMemoryDB {
val newCompany = Company(name = "New Company1")
val fr = FakeRequest(POST, "/company")
.withSession(("email", "bob#villa.com"))
.withHeaders(CONTENT_TYPE -> "application/json")
.withJsonBody(Json.toJson(newCompany))
val action = controllers.CompanyController.addCompany
val result = action(fr).run
status(result) should be_==(OK)
(contentAsJson(result) \ "id").as[Long] should be_>(1L)
}
The InMemoryDB class is just a FakeApplication with a pre-populated in memory database.
The issue that I am having is that when the test runs the result is always a 400 with body content containing a message saying [Invalid Json]. When I call the service using curl with the same JSON body content, it works and the id is returned.
I decided to build a separate test project, and I used the activator to create a seed for the new project. I noticed that in the generated test that a different method of calling the action was used, so I switched my project to use this method. It worked, but I don't know why.
New code:
"should create a Company from a Json request" in new InMemoryDB {
val newCompany = Company(name = "New Company1")
val action = route(
FakeRequest(POST, "/company")
.withSession(("email", "bob#villa.com"))
.withHeaders(CONTENT_TYPE -> "application/json")
.withJsonBody(Json.toJson(newCompany))
).get
status(action) should be_==(OK)
(contentAsJson(action) \ "id").as[Long] should be_>(1L)
}
As you can see it uses a call to route instead of calling the controller.