1+ import http from 'k6/http' ;
2+ import { check , group , sleep } from 'k6' ;
3+ import { SharedArray } from 'k6/data' ;
4+
5+ const baseUrl = __ENV . BASE_URL || "http://localhost:9999" ;
6+
7+ // Helper functions
8+ const randomClienteId = ( ) => Math . floor ( Math . random ( ) * 5 ) + 1 ;
9+ const randomValorTransacao = ( ) => Math . floor ( Math . random ( ) * 10000 ) + 1 ;
10+ const randomDescricao = ( ) => {
11+ const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' ;
12+ return Array . from ( { length : 10 } , ( ) => chars [ Math . floor ( Math . random ( ) * chars . length ) ] ) . join ( '' ) ;
13+ } ;
14+
15+ // Validation function: checks that saldo is not below (limite * -1)
16+ const validateSaldoLimite = ( saldo , limite ) => {
17+ return saldo >= limite * - 1 ;
18+ } ;
19+
20+ // Shared initial client data (same as used in the validacoes folder)
21+ const saldosIniciaisClientes = new SharedArray ( 'clientes' , ( ) => [
22+ { id : 1 , limite : 1000 * 100 } ,
23+ { id : 2 , limite : 800 * 100 } ,
24+ { id : 3 , limite : 10000 * 100 } ,
25+ { id : 4 , limite : 100000 * 100 } ,
26+ { id : 5 , limite : 5000 * 100 } ,
27+ ] ) ;
28+
29+ export const options = {
30+ scenarios : {
31+ // Validation scenarios start at 0s to use fresh state
32+ validacoes : {
33+ executor : 'per-vu-iterations' ,
34+ vus : saldosIniciaisClientes . length ,
35+ iterations : 1 ,
36+ startTime : '0s' ,
37+ exec : 'validacoes' ,
38+ } ,
39+ cliente_nao_encontrado : {
40+ executor : 'per-vu-iterations' ,
41+ vus : 1 ,
42+ iterations : 1 ,
43+ startTime : '0s' ,
44+ exec : 'cliente_nao_encontrado' ,
45+ } ,
46+ // Load scenarios for debitos and creditos start after 10s
47+ debitos : {
48+ executor : 'ramping-vus' ,
49+ startVUs : 1 ,
50+ stages : [
51+ { duration : '2m' , target : 220 } ,
52+ { duration : '2m' , target : 220 } ,
53+ ] ,
54+ startTime : '10s' ,
55+ exec : 'debitos' ,
56+ } ,
57+ creditos : {
58+ executor : 'ramping-vus' ,
59+ startVUs : 1 ,
60+ stages : [
61+ { duration : '2m' , target : 110 } ,
62+ { duration : '2m' , target : 110 } ,
63+ ] ,
64+ startTime : '10s' ,
65+ exec : 'creditos' ,
66+ } ,
67+ // The extratos scenario is now executed only once with exactly 10 VUs
68+ extratos : {
69+ executor : 'per-vu-iterations' ,
70+ vus : 10 ,
71+ iterations : 1 ,
72+ startTime : '10s' ,
73+ exec : 'extratos' ,
74+ } ,
75+ } ,
76+ } ;
77+
78+ export function debitos ( ) {
79+ const payload = JSON . stringify ( {
80+ valor : randomValorTransacao ( ) ,
81+ tipo : 'd' ,
82+ descricao : randomDescricao ( ) ,
83+ } ) ;
84+
85+ const res = http . post (
86+ `${ baseUrl } /clientes/${ randomClienteId ( ) } /transacoes` ,
87+ payload ,
88+ { headers : { 'Content-Type' : 'application/json' } }
89+ ) ;
90+
91+ check ( res , {
92+ 'status 200 or 422' : ( r ) => [ 200 , 422 ] . includes ( r . status ) ,
93+ } ) ;
94+
95+ if ( res . status === 200 ) {
96+ check ( res , {
97+ 'Consistência saldo/limite' : ( r ) => {
98+ try {
99+ const saldo = r . json ( 'saldo' ) ;
100+ const limite = r . json ( 'limite' ) ;
101+ return validateSaldoLimite ( saldo , limite ) ;
102+ } catch ( e ) {
103+ return false ;
104+ }
105+ }
106+ } ) ;
107+ }
108+ }
109+
110+ export function creditos ( ) {
111+ const payload = JSON . stringify ( {
112+ valor : randomValorTransacao ( ) ,
113+ tipo : 'c' ,
114+ descricao : randomDescricao ( ) ,
115+ } ) ;
116+
117+ const res = http . post (
118+ `${ baseUrl } /clientes/${ randomClienteId ( ) } /transacoes` ,
119+ payload ,
120+ { headers : { 'Content-Type' : 'application/json' } }
121+ ) ;
122+
123+ check ( res , {
124+ 'status 200' : ( r ) => r . status === 200 ,
125+ } ) ;
126+
127+ if ( res . status === 200 ) {
128+ check ( res , {
129+ 'Consistência saldo/limite' : ( r ) => {
130+ try {
131+ const saldo = r . json ( 'saldo' ) ;
132+ const limite = r . json ( 'limite' ) ;
133+ return validateSaldoLimite ( saldo , limite ) ;
134+ } catch ( e ) {
135+ return false ;
136+ }
137+ }
138+ } ) ;
139+ }
140+ }
141+
142+ export function extratos ( ) {
143+ const res = http . get ( `${ baseUrl } /clientes/${ randomClienteId ( ) } /extrato` ) ;
144+
145+ check ( res , {
146+ 'status 200' : ( r ) => r . status === 200 ,
147+ } ) ;
148+
149+ if ( res . status === 200 ) {
150+ check ( res , {
151+ 'Consistência extrato' : ( r ) => {
152+ try {
153+ return validateSaldoLimite (
154+ r . json ( 'saldo.total' ) ,
155+ r . json ( 'saldo.limite' )
156+ ) ;
157+ } catch ( e ) {
158+ return false ;
159+ }
160+ }
161+ } ) ;
162+ }
163+ }
164+
165+ export function validacoes ( ) {
166+ // Each VU uses a client from the shared array based on __VU index.
167+ const index = ( __VU - 1 ) % saldosIniciaisClientes . length ;
168+ const cliente = saldosIniciaisClientes [ index ] ;
169+
170+ group ( 'Validações cliente' , ( ) => {
171+ // Confirm initial client state (saldo.total should be 0)
172+ let res = http . get ( `${ baseUrl } /clientes/${ cliente . id } /extrato` ) ;
173+ check ( res , {
174+ 'status 200' : ( r ) => r . status === 200 ,
175+ 'limite correto' : ( r ) => r . json ( 'saldo.limite' ) === cliente . limite ,
176+ 'saldo inicial 0' : ( r ) => r . json ( 'saldo.total' ) === 0 ,
177+ } ) ;
178+
179+ // Execute 2 transactions in sequence: a credit ("toma") then a debit ("devolve")
180+ res = http . post (
181+ `${ baseUrl } /clientes/${ cliente . id } /transacoes` ,
182+ JSON . stringify ( { valor : 1 , tipo : 'c' , descricao : 'toma' } ) ,
183+ { headers : { 'Content-Type' : 'application/json' } }
184+ ) ;
185+ check ( res , {
186+ 'status 200' : ( r ) => r . status === 200 ,
187+ 'Consistência saldo/limite' : ( r ) => validateSaldoLimite ( r . json ( 'saldo' ) , r . json ( 'limite' ) ) ,
188+ } ) ;
189+
190+ res = http . post (
191+ `${ baseUrl } /clientes/${ cliente . id } /transacoes` ,
192+ JSON . stringify ( { valor : 1 , tipo : 'd' , descricao : 'devolve' } ) ,
193+ { headers : { 'Content-Type' : 'application/json' } }
194+ ) ;
195+ check ( res , {
196+ 'status 200' : ( r ) => r . status === 200 ,
197+ 'Consistência saldo/limite' : ( r ) => validateSaldoLimite ( r . json ( 'saldo' ) , r . json ( 'limite' ) ) ,
198+ } ) ;
199+
200+ // Allow a pause for transactions to be properly processed
201+ sleep ( 1 ) ;
202+
203+ // Validate that the latest extrato shows the two transactions in the expected order.
204+ res = http . get ( `${ baseUrl } /clientes/${ cliente . id } /extrato` ) ;
205+ check ( res , {
206+ 'transações recentes' : ( r ) => {
207+ const transacoes = r . json ( 'ultimas_transacoes' ) ;
208+ return transacoes &&
209+ transacoes . length >= 2 &&
210+ transacoes [ 0 ] . descricao === 'devolve' &&
211+ transacoes [ 0 ] . tipo === 'd' &&
212+ transacoes [ 1 ] . descricao === 'toma' &&
213+ transacoes [ 1 ] . tipo === 'c' ;
214+ } ,
215+ } ) ;
216+
217+ // Execute invalid requests; allow either 422 or 400 as valid responses
218+ const invalidRequests = [
219+ { valor : 1.2 , tipo : 'd' , descricao : 'devolve' , expectedStatus : 422 } ,
220+ { valor : 1 , tipo : 'x' , descricao : 'devolve' , expectedStatus : 422 } ,
221+ { valor : 1 , tipo : 'c' , descricao : '123456789 e mais' , expectedStatus : 422 } ,
222+ { valor : 1 , tipo : 'c' , descricao : '' , expectedStatus : 422 } ,
223+ { valor : 1 , tipo : 'c' , descricao : null , expectedStatus : 422 } ,
224+ ] ;
225+
226+ invalidRequests . forEach ( ( req ) => {
227+ res = http . post (
228+ `${ baseUrl } /clientes/${ cliente . id } /transacoes` ,
229+ JSON . stringify ( req ) ,
230+ { headers : { 'Content-Type' : 'application/json' } }
231+ ) ;
232+ check ( res , {
233+ [ `status ${ req . expectedStatus } ` ] : ( r ) =>
234+ r . status === req . expectedStatus || r . status === 400 ,
235+ } ) ;
236+ } ) ;
237+ } ) ;
238+ }
239+
240+ export function cliente_nao_encontrado ( ) {
241+ const res = http . get ( `${ baseUrl } /clientes/6/extrato` ) ;
242+ check ( res , {
243+ 'status 404' : ( r ) => r . status === 404 ,
244+ } ) ;
245+ }
0 commit comments