k6ctl 1.0.0

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.
Files changed (86) hide show
  1. package/.github/workflows/ci.yml +61 -0
  2. package/README.md +45 -0
  3. package/dist/cli.d.ts +3 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +28 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/commands/list.d.ts +4 -0
  8. package/dist/commands/list.d.ts.map +1 -0
  9. package/dist/commands/list.js +35 -0
  10. package/dist/commands/list.js.map +1 -0
  11. package/dist/commands/run.d.ts +9 -0
  12. package/dist/commands/run.d.ts.map +1 -0
  13. package/dist/commands/run.js +76 -0
  14. package/dist/commands/run.js.map +1 -0
  15. package/dist/services/kubernetes.service.d.ts +20 -0
  16. package/dist/services/kubernetes.service.d.ts.map +1 -0
  17. package/dist/services/kubernetes.service.js +278 -0
  18. package/dist/services/kubernetes.service.js.map +1 -0
  19. package/dist/services/script.service.d.ts +8 -0
  20. package/dist/services/script.service.d.ts.map +1 -0
  21. package/dist/services/script.service.js +82 -0
  22. package/dist/services/script.service.js.map +1 -0
  23. package/dist/types/config.types.d.ts +26 -0
  24. package/dist/types/config.types.d.ts.map +1 -0
  25. package/dist/types/config.types.js +3 -0
  26. package/dist/types/config.types.js.map +1 -0
  27. package/dist/types/kubernetes.types.d.ts +9 -0
  28. package/dist/types/kubernetes.types.d.ts.map +1 -0
  29. package/dist/types/kubernetes.types.js +3 -0
  30. package/dist/types/kubernetes.types.js.map +1 -0
  31. package/dist/types/script.types.d.ts +11 -0
  32. package/dist/types/script.types.d.ts.map +1 -0
  33. package/dist/types/script.types.js +3 -0
  34. package/dist/types/script.types.js.map +1 -0
  35. package/dist/types/testRunManifest.types.d.ts +43 -0
  36. package/dist/types/testRunManifest.types.d.ts.map +1 -0
  37. package/dist/types/testRunManifest.types.js +3 -0
  38. package/dist/types/testRunManifest.types.js.map +1 -0
  39. package/dist/utils/configLoader.d.ts +30 -0
  40. package/dist/utils/configLoader.d.ts.map +1 -0
  41. package/dist/utils/configLoader.js +63 -0
  42. package/dist/utils/configLoader.js.map +1 -0
  43. package/dist/utils/env.d.ts +17 -0
  44. package/dist/utils/env.d.ts.map +1 -0
  45. package/dist/utils/env.js +111 -0
  46. package/dist/utils/env.js.map +1 -0
  47. package/dist/utils/logger.d.ts +4 -0
  48. package/dist/utils/logger.d.ts.map +1 -0
  49. package/dist/utils/logger.js +16 -0
  50. package/dist/utils/logger.js.map +1 -0
  51. package/dist/utils/table.util.d.ts +10 -0
  52. package/dist/utils/table.util.d.ts.map +1 -0
  53. package/dist/utils/table.util.js +24 -0
  54. package/dist/utils/table.util.js.map +1 -0
  55. package/dist/utils/testRunManifestBuilder.d.ts +5 -0
  56. package/dist/utils/testRunManifestBuilder.d.ts.map +1 -0
  57. package/dist/utils/testRunManifestBuilder.js +89 -0
  58. package/dist/utils/testRunManifestBuilder.js.map +1 -0
  59. package/package.json +44 -0
  60. package/src/cli.ts +31 -0
  61. package/src/commands/list.ts +29 -0
  62. package/src/commands/run.ts +52 -0
  63. package/src/services/kubernetes.service.ts +238 -0
  64. package/src/services/script.service.ts +79 -0
  65. package/src/types/config.types.ts +25 -0
  66. package/src/types/kubernetes.types.ts +9 -0
  67. package/src/types/script.types.ts +8 -0
  68. package/src/types/testRunManifest.types.ts +42 -0
  69. package/src/utils/configLoader.ts +63 -0
  70. package/src/utils/env.ts +90 -0
  71. package/src/utils/logger.ts +17 -0
  72. package/src/utils/table.util.ts +23 -0
  73. package/src/utils/testRunManifestBuilder.ts +102 -0
  74. package/test/integration/env.test.ts +33 -0
  75. package/test/integration/kubernetes.service.test.ts +70 -0
  76. package/test/integration/script.service.test.ts +64 -0
  77. package/test/jest.config.integration.js +20 -0
  78. package/test/jest.config.unit.js +14 -0
  79. package/test/samples/data_sample_1.csv +4 -0
  80. package/test/samples/k6_script_sample_1.js +9 -0
  81. package/test/samples/k6_script_sample_2.js +18 -0
  82. package/test/unit/configLoader.test.ts +53 -0
  83. package/test/unit/env.test.ts +1029 -0
  84. package/test/unit/kubernetes.service.test.ts +197 -0
  85. package/test/unit/script.service.test.ts +112 -0
  86. package/tsconfig.json +20 -0
@@ -0,0 +1,1029 @@
1
+ import { beforeEach, describe, expect, jest, it } from '@jest/globals';
2
+ import * as fs from "fs";
3
+ import * as path from "path";
4
+ import {
5
+ loadAndValidateEnv,
6
+ } from "../../src/utils/env";
7
+
8
+ // Mock de fs
9
+ jest.mock("fs");
10
+ const mockedFs = fs as jest.Mocked<typeof fs>;
11
+
12
+ describe("loadAndValidateEnv", () => {
13
+ const mockEnvPath = ".env";
14
+ const absolutePath = path.resolve(process.cwd(), mockEnvPath);
15
+
16
+ beforeEach(() => {
17
+ jest.clearAllMocks();
18
+ });
19
+
20
+ it("debe cargar y validar un archivo .env válido", () => {
21
+ const envContent = "API_KEY=test123\nPORT=3000\nDEBUG=true";
22
+
23
+ mockedFs.existsSync.mockReturnValue(true);
24
+ mockedFs.readFileSync.mockReturnValue(envContent);
25
+
26
+ const result = loadAndValidateEnv(mockEnvPath);
27
+
28
+ expect(result).toEqual({
29
+ API_KEY: "test123",
30
+ PORT: "3000",
31
+ DEBUG: "true",
32
+ });
33
+ });
34
+
35
+ it("debe lanzar error si el archivo no existe", () => {
36
+ mockedFs.existsSync.mockReturnValue(false);
37
+
38
+ expect(() => loadAndValidateEnv(mockEnvPath)).toThrow(
39
+ `Archivo .env no encontrado: ${absolutePath}`
40
+ );
41
+ });
42
+
43
+ it("debe lanzar error si no puede leer el archivo", () => {
44
+ mockedFs.existsSync.mockReturnValue(true);
45
+ mockedFs.readFileSync.mockImplementation(() => {
46
+ throw new Error("Permission denied");
47
+ });
48
+
49
+ expect(() => loadAndValidateEnv(mockEnvPath)).toThrow(
50
+ "Error al leer el archivo .env: Permission denied"
51
+ );
52
+ });
53
+
54
+ it("debe rechazar nombres de variables en minúsculas", () => {
55
+ const envContent = "api_key=test123";
56
+
57
+ mockedFs.existsSync.mockReturnValue(true);
58
+ mockedFs.readFileSync.mockReturnValue(envContent);
59
+
60
+ expect(() => loadAndValidateEnv(mockEnvPath)).toThrow(
61
+ /Nombre inválido: "api_key"/
62
+ );
63
+ });
64
+
65
+ it("debe rechazar nombres de variables con caracteres especiales", () => {
66
+ const envContent = "API-KEY=test123";
67
+
68
+ mockedFs.existsSync.mockReturnValue(true);
69
+ mockedFs.readFileSync.mockReturnValue(envContent);
70
+
71
+ expect(() => loadAndValidateEnv(mockEnvPath)).toThrow(
72
+ /Nombre inválido: "API-KEY"/
73
+ );
74
+ });
75
+
76
+ it("debe rechazar variables vacías", () => {
77
+ const envContent = "API_KEY=";
78
+
79
+ mockedFs.existsSync.mockReturnValue(true);
80
+ mockedFs.readFileSync.mockReturnValue(envContent);
81
+
82
+ expect(() => loadAndValidateEnv(mockEnvPath)).toThrow(
83
+ /Variable "API_KEY" no tiene valor/
84
+ );
85
+ });
86
+
87
+ it("debe aceptar valores con espacios", () => {
88
+ const envContent = "MESSAGE=Hello World";
89
+
90
+ mockedFs.existsSync.mockReturnValue(true);
91
+ mockedFs.readFileSync.mockReturnValue(envContent);
92
+
93
+ const result = loadAndValidateEnv(mockEnvPath);
94
+
95
+ expect(result).toEqual({
96
+ MESSAGE: "Hello World",
97
+ });
98
+ });
99
+
100
+ it("debe aceptar nombres con números y guiones bajos", () => {
101
+ const envContent = "API_KEY_V2=test123\nDB_HOST_1=localhost";
102
+
103
+ mockedFs.existsSync.mockReturnValue(true);
104
+ mockedFs.readFileSync.mockReturnValue(envContent);
105
+
106
+ const result = loadAndValidateEnv(mockEnvPath);
107
+
108
+ expect(result).toEqual({
109
+ API_KEY_V2: "test123",
110
+ DB_HOST_1: "localhost",
111
+ });
112
+ });
113
+
114
+ it("debe manejar múltiples errores a la vez", () => {
115
+ const envContent = "api_key=test\nVALID_VAR=value\nEMPTY_VAR=";
116
+
117
+ mockedFs.existsSync.mockReturnValue(true);
118
+ mockedFs.readFileSync.mockReturnValue(envContent);
119
+
120
+ expect(() => loadAndValidateEnv(mockEnvPath)).toThrow(
121
+ /Errores en variables de ambiente/
122
+ );
123
+ });
124
+
125
+ it("debe usar ruta absoluta si se proporciona", () => {
126
+ const absoluteEnvPath = "/absolute/path/.env";
127
+ const envContent = "TEST_VAR=value";
128
+
129
+ mockedFs.existsSync.mockReturnValue(true);
130
+ mockedFs.readFileSync.mockReturnValue(envContent);
131
+
132
+ loadAndValidateEnv(absoluteEnvPath);
133
+
134
+ expect(mockedFs.existsSync).toHaveBeenCalledWith(absoluteEnvPath);
135
+ });
136
+
137
+ it("debe aceptar reglas personalizadas", () => {
138
+ const envContent = "api_key=test123";
139
+ const customRules = {
140
+ keyPattern: /^[a-z_]+$/,
141
+ valuePattern: /^[\x20-\x7E]*$/,
142
+ };
143
+
144
+ mockedFs.existsSync.mockReturnValue(true);
145
+ mockedFs.readFileSync.mockReturnValue(envContent);
146
+
147
+ const result = loadAndValidateEnv(mockEnvPath, customRules);
148
+
149
+ expect(result).toEqual({
150
+ api_key: "test123",
151
+ });
152
+ });
153
+ });
154
+
155
+ describe("loadAndValidateEnv - Pruebas de Rutas", () => {
156
+ beforeEach(() => {
157
+ jest.clearAllMocks();
158
+ });
159
+
160
+ it("debe cargar .env desde la raíz del proyecto por defecto", () => {
161
+ const envContent = "DEFAULT_VAR=value1";
162
+ const expectedPath = path.resolve(process.cwd(), ".env");
163
+
164
+ mockedFs.existsSync.mockReturnValue(true);
165
+ mockedFs.readFileSync.mockReturnValue(envContent);
166
+
167
+ loadAndValidateEnv();
168
+
169
+ expect(mockedFs.existsSync).toHaveBeenCalledWith(expectedPath);
170
+ expect(mockedFs.readFileSync).toHaveBeenCalledWith(expectedPath, "utf-8");
171
+ });
172
+
173
+ it("debe cargar .env.development desde ruta relativa", () => {
174
+ const envContent = "DEV_VAR=dev_value";
175
+ const expectedPath = path.resolve(process.cwd(), ".env.development");
176
+
177
+ mockedFs.existsSync.mockReturnValue(true);
178
+ mockedFs.readFileSync.mockReturnValue(envContent);
179
+
180
+ const result = loadAndValidateEnv(".env.development");
181
+
182
+ expect(mockedFs.existsSync).toHaveBeenCalledWith(expectedPath);
183
+ expect(result).toEqual({ DEV_VAR: "dev_value" });
184
+ });
185
+
186
+ it("debe cargar .env.production desde ruta relativa", () => {
187
+ const envContent = "PROD_VAR=prod_value";
188
+ const expectedPath = path.resolve(process.cwd(), ".env.production");
189
+
190
+ mockedFs.existsSync.mockReturnValue(true);
191
+ mockedFs.readFileSync.mockReturnValue(envContent);
192
+
193
+ const result = loadAndValidateEnv(".env.production");
194
+
195
+ expect(mockedFs.existsSync).toHaveBeenCalledWith(expectedPath);
196
+ expect(result).toEqual({ PROD_VAR: "prod_value" });
197
+ });
198
+
199
+ it("debe cargar .env.test desde ruta relativa", () => {
200
+ const envContent = "TEST_VAR=test_value";
201
+ const expectedPath = path.resolve(process.cwd(), ".env.test");
202
+
203
+ mockedFs.existsSync.mockReturnValue(true);
204
+ mockedFs.readFileSync.mockReturnValue(envContent);
205
+
206
+ const result = loadAndValidateEnv(".env.test");
207
+
208
+ expect(mockedFs.existsSync).toHaveBeenCalledWith(expectedPath);
209
+ expect(result).toEqual({ TEST_VAR: "test_value" });
210
+ });
211
+
212
+ it("debe cargar desde subdirectorio (config/.env)", () => {
213
+ const envContent = "CONFIG_VAR=config_value";
214
+ const expectedPath = path.resolve(process.cwd(), "config/.env");
215
+
216
+ mockedFs.existsSync.mockReturnValue(true);
217
+ mockedFs.readFileSync.mockReturnValue(envContent);
218
+
219
+ const result = loadAndValidateEnv("config/.env");
220
+
221
+ expect(mockedFs.existsSync).toHaveBeenCalledWith(expectedPath);
222
+ expect(result).toEqual({ CONFIG_VAR: "config_value" });
223
+ });
224
+
225
+ it("debe cargar desde subdirectorio profundo (config/env/.env.local)", () => {
226
+ const envContent = "LOCAL_VAR=local_value";
227
+ const expectedPath = path.resolve(process.cwd(), "config/env/.env.local");
228
+
229
+ mockedFs.existsSync.mockReturnValue(true);
230
+ mockedFs.readFileSync.mockReturnValue(envContent);
231
+
232
+ const result = loadAndValidateEnv("config/env/.env.local");
233
+
234
+ expect(mockedFs.existsSync).toHaveBeenCalledWith(expectedPath);
235
+ expect(result).toEqual({ LOCAL_VAR: "local_value" });
236
+ });
237
+
238
+ it("debe manejar ruta absoluta en Unix/Linux/Mac", () => {
239
+ const absolutePath = "/home/user/project/.env";
240
+ const envContent = "UNIX_VAR=unix_value";
241
+
242
+ mockedFs.existsSync.mockReturnValue(true);
243
+ mockedFs.readFileSync.mockReturnValue(envContent);
244
+
245
+ const result = loadAndValidateEnv(absolutePath);
246
+
247
+ expect(mockedFs.existsSync).toHaveBeenCalledWith(absolutePath);
248
+ expect(result).toEqual({ UNIX_VAR: "unix_value" });
249
+ });
250
+
251
+ it("debe manejar ruta absoluta en Windows", () => {
252
+ const absolutePath = "C:\\Users\\Admin\\project\\.env";
253
+ const envContent = "WINDOWS_VAR=windows_value";
254
+
255
+ // En sistemas no-Windows, path.isAbsolute() puede no reconocer rutas de Windows
256
+ // pero la función debería intentar usarla tal cual
257
+ const isWindows = process.platform === "win32";
258
+ const expectedPath = path.isAbsolute(absolutePath)
259
+ ? absolutePath
260
+ : path.resolve(process.cwd(), absolutePath);
261
+
262
+ mockedFs.existsSync.mockReturnValue(true);
263
+ mockedFs.readFileSync.mockReturnValue(envContent);
264
+
265
+ const result = loadAndValidateEnv(absolutePath);
266
+
267
+ expect(mockedFs.existsSync).toHaveBeenCalledWith(expectedPath);
268
+ expect(result).toEqual({ WINDOWS_VAR: "windows_value" });
269
+ });
270
+
271
+ it("debe cargar archivo con nombre personalizado (my-vars.env)", () => {
272
+ const envContent = "CUSTOM_VAR=custom_value";
273
+ const expectedPath = path.resolve(process.cwd(), "my-vars.env");
274
+
275
+ mockedFs.existsSync.mockReturnValue(true);
276
+ mockedFs.readFileSync.mockReturnValue(envContent);
277
+
278
+ const result = loadAndValidateEnv("my-vars.env");
279
+
280
+ expect(mockedFs.existsSync).toHaveBeenCalledWith(expectedPath);
281
+ expect(result).toEqual({ CUSTOM_VAR: "custom_value" });
282
+ });
283
+
284
+ it("debe lanzar error con ruta relativa si el archivo no existe", () => {
285
+ const relativePath = "config/.env.missing";
286
+ const expectedPath = path.resolve(process.cwd(), relativePath);
287
+
288
+ mockedFs.existsSync.mockReturnValue(false);
289
+
290
+ expect(() => loadAndValidateEnv(relativePath)).toThrow(
291
+ `Archivo .env no encontrado: ${expectedPath}`
292
+ );
293
+ });
294
+
295
+ it("debe lanzar error con ruta absoluta si el archivo no existe", () => {
296
+ const absolutePath = "/absolute/path/to/.env.missing";
297
+
298
+ mockedFs.existsSync.mockReturnValue(false);
299
+
300
+ expect(() => loadAndValidateEnv(absolutePath)).toThrow(
301
+ `Archivo .env no encontrado: ${absolutePath}`
302
+ );
303
+ });
304
+
305
+ it("debe resolver correctamente rutas con '..'", () => {
306
+ const relativePath = "../parent/.env";
307
+ const expectedPath = path.resolve(process.cwd(), relativePath);
308
+ const envContent = "PARENT_VAR=parent_value";
309
+
310
+ mockedFs.existsSync.mockReturnValue(true);
311
+ mockedFs.readFileSync.mockReturnValue(envContent);
312
+
313
+ const result = loadAndValidateEnv(relativePath);
314
+
315
+ expect(mockedFs.existsSync).toHaveBeenCalledWith(expectedPath);
316
+ expect(result).toEqual({ PARENT_VAR: "parent_value" });
317
+ });
318
+
319
+ it("debe resolver correctamente rutas con './'", () => {
320
+ const relativePath = "./.env.local";
321
+ const expectedPath = path.resolve(process.cwd(), relativePath);
322
+ const envContent = "LOCAL_VAR=local_value";
323
+
324
+ mockedFs.existsSync.mockReturnValue(true);
325
+ mockedFs.readFileSync.mockReturnValue(envContent);
326
+
327
+ const result = loadAndValidateEnv(relativePath);
328
+
329
+ expect(mockedFs.existsSync).toHaveBeenCalledWith(expectedPath);
330
+ expect(result).toEqual({ LOCAL_VAR: "local_value" });
331
+ });
332
+
333
+ it("debe manejar rutas con espacios en el nombre", () => {
334
+ const pathWithSpaces = "my config/.env.local";
335
+ const expectedPath = path.resolve(process.cwd(), pathWithSpaces);
336
+ const envContent = "SPACE_VAR=space_value";
337
+
338
+ mockedFs.existsSync.mockReturnValue(true);
339
+ mockedFs.readFileSync.mockReturnValue(envContent);
340
+
341
+ const result = loadAndValidateEnv(pathWithSpaces);
342
+
343
+ expect(mockedFs.existsSync).toHaveBeenCalledWith(expectedPath);
344
+ expect(result).toEqual({ SPACE_VAR: "space_value" });
345
+ });
346
+
347
+ it("debe distinguir entre diferentes archivos .env en la misma ruta", () => {
348
+ const envContent1 = "VAR_DEV=dev";
349
+ const envContent2 = "VAR_PROD=prod";
350
+
351
+ mockedFs.existsSync.mockReturnValue(true);
352
+
353
+ // Primer archivo
354
+ mockedFs.readFileSync.mockReturnValueOnce(envContent1);
355
+ const result1 = loadAndValidateEnv(".env.development");
356
+
357
+ // Segundo archivo
358
+ mockedFs.readFileSync.mockReturnValueOnce(envContent2);
359
+ const result2 = loadAndValidateEnv(".env.production");
360
+
361
+ expect(result1).toEqual({ VAR_DEV: "dev" });
362
+ expect(result2).toEqual({ VAR_PROD: "prod" });
363
+ });
364
+
365
+ it("debe cargar desde carpeta environments con .env.staging", () => {
366
+ const envContent = "STAGING_API=https://staging.api.com\nSTAGING_PORT=8080";
367
+ const expectedPath = path.resolve(process.cwd(), "environments/.env.staging");
368
+
369
+ mockedFs.existsSync.mockReturnValue(true);
370
+ mockedFs.readFileSync.mockReturnValue(envContent);
371
+
372
+ const result = loadAndValidateEnv("environments/.env.staging");
373
+
374
+ expect(mockedFs.existsSync).toHaveBeenCalledWith(expectedPath);
375
+ expect(result).toEqual({
376
+ STAGING_API: "https://staging.api.com",
377
+ STAGING_PORT: "8080"
378
+ });
379
+ });
380
+
381
+ it("debe cargar desde carpeta config/envs/.env.qa", () => {
382
+ const envContent = "QA_DATABASE=qa-db.example.com\nQA_USER=qauser";
383
+ const expectedPath = path.resolve(process.cwd(), "config/envs/.env.qa");
384
+
385
+ mockedFs.existsSync.mockReturnValue(true);
386
+ mockedFs.readFileSync.mockReturnValue(envContent);
387
+
388
+ const result = loadAndValidateEnv("config/envs/.env.qa");
389
+
390
+ expect(mockedFs.existsSync).toHaveBeenCalledWith(expectedPath);
391
+ expect(result).toEqual({
392
+ QA_DATABASE: "qa-db.example.com",
393
+ QA_USER: "qauser"
394
+ });
395
+ });
396
+
397
+ it("debe cargar .env.secrets desde subcarpeta secure", () => {
398
+ const envContent = "API_SECRET=super_secret_key\nDB_PASSWORD=encrypted_pass";
399
+ const expectedPath = path.resolve(process.cwd(), "secure/.env.secrets");
400
+
401
+ mockedFs.existsSync.mockReturnValue(true);
402
+ mockedFs.readFileSync.mockReturnValue(envContent);
403
+
404
+ const result = loadAndValidateEnv("secure/.env.secrets");
405
+
406
+ expect(mockedFs.existsSync).toHaveBeenCalledWith(expectedPath);
407
+ expect(result).toEqual({
408
+ API_SECRET: "super_secret_key",
409
+ DB_PASSWORD: "encrypted_pass"
410
+ });
411
+ });
412
+
413
+ it("debe cargar .env.k8s desde carpeta kubernetes", () => {
414
+ const envContent = "K8S_CLUSTER=prod-cluster\nK8S_TOKEN=abc123xyz";
415
+ const expectedPath = path.resolve(process.cwd(), "kubernetes/.env.k8s");
416
+
417
+ mockedFs.existsSync.mockReturnValue(true);
418
+ mockedFs.readFileSync.mockReturnValue(envContent);
419
+
420
+ const result = loadAndValidateEnv("kubernetes/.env.k8s");
421
+
422
+ expect(mockedFs.existsSync).toHaveBeenCalledWith(expectedPath);
423
+ expect(result).toEqual({
424
+ K8S_CLUSTER: "prod-cluster",
425
+ K8S_TOKEN: "abc123xyz"
426
+ });
427
+ });
428
+
429
+ it("debe cargar desde ruta con múltiples niveles de subdirectorios", () => {
430
+ const envContent = "DEEP_VAR=deep_value";
431
+ const expectedPath = path.resolve(process.cwd(), "a/b/c/d/.env.deep");
432
+
433
+ mockedFs.existsSync.mockReturnValue(true);
434
+ mockedFs.readFileSync.mockReturnValue(envContent);
435
+
436
+ const result = loadAndValidateEnv("a/b/c/d/.env.deep");
437
+
438
+ expect(mockedFs.existsSync).toHaveBeenCalledWith(expectedPath);
439
+ expect(result).toEqual({ DEEP_VAR: "deep_value" });
440
+ });
441
+
442
+ it("debe cargar .env.docker desde carpeta docker", () => {
443
+ const envContent = "DOCKER_IMAGE=myapp:latest\nDOCKER_PORT=3000";
444
+ const expectedPath = path.resolve(process.cwd(), "docker/.env.docker");
445
+
446
+ mockedFs.existsSync.mockReturnValue(true);
447
+ mockedFs.readFileSync.mockReturnValue(envContent);
448
+
449
+ const result = loadAndValidateEnv("docker/.env.docker");
450
+
451
+ expect(mockedFs.existsSync).toHaveBeenCalledWith(expectedPath);
452
+ expect(result).toEqual({
453
+ DOCKER_IMAGE: "myapp:latest",
454
+ DOCKER_PORT: "3000"
455
+ });
456
+ });
457
+
458
+ it("debe cargar vars.env con nombre no estándar", () => {
459
+ const envContent = "CUSTOM_VAR_1=value1\nCUSTOM_VAR_2=value2";
460
+ const expectedPath = path.resolve(process.cwd(), "vars.env");
461
+
462
+ mockedFs.existsSync.mockReturnValue(true);
463
+ mockedFs.readFileSync.mockReturnValue(envContent);
464
+
465
+ const result = loadAndValidateEnv("vars.env");
466
+
467
+ expect(mockedFs.existsSync).toHaveBeenCalledWith(expectedPath);
468
+ expect(result).toEqual({
469
+ CUSTOM_VAR_1: "value1",
470
+ CUSTOM_VAR_2: "value2"
471
+ });
472
+ });
473
+
474
+ it("debe cargar environment.conf con extensión no estándar", () => {
475
+ const envContent = "CONF_VAR=conf_value";
476
+ const expectedPath = path.resolve(process.cwd(), "environment.conf");
477
+
478
+ mockedFs.existsSync.mockReturnValue(true);
479
+ mockedFs.readFileSync.mockReturnValue(envContent);
480
+
481
+ const result = loadAndValidateEnv("environment.conf");
482
+
483
+ expect(mockedFs.existsSync).toHaveBeenCalledWith(expectedPath);
484
+ expect(result).toEqual({ CONF_VAR: "conf_value" });
485
+ });
486
+
487
+ it("debe lanzar error para ruta profunda inexistente", () => {
488
+ const deepPath = "config/envs/prod/.env.missing";
489
+ const expectedPath = path.resolve(process.cwd(), deepPath);
490
+
491
+ mockedFs.existsSync.mockReturnValue(false);
492
+
493
+ expect(() => loadAndValidateEnv(deepPath)).toThrow(
494
+ `Archivo .env no encontrado: ${expectedPath}`
495
+ );
496
+ });
497
+ });
498
+
499
+ describe("Validación de valores especiales", () => {
500
+ beforeEach(() => {
501
+ jest.clearAllMocks();
502
+ });
503
+
504
+ it("debe aceptar URLs como valores", () => {
505
+ const envContent = "API_URL=https://api.example.com:8080/v1/users?limit=10";
506
+ mockedFs.existsSync.mockReturnValue(true);
507
+ mockedFs.readFileSync.mockReturnValue(envContent);
508
+
509
+ const result = loadAndValidateEnv();
510
+ expect(result.API_URL).toBe("https://api.example.com:8080/v1/users?limit=10");
511
+ });
512
+
513
+ it("debe aceptar rutas de archivos Unix", () => {
514
+ const envContent = "LOG_PATH=/var/log/app/error.log";
515
+ mockedFs.existsSync.mockReturnValue(true);
516
+ mockedFs.readFileSync.mockReturnValue(envContent);
517
+
518
+ const result = loadAndValidateEnv();
519
+ expect(result.LOG_PATH).toBe("/var/log/app/error.log");
520
+ });
521
+
522
+ it("debe aceptar rutas de archivos Windows", () => {
523
+ const envContent = "LOG_PATH=C:\\Users\\Admin\\logs\\app.log";
524
+ mockedFs.existsSync.mockReturnValue(true);
525
+ mockedFs.readFileSync.mockReturnValue(envContent);
526
+
527
+ const result = loadAndValidateEnv();
528
+ expect(result.LOG_PATH).toBe("C:\\Users\\Admin\\logs\\app.log");
529
+ });
530
+
531
+ it("debe aceptar direcciones de email", () => {
532
+ const envContent = "ADMIN_EMAIL=admin@example.com";
533
+ mockedFs.existsSync.mockReturnValue(true);
534
+ mockedFs.readFileSync.mockReturnValue(envContent);
535
+
536
+ const result = loadAndValidateEnv();
537
+ expect(result.ADMIN_EMAIL).toBe("admin@example.com");
538
+ });
539
+
540
+ it("debe aceptar valores con símbolos especiales ASCII (sin #)", () => {
541
+ // Nota: # se interpreta como comentario en dotenv, por lo que no se incluye
542
+ const envContent = "SPECIAL_CHARS=!@$%^&*()_+-=[]{}|;:,.<>?";
543
+ mockedFs.existsSync.mockReturnValue(true);
544
+ mockedFs.readFileSync.mockReturnValue(envContent);
545
+
546
+ const result = loadAndValidateEnv();
547
+ expect(result.SPECIAL_CHARS).toBe("!@$%^&*()_+-=[]{}|;:,.<>?");
548
+ });
549
+
550
+ it("debe aceptar JSON como valor", () => {
551
+ const envContent = 'JSON_CONFIG={"name":"test","value":123}';
552
+ mockedFs.existsSync.mockReturnValue(true);
553
+ mockedFs.readFileSync.mockReturnValue(envContent);
554
+
555
+ const result = loadAndValidateEnv();
556
+ expect(result.JSON_CONFIG).toBe('{"name":"test","value":123}');
557
+ });
558
+
559
+ it("debe aceptar valores sin comillas (plain)", () => {
560
+ const envContent = "PLAIN_VALUE=simple_text_without_quotes";
561
+ mockedFs.existsSync.mockReturnValue(true);
562
+ mockedFs.readFileSync.mockReturnValue(envContent);
563
+
564
+ const result = loadAndValidateEnv();
565
+ expect(result.PLAIN_VALUE).toBe("simple_text_without_quotes");
566
+ });
567
+
568
+ it("debe aceptar valores envueltos en comillas simples", () => {
569
+ const envContent = "QUOTED_VALUE='valor con espacios'";
570
+ mockedFs.existsSync.mockReturnValue(true);
571
+ mockedFs.readFileSync.mockReturnValue(envContent);
572
+
573
+ const result = loadAndValidateEnv();
574
+ expect(result.QUOTED_VALUE).toBe("valor con espacios");
575
+ });
576
+
577
+ it("debe aceptar valores envueltos en comillas dobles", () => {
578
+ const envContent = 'DOUBLE_QUOTED="valor con espacios"';
579
+ mockedFs.existsSync.mockReturnValue(true);
580
+ mockedFs.readFileSync.mockReturnValue(envContent);
581
+
582
+ const result = loadAndValidateEnv();
583
+ expect(result.DOUBLE_QUOTED).toBe("valor con espacios");
584
+ });
585
+
586
+ it("debe aceptar valores con comillas simples dentro del texto", () => {
587
+ const envContent = "MESSAGE=It's working!";
588
+ mockedFs.existsSync.mockReturnValue(true);
589
+ mockedFs.readFileSync.mockReturnValue(envContent);
590
+
591
+ const result = loadAndValidateEnv();
592
+ expect(result.MESSAGE).toBe("It's working!");
593
+ });
594
+
595
+ it("debe aceptar valores con comillas dobles dentro del texto", () => {
596
+ const envContent = 'QUOTE=He said "Hello"';
597
+ mockedFs.existsSync.mockReturnValue(true);
598
+ mockedFs.readFileSync.mockReturnValue(envContent);
599
+
600
+ const result = loadAndValidateEnv();
601
+ expect(result.QUOTE).toBe('He said "Hello"');
602
+ });
603
+
604
+ it("debe manejar comillas escapadas según el comportamiento de dotenv", () => {
605
+ // Nota: dotenv preserva las barras invertidas en el parsing
606
+ const envContent = 'ESCAPED="He said \\"Hello\\""';
607
+ mockedFs.existsSync.mockReturnValue(true);
608
+ mockedFs.readFileSync.mockReturnValue(envContent);
609
+
610
+ const result = loadAndValidateEnv();
611
+ // dotenv mantiene las barras invertidas tal como están
612
+ expect(result.ESCAPED).toBe('He said \\"Hello\\"');
613
+ });
614
+
615
+ it("debe aceptar conexión strings de base de datos", () => {
616
+ const envContent = "DB_CONNECTION=postgresql://user:pass@localhost:5432/mydb";
617
+ mockedFs.existsSync.mockReturnValue(true);
618
+ mockedFs.readFileSync.mockReturnValue(envContent);
619
+
620
+ const result = loadAndValidateEnv();
621
+ expect(result.DB_CONNECTION).toBe("postgresql://user:pass@localhost:5432/mydb");
622
+ });
623
+
624
+ it("debe aceptar tokens y hashes largos", () => {
625
+ const envContent = "JWT_SECRET=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0";
626
+ mockedFs.existsSync.mockReturnValue(true);
627
+ mockedFs.readFileSync.mockReturnValue(envContent);
628
+
629
+ const result = loadAndValidateEnv();
630
+ expect(result.JWT_SECRET).toBe("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0");
631
+ });
632
+
633
+ it("debe aceptar códigos base64", () => {
634
+ const envContent = "ENCODED_DATA=SGVsbG8gV29ybGQ=";
635
+ mockedFs.existsSync.mockReturnValue(true);
636
+ mockedFs.readFileSync.mockReturnValue(envContent);
637
+
638
+ const result = loadAndValidateEnv();
639
+ expect(result.ENCODED_DATA).toBe("SGVsbG8gV29ybGQ=");
640
+ });
641
+
642
+ it("debe aceptar valores numéricos negativos", () => {
643
+ const envContent = "TEMPERATURE=-15";
644
+ mockedFs.existsSync.mockReturnValue(true);
645
+ mockedFs.readFileSync.mockReturnValue(envContent);
646
+
647
+ const result = loadAndValidateEnv();
648
+ expect(result.TEMPERATURE).toBe("-15");
649
+ });
650
+
651
+ it("debe aceptar valores con múltiples espacios", () => {
652
+ const envContent = "MULTI_SPACE=Hello World";
653
+ mockedFs.existsSync.mockReturnValue(true);
654
+ mockedFs.readFileSync.mockReturnValue(envContent);
655
+
656
+ const result = loadAndValidateEnv();
657
+ expect(result.MULTI_SPACE).toBe("Hello World");
658
+ });
659
+
660
+ it("debe trimear espacios al inicio (comportamiento de dotenv)", () => {
661
+ // Nota: dotenv hace trim automático de los espacios al inicio y final
662
+ const envContent = "LEADING_SPACE= value";
663
+ mockedFs.existsSync.mockReturnValue(true);
664
+ mockedFs.readFileSync.mockReturnValue(envContent);
665
+
666
+ const result = loadAndValidateEnv();
667
+ expect(result.LEADING_SPACE).toBe("value");
668
+ });
669
+
670
+ it("debe trimear espacios al final (comportamiento de dotenv)", () => {
671
+ // Nota: dotenv hace trim automático de los espacios al inicio y final
672
+ const envContent = "TRAILING_SPACE=value ";
673
+ mockedFs.existsSync.mockReturnValue(true);
674
+ mockedFs.readFileSync.mockReturnValue(envContent);
675
+
676
+ const result = loadAndValidateEnv();
677
+ expect(result.TRAILING_SPACE).toBe("value");
678
+ });
679
+ });
680
+
681
+ // describe("Pruebas de números avanzadas", () => {
682
+ // beforeEach(() => {
683
+ // resetEnvCache();
684
+ // jest.clearAllMocks();
685
+ // });
686
+
687
+ // it("debe manejar números negativos", () => {
688
+ // const envContent = "TEMPERATURE=-25\nBALANCE=-100.50";
689
+ // mockedFs.existsSync.mockReturnValue(true);
690
+ // mockedFs.readFileSync.mockReturnValue(envContent);
691
+
692
+ // expect(getEnvNumber("TEMPERATURE")).toBe(-25);
693
+ // expect(getEnvNumber("BALANCE")).toBe(-100.50);
694
+ // });
695
+
696
+ // it("debe manejar el número cero", () => {
697
+ // const envContent = "ZERO=0\nZERO_FLOAT=0.0";
698
+ // mockedFs.existsSync.mockReturnValue(true);
699
+ // mockedFs.readFileSync.mockReturnValue(envContent);
700
+
701
+ // expect(getEnvNumber("ZERO")).toBe(0);
702
+ // expect(getEnvNumber("ZERO_FLOAT")).toBe(0);
703
+ // });
704
+
705
+ // it("debe manejar números muy grandes", () => {
706
+ // const envContent = "BIG_NUMBER=999999999999";
707
+ // mockedFs.existsSync.mockReturnValue(true);
708
+ // mockedFs.readFileSync.mockReturnValue(envContent);
709
+
710
+ // expect(getEnvNumber("BIG_NUMBER")).toBe(999999999999);
711
+ // });
712
+
713
+ // it("debe manejar números en notación científica", () => {
714
+ // const envContent = "SCIENTIFIC=1.5e10";
715
+ // mockedFs.existsSync.mockReturnValue(true);
716
+ // mockedFs.readFileSync.mockReturnValue(envContent);
717
+
718
+ // expect(getEnvNumber("SCIENTIFIC")).toBe(1.5e10);
719
+ // });
720
+
721
+ // it("debe retornar undefined para strings vacíos cuando se esperan números", () => {
722
+ // const envContent = "API_KEY=valid";
723
+ // mockedFs.existsSync.mockReturnValue(true);
724
+ // mockedFs.readFileSync.mockReturnValue(envContent);
725
+
726
+ // expect(getEnvNumber("NONEXISTENT")).toBeUndefined();
727
+ // });
728
+
729
+ // it("debe rechazar valores con espacios como números inválidos", () => {
730
+ // const consoleWarnSpy = jest.spyOn(console, "warn").mockImplementation();
731
+ // const envContent = "SPACED_NUMBER=10 20";
732
+ // mockedFs.existsSync.mockReturnValue(true);
733
+ // mockedFs.readFileSync.mockReturnValue(envContent);
734
+
735
+ // expect(getEnvNumber("SPACED_NUMBER", 0)).toBe(0);
736
+ // expect(consoleWarnSpy).toHaveBeenCalled();
737
+
738
+ // consoleWarnSpy.mockRestore();
739
+ // });
740
+ // });
741
+
742
+ // describe("Pruebas de booleanos avanzadas", () => {
743
+ // beforeEach(() => {
744
+ // resetEnvCache();
745
+ // jest.clearAllMocks();
746
+ // });
747
+
748
+ // it("debe manejar 'TRUE' en mayúsculas", () => {
749
+ // const envContent = "FLAG=TRUE";
750
+ // mockedFs.existsSync.mockReturnValue(true);
751
+ // mockedFs.readFileSync.mockReturnValue(envContent);
752
+
753
+ // expect(getEnvBoolean("FLAG")).toBe(true);
754
+ // });
755
+
756
+ // it("debe manejar 'Yes' en mixed case", () => {
757
+ // const envContent = "FLAG=Yes";
758
+ // mockedFs.existsSync.mockReturnValue(true);
759
+ // mockedFs.readFileSync.mockReturnValue(envContent);
760
+
761
+ // expect(getEnvBoolean("FLAG")).toBe(true);
762
+ // });
763
+
764
+ // it("debe manejar 'ON' en mayúsculas", () => {
765
+ // const envContent = "FLAG=ON";
766
+ // mockedFs.existsSync.mockReturnValue(true);
767
+ // mockedFs.readFileSync.mockReturnValue(envContent);
768
+
769
+ // expect(getEnvBoolean("FLAG")).toBe(true);
770
+ // });
771
+
772
+ // it("debe retornar false para 'no'", () => {
773
+ // const envContent = "FLAG=no";
774
+ // mockedFs.existsSync.mockReturnValue(true);
775
+ // mockedFs.readFileSync.mockReturnValue(envContent);
776
+
777
+ // expect(getEnvBoolean("FLAG")).toBe(false);
778
+ // });
779
+
780
+ // it("debe retornar false para 'off'", () => {
781
+ // const envContent = "FLAG=off";
782
+ // mockedFs.existsSync.mockReturnValue(true);
783
+ // mockedFs.readFileSync.mockReturnValue(envContent);
784
+
785
+ // expect(getEnvBoolean("FLAG")).toBe(false);
786
+ // });
787
+
788
+ // it("debe retornar false para valores numéricos diferentes de 1", () => {
789
+ // const envContent = "FLAG=2";
790
+ // mockedFs.existsSync.mockReturnValue(true);
791
+ // mockedFs.readFileSync.mockReturnValue(envContent);
792
+
793
+ // expect(getEnvBoolean("FLAG")).toBe(false);
794
+ // });
795
+
796
+ // it("debe retornar false para valores aleatorios", () => {
797
+ // const envContent = "FLAG=random";
798
+ // mockedFs.existsSync.mockReturnValue(true);
799
+ // mockedFs.readFileSync.mockReturnValue(envContent);
800
+
801
+ // expect(getEnvBoolean("FLAG")).toBe(false);
802
+ // });
803
+ // });
804
+
805
+ // describe("Test de caché y reseteo", () => {
806
+ // beforeEach(() => {
807
+ // resetEnvCache();
808
+ // jest.clearAllMocks();
809
+ // });
810
+
811
+ // it("debe resetear el caché correctamente", () => {
812
+ // const envContent = "VAR1=value1";
813
+ // mockedFs.existsSync.mockReturnValue(true);
814
+ // mockedFs.readFileSync.mockReturnValue(envContent);
815
+
816
+ // // Cargar una vez
817
+ // getEnvVar("VAR1");
818
+ // expect(mockedFs.readFileSync).toHaveBeenCalledTimes(1);
819
+
820
+ // // Resetear el caché
821
+ // resetEnvCache();
822
+
823
+ // // Cargar de nuevo debería leer el archivo otra vez
824
+ // const envContent2 = "VAR1=value2";
825
+ // mockedFs.readFileSync.mockReturnValue(envContent2);
826
+
827
+ // const value = getEnvVar("VAR1");
828
+ // expect(value).toBe("value2");
829
+ // expect(mockedFs.readFileSync).toHaveBeenCalledTimes(2);
830
+ // });
831
+
832
+ // it("debe cargar solo una vez sin resetear el caché", () => {
833
+ // const envContent = "VAR1=value1";
834
+ // mockedFs.existsSync.mockReturnValue(true);
835
+ // mockedFs.readFileSync.mockReturnValue(envContent);
836
+
837
+ // // Múltiples llamadas
838
+ // getEnvVar("VAR1");
839
+ // getEnvVar("VAR1");
840
+ // hasEnvVar("VAR1");
841
+ // getAllEnvVars();
842
+
843
+ // // Solo una lectura de archivo
844
+ // expect(mockedFs.readFileSync).toHaveBeenCalledTimes(1);
845
+ // });
846
+
847
+ // it("debe permitir cambiar entre diferentes archivos .env después de resetear", () => {
848
+ // // Cargar .env.development
849
+ // const devContent = "ENV=development";
850
+ // mockedFs.existsSync.mockReturnValue(true);
851
+ // mockedFs.readFileSync.mockReturnValue(devContent);
852
+
853
+ // loadAndValidateEnv(".env.development");
854
+ // expect(getEnvVar("ENV")).toBe("development");
855
+
856
+ // // Resetear y cargar .env.production
857
+ // resetEnvCache();
858
+ // const prodContent = "ENV=production";
859
+ // mockedFs.readFileSync.mockReturnValue(prodContent);
860
+
861
+ // loadAndValidateEnv(".env.production");
862
+ // expect(getEnvVar("ENV")).toBe("production");
863
+ // });
864
+ // });
865
+
866
+ describe("Pruebas de edge cases", () => {
867
+ beforeEach(() => {
868
+ jest.clearAllMocks();
869
+ });
870
+
871
+ it("debe manejar nombres de variables con muchos guiones bajos", () => {
872
+ const envContent = "MY_SUPER_LONG_VAR_NAME_WITH_UNDERSCORES_123=value";
873
+ mockedFs.existsSync.mockReturnValue(true);
874
+ mockedFs.readFileSync.mockReturnValue(envContent);
875
+
876
+ const result = loadAndValidateEnv();
877
+ expect(result.MY_SUPER_LONG_VAR_NAME_WITH_UNDERSCORES_123).toBe("value");
878
+ });
879
+
880
+ it("debe manejar valores muy largos", () => {
881
+ const longValue = "a".repeat(1000);
882
+ const envContent = `LONG_VALUE=${longValue}`;
883
+ mockedFs.existsSync.mockReturnValue(true);
884
+ mockedFs.readFileSync.mockReturnValue(envContent);
885
+
886
+ const result = loadAndValidateEnv();
887
+ expect(result.LONG_VALUE).toBe(longValue);
888
+ expect(result.LONG_VALUE.length).toBe(1000);
889
+ });
890
+
891
+ it("debe manejar múltiples variables (100+)", () => {
892
+ const vars = Array.from({ length: 100 }, (_, i) => `VAR_${i}=value${i}`).join("\n");
893
+ mockedFs.existsSync.mockReturnValue(true);
894
+ mockedFs.readFileSync.mockReturnValue(vars);
895
+
896
+ const result = loadAndValidateEnv();
897
+ expect(Object.keys(result).length).toBe(100);
898
+ expect(result.VAR_0).toBe("value0");
899
+ expect(result.VAR_99).toBe("value99");
900
+ });
901
+
902
+ it("debe rechazar nombres que empiezan con número", () => {
903
+ const envContent = "1ST_VAR=value";
904
+ mockedFs.existsSync.mockReturnValue(true);
905
+ mockedFs.readFileSync.mockReturnValue(envContent);
906
+
907
+ expect(() => loadAndValidateEnv()).toThrow(/Nombre inválido: "1ST_VAR"/);
908
+ });
909
+
910
+ it("debe rechazar nombres que empiezan con guion bajo", () => {
911
+ const envContent = "_PRIVATE=value";
912
+ mockedFs.existsSync.mockReturnValue(true);
913
+ mockedFs.readFileSync.mockReturnValue(envContent);
914
+
915
+ expect(() => loadAndValidateEnv()).toThrow(/Nombre inválido: "_PRIVATE"/);
916
+ });
917
+
918
+ it("debe aceptar nombres que terminan con números", () => {
919
+ const envContent = "API_KEY_V2=value";
920
+ mockedFs.existsSync.mockReturnValue(true);
921
+ mockedFs.readFileSync.mockReturnValue(envContent);
922
+
923
+ const result = loadAndValidateEnv();
924
+ expect(result.API_KEY_V2).toBe("value");
925
+ });
926
+
927
+ it("debe manejar valores que parecen asignaciones", () => {
928
+ const envContent = "EQUATION=x=y+z";
929
+ mockedFs.existsSync.mockReturnValue(true);
930
+ mockedFs.readFileSync.mockReturnValue(envContent);
931
+
932
+ const result = loadAndValidateEnv();
933
+ expect(result.EQUATION).toBe("x=y+z");
934
+ });
935
+ });
936
+
937
+ describe("Pruebas de errores específicos", () => {
938
+ beforeEach(() => {
939
+ jest.clearAllMocks();
940
+ });
941
+
942
+ it("debe manejar error ENOENT", () => {
943
+ mockedFs.existsSync.mockReturnValue(true);
944
+ const error = new Error("ENOENT: no such file or directory");
945
+ (error as any).code = "ENOENT";
946
+ mockedFs.readFileSync.mockImplementation(() => {
947
+ throw error;
948
+ });
949
+
950
+ expect(() => loadAndValidateEnv()).toThrow(
951
+ "Error al leer el archivo .env: ENOENT: no such file or directory"
952
+ );
953
+ });
954
+
955
+ it("debe manejar error EACCES (permiso denegado)", () => {
956
+ mockedFs.existsSync.mockReturnValue(true);
957
+ const error = new Error("EACCES: permission denied");
958
+ (error as any).code = "EACCES";
959
+ mockedFs.readFileSync.mockImplementation(() => {
960
+ throw error;
961
+ });
962
+
963
+ expect(() => loadAndValidateEnv()).toThrow(
964
+ "Error al leer el archivo .env: EACCES: permission denied"
965
+ );
966
+ });
967
+
968
+ it("debe manejar archivo con encoding incorrecto", () => {
969
+ mockedFs.existsSync.mockReturnValue(true);
970
+ mockedFs.readFileSync.mockImplementation(() => {
971
+ throw new Error("Invalid character encoding");
972
+ });
973
+
974
+ expect(() => loadAndValidateEnv()).toThrow(
975
+ "Error al leer el archivo .env: Invalid character encoding"
976
+ );
977
+ });
978
+ });
979
+
980
+ // describe("Integración de múltiples funciones", () => {
981
+ // beforeEach(() => {
982
+ // resetEnvCache();
983
+ // jest.clearAllMocks();
984
+ // });
985
+
986
+ // it("debe combinar getEnvVar, getEnvNumber y getEnvBoolean", () => {
987
+ // const envContent = "APP_NAME=MyApp\nPORT=3000\nDEBUG=true";
988
+ // mockedFs.existsSync.mockReturnValue(true);
989
+ // mockedFs.readFileSync.mockReturnValue(envContent);
990
+
991
+ // const name = getEnvVar("APP_NAME");
992
+ // const port = getEnvNumber("PORT");
993
+ // const debug = getEnvBoolean("DEBUG");
994
+
995
+ // expect(name).toBe("MyApp");
996
+ // expect(port).toBe(3000);
997
+ // expect(debug).toBe(true);
998
+ // });
999
+
1000
+ // it("debe usar hasEnvVar antes de requireEnvVar", () => {
1001
+ // const envContent = "REQUIRED_VAR=exists";
1002
+ // mockedFs.existsSync.mockReturnValue(true);
1003
+ // mockedFs.readFileSync.mockReturnValue(envContent);
1004
+
1005
+ // if (hasEnvVar("REQUIRED_VAR")) {
1006
+ // const value = requireEnvVar("REQUIRED_VAR");
1007
+ // expect(value).toBe("exists");
1008
+ // }
1009
+
1010
+ // if (!hasEnvVar("OPTIONAL_VAR")) {
1011
+ // const value = getEnvVar("OPTIONAL_VAR", "default");
1012
+ // expect(value).toBe("default");
1013
+ // }
1014
+ // });
1015
+
1016
+ // it("debe usar getAllEnvVars para validar configuración completa", () => {
1017
+ // const envContent = "DB_HOST=localhost\nDB_PORT=5432\nDB_NAME=mydb";
1018
+ // mockedFs.existsSync.mockReturnValue(true);
1019
+ // mockedFs.readFileSync.mockReturnValue(envContent);
1020
+
1021
+ // const allVars = getAllEnvVars();
1022
+ // const requiredVars = ["DB_HOST", "DB_PORT", "DB_NAME"];
1023
+
1024
+ // requiredVars.forEach(varName => {
1025
+ // expect(allVars).toHaveProperty(varName);
1026
+ // });
1027
+ // });
1028
+ // });
1029
+