@jango-blockchained/hoox-cli 0.3.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.
Files changed (94) hide show
  1. package/README.md +403 -0
  2. package/bin/hoox.js +12 -0
  3. package/package.json +60 -0
  4. package/src/commands/check/check-command.test.ts +468 -0
  5. package/src/commands/check/check-command.ts +1144 -0
  6. package/src/commands/check/index.ts +10 -0
  7. package/src/commands/check/prerequisites-command.test.ts +19 -0
  8. package/src/commands/check/prerequisites-command.ts +92 -0
  9. package/src/commands/check/types.ts +103 -0
  10. package/src/commands/clone/clone-command.test.ts +442 -0
  11. package/src/commands/clone/clone-command.ts +440 -0
  12. package/src/commands/clone/index.ts +1 -0
  13. package/src/commands/config/config-command.test.ts +583 -0
  14. package/src/commands/config/config-command.ts +901 -0
  15. package/src/commands/config/env-command.test.ts +43 -0
  16. package/src/commands/config/env-command.ts +314 -0
  17. package/src/commands/config/index.ts +3 -0
  18. package/src/commands/config/kv-command.test.ts +14 -0
  19. package/src/commands/config/kv-command.ts +329 -0
  20. package/src/commands/dashboard/dashboard-command.test.ts +47 -0
  21. package/src/commands/dashboard/dashboard-command.ts +127 -0
  22. package/src/commands/dashboard/index.ts +1 -0
  23. package/src/commands/db/db-command.test.ts +21 -0
  24. package/src/commands/db/db-command.ts +314 -0
  25. package/src/commands/db/index.ts +1 -0
  26. package/src/commands/deploy/deploy-command.test.ts +304 -0
  27. package/src/commands/deploy/deploy-command.ts +1053 -0
  28. package/src/commands/deploy/index.ts +2 -0
  29. package/src/commands/deploy/telegram-service.ts +61 -0
  30. package/src/commands/deploy/types.ts +34 -0
  31. package/src/commands/dev/dev-command.test.ts +383 -0
  32. package/src/commands/dev/dev-command.ts +407 -0
  33. package/src/commands/dev/index.ts +1 -0
  34. package/src/commands/infra/index.ts +5 -0
  35. package/src/commands/infra/infra-command.test.ts +719 -0
  36. package/src/commands/infra/infra-command.ts +940 -0
  37. package/src/commands/infra/types.ts +23 -0
  38. package/src/commands/init/index.ts +1 -0
  39. package/src/commands/init/init-command.test.ts +827 -0
  40. package/src/commands/init/init-command.ts +627 -0
  41. package/src/commands/init/types.ts +185 -0
  42. package/src/commands/monitor/index.ts +2 -0
  43. package/src/commands/monitor/monitor-command.test.ts +235 -0
  44. package/src/commands/monitor/monitor-command.ts +245 -0
  45. package/src/commands/monitor/monitor-service.ts +50 -0
  46. package/src/commands/monitor/types.ts +13 -0
  47. package/src/commands/repair/index.ts +2 -0
  48. package/src/commands/repair/repair-command.test.ts +204 -0
  49. package/src/commands/repair/repair-command.ts +199 -0
  50. package/src/commands/repair/repair-service.ts +102 -0
  51. package/src/commands/repair/types.ts +13 -0
  52. package/src/commands/test/index.ts +2 -0
  53. package/src/commands/test/test-command.test.ts +319 -0
  54. package/src/commands/test/test-command.ts +412 -0
  55. package/src/commands/waf/index.ts +2 -0
  56. package/src/commands/waf/types.ts +48 -0
  57. package/src/commands/waf/waf-command.test.ts +506 -0
  58. package/src/commands/waf/waf-command.ts +548 -0
  59. package/src/index.ts +198 -0
  60. package/src/services/cloudflare/cloudflare-service.test.ts +654 -0
  61. package/src/services/cloudflare/cloudflare-service.ts +435 -0
  62. package/src/services/cloudflare/index.ts +2 -0
  63. package/src/services/cloudflare/types.ts +29 -0
  64. package/src/services/config/config-service.test.ts +395 -0
  65. package/src/services/config/config-service.ts +207 -0
  66. package/src/services/config/index.ts +9 -0
  67. package/src/services/config/types.ts +66 -0
  68. package/src/services/db/db-service.test.ts +51 -0
  69. package/src/services/db/db-service.ts +140 -0
  70. package/src/services/db/index.ts +1 -0
  71. package/src/services/docker/docker-service.ts +155 -0
  72. package/src/services/docker/index.ts +1 -0
  73. package/src/services/env/env-service.test.ts +210 -0
  74. package/src/services/env/env-service.ts +156 -0
  75. package/src/services/env/index.ts +1 -0
  76. package/src/services/kv/index.ts +1 -0
  77. package/src/services/kv/kv-sync-service.test.ts +38 -0
  78. package/src/services/kv/kv-sync-service.ts +151 -0
  79. package/src/services/prerequisites/index.ts +1 -0
  80. package/src/services/prerequisites/prerequisites-service.test.ts +89 -0
  81. package/src/services/prerequisites/prerequisites-service.ts +269 -0
  82. package/src/services/prerequisites/types.ts +48 -0
  83. package/src/services/secrets/index.ts +12 -0
  84. package/src/services/secrets/secrets-service.test.ts +486 -0
  85. package/src/services/secrets/secrets-service.ts +293 -0
  86. package/src/services/secrets/types.ts +57 -0
  87. package/src/ui/banner.ts +52 -0
  88. package/src/ui/index.ts +8 -0
  89. package/src/ui/menu.ts +473 -0
  90. package/src/utils/errors.test.ts +69 -0
  91. package/src/utils/errors.ts +23 -0
  92. package/src/utils/formatters.test.ts +180 -0
  93. package/src/utils/formatters.ts +252 -0
  94. package/src/utils/theme.ts +94 -0
@@ -0,0 +1,468 @@
1
+ /**
2
+ * Unit tests for the check command.
3
+ *
4
+ * Uses prototype mocking on ConfigService, CloudflareService, and SecretsService
5
+ * to verify the command logic in isolation. @clack/prompts spinner is not
6
+ * mocked — it degrades gracefully in non-TTY test environments.
7
+ */
8
+ import { afterEach, beforeEach, describe, expect, it, mock } from "bun:test";
9
+
10
+ // ---------------------------------------------------------------------------
11
+ // Stub service prototypes
12
+ // ---------------------------------------------------------------------------
13
+
14
+ import { ConfigService } from "../../services/config/config-service.js";
15
+ import { CloudflareService } from "../../services/cloudflare/cloudflare-service.js";
16
+ import { SecretsService } from "../../services/secrets/secrets-service.js";
17
+
18
+ // ---------------------------------------------------------------------------
19
+ // Stub variables — reassigned in beforeEach
20
+ // ---------------------------------------------------------------------------
21
+
22
+ let loadMock: ReturnType<typeof mock>;
23
+ let validateMock: ReturnType<typeof mock>;
24
+ let getGlobalMock: ReturnType<typeof mock>;
25
+ let listWorkersMock: ReturnType<typeof mock>;
26
+ let listEnabledWorkersMock: ReturnType<typeof mock>;
27
+ let getWorkerMock: ReturnType<typeof mock>;
28
+
29
+ let d1ListMock: ReturnType<typeof mock>;
30
+ let kvListMock: ReturnType<typeof mock>;
31
+ let r2ListMock: ReturnType<typeof mock>;
32
+ let queueListMock: ReturnType<typeof mock>;
33
+ let tailMock: ReturnType<typeof mock>;
34
+ let secretListMock: ReturnType<typeof mock>;
35
+
36
+ let secretsCreateMock: ReturnType<typeof mock>;
37
+ let checkLocalSecretsMock: ReturnType<typeof mock>;
38
+ let listSecretsMock: ReturnType<typeof mock>;
39
+
40
+ // Preserve originals for cleanup
41
+ const origLoad = ConfigService.prototype.load;
42
+ const origValidate = ConfigService.prototype.validate;
43
+ const origGetGlobal = ConfigService.prototype.getGlobal;
44
+ const origListWorkers = ConfigService.prototype.listWorkers;
45
+ const origListEnabled = ConfigService.prototype.listEnabledWorkers;
46
+ const origGetWorker = ConfigService.prototype.getWorker;
47
+
48
+ const origD1List = CloudflareService.prototype.d1List;
49
+ const origKvList = CloudflareService.prototype.kvList;
50
+ const origR2List = CloudflareService.prototype.r2List;
51
+ const origQueueList = CloudflareService.prototype.queueList;
52
+ const origTail = CloudflareService.prototype.tail;
53
+ const origSecretList = CloudflareService.prototype.secretList;
54
+
55
+ const origCreate = SecretsService.create;
56
+ const origCheckLocal = SecretsService.prototype.checkLocalSecrets;
57
+ const origListSecrets = SecretsService.prototype.listSecrets;
58
+
59
+ beforeEach(() => {
60
+ mock.restore();
61
+
62
+ // Reset process.exitCode between tests
63
+ process.exitCode = undefined;
64
+
65
+ // Reset to originals on prototypes
66
+ ConfigService.prototype.load = origLoad;
67
+ ConfigService.prototype.validate = origValidate;
68
+ ConfigService.prototype.getGlobal = origGetGlobal;
69
+ ConfigService.prototype.listWorkers = origListWorkers;
70
+ ConfigService.prototype.listEnabledWorkers = origListEnabled;
71
+ ConfigService.prototype.getWorker = origGetWorker;
72
+
73
+ CloudflareService.prototype.d1List = origD1List;
74
+ CloudflareService.prototype.kvList = origKvList;
75
+ CloudflareService.prototype.r2List = origR2List;
76
+ CloudflareService.prototype.queueList = origQueueList;
77
+ CloudflareService.prototype.tail = origTail;
78
+ CloudflareService.prototype.secretList = origSecretList;
79
+
80
+ // Fresh mocks
81
+ loadMock = mock(async () => ({}));
82
+ validateMock = mock(() => ({ valid: true, errors: [] }));
83
+ getGlobalMock = mock(() => ({
84
+ cloudflare_account_id: "abc123",
85
+ }));
86
+ listWorkersMock = mock(() => ["d1-worker", "hoox", "trade-worker"]);
87
+ listEnabledWorkersMock = mock(() => ["d1-worker", "hoox", "trade-worker"]);
88
+ getWorkerMock = mock((_name: string) => ({
89
+ enabled: true,
90
+ path: "workers/test-worker",
91
+ }));
92
+
93
+ d1ListMock = mock(async () => ({
94
+ ok: true as const,
95
+ data: "my-database (abc-123)",
96
+ }));
97
+ kvListMock = mock(async () => ({
98
+ ok: true as const,
99
+ data: "namespace-1 (id1)\nnamespace-2 (id2)",
100
+ }));
101
+ r2ListMock = mock(async () => ({
102
+ ok: true as const,
103
+ data: "bucket-1\nbucket-2",
104
+ }));
105
+ queueListMock = mock(async () => ({
106
+ ok: true as const,
107
+ data: "queue-1\nqueue-2",
108
+ }));
109
+ tailMock = mock(async () => ({
110
+ ok: true as const,
111
+ data: "Connected to worker",
112
+ }));
113
+ secretListMock = mock(async () => ({
114
+ ok: true as const,
115
+ data: "Secret names: API_KEY, DB_PASSWORD",
116
+ }));
117
+
118
+ secretsCreateMock = mock(async () =>
119
+ Object.create(SecretsService.prototype, {
120
+ checkLocalSecrets: {
121
+ value: mock(async () => ({ allSet: true, missing: [], secrets: [] })),
122
+ },
123
+ listSecrets: { value: mock(() => []) },
124
+ })
125
+ );
126
+
127
+ checkLocalSecretsMock = mock(async () => ({
128
+ worker: "test-worker",
129
+ secrets: [],
130
+ allSet: true,
131
+ missing: [],
132
+ }));
133
+ listSecretsMock = mock(() => ["API_KEY"]);
134
+
135
+ // Apply stubs
136
+ ConfigService.prototype.load = loadMock;
137
+ ConfigService.prototype.validate = validateMock;
138
+ ConfigService.prototype.getGlobal = getGlobalMock;
139
+ ConfigService.prototype.listWorkers = listWorkersMock;
140
+ ConfigService.prototype.listEnabledWorkers = listEnabledWorkersMock;
141
+ ConfigService.prototype.getWorker = getWorkerMock;
142
+
143
+ CloudflareService.prototype.d1List = d1ListMock;
144
+ CloudflareService.prototype.kvList = kvListMock;
145
+ CloudflareService.prototype.r2List = r2ListMock;
146
+ CloudflareService.prototype.queueList = queueListMock;
147
+ CloudflareService.prototype.tail = tailMock;
148
+ CloudflareService.prototype.secretList = secretListMock;
149
+
150
+ // Replace static create on SecretsService
151
+ (SecretsService as unknown as Record<string, unknown>).create =
152
+ secretsCreateMock;
153
+ });
154
+
155
+ afterEach(() => {
156
+ mock.restore();
157
+
158
+ // Restore originals
159
+ ConfigService.prototype.load = origLoad;
160
+ ConfigService.prototype.validate = origValidate;
161
+ ConfigService.prototype.getGlobal = origGetGlobal;
162
+ ConfigService.prototype.listWorkers = origListWorkers;
163
+ ConfigService.prototype.listEnabledWorkers = origListEnabled;
164
+ ConfigService.prototype.getWorker = origGetWorker;
165
+
166
+ CloudflareService.prototype.d1List = origD1List;
167
+ CloudflareService.prototype.kvList = origKvList;
168
+ CloudflareService.prototype.r2List = origR2List;
169
+ CloudflareService.prototype.queueList = origQueueList;
170
+ CloudflareService.prototype.tail = origTail;
171
+ CloudflareService.prototype.secretList = origSecretList;
172
+
173
+ (SecretsService as unknown as Record<string, unknown>).create = origCreate;
174
+ SecretsService.prototype.checkLocalSecrets = origCheckLocal;
175
+ SecretsService.prototype.listSecrets = origListSecrets;
176
+ });
177
+
178
+ // ---------------------------------------------------------------------------
179
+ // Helpers
180
+ // ---------------------------------------------------------------------------
181
+
182
+ /**
183
+ * Dynamically import the check command after mocks are in place.
184
+ */
185
+ async function importCheckCommand(): Promise<{
186
+ registerCheckCommand: typeof import("./check-command.js").registerCheckCommand;
187
+ }> {
188
+ return import("./check-command.js");
189
+ }
190
+
191
+ import { Command } from "commander";
192
+ import { ExitCode } from "../../utils/errors.js";
193
+
194
+ async function createProgram() {
195
+ const { registerCheckCommand } = await importCheckCommand();
196
+ const program = new Command()
197
+ .name("hoox-test")
198
+ .option("--json", "Output in JSON format")
199
+ .option("--quiet", "Minimal output")
200
+ .exitOverride(() => {
201
+ // Suppress Commander's own exit during tests
202
+ });
203
+ registerCheckCommand(program);
204
+ return program;
205
+ }
206
+
207
+ // ---------------------------------------------------------------------------
208
+ // Tests
209
+ // ---------------------------------------------------------------------------
210
+
211
+ describe("registerCheckCommand", () => {
212
+ // -- Command registration -------------------------------------------------
213
+
214
+ it("registers 'check' as a command on the program", async () => {
215
+ const program = await createProgram();
216
+ const checkCmd = program.commands.find((c) => c.name() === "check");
217
+ expect(checkCmd).toBeDefined();
218
+ });
219
+
220
+ it("registers 'check setup' subcommand", async () => {
221
+ const program = await createProgram();
222
+ const checkCmd = program.commands.find((c) => c.name() === "check")!;
223
+ const setupCmd = checkCmd.commands.find((c) => c.name() === "setup");
224
+ expect(setupCmd).toBeDefined();
225
+ expect(setupCmd!.description()).toContain("validation");
226
+ });
227
+
228
+ it("registers 'check health' subcommand", async () => {
229
+ const program = await createProgram();
230
+ const checkCmd = program.commands.find((c) => c.name() === "check")!;
231
+ const healthCmd = checkCmd.commands.find((c) => c.name() === "health");
232
+ expect(healthCmd).toBeDefined();
233
+ expect(healthCmd!.description()).toContain("health");
234
+ });
235
+
236
+ it("registers 'check fix' subcommand", async () => {
237
+ const program = await createProgram();
238
+ const checkCmd = program.commands.find((c) => c.name() === "check")!;
239
+ const fixCmd = checkCmd.commands.find((c) => c.name() === "fix");
240
+ expect(fixCmd).toBeDefined();
241
+ expect(fixCmd!.description()).toContain("Repair");
242
+ });
243
+
244
+ // -- check setup ----------------------------------------------------------
245
+
246
+ describe("check setup", () => {
247
+ it("loads config and runs all 4 categories", { timeout: 30000 }, async () => {
248
+ const program = await createProgram();
249
+ await program.parseAsync(["check", "setup"], { from: "user" });
250
+
251
+ expect(loadMock).toHaveBeenCalled();
252
+ expect(d1ListMock).toHaveBeenCalled();
253
+ expect(kvListMock).toHaveBeenCalled();
254
+ expect(r2ListMock).toHaveBeenCalled();
255
+ expect(queueListMock).toHaveBeenCalled();
256
+ });
257
+
258
+ it("outputs JSON when --json flag is set", { timeout: 30000 }, async () => {
259
+ const program = await createProgram();
260
+ const logSpy = mock((..._args: unknown[]) => {});
261
+ const origWrite = process.stdout.write;
262
+ (process.stdout as unknown as Record<string, unknown>).write = logSpy;
263
+
264
+ try {
265
+ await program.parseAsync(["--json", "check", "setup"], {
266
+ from: "user",
267
+ });
268
+
269
+ // Should have called stdout.write with JSON
270
+ const jsonCalls = (
271
+ logSpy as unknown as { mock: { calls: Array<unknown[]> } }
272
+ ).mock.calls.filter((call: unknown[]) => {
273
+ const str = String(call[0] ?? "");
274
+ try {
275
+ JSON.parse(str);
276
+ return str.includes("categories") && str.includes("summary");
277
+ } catch {
278
+ return false;
279
+ }
280
+ });
281
+ expect(jsonCalls.length).toBeGreaterThan(0);
282
+ } finally {
283
+ (process.stdout as unknown as Record<string, unknown>).write =
284
+ origWrite;
285
+ }
286
+ });
287
+
288
+ it("sets exitCode to ERROR when validation fails", { timeout: 30000 }, async () => {
289
+ validateMock = mock(() => ({
290
+ valid: false,
291
+ errors: ["global.cloudflare_account_id is required"],
292
+ }));
293
+ ConfigService.prototype.validate = validateMock;
294
+
295
+ const program = await createProgram();
296
+ await program.parseAsync(["check", "setup"], { from: "user" });
297
+
298
+ expect(process.exitCode).toBe(ExitCode.ERROR);
299
+ });
300
+
301
+ it("sets exitCode to ERROR when infra check fails", async () => {
302
+ d1ListMock = mock(async () => ({
303
+ ok: false as const,
304
+ error: "Authentication failed",
305
+ }));
306
+ CloudflareService.prototype.d1List = d1ListMock;
307
+
308
+ const program = await createProgram();
309
+ await program.parseAsync(["check", "setup"], { from: "user" });
310
+
311
+ expect(process.exitCode).toBe(ExitCode.ERROR);
312
+ });
313
+
314
+ it("handles config load failure gracefully", async () => {
315
+ loadMock = mock(async () => {
316
+ throw new Error("Config file not found");
317
+ });
318
+ ConfigService.prototype.load = loadMock;
319
+
320
+ const program = await createProgram();
321
+ await program.parseAsync(["check", "setup"], { from: "user" });
322
+
323
+ expect(process.exitCode).toBe(ExitCode.ERROR);
324
+ });
325
+ });
326
+
327
+ // -- check health ---------------------------------------------------------
328
+
329
+ describe("check health", () => {
330
+ it("checks connectivity for all enabled workers", async () => {
331
+ const program = await createProgram();
332
+ await program.parseAsync(["check", "health"], { from: "user" });
333
+
334
+ expect(loadMock).toHaveBeenCalled();
335
+ // tailMock should have been called for each enabled worker
336
+ expect(tailMock).toHaveBeenCalled();
337
+ });
338
+
339
+ it("outputs JSON when --json flag is set", async () => {
340
+ const program = await createProgram();
341
+ const logSpy = mock((..._args: unknown[]) => {});
342
+ const origWrite = process.stdout.write;
343
+ (process.stdout as unknown as Record<string, unknown>).write = logSpy;
344
+
345
+ try {
346
+ await program.parseAsync(["--json", "check", "health"], {
347
+ from: "user",
348
+ });
349
+
350
+ const jsonCalls = (
351
+ logSpy as unknown as { mock: { calls: Array<unknown[]> } }
352
+ ).mock.calls.filter((call: unknown[]) => {
353
+ const str = String(call[0] ?? "");
354
+ try {
355
+ const parsed = JSON.parse(str);
356
+ return Array.isArray(parsed) && parsed.length > 0;
357
+ } catch {
358
+ return false;
359
+ }
360
+ });
361
+ expect(jsonCalls.length).toBeGreaterThan(0);
362
+ } finally {
363
+ (process.stdout as unknown as Record<string, unknown>).write =
364
+ origWrite;
365
+ }
366
+ });
367
+
368
+ it("sets exitCode to ERROR when any worker is unhealthy", async () => {
369
+ tailMock = mock(async () => ({
370
+ ok: false as const,
371
+ error: "Worker not reachable",
372
+ }));
373
+ CloudflareService.prototype.tail = tailMock;
374
+
375
+ const program = await createProgram();
376
+ await program.parseAsync(["check", "health"], { from: "user" });
377
+
378
+ expect(process.exitCode).toBe(ExitCode.ERROR);
379
+ });
380
+
381
+ it("handles empty worker list gracefully", async () => {
382
+ listEnabledWorkersMock = mock(() => []);
383
+ ConfigService.prototype.listEnabledWorkers = listEnabledWorkersMock;
384
+
385
+ const program = await createProgram();
386
+ await program.parseAsync(["check", "health"], { from: "user" });
387
+
388
+ // Should exit cleanly — the handler outputs success message
389
+ expect(process.exitCode).toBe(1);
390
+ });
391
+ });
392
+
393
+ // -- check fix ------------------------------------------------------------
394
+
395
+ describe("check fix", () => {
396
+ it("scans for issues and reports when dry-run", async () => {
397
+ const program = await createProgram();
398
+
399
+ // Mock Bun.file so .dev.vars doesn't exist (triggers fix action)
400
+ const origBunFile = Bun.file;
401
+ (Bun as Record<string, unknown>).file = mock((_path: string) => ({
402
+ exists: mock(async () => false),
403
+ text: mock(async () => ""),
404
+ name: _path,
405
+ }));
406
+
407
+ try {
408
+ await program.parseAsync(["check", "fix", "--dry-run"], {
409
+ from: "user",
410
+ });
411
+
412
+ // Should complete without errors
413
+ // In dry-run mode, exitCode should not be ERROR (no actual failures to apply)
414
+ } finally {
415
+ (Bun as Record<string, unknown>).file = origBunFile;
416
+ }
417
+ });
418
+
419
+ it("outputs JSON when --json flag is set", async () => {
420
+ const program = await createProgram();
421
+ const logSpy = mock((..._args: unknown[]) => {});
422
+ const origWrite = process.stdout.write;
423
+ (process.stdout as unknown as Record<string, unknown>).write = logSpy;
424
+
425
+ const origBunFile = Bun.file;
426
+ (Bun as Record<string, unknown>).file = mock((_path: string) => ({
427
+ exists: mock(async () => false),
428
+ text: mock(async () => ""),
429
+ name: _path,
430
+ }));
431
+
432
+ try {
433
+ await program.parseAsync(["--json", "check", "fix", "--dry-run"], {
434
+ from: "user",
435
+ });
436
+
437
+ const jsonCalls = (
438
+ logSpy as unknown as { mock: { calls: Array<unknown[]> } }
439
+ ).mock.calls.filter((call: unknown[]) => {
440
+ const str = String(call[0] ?? "");
441
+ try {
442
+ const parsed = JSON.parse(str);
443
+ return parsed.actions !== undefined && parsed.summary !== undefined;
444
+ } catch {
445
+ return false;
446
+ }
447
+ });
448
+ expect(jsonCalls.length).toBeGreaterThan(0);
449
+ } finally {
450
+ (process.stdout as unknown as Record<string, unknown>).write =
451
+ origWrite;
452
+ (Bun as Record<string, unknown>).file = origBunFile;
453
+ }
454
+ });
455
+
456
+ it("handles config load failure gracefully", async () => {
457
+ loadMock = mock(async () => {
458
+ throw new Error("Config file not found");
459
+ });
460
+ ConfigService.prototype.load = loadMock;
461
+
462
+ const program = await createProgram();
463
+ await program.parseAsync(["check", "fix"], { from: "user" });
464
+
465
+ expect(process.exitCode).toBe(ExitCode.ERROR);
466
+ });
467
+ });
468
+ });