Passing Values to Multiple Files in Gatling - scala

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

Related

Build Jenkins pipeline using HttpRequest

I try to execute a jenkins pipeline from a Scala app, but the authentication is required. So, I think before this request, I need to call the auth endpoint and get a token, then assign it on the headers.
val userToken = "user_token_generated_in_jenkins"
val pipelineToken = "pipeline_token"
val pipelineName = "HttpRequestTest"
// test1
val baseUrl = s"http://jenkins_address/view/Development/job/$pipelineName/build?token=$pipelineToken"
val response = Http(baseUrl).headers("token" -> "tried_a_valid_token_auth").asString
// test2
val url = s"http://auto:$userToken#jenkins_address/job/$pipelineName/build?token=$pipelineToken"
val response2 = Http(url).asString
If I can be sure that first test is wrong, why for test2, where I use the user token (generated in Users manager), it asks me to use extra token (generated by authentication)? Is a double verification and useless in my opinion.
As an emergency solution, I can execute the pipeline with curl using sys.process. But I prefer to solve it using Http library because I want to use generated token and not a specific username in my project code.
// test3
import sys.process._
val data = List("curl", "-u", "username:password", baseUrl).!!
How can I execute an HttpRequest using Jenkins User Token?
Thanks
If your Http(..) is from Akk-HTTP, the solution could be derived from here:
val authorization = headers.Authorization(BasicHttpCredentials("username", "password"))
HttpRequest(
PUT,
uri = "/user",
entity = HttpEntity(`text/plain` withCharset `UTF-8`, userData),
headers = List(authorization),
protocol = `HTTP/1.0`)
In case of scalaj-http it could be derived from here
With applying to your existing code, it should be like this:
Http(baseUrl).auth("username", "password").asString
By the way
I want to use generated token and not a specific username in my
project code
It is not a problem. You can construct your process' auth string dynamically. You are able to get the credentials from somewhere in the "native" Http solution, right? So you can construct your sys.process with the same getted-from-somewhere credentials by concatinating strings or string interpolation:
// test3
import sys.process._
val (username, password) = gettingFromSomewhereCreadentials()
val data = List("curl", "-u", s"$username:$password", baseUrl).!!

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.

Not able to extract values in Gatling

I have written 2 sets of Gatling code. My usecase in example 1 is make a post call. extract a value. make a get call and use the extracted value in header.
My usecase in example 2 is make a post call, extract a value, make a get call use the extracted value in cookie.
Example 1
val login = http("Login")
.post("login")
.body(StringBody("""{"username": "foo", "password": "bar"}""")).asJSON
.check(status.is(200), jsonPath("$..response.id").ofType[String].saveAs("id"))
val get = http("get")
.get("foo")
.header("token", "$id")
.check(status.is(200), jsonPath("$..response").exists)
var id = ""
val scn = scenario("scenario")
.exec(login)
.exec(session => {
id = session("id").as[String].trim
println("+++++++" + id)
session}
)
.pause(3)
.exec(get)
When I run this code I see that the print statement above prints the correct ID. The server throws 403 on the get call because the ID is not being set correctly. If I take any of the printed values and then remote $id and replace it with that. the test runs correctly.
So how do I access the saved variable?
Example 2
val login = http("Login")
.post("login")
.body(StringBody("""{"username": "foo", "password": "bar"}""")).asJSON
.check(status.is(200), jsonPath("$..response.id").ofType[String].saveAs("id"))
val get = http("get")
.get("foo")
.check(status.is(200), jsonPath("$..response").exists)
val testCookie = scenario("test-cookie")
.exec(login)
.pause(3)
.exec(addCookie(Cookie("foo_cookie", "$id")))
.exec(get)
Here also the value is not extracted successfully and I get a 403 when I run it because the "foo_cookie" was not set correctly and the server will throw a 403 if the cookie is not found. (in postman I can make the same call work by specifying the cookie correctly)
I was able to resolve the issue. The problem was this line
.header("token", s"$id")
In Scala $id and ${id} are same but it appears that in Gatling they are not.
when I replaced my code to
.header("token", "${id}")
note that there is no "s" behind the string.
it worked!

PlayFramework Testing: Uploading File in Fake Request Errors

I want to test my method, which requires uploading a file. It is initialized like this:
val tempFile = TemporaryFile(new java.io.File("/home/ophelia/Desktop/students"))
val part = FilePart[TemporaryFile](
key = "students",
filename = "students",
contentType = Some("text/plain"),
ref = tempFile)
val files = Seq[FilePart[TemporaryFile]](part)
val formData = MultipartFormData(
dataParts = Map(),
files = Seq(part),
badParts = Seq(),
missingFileParts = Seq())
I pass it into the FakeRequest:
val result = route(
FakeRequest(POST, "/api/courses/"+"4f3c4ec9-46bf-4a05-a0b2-886c2040f2f6"+"/import" )
.withHeaders("Authorization" -> ("Session " + testSessionA.id.string))
.withMultipartFormDataBody(formData)
)
But when I run the test I get the following error:
Cannot write an instance of play.api.mvc.AnyContentAsMultipartFormData to HTTP response. Try to define a Writeable[play.api.mvc.AnyContentAsMultipartFormData]
What am I doing wrong and how to fix it? I looked on the internet, I didnt find any useful way to understand and resolve this problem.
It's important to remember that http requests are entirely text. route() takes an implicit Writeable to convert the body type of the provided request into text. Without the right Writeable, there is no way to know how to turn MultipartFormData into a request body.
There doesn't seem to be a Writeable for MultipartFormData, but you can provide your own. jroper has a great Writeable you could use for reference. (EDIT: That code is buggy, here's a working Writeable for AnyContentAsMultipartFormData)
Once you have your Writeable, you will need to make it accessible to your call to route(). Bear in mind, you currently have a FakeRequest[AnyContentAsMultipartFormData], not a FakeRequest[MultipartFormData]. You can either convert your request first:
val request = FakeRequest(POST,
"/api/courses/"+"4f3c4ec9-46bf-4a05-a0b2-886c2040f2f6"+"/import" )
.withHeaders("Authorization" -> ("Session "))
.withMultipartFormDataBody(formData)
route(request.map(_.mdf).asInstanceOf[FakeRequest[MultipartFormData[TemporaryFile]]])
or make your Writeable a Writeable[AnyContentAsMultipartFormData].
route for a given Request[T] requires an implicit parameter of type Writeable[T] that knows how to serialize the request body, because it will actually call the controller action just like an actual web request would, by pushing bytes onto it.
The problem is that there is no Writeable[MultipartFormData] predefined (you can see which are in play.api.test.Writeables).
This means you basically have two options:
write your own Writeable that serializes a MultipartFormData into bytes
Skip the routing part and call the action directly instead, like in the accepted answer in Play Framework Testing using MultipartFormData in a FakeRequest. This way of testing actions takes a shortcut and does not actually serialize and deserialize the request.
IMHO the first option is way too much pain for the gain, but if you go down that road, maybe contribute it to play when you succeed.
One of the possible solutions is to use wsUrl. For example
"File uploading action" should {
"upload sent file and result in ID" in {
val file = Paths.get(getClass.getResource("/1.txt").toURI)
val action = wsUrl("/upload").post(Source.single(FilePart("file", "hello.txt", Option("text/plain"), FileIO.fromPath(file))))
val res = Await.result(action, timeout)
res.status mustBe OK
res.body contains "123"
}
}

Source.fromURL not throwing an exception for an invalid URL

I am attempting to retrieve the sizes various websites whose URL will be passed to my script, but I'm not getting an exception when I pass an invalid URL, instead simply getting a very small page. I'm using Source.fromURL, and I get the following results:
thisIsClearlyABoggusURLThatCantPossiblyLeadAnyway 1052
www.bbc.co.uk 113871
The first one, as it says, shouldn't have anything in it, but it does. My script is as follows:
def main( args:Array[String] ){
val tasks = for(arg <- args) yield future {
try {
println(arg + " " + Source.fromURL( attachPrefix(arg) ).length)
} catch {
case e : java.net.UnknownHostException => println(arg + " *")
}
}
awaitAll(20000L, tasks: _*)
}
def attachPrefix( url:String ) = url.slice(0, 4) match {
case "http" => url
case "www." => "http://" + url
case _ => "http://www." + url
}
Each argument is being passed into the function attachPrefix to ensure it has the necessary prefix before being used. This problem has only come about since I started passing the url in as a parameter instead of mapping it onto the arg, which is what I was doing earlier with
args map attachPrefix
What's the difference between the two, and why is my current one giving such behaviour?
You can use the Source.fromURL(URI) signature. Creating a URI will effectively validate the URL as documented here. However, in this case, the URL http://www.thisIsClearlyABoggusURLThatCantPossiblyLead‌​Anyway is valid as far as the URI is concerned. On the other hand, the UrlValidator suggested by om-nom-nom considers it invalid, because the top level domain segment has more than 4 characters which is already out of date. I don't know of any entirely Scala validation libraries or why this would be a requirement, but you could try using a regular expression for validation. For example, this will catch your example, because the top level domain exceeds 6 letters:
val re = """^(https?://)?(([\w!~*'().&=+$%-]+: )?[\w!~*'().&=+$%-]+#)?(([0-9]{1,3}\.){3}[0-9]{1,3}|([\w!~*'()-]+\.)*([\w^-][\w-]{0,61})?[\w]\.[a-z]{2,6})(:[0-9]{1,4})?((/*)|(/+[\w!~*'().;?:#&=+$,%#-]+)+/*)$""".r
re.pattern.matcher("http://google.com").matches // true
re.pattern.matcher("http://www.thisIsClearlyABoggusURLThatCantPossiblyLeadAnyway").matches // false