summaryrefslogtreecommitdiff
path: root/load-test/user-files/simulations/rinhabackend/RinhaBackendCrebitosSimulation.scala
diff options
context:
space:
mode:
authorMateus Cruz <mateuscolvr@gmail.com>2024-02-05 21:55:58 -0300
committerMateus Cruz <mateuscolvr@gmail.com>2024-02-05 21:55:58 -0300
commitbd700d5098c6b51db63cdabda81d676f5c354eda (patch)
treeaad4c459f89f602f2ebdc5aeb350893cba0d15b1 /load-test/user-files/simulations/rinhabackend/RinhaBackendCrebitosSimulation.scala
parent00ade6066e9dd88c65b176bf4788eea4c3e1a15d (diff)
test: add gatling test
Diffstat (limited to 'load-test/user-files/simulations/rinhabackend/RinhaBackendCrebitosSimulation.scala')
-rw-r--r--load-test/user-files/simulations/rinhabackend/RinhaBackendCrebitosSimulation.scala237
1 files changed, 237 insertions, 0 deletions
diff --git a/load-test/user-files/simulations/rinhabackend/RinhaBackendCrebitosSimulation.scala b/load-test/user-files/simulations/rinhabackend/RinhaBackendCrebitosSimulation.scala
new file mode 100644
index 0000000..1ad31cb
--- /dev/null
+++ b/load-test/user-files/simulations/rinhabackend/RinhaBackendCrebitosSimulation.scala
@@ -0,0 +1,237 @@
+import scala.concurrent.duration._
+
+import scala.util.Random
+
+import util.Try
+
+import io.gatling.commons.validation._
+import io.gatling.core.session.Session
+import io.gatling.core.Predef._
+import io.gatling.http.Predef._
+
+
+class RinhaBackendCrebitosSimulation
+ extends Simulation {
+
+ def randomClienteId() = Random.between(1, 5 + 1)
+ def randomValorTransacao() = Random.between(1, 10000 + 1)
+ def randomDescricao() = Random.alphanumeric.take(10).mkString
+ def randomTipoTransacao() = Seq("c", "d", "d")(Random.between(0, 2 + 1)) // not used
+ def toInt(s: String): Option[Int] = {
+ try {
+ Some(s.toInt)
+ } catch {
+ case e: Exception => None
+ }
+ }
+
+ val validarConsistenciaSaldoLimite = (valor: Option[String], session: Session) => {
+ /*
+ Essa função é frágil porque depende que haja uma entrada
+ chamada 'limite' com valor conversível para int na session
+ e também que seja encadeada com com jmesPath("saldo") para
+ que 'valor' seja o primeiro argumento da função validadora
+ de 'validate(.., ..)'.
+
+ =============================================================
+
+ Nota para quem não tem experiência em testes de performance:
+ O teste de lógica de saldo/limite extrapola o que é comumente
+ feito em testes de performance apenas por causa da natureza
+ da Rinha de Backend. Evite fazer esse tipo de coisa em
+ testes de performance, pois não é uma prática recomendada
+ normalmente.
+ */
+
+ val saldo = valor.flatMap(s => Try(s.toInt).toOption)
+ val limite = toInt(session("limite").as[String])
+
+ (saldo, limite) match {
+ case (Some(s), Some(l)) if s.toInt < l.toInt * -1 => Failure("Limite ultrapassado!")
+ case (Some(s), Some(l)) if s.toInt >= l.toInt * -1 => Success(Option("ok"))
+ case _ => Failure("WTF?!")
+ }
+ }
+
+ val httpProtocol = http
+ .baseUrl("http://localhost:9999")
+ .userAgentHeader("Agente do Caos - 2024/Q1")
+
+ val debitos = scenario("débitos")
+ .exec {s =>
+ val descricao = randomDescricao()
+ val cliente_id = randomClienteId()
+ val valor = randomValorTransacao()
+ val payload = s"""{"valor": ${valor}, "tipo": "d", "descricao": "${descricao}"}"""
+ val session = s.setAll(Map("descricao" -> descricao, "cliente_id" -> cliente_id, "payload" -> payload))
+ session
+ }
+ .exec(
+ http("débitos")
+ .post(s => s"/clientes/${s("cliente_id").as[String]}/transacoes")
+ .header("content-type", "application/json")
+ .body(StringBody(s => s("payload").as[String]))
+ .check(
+ status.in(200, 422),
+ status.saveAs("httpStatus"))
+ .checkIf(s => s("httpStatus").as[String] == "200") { jmesPath("limite").saveAs("limite") }
+ .checkIf(s => s("httpStatus").as[String] == "200") {
+ jmesPath("saldo").validate("ConsistenciaSaldoLimite - Transação", validarConsistenciaSaldoLimite)
+ }
+ )
+
+ val creditos = scenario("créditos")
+ .exec {s =>
+ val descricao = randomDescricao()
+ val cliente_id = randomClienteId()
+ val valor = randomValorTransacao()
+ val payload = s"""{"valor": ${valor}, "tipo": "c", "descricao": "${descricao}"}"""
+ val session = s.setAll(Map("descricao" -> descricao, "cliente_id" -> cliente_id, "payload" -> payload))
+ session
+ }
+ .exec(
+ http("créditos")
+ .post(s => s"/clientes/${s("cliente_id").as[String]}/transacoes")
+ .header("content-type", "application/json")
+ .body(StringBody(s => s("payload").as[String]))
+ .check(
+ status.in(200),
+ jmesPath("limite").saveAs("limite"),
+ jmesPath("saldo").validate("ConsistenciaSaldoLimite - Transação", validarConsistenciaSaldoLimite)
+ )
+ )
+
+ val extratos = scenario("extratos")
+ .exec(
+ http("extratos")
+ .get(s => s"/clientes/${randomClienteId()}/extrato")
+ .check(
+ jmesPath("saldo.limite").saveAs("limite"),
+ jmesPath("saldo.total").validate("ConsistenciaSaldoLimite - Extrato", validarConsistenciaSaldoLimite)
+ )
+ )
+
+ val saldosIniciaisClientes = Array(
+ Map("id" -> 1, "limite" -> 1000 * 100),
+ Map("id" -> 2, "limite" -> 800 * 100),
+ Map("id" -> 3, "limite" -> 10000 * 100),
+ Map("id" -> 4, "limite" -> 100000 * 100),
+ Map("id" -> 5, "limite" -> 5000 * 100),
+ )
+
+ val criteriosClientes = scenario("validações")
+ .feed(saldosIniciaisClientes)
+ .exec(
+ /*
+ Os valores de http(...) essão duplicados propositalmente
+ para que sejam agrupados no relatório e ocupem menos espaço.
+ O lado negativo é que, em caso de falha, pode não ser possível
+ saber sua causa exata.
+ */
+ http("validações")
+ .get("/clientes/#{id}/extrato")
+ .check(
+ status.is(200),
+ jmesPath("saldo.limite").ofType[String].is("#{limite}"),
+ jmesPath("saldo.total").ofType[String].is("0")
+ )
+ )
+ .exec(
+ http("validações")
+ .get("/clientes/6/extrato")
+ .check(status.is(404))
+ )
+ .exec(
+ http("validações")
+ .post("/clientes/1/transacoes")
+ .header("content-type", "application/json")
+ .body(StringBody(s"""{"valor": 1, "tipo": "c", "descricao": "toma"}"""))
+ .check(
+ status.in(200),
+ jmesPath("limite").saveAs("limite"),
+ jmesPath("saldo").validate("ConsistenciaSaldoLimite - Transação", validarConsistenciaSaldoLimite)
+ )
+ )
+ .exec(
+ http("validações")
+ .post("/clientes/1/transacoes")
+ .header("content-type", "application/json")
+ .body(StringBody(s"""{"valor": 1, "tipo": "d", "descricao": "devolve"}"""))
+ .check(
+ status.in(200),
+ jmesPath("limite").saveAs("limite"),
+ jmesPath("saldo").validate("ConsistenciaSaldoLimite - Transação", validarConsistenciaSaldoLimite)
+ )
+ )
+ .exec(
+ http("validações")
+ .get("/clientes/1/extrato")
+ .check(
+ jmesPath("ultimas_transacoes[0].descricao").ofType[String].is("devolve"),
+ jmesPath("ultimas_transacoes[0].tipo").ofType[String].is("d"),
+ jmesPath("ultimas_transacoes[0].valor").ofType[Int].is("1"),
+ jmesPath("ultimas_transacoes[1].descricao").ofType[String].is("toma"),
+ jmesPath("ultimas_transacoes[1].tipo").ofType[String].is("c"),
+ jmesPath("ultimas_transacoes[1].valor").ofType[Int].is("1")
+ )
+ )
+ .exec(
+ http("validações")
+ .post("/clientes/1/transacoes")
+ .header("content-type", "application/json")
+ .body(StringBody(s"""{"valor": 1.2, "tipo": "d", "descricao": "devolve"}"""))
+ .check(status.in(422))
+ )
+ .exec(
+ http("validações")
+ .post("/clientes/1/transacoes")
+ .header("content-type", "application/json")
+ .body(StringBody(s"""{"valor": 1, "tipo": "x", "descricao": "devolve"}"""))
+ .check(status.in(422))
+ )
+ .exec(
+ http("validações")
+ .post("/clientes/1/transacoes")
+ .header("content-type", "application/json")
+ .body(StringBody(s"""{"valor": 1, "tipo": "c", "descricao": "123456789 e mais um pouco"}"""))
+ .check(status.in(422))
+ )
+ .exec(
+ http("validações")
+ .post("/clientes/1/transacoes")
+ .header("content-type", "application/json")
+ .body(StringBody(s"""{"valor": 1, "tipo": "c", "descricao": ""}"""))
+ .check(status.in(422))
+ )
+ .exec(
+ http("validações")
+ .post("/clientes/1/transacoes")
+ .header("content-type", "application/json")
+ .body(StringBody(s"""{"valor": 1, "tipo": "c", "descricao": null}"""))
+ .check(status.in(422))
+ )
+
+ /*
+ Separar créditos e débitos dá uma visão
+ melhor sobre como as duas operações se
+ comportam individualmente.
+ */
+ setUp(
+ criteriosClientes.inject(
+ atOnceUsers(1)
+ ).andThen(
+ debitos.inject(
+ rampUsersPerSec(1).to(220).during(2.minutes),
+ constantUsersPerSec(220).during(2.minutes)
+ ),
+ creditos.inject(
+ rampUsersPerSec(1).to(110).during(2.minutes),
+ constantUsersPerSec(110).during(2.minutes)
+ ),
+ extratos.inject(
+ rampUsersPerSec(1).to(10).during(2.minutes),
+ constantUsersPerSec(10).during(2.minutes)
+ )
+ )
+ ).protocols(httpProtocol)
+}