automacao-core-carga-back 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/k6.mjs ADDED
@@ -0,0 +1,389 @@
1
+ import http from 'k6/http';
2
+ import { check, sleep, fail } from 'k6';
3
+ import { Trend } from 'k6/metrics';
4
+ import sql from 'k6/x/sql';
5
+ import driver from 'k6/x/sql/driver/postgres';
6
+
7
+ const LOGIN_URL = 'https://platform-homologx.senior.com.br/t/senior.com.br/bridge/1.0/rest/platform/authentication/actions/login';
8
+ const DURACAOLOGIN_TREND = new Trend('tempo_resposta_login');
9
+
10
+ /**
11
+ * Utilitários de autenticação k6 para a plataforma Senior.
12
+ * Funções para login e montagem de headers Bearer.
13
+ */
14
+ class K6AuthUtils {
15
+ /**
16
+ * Monta o objeto de headers com Authorization Bearer para uso nas requisições.
17
+ * @param {string} tokenId - Access token retornado pelo login
18
+ * @returns {Object} Objeto de headers com Content-Type e Authorization
19
+ * @example
20
+ * const params = K6AuthUtils.paramsHeader(token);
21
+ * http.get(url, params);
22
+ */
23
+ static paramsHeader(tokenId) {
24
+ return {
25
+ headers: {
26
+ 'Content-Type': 'application/json',
27
+ 'Authorization': `Bearer ${tokenId}`
28
+ }
29
+ };
30
+ }
31
+
32
+ /**
33
+ * Realiza o login na plataforma Senior e retorna o access_token.
34
+ * Registra a duração da chamada na trend `tempo_resposta_login`.
35
+ * @param {string} inputLogin - JSON stringificado com { "username": "...", "password": "..." }
36
+ * @returns {Promise<string>} access_token para uso nas requisições autenticadas
37
+ * @example
38
+ * import { K6AuthUtils } from 'automacao-core-carga-back/k6';
39
+ *
40
+ * // Credenciais vêm do seu repositório de teste, nunca deste core.
41
+ * // k6 run -e K6_USERNAME=... -e K6_PASSWORD=... test.js
42
+ * export async function setup() {
43
+ * return await K6AuthUtils.login(JSON.stringify({
44
+ * username: __ENV.K6_USERNAME,
45
+ * password: __ENV.K6_PASSWORD,
46
+ * }));
47
+ * }
48
+ */
49
+ static async login(inputLogin) {
50
+ const headers = {
51
+ headers: {
52
+ 'Content-Type': 'application/json'
53
+ }
54
+ };
55
+ const loginRes = http.post(LOGIN_URL, inputLogin, headers, {
56
+ tags: {
57
+ login_tag: 'login'
58
+ }
59
+ });
60
+ check(loginRes, {
61
+ 'Login status code 200': r => r.status === 200
62
+ }, {
63
+ login_tag: 'login'
64
+ });
65
+ DURACAOLOGIN_TREND.add(loginRes.timings.duration, {
66
+ login_tag: 'login'
67
+ });
68
+ const body = loginRes.json();
69
+ const bodyAccess = JSON.parse(body.jsonToken);
70
+ return bodyAccess.access_token;
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Utilitários de banco de dados para testes k6.
76
+ * Encapsula abertura/fechamento de conexão Postgres e execução de queries,
77
+ * usando a extensão nativa `k6/x/sql`.
78
+ */
79
+ class K6DbUtils {
80
+ /**
81
+ * Abre uma conexão com o banco de dados Postgres.
82
+ * @param {string} connectionString - String de conexão no formato postgres://user:pass@host:port/db
83
+ * Monte a partir de variáveis de ambiente no seu repositório de teste; este
84
+ * core não guarda credenciais nem endpoints de banco.
85
+ * @returns {Object} Objeto de conexão para uso nos demais métodos
86
+ * @example
87
+ * import { K6DbUtils } from 'automacao-core-carga-back/k6';
88
+ *
89
+ * // k6 run -e DB_USER=... -e DB_PASSWORD=... -e DB_HOST=... -e DB_PORT=... -e DB_NAME=... test.js
90
+ * const CONEXAO = `postgres://${__ENV.DB_USER}:${__ENV.DB_PASSWORD}@${__ENV.DB_HOST}:${__ENV.DB_PORT}/${__ENV.DB_NAME}`;
91
+ *
92
+ * export function setup() {
93
+ * const db = K6DbUtils.abreConexao(CONEXAO);
94
+ * const resultado = K6DbUtils.pesquisa(db, 'SELECT id FROM schema.tabela LIMIT 1');
95
+ * K6DbUtils.fechaConexao(db);
96
+ * }
97
+ */
98
+ static abreConexao(connectionString) {
99
+ console.log('Iniciando conexão com o Postgres');
100
+ return sql.open(driver, connectionString);
101
+ }
102
+
103
+ /**
104
+ * Executa uma query SELECT no banco de dados.
105
+ * @param {Object} conexao - Conexão aberta via abreConexao
106
+ * @param {string} query - Comando SQL. Ex: 'SELECT vlrbpr FROM schema.tabela WHERE id = 1'
107
+ * @returns {Array} Resultado da query
108
+ */
109
+ static pesquisa(conexao, query) {
110
+ return conexao.query(query);
111
+ }
112
+
113
+ /**
114
+ * Converte um único valor retornado do banco de code ASCII para string.
115
+ * @param {Array} result - Resultado da query (retorno de pesquisa)
116
+ * @param {string} coluna - Nome da coluna. Ex: 'vlrbpr'
117
+ * @returns {string} Valor convertido para string
118
+ */
119
+ static converteDado(result, coluna) {
120
+ for (const row of result) {
121
+ return `${String.fromCharCode(...row[coluna])}`;
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Converte um array de valores retornados do banco de code ASCII para strings.
127
+ * @param {Array} results - Resultado da query (retorno de pesquisa)
128
+ * @param {string} coluna - Nome da coluna. Ex: 'vlrbpr'
129
+ * @returns {string[]} Array de valores convertidos para string
130
+ */
131
+ static converteArrayDados(results, coluna) {
132
+ const formatado = [];
133
+ results.forEach((value, index) => {
134
+ for (let row = 1; row <= results.length; row++) {
135
+ formatado[index] = `${String.fromCharCode(...value[coluna])}`;
136
+ }
137
+ });
138
+ return formatado;
139
+ }
140
+
141
+ /**
142
+ * Fecha a conexão com o banco de dados.
143
+ * @param {Object} conexao - Conexão aberta via abreConexao
144
+ */
145
+ static fechaConexao(conexao) {
146
+ conexao.close();
147
+ console.log('Fechando conexão');
148
+ }
149
+
150
+ /**
151
+ * Executa um comando DML/DDL no banco (UPDATE, INSERT, DELETE, DROP, etc).
152
+ * @param {Object} conexao - Conexão aberta via abreConexao
153
+ * @param {string} comando - Comando SQL a ser executado
154
+ */
155
+ static executaComando(conexao, comando) {
156
+ conexao.exec(comando);
157
+ }
158
+ }
159
+
160
+ /**
161
+ * Utilitários de relatório para testes k6.
162
+ * Gera o resumo das métricas ao final do teste via handleSummary,
163
+ * produzindo um arquivo JSON que alimenta a skill de relatório de carga.
164
+ *
165
+ * @example
166
+ * import { K6ReportUtils } from 'automacao-core-carga-back/k6';
167
+ *
168
+ * export function handleSummary(data) {
169
+ * return K6ReportUtils.gerarSummary(data, { nome: 'calculaImpostos' });
170
+ * // gera: k6/imagensIA/calculaImpostos/k6calculaImpostos.json
171
+ * }
172
+ *
173
+ * Override por variáveis de ambiente:
174
+ * k6 run -e K6_JSON_DIR=imagensIA -e K6_JSON_FILE=k6meuTeste.json test.js
175
+ */
176
+ class K6ReportUtils {
177
+ /**
178
+ * @private
179
+ */
180
+ static _lerValores(data, metrica) {
181
+ if (data && data.metrics && data.metrics[metrica] && data.metrics[metrica].values) {
182
+ return data.metrics[metrica].values;
183
+ }
184
+ return {};
185
+ }
186
+
187
+ /**
188
+ * @private
189
+ */
190
+ static _arredonda(valor) {
191
+ return typeof valor === 'number' && isFinite(valor) ? Number(valor.toFixed(2)) : null;
192
+ }
193
+
194
+ /**
195
+ * @private
196
+ */
197
+ static _inteiro(valor) {
198
+ return typeof valor === 'number' && isFinite(valor) ? valor : null;
199
+ }
200
+
201
+ /**
202
+ * @private
203
+ */
204
+ static _montaLatencia(v) {
205
+ const b = {
206
+ unidade: 'ms',
207
+ avg: K6ReportUtils._arredonda(v.avg),
208
+ min: K6ReportUtils._arredonda(v.min),
209
+ med: K6ReportUtils._arredonda(v.med),
210
+ max: K6ReportUtils._arredonda(v.max)
211
+ };
212
+ b['p(90)'] = K6ReportUtils._arredonda(v['p(90)']);
213
+ b['p(95)'] = K6ReportUtils._arredonda(v['p(95)']);
214
+ return b;
215
+ }
216
+
217
+ /**
218
+ * @private
219
+ */
220
+ static _textoTerminal(r, caminho) {
221
+ const d = r.http_req_duration;
222
+ return ['', `── Resumo K6 ${r.teste ? '· ' + r.teste + ' ' : ''}──`, `duração: ${r.duracao.min} min (${r.duracao.ms} ms)`, `http_req_duration: avg=${d.avg}ms min=${d.min}ms med=${d.med}ms max=${d.max}ms p(95)=${d['p(95)']}ms`, `http_reqs: ${r.http_reqs.total} (${r.http_reqs.por_segundo}/s)`, `iterations: ${r.iterations.total}`, `taxa de erro: ${r.http_req_failed.taxa_erro_pct}%`, `JSON salvo em: ${caminho}`, ''].join('\n');
223
+ }
224
+
225
+ /**
226
+ * Gera o resumo do k6 em JSON e texto para o terminal.
227
+ * Deve ser retornado dentro da função nativa handleSummary do teste.
228
+ * @param {Object} data - Objeto de summary fornecido pelo k6 ao handleSummary
229
+ * @param {Object} [opcoes] - Opções de saída
230
+ * @param {string} [opcoes.nome] - Nome do teste (usado no nome do arquivo e no campo `teste` do JSON)
231
+ * @returns {Object} Mapa aceito pelo handleSummary: { [caminho]: jsonString, stdout: texto }
232
+ * @example
233
+ * export function handleSummary(data) {
234
+ * return K6ReportUtils.gerarSummary(data, { nome: 'baixaPagamento' });
235
+ * }
236
+ */
237
+ static gerarSummary(data, opcoes) {
238
+ const opts = opcoes || {};
239
+ const temEnv = typeof __ENV !== 'undefined' && __ENV;
240
+ const basedir = temEnv && __ENV.K6_JSON_DIR ? __ENV.K6_JSON_DIR : 'k6/imagensIA';
241
+ const dir = opts.nome ? `${basedir}/${opts.nome}` : basedir;
242
+ const nomeArquivo = temEnv && __ENV.K6_JSON_FILE ? __ENV.K6_JSON_FILE : opts.nome ? `k6${opts.nome}.json` : 'k6.json';
243
+ const caminho = `${dir}/${nomeArquivo}`;
244
+ const duracaoMs = data && data.state && typeof data.state.testRunDurationMs === 'number' ? data.state.testRunDurationMs : null;
245
+ const a = K6ReportUtils._arredonda;
246
+ const i = K6ReportUtils._inteiro;
247
+ const l = m => K6ReportUtils._lerValores(data, m);
248
+ const reqDuration = l('http_req_duration');
249
+ const reqWaiting = l('http_req_waiting');
250
+ const reqFailed = l('http_req_failed');
251
+ const httpReqs = l('http_reqs');
252
+ const iteracoes = l('iterations');
253
+ const checks = l('checks');
254
+ const vusMax = l('vus_max');
255
+ const resumo = {
256
+ gerado_em: new Date().toISOString(),
257
+ teste: opts.nome || null,
258
+ duracao: {
259
+ ms: a(duracaoMs),
260
+ min: duracaoMs !== null ? a(duracaoMs / 60000) : null
261
+ },
262
+ vus_max: i(vusMax.value),
263
+ http_req_duration: K6ReportUtils._montaLatencia(reqDuration),
264
+ http_req_waiting: K6ReportUtils._montaLatencia(reqWaiting),
265
+ http_req_failed: {
266
+ taxa_erro_pct: a((reqFailed.rate || 0) * 100),
267
+ sucessos: i(reqFailed.passes),
268
+ falhas: i(reqFailed.fails)
269
+ },
270
+ http_reqs: {
271
+ total: i(httpReqs.count),
272
+ por_segundo: a(httpReqs.rate)
273
+ },
274
+ iterations: {
275
+ total: i(iteracoes.count),
276
+ por_segundo: a(iteracoes.rate)
277
+ },
278
+ checks: {
279
+ taxa_sucesso_pct: typeof checks.rate === 'number' ? a(checks.rate * 100) : null,
280
+ passes: i(checks.passes),
281
+ falhas: i(checks.fails)
282
+ }
283
+ };
284
+ const saida = {};
285
+ saida[caminho] = JSON.stringify(resumo, null, 2);
286
+ saida.stdout = K6ReportUtils._textoTerminal(resumo, caminho);
287
+ return saida;
288
+ }
289
+ }
290
+
291
+ /**
292
+ * Utilitários de manipulação de dados para testes k6.
293
+ * Helpers de array e cálculo usados transversalmente nos módulos.
294
+ */
295
+ class K6DataUtils {
296
+ /**
297
+ * Cria um array bidimensional a partir de um array plano.
298
+ * Útil para processar registros em lotes (ex: baixar títulos em grupos de 10).
299
+ * @param {Array} array - Array de entrada
300
+ * @param {number} tamanho - Quantidade de itens por subarray
301
+ * @returns {Array[]} Array de subarrays com `tamanho` itens cada
302
+ * @example
303
+ * import { K6DataUtils } from 'automacao-core-carga-back/k6';
304
+ *
305
+ * const titulos = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
306
+ * const lotes = K6DataUtils.criaSubArrays(titulos, 3);
307
+ * // [[1,2,3], [4,5,6], [7,8,9], [10]]
308
+ */
309
+ static criaSubArrays(array, tamanho) {
310
+ const subarrays = [];
311
+ for (let i = 0; i < array.length; i += tamanho) {
312
+ subarrays.push(array.slice(i, i + tamanho));
313
+ }
314
+ return subarrays;
315
+ }
316
+
317
+ /**
318
+ * Soma os valores de cada subarray de um array bidimensional.
319
+ * @param {Array[]} array - Array bidimensional com valores numéricos
320
+ * @returns {string[]} Array com a soma de cada subarray formatada em 2 casas decimais
321
+ * @example
322
+ * K6DataUtils.somaValores([[0, 1, 2, 3], [2, 3, 9, 8]]);
323
+ * // ['6.00', '22.00']
324
+ */
325
+ static somaValores(array) {
326
+ return array.map(sub => sub.reduce((total, valor) => total + valor, 0).toFixed(2));
327
+ }
328
+ }
329
+
330
+ const SEARCHNOTIFICATIONS_URL = 'https://platform-homologx.senior.com.br/t/senior.com.br/bridge/1.0/rest/platform/notifications/actions/searchNotifications';
331
+
332
+ /**
333
+ * Utilitários de notificações da plataforma Senior para testes k6.
334
+ * Funções para pesquisa e polling de notificações.
335
+ */
336
+ class K6NotificationsUtils {
337
+ /**
338
+ * Realiza POST no endpoint SearchNotifications.
339
+ * @param {Object|string} input - Payload da pesquisa (objeto ou JSON stringificado)
340
+ * @param {Object} params - Headers (retorno de K6AuthUtils.paramsHeader())
341
+ * @param {boolean} [fullResponse=false] - Se true, retorna o objeto Response completo;
342
+ * se false, retorna apenas o JSON da resposta
343
+ * @returns {Object|Response} Resposta da chamada
344
+ * @example
345
+ * import { K6NotificationsUtils, K6AuthUtils } from 'automacao-core-carga-back/k6';
346
+ *
347
+ * const params = K6AuthUtils.paramsHeader(token);
348
+ * const resultado = K6NotificationsUtils.pesquisar(input, params);
349
+ * console.log(resultado.listInformation.totalElements);
350
+ */
351
+ static pesquisar(input, params, fullResponse = false) {
352
+ const retorno = http.post(SEARCHNOTIFICATIONS_URL, input, params);
353
+ if (retorno.status !== 200) {
354
+ console.log(`📥 Response: ${retorno.body}`);
355
+ }
356
+ check(retorno, {
357
+ 'SearchNotifications status 200': rs => rs.status === 200
358
+ });
359
+ return fullResponse ? retorno : retorno.json();
360
+ }
361
+
362
+ /**
363
+ * Polling até que o total de notificações esperado seja atingido.
364
+ * Realiza até 100 tentativas com intervalo de 4 segundos entre cada.
365
+ * Falha o teste (fail) se a quantidade não for atingida.
366
+ * @param {Object|string} input - Payload da pesquisa
367
+ * @param {Object} params - Headers (retorno de K6AuthUtils.paramsHeader())
368
+ * @param {number} quantidadeEsperada - Total de notificações esperadas
369
+ * @returns {number} Total de notificações encontradas ao final
370
+ * @example
371
+ * const total = K6NotificationsUtils.aguardarTotal(input, params, 5);
372
+ * console.log(`Notificações recebidas: ${total}`);
373
+ */
374
+ static aguardarTotal(input, params, quantidadeEsperada) {
375
+ let quantidadeAtual = 0;
376
+ for (let tentativa = 0; tentativa < 100 && quantidadeAtual < quantidadeEsperada; tentativa++) {
377
+ const retorno = K6NotificationsUtils.pesquisar(input, params);
378
+ quantidadeAtual = retorno.listInformation.totalElements;
379
+ console.log(`Tentativa: ${tentativa} - Atual: ${quantidadeAtual} - Esperado: ${quantidadeEsperada}`);
380
+ sleep(4);
381
+ if (tentativa === 99 && quantidadeAtual !== quantidadeEsperada) {
382
+ fail('As notificações não foram geradas corretamente');
383
+ }
384
+ }
385
+ return quantidadeAtual;
386
+ }
387
+ }
388
+
389
+ export { K6AuthUtils, K6DataUtils, K6DbUtils, K6NotificationsUtils, K6ReportUtils };