diff --git a/build.gradle b/build.gradle index 2df553a..e2dedb6 100644 --- a/build.gradle +++ b/build.gradle @@ -19,7 +19,10 @@ tasks.withType(io.gatling.gradle.GatlingRunTask) { jvmArgs = [ '--add-opens=java.base/java.lang=ALL-UNNAMED', "-Dtarget.baseUrl=${findProperty('baseUrl') ?: 'http://localhost:8080'}", - "-Dgatling.authToken=${findProperty('authToken') ?: System.getenv('GATLING_AUTH_TOKEN') ?: ''}" + "-Dgatling.authToken=${findProperty('authToken') ?: System.getenv('GATLING_AUTH_TOKEN') ?: ''}", + "-Dhealthz.path=${findProperty('healthzPath') ?: '/health'}", + "-DmaxUsers=${findProperty('maxUsers') ?: '10'}", + "-DrampDuration=${findProperty('rampDuration') ?: '60'}" ] } diff --git a/src/gatling/scala/base/BaseSimulation.scala b/src/gatling/scala/base/BaseSimulation.scala index 1011f40..39bb3d2 100644 --- a/src/gatling/scala/base/BaseSimulation.scala +++ b/src/gatling/scala/base/BaseSimulation.scala @@ -1,16 +1,10 @@ package base import io.gatling.core.Predef._ +import io.gatling.core.structure.ScenarioBuilder import io.gatling.http.Predef._ import io.gatling.http.protocol.HttpProtocolBuilder -/** - * Base class for all simulations. - * - * Reads runtime configuration from JVM system properties set by Gradle: - * -Dtarget.baseUrl=... (via -PbaseUrl=... on the Gradle command line) - * -Dgatling.authToken=... (via -PauthToken=... or env GATLING_AUTH_TOKEN) - */ abstract class BaseSimulation extends Simulation { protected val baseUrl: String = @@ -23,4 +17,21 @@ abstract class BaseSimulation extends Simulation { .baseUrl(baseUrl) .header("Authorization", s"Bearer $authToken") .header("Accept", "application/json") + .header("Content-Type", "application/json") + + protected def scenarioFromEndpoint(endpoint: base.Endpoint): ScenarioBuilder = { + val base = endpoint.method.toUpperCase match { + case "GET" => http(endpoint.name).get(endpoint.path) + case "POST" => http(endpoint.name).post(endpoint.path) + case "PUT" => http(endpoint.name).put(endpoint.path) + case "DELETE" => http(endpoint.name).delete(endpoint.path) + case "PATCH" => http(endpoint.name).patch(endpoint.path) + } + val withBody = endpoint.body.fold(base)(b => base.body(StringBody(b))) + scenario(endpoint.name).exec( + withBody + .check(status.is(endpoint.expectedStatus)) + .check(responseTimeInMillis.lte(endpoint.maxResponseTimeMs)) + ) + } } diff --git a/src/gatling/scala/base/Endpoint.scala b/src/gatling/scala/base/Endpoint.scala new file mode 100644 index 0000000..f7ccecc --- /dev/null +++ b/src/gatling/scala/base/Endpoint.scala @@ -0,0 +1,10 @@ +package base + +case class Endpoint( + name: String, + method: String, + path: String, + body: Option[String] = None, + expectedStatus: Int = 200, + maxResponseTimeMs: Int = 5000 +) diff --git a/src/gatling/scala/endpoints/BoardEndpoints.scala b/src/gatling/scala/endpoints/BoardEndpoints.scala new file mode 100644 index 0000000..ae2057b --- /dev/null +++ b/src/gatling/scala/endpoints/BoardEndpoints.scala @@ -0,0 +1,15 @@ +package endpoints + +import base.Endpoint + +object BoardEndpoints { + + val createGame: Endpoint = Endpoint( + name = "Create Game", + method = "POST", + path = "/api/board/game/", + expectedStatus = 201 + ) + + val all: List[Endpoint] = List(createGame) +} diff --git a/src/gatling/scala/simulations/LoadTestSimulation.scala b/src/gatling/scala/simulations/LoadTestSimulation.scala new file mode 100644 index 0000000..9da4f82 --- /dev/null +++ b/src/gatling/scala/simulations/LoadTestSimulation.scala @@ -0,0 +1,20 @@ +package simulations + +import base.BaseSimulation +import endpoints.BoardEndpoints +import io.gatling.core.Predef._ + +import scala.concurrent.duration._ + +class LoadTestSimulation extends BaseSimulation { + + private val maxUsers = sys.props.getOrElse("maxUsers", "10").toInt + private val rampDuration = sys.props.getOrElse("rampDuration", "60").toInt + + setUp( + BoardEndpoints.all.map { endpoint => + scenarioFromEndpoint(endpoint) + .inject(rampUsers(maxUsers).during(rampDuration.seconds)) + }: _* + ).protocols(httpProtocol) +} diff --git a/src/gatling/scala/simulations/SmokeTestSimulation.scala b/src/gatling/scala/simulations/SmokeTestSimulation.scala index e37ad42..730bb7b 100644 --- a/src/gatling/scala/simulations/SmokeTestSimulation.scala +++ b/src/gatling/scala/simulations/SmokeTestSimulation.scala @@ -1,35 +1,13 @@ package simulations import base.BaseSimulation +import endpoints.BoardEndpoints import io.gatling.core.Predef._ -import io.gatling.http.Predef._ -/** - * Smoke test: verifies the cluster is reachable and the bearer token is accepted. - * - * Runs a single virtual user that hits GET /healthz and asserts: - * - HTTP 200 - * - Response time under 2 000 ms - * - * Use this as a reference when writing new simulations. - * - * Run: - * ./gradlew gatlingRun -PbaseUrl=http:// -PauthToken= - */ class SmokeTestSimulation extends BaseSimulation { - private val healthzPath: String = - sys.props.getOrElse("healthz.path", "/healthz") - - private val scn = scenario("Smoke Test") - .exec( - http(s"GET $healthzPath") - .get(healthzPath) - .check(status.is(200)) - .check(responseTimeInMillis.lte(2000)) - ) - setUp( - scn.inject(atOnceUsers(1)) + scenarioFromEndpoint(BoardEndpoints.createGame) + .inject(atOnceUsers(1)) ).protocols(httpProtocol) }