prix-r9 2.0.2 → 2.0.4
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/README.md +49 -20
- package/import-curl.js +3 -1
- package/index.js +33 -1
- package/package.json +2 -4
package/README.md
CHANGED
|
@@ -9,6 +9,7 @@ CLI para pruebas de carga HTTP/REST con ramp-up, multipart y escenarios encadena
|
|
|
9
9
|
- Permite escenarios encadenados dentro de una misma iteracion usando `steps`.
|
|
10
10
|
- Extrae valores del response JSON y los reutiliza en los siguientes steps.
|
|
11
11
|
- Soporta escenarios simples de un solo request y escenarios encadenados por steps.
|
|
12
|
+
- Permite usar `insecureHttps: true` para endpoints HTTPS locales con certificados autofirmados.
|
|
12
13
|
- Genera `reporte.txt` con metricas generales del escenario y metricas por step.
|
|
13
14
|
|
|
14
15
|
## Instalacion
|
|
@@ -54,32 +55,64 @@ Este formato sigue funcionando sin cambios:
|
|
|
54
55
|
|
|
55
56
|
```json
|
|
56
57
|
{
|
|
57
|
-
"url": "https://api.
|
|
58
|
+
"url": "https://api.example.test/v1/usuarios",
|
|
58
59
|
"method": "POST",
|
|
59
60
|
"startRate": 5,
|
|
60
61
|
"targetRate": 50,
|
|
61
62
|
"rampUpTime": 5,
|
|
62
63
|
"duration": 10,
|
|
63
64
|
"headers": {
|
|
64
|
-
"Authorization": "Bearer
|
|
65
|
+
"Authorization": "Bearer {{uuid}}",
|
|
65
66
|
"X-Request-ID": "{{uuid}}"
|
|
66
67
|
},
|
|
67
68
|
"body": {
|
|
68
69
|
"id_unico": "{{uuid}}",
|
|
69
|
-
"correo_usuario": "prueba_{{timestamp}}@
|
|
70
|
+
"correo_usuario": "prueba_{{timestamp}}@example.test"
|
|
70
71
|
}
|
|
71
72
|
}
|
|
72
73
|
```
|
|
73
74
|
|
|
74
75
|
Este formato representa un escenario de endpoint unico.
|
|
75
76
|
|
|
77
|
+
## HTTPS local con certificados no confiables
|
|
78
|
+
|
|
79
|
+
Si necesitas probar un endpoint local como `https://localhost:64847/...` con certificado autofirmado o no confiable, puedes activar:
|
|
80
|
+
|
|
81
|
+
```json
|
|
82
|
+
{
|
|
83
|
+
"url": "https://localhost:64847/api/reportes/GetByCompany",
|
|
84
|
+
"method": "POST",
|
|
85
|
+
"rate": 1,
|
|
86
|
+
"duration": 1,
|
|
87
|
+
"insecureHttps": true,
|
|
88
|
+
"headers": {
|
|
89
|
+
"Content-Type": "application/json",
|
|
90
|
+
"ApiKey": "dev-local-api-key"
|
|
91
|
+
},
|
|
92
|
+
"body": {
|
|
93
|
+
"StartDate": "2026-03-01",
|
|
94
|
+
"EndDate": "2026-03-17"
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Cuando `insecureHttps` es `true`, Prix-R9 usa un `https.Agent` con `rejectUnauthorized: false`.
|
|
100
|
+
|
|
101
|
+
Importante:
|
|
102
|
+
- usalo solo en ambientes locales o de desarrollo
|
|
103
|
+
- no lo uses en QA formal, staging productivo o produccion
|
|
104
|
+
- con esto ya no necesitas ejecutar `NODE_TLS_REJECT_UNAUTHORIZED=0`
|
|
105
|
+
- la herramienta muestra una advertencia en consola cuando esta opcion esta activa
|
|
106
|
+
|
|
107
|
+
En escenarios con `steps`, `insecureHttps` se puede declarar a nivel del escenario y aplica a todos los steps.
|
|
108
|
+
|
|
76
109
|
## Formato recomendado para escenarios encadenados
|
|
77
110
|
|
|
78
111
|
La forma mas simple es usar `steps` con el mismo formato que genera `prix-r9-curl`, sin obligarte a anidar `request`.
|
|
79
112
|
|
|
80
113
|
```json
|
|
81
114
|
{
|
|
82
|
-
"name": "Carga encadenada
|
|
115
|
+
"name": "Carga encadenada de aprobacion",
|
|
83
116
|
"startRate": 2,
|
|
84
117
|
"targetRate": 5,
|
|
85
118
|
"rampUpTime": 5,
|
|
@@ -87,19 +120,19 @@ La forma mas simple es usar `steps` con el mismo formato que genera `prix-r9-cur
|
|
|
87
120
|
"steps": [
|
|
88
121
|
{
|
|
89
122
|
"name": "uploadProcess",
|
|
90
|
-
"url": "https://
|
|
123
|
+
"url": "https://api.example.test/modulo-cargas/api/blob/uploadProcess",
|
|
91
124
|
"method": "post",
|
|
92
125
|
"headers": {
|
|
93
126
|
"Accept": "application/json, text/plain, */*",
|
|
94
|
-
"Authorization": "Bearer
|
|
127
|
+
"Authorization": "Bearer {{uuid}}"
|
|
95
128
|
},
|
|
96
129
|
"file": "./Plantillas/PlantillaMovimientoManual.csv",
|
|
97
130
|
"filekey": "File",
|
|
98
131
|
"body": {
|
|
99
132
|
"TypeFile": "1",
|
|
100
|
-
"ReasonProcess": "Carga
|
|
101
|
-
"CreatedBy": "
|
|
102
|
-
"TypeProcessId": "
|
|
133
|
+
"ReasonProcess": "Carga ejemplo {{timestamp}}",
|
|
134
|
+
"CreatedBy": "Usuario QA",
|
|
135
|
+
"TypeProcessId": "11111111-2222-3333-4444-555555555555"
|
|
103
136
|
},
|
|
104
137
|
"extract": {
|
|
105
138
|
"uploadFileProcessId": "$.uploadFileProcessId"
|
|
@@ -107,17 +140,17 @@ La forma mas simple es usar `steps` con el mismo formato que genera `prix-r9-cur
|
|
|
107
140
|
},
|
|
108
141
|
{
|
|
109
142
|
"name": "executeLoadProcess",
|
|
110
|
-
"url": "https://
|
|
143
|
+
"url": "https://api.example.test/modulo-cargas/api/approvals/ExecuteLoadProcess",
|
|
111
144
|
"method": "post",
|
|
112
145
|
"headers": {
|
|
113
146
|
"Accept": "application/json, text/plain, */*",
|
|
114
147
|
"Content-Type": "application/json",
|
|
115
|
-
"apiKey": "
|
|
148
|
+
"apiKey": "example-api-key"
|
|
116
149
|
},
|
|
117
150
|
"body": {
|
|
118
151
|
"UploadFileProcessId": "{{uploadFileProcessId}}",
|
|
119
152
|
"AprovalStatus": 1,
|
|
120
|
-
"ApprovedBy": "
|
|
153
|
+
"ApprovedBy": "Usuario QA"
|
|
121
154
|
}
|
|
122
155
|
}
|
|
123
156
|
]
|
|
@@ -133,6 +166,7 @@ Tambien se acepta la forma anidada con `request`, pero la forma directa suele se
|
|
|
133
166
|
- `extract` guarda valores del response JSON para los steps siguientes.
|
|
134
167
|
- Si un step falla por HTTP, red o extraccion, la iteracion termina y los steps restantes quedan omitidos por cascade.
|
|
135
168
|
- En escenarios multi-step, el rate representa iteraciones por segundo.
|
|
169
|
+
- Si `insecureHttps` no esta presente o es `false`, el comportamiento TLS queda igual que antes.
|
|
136
170
|
|
|
137
171
|
## Extraccion de valores
|
|
138
172
|
|
|
@@ -169,10 +203,10 @@ Ejemplo:
|
|
|
169
203
|
|
|
170
204
|
```json
|
|
171
205
|
{
|
|
172
|
-
"url": "https://api/
|
|
206
|
+
"url": "https://api.example.test/uploads",
|
|
173
207
|
"method": "post",
|
|
174
208
|
"headers": {
|
|
175
|
-
"Authorization": "Bearer
|
|
209
|
+
"Authorization": "Bearer {{uuid}}"
|
|
176
210
|
},
|
|
177
211
|
"file": "./foto.jpg",
|
|
178
212
|
"filekey": "File",
|
|
@@ -200,9 +234,4 @@ Al finalizar, el CLI imprime un resumen y guarda `reporte.txt` con:
|
|
|
200
234
|
prix-r9 --prompt
|
|
201
235
|
```
|
|
202
236
|
|
|
203
|
-
Copia `promptInforme.txt` al directorio actual. El template ya esta orientado a analizar escenarios multi-step y distinguir entre metricas por iteracion y metricas por step.
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
237
|
+
Copia `promptInforme.txt` al directorio actual. El template ya esta orientado a analizar escenarios multi-step y distinguir entre metricas por iteracion y metricas por step.
|
package/import-curl.js
CHANGED
|
@@ -5,7 +5,7 @@ const { program } = require('commander');
|
|
|
5
5
|
const { toJsonString } = require('curlconverter');
|
|
6
6
|
|
|
7
7
|
program
|
|
8
|
-
.version('2.0.
|
|
8
|
+
.version('2.0.4')
|
|
9
9
|
.description('Importador de cURL a config JSON base para pruebas de carga')
|
|
10
10
|
.requiredOption('-i, --input <path>', 'Archivo de texto (.txt) que contiene el comando cURL crudo')
|
|
11
11
|
.requiredOption('-o, --output <path>', 'Ruta de destino para el config JSON de la prueba (ej: casos/az/nuevo-endpoint.json)')
|
|
@@ -116,3 +116,5 @@ try {
|
|
|
116
116
|
|
|
117
117
|
|
|
118
118
|
|
|
119
|
+
|
|
120
|
+
|
package/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
const axios = require('axios');
|
|
3
|
+
const https = require('https');
|
|
3
4
|
const fs = require('fs');
|
|
4
5
|
const path = require('path');
|
|
5
6
|
const FormData = require('form-data');
|
|
@@ -12,7 +13,7 @@ const BODY_METHODS = new Set(['POST', 'PUT', 'PATCH']);
|
|
|
12
13
|
const PLACEHOLDER_PATTERN = /\{\{([^{}]+)\}\}/g;
|
|
13
14
|
|
|
14
15
|
program
|
|
15
|
-
.version('2.0.
|
|
16
|
+
.version('2.0.4')
|
|
16
17
|
.description('Herramienta Avanzada HTTP/REST (Ramp-up y Datos Dinámicos)')
|
|
17
18
|
.option('-c, --config <path>', 'Ruta a un archivo JSON con toda la configuración')
|
|
18
19
|
.option('--prompt', 'Copia el template promptInforme.txt al directorio actual')
|
|
@@ -70,6 +71,7 @@ function normalizeStep(step, index, fallbackRequest) {
|
|
|
70
71
|
body: sourceStep.body,
|
|
71
72
|
file: sourceStep.file,
|
|
72
73
|
filekey: sourceStep.filekey,
|
|
74
|
+
insecureHttps: sourceStep.insecureHttps,
|
|
73
75
|
};
|
|
74
76
|
|
|
75
77
|
return {
|
|
@@ -81,6 +83,7 @@ function normalizeStep(step, index, fallbackRequest) {
|
|
|
81
83
|
body: request.body,
|
|
82
84
|
file: request.file,
|
|
83
85
|
filekey: request.filekey || 'file',
|
|
86
|
+
insecureHttps: request.insecureHttps,
|
|
84
87
|
},
|
|
85
88
|
extract: isPlainObject(sourceStep.extract) ? sourceStep.extract : {},
|
|
86
89
|
};
|
|
@@ -101,10 +104,16 @@ function normalizeConfig(options) {
|
|
|
101
104
|
targetRate: options.targetRate,
|
|
102
105
|
rampUpTime: options.rampUpTime,
|
|
103
106
|
duration: options.duration || 10,
|
|
107
|
+
insecureHttps: options.insecureHttps,
|
|
104
108
|
};
|
|
105
109
|
}
|
|
106
110
|
|
|
107
111
|
function validateScenarioConfig(currentScenario) {
|
|
112
|
+
if (currentScenario.insecureHttps !== undefined && typeof currentScenario.insecureHttps !== 'boolean') {
|
|
113
|
+
console.error('Error: "insecureHttps" debe ser true o false.');
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
116
|
+
|
|
108
117
|
if (!Array.isArray(currentScenario.steps) || currentScenario.steps.length === 0) {
|
|
109
118
|
console.error('Error: Debes proporcionar al menos un step en "steps" o un request legacy con "url" y "method".');
|
|
110
119
|
process.exit(1);
|
|
@@ -123,6 +132,11 @@ function validateScenarioConfig(currentScenario) {
|
|
|
123
132
|
process.exit(1);
|
|
124
133
|
}
|
|
125
134
|
|
|
135
|
+
if (request.insecureHttps !== undefined && typeof request.insecureHttps !== 'boolean') {
|
|
136
|
+
console.error(`Error: "insecureHttps" del step "${step.name}" debe ser true o false.`);
|
|
137
|
+
process.exit(1);
|
|
138
|
+
}
|
|
139
|
+
|
|
126
140
|
if (request.file && !containsPlaceholders(request.file)) {
|
|
127
141
|
const resolvedFile = path.isAbsolute(request.file)
|
|
128
142
|
? request.file
|
|
@@ -152,6 +166,8 @@ const startRate = isRampUp ? (scenario.startRate || 1) : (scenario.rate || 10);
|
|
|
152
166
|
const targetRate = isRampUp ? scenario.targetRate : (scenario.rate || 10);
|
|
153
167
|
const rampUpTime = isRampUp ? scenario.rampUpTime : 0;
|
|
154
168
|
const duration = scenario.duration;
|
|
169
|
+
const insecureHttpsAgent = new https.Agent({ rejectUnauthorized: false });
|
|
170
|
+
const usesInsecureHttps = scenario.insecureHttps === true || scenario.steps.some((step) => step.request.insecureHttps === true);
|
|
155
171
|
|
|
156
172
|
function calculateRate(second) {
|
|
157
173
|
if (!isRampUp) return startRate;
|
|
@@ -382,6 +398,8 @@ function buildRequestConfig(step, context) {
|
|
|
382
398
|
const resolvedFile = renderValue(step.request.file, context);
|
|
383
399
|
const resolvedFileKey = renderValue(step.request.filekey || 'file', context);
|
|
384
400
|
|
|
401
|
+
const effectiveInsecureHttps = step.request.insecureHttps !== undefined ? step.request.insecureHttps : scenario.insecureHttps;
|
|
402
|
+
|
|
385
403
|
const reqConfig = {
|
|
386
404
|
method: step.request.method,
|
|
387
405
|
url: resolvedUrl,
|
|
@@ -390,6 +408,10 @@ function buildRequestConfig(step, context) {
|
|
|
390
408
|
timeout: 60000,
|
|
391
409
|
};
|
|
392
410
|
|
|
411
|
+
if (effectiveInsecureHttps && typeof resolvedUrl === 'string' && resolvedUrl.startsWith('https://')) {
|
|
412
|
+
reqConfig.httpsAgent = insecureHttpsAgent;
|
|
413
|
+
}
|
|
414
|
+
|
|
393
415
|
if (resolvedFile && BODY_METHODS.has(step.request.method)) {
|
|
394
416
|
const finalFilePath = resolveFilePath(resolvedFile);
|
|
395
417
|
if (!fs.existsSync(finalFilePath)) {
|
|
@@ -507,6 +529,10 @@ async function runTest() {
|
|
|
507
529
|
metrics.scenario.configHeader.push(`Step ${index + 1}: ${step.name} [${step.request.method}] ${step.request.url}`);
|
|
508
530
|
});
|
|
509
531
|
|
|
532
|
+
if (usesInsecureHttps) {
|
|
533
|
+
metrics.scenario.configHeader.push('HTTPS inseguro: habilitado (rejectUnauthorized=false)');
|
|
534
|
+
}
|
|
535
|
+
|
|
510
536
|
if (isRampUp) {
|
|
511
537
|
metrics.scenario.configHeader.push(`Ramp-up: De ${startRate} a ${targetRate} iter/s en ${rampUpTime}s`);
|
|
512
538
|
metrics.scenario.configHeader.push(`Duración total: ${duration} segundos`);
|
|
@@ -523,6 +549,10 @@ async function runTest() {
|
|
|
523
549
|
console.log(` ${index + 1}. ${step.name} [${step.request.method}] ${step.request.url}`);
|
|
524
550
|
});
|
|
525
551
|
|
|
552
|
+
if (usesInsecureHttps) {
|
|
553
|
+
console.warn('⚠️ insecureHttps=true: se deshabilitó la validación TLS. Úsalo solo en localhost/dev con certificados autofirmados.');
|
|
554
|
+
}
|
|
555
|
+
|
|
526
556
|
if (isRampUp) {
|
|
527
557
|
console.log(`Ramp-up: De ${startRate} a ${targetRate} iter/s en ${rampUpTime}s`);
|
|
528
558
|
console.log(`Mantenido en ${targetRate} iter/s hasta alcanzar los ${duration}s de prueba.`);
|
|
@@ -723,3 +753,5 @@ runTest()
|
|
|
723
753
|
|
|
724
754
|
|
|
725
755
|
|
|
756
|
+
|
|
757
|
+
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "prix-r9",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.4",
|
|
4
4
|
"description": "CLI para pruebas de carga HTTP/REST con ramp-up, multipart y escenarios encadenados por steps.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -47,6 +47,4 @@
|
|
|
47
47
|
"form-data": "^4.0.5",
|
|
48
48
|
"uuid": "^13.0.0"
|
|
49
49
|
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
}
|