@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,486 @@
1
+ import { describe, it, expect, mock } from "bun:test";
2
+ import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { SecretsService } from "./secrets-service.js";
6
+
7
+ // ---------------------------------------------------------------------------
8
+ // Helpers
9
+ // ---------------------------------------------------------------------------
10
+
11
+ const WORKERS_JSONC = JSON.stringify({
12
+ global: {},
13
+ workers: {
14
+ "telegram-worker": {
15
+ enabled: true,
16
+ path: "workers/telegram-worker",
17
+ secrets: ["TELEGRAM_BOT_TOKEN"],
18
+ },
19
+ "trade-worker": {
20
+ enabled: true,
21
+ path: "workers/trade-worker",
22
+ secrets: ["API_SERVICE_KEY", "BINANCE_API_KEY", "BINANCE_API_SECRET"],
23
+ },
24
+ "d1-worker": {
25
+ enabled: true,
26
+ path: "workers/d1-worker",
27
+ },
28
+ "no-secrets-worker": {
29
+ enabled: true,
30
+ path: "workers/no-secrets",
31
+ secrets: [],
32
+ },
33
+ },
34
+ });
35
+
36
+ /**
37
+ * Creates a SecretsService that reads from a synthetic wrangler.jsonc string
38
+ * instead of hitting the real file-system. We override `Bun.file` for the
39
+ * config path only and call the private constructor via a test-only subclass.
40
+ */
41
+ async function createService(workersJsonc?: string): Promise<SecretsService> {
42
+ const jsonc = workersJsonc ?? WORKERS_JSONC;
43
+
44
+ // Spy on Bun.file so we can intercept the config read while letting
45
+ // real .dev.vars file-system calls through for the integration-style tests.
46
+ const originalBunFile = Bun.file;
47
+
48
+ // Use direct property assignment for Bun.file mock.
49
+ // Object.defineProperty with configurable:true fails because Bun.file
50
+ // is not configurable, but direct assignment (which requires only writable)
51
+ // works in Bun 1.3.x for individual Bun properties.
52
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
53
+ (Bun as any).file = mock((path: string) => {
54
+ if (typeof path === "string" && path.endsWith("wrangler.jsonc")) {
55
+ return {
56
+ exists: mock(async () => true),
57
+ text: mock(async () => jsonc),
58
+ };
59
+ }
60
+ // Fall through to the real Bun.file for everything else
61
+ return originalBunFile(path);
62
+ });
63
+
64
+ const svc = await SecretsService.create("wrangler.jsonc");
65
+
66
+ // Restore original so subsequent tests aren't polluted
67
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
68
+ (Bun as any).file = originalBunFile;
69
+
70
+ return svc;
71
+ }
72
+
73
+ /** Returns a fresh temporary directory that is cleaned up after the test. */
74
+ function tmpDir(): string {
75
+ const dir = mkdtempSync(join(tmpdir(), "hoox-secrets-test-"));
76
+ return dir;
77
+ }
78
+
79
+ // ---------------------------------------------------------------------------
80
+ // Tests
81
+ // ---------------------------------------------------------------------------
82
+
83
+ describe("SecretsService", () => {
84
+ // -- listSecrets ----------------------------------------------------------
85
+
86
+ describe("listSecrets", () => {
87
+ it("returns correct secret names for a worker that has secrets", async () => {
88
+ const svc = await createService();
89
+ expect(svc.listSecrets("trade-worker")).toEqual([
90
+ "API_SERVICE_KEY",
91
+ "BINANCE_API_KEY",
92
+ "BINANCE_API_SECRET",
93
+ ]);
94
+ });
95
+
96
+ it("returns single secret for telegram-worker", async () => {
97
+ const svc = await createService();
98
+ expect(svc.listSecrets("telegram-worker")).toEqual([
99
+ "TELEGRAM_BOT_TOKEN",
100
+ ]);
101
+ });
102
+
103
+ it("returns empty array for worker not in config", async () => {
104
+ const svc = await createService();
105
+ expect(svc.listSecrets("nonexistent")).toEqual([]);
106
+ });
107
+
108
+ it("returns empty array for worker with no secrets declared", async () => {
109
+ const svc = await createService();
110
+ expect(svc.listSecrets("d1-worker")).toEqual([]);
111
+ });
112
+
113
+ it("returns empty array for worker with explicitly empty secrets", async () => {
114
+ const svc = await createService();
115
+ expect(svc.listSecrets("no-secrets-worker")).toEqual([]);
116
+ });
117
+ });
118
+
119
+ // -- listAllSecrets -------------------------------------------------------
120
+
121
+ describe("listAllSecrets", () => {
122
+ it("returns map of workers that have secrets", async () => {
123
+ const svc = await createService();
124
+ const all = svc.listAllSecrets();
125
+
126
+ expect(Object.keys(all)).toHaveLength(2);
127
+ expect(all["telegram-worker"]).toEqual(["TELEGRAM_BOT_TOKEN"]);
128
+ expect(all["trade-worker"]).toEqual([
129
+ "API_SERVICE_KEY",
130
+ "BINANCE_API_KEY",
131
+ "BINANCE_API_SECRET",
132
+ ]);
133
+ });
134
+
135
+ it("omits workers without secrets", async () => {
136
+ const svc = await createService();
137
+ const all = svc.listAllSecrets();
138
+ expect(all["d1-worker"]).toBeUndefined();
139
+ expect(all["no-secrets-worker"]).toBeUndefined();
140
+ });
141
+ });
142
+
143
+ // -- checkLocalSecrets ----------------------------------------------------
144
+
145
+ describe("checkLocalSecrets", () => {
146
+ it("reports all secrets missing when .dev.vars does not exist", async () => {
147
+ const svc = await createService();
148
+ // Use a temp dir so no .dev.vars file exists
149
+ const dir = tmpDir();
150
+ try {
151
+ // Override the worker path to point at the temp dir
152
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
153
+ (svc as any).config.workers["trade-worker"].path = dir;
154
+
155
+ const result = await svc.checkLocalSecrets("trade-worker");
156
+ expect(result.worker).toBe("trade-worker");
157
+ expect(result.allSet).toBe(false);
158
+ expect(result.missing).toEqual([
159
+ "API_SERVICE_KEY",
160
+ "BINANCE_API_KEY",
161
+ "BINANCE_API_SECRET",
162
+ ]);
163
+ expect(result.secrets).toHaveLength(3);
164
+ for (const s of result.secrets) {
165
+ expect(s.set).toBe(false);
166
+ }
167
+ } finally {
168
+ rmSync(dir, { recursive: true, force: true });
169
+ }
170
+ });
171
+
172
+ it("reports secrets as set when .dev.vars has real values", async () => {
173
+ const dir = tmpDir();
174
+ try {
175
+ writeFileSync(
176
+ join(dir, ".dev.vars"),
177
+ "API_SERVICE_KEY=abc123\nBINANCE_API_KEY=xyz789\nBINANCE_API_SECRET=sec456\n"
178
+ );
179
+
180
+ const svc = await createService();
181
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
182
+ (svc as any).config.workers["trade-worker"].path = dir;
183
+
184
+ const result = await svc.checkLocalSecrets("trade-worker");
185
+ expect(result.allSet).toBe(true);
186
+ expect(result.missing).toEqual([]);
187
+ for (const s of result.secrets) {
188
+ expect(s.set).toBe(true);
189
+ expect(s.source).toBe(join(dir, ".dev.vars"));
190
+ }
191
+ } finally {
192
+ rmSync(dir, { recursive: true, force: true });
193
+ }
194
+ });
195
+
196
+ it("flags placeholder values as missing", async () => {
197
+ const dir = tmpDir();
198
+ try {
199
+ writeFileSync(
200
+ join(dir, ".dev.vars"),
201
+ "API_SERVICE_KEY=placeholder_api_service_key\nBINANCE_API_KEY=binance-real-key\nBINANCE_API_SECRET=your_secret\n"
202
+ );
203
+
204
+ const svc = await createService();
205
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
206
+ (svc as any).config.workers["trade-worker"].path = dir;
207
+
208
+ const result = await svc.checkLocalSecrets("trade-worker");
209
+ expect(result.allSet).toBe(false);
210
+ // Only BINANCE_API_KEY should be set (not placeholder)
211
+ expect(result.missing).toContain("API_SERVICE_KEY");
212
+ expect(result.missing).toContain("BINANCE_API_SECRET");
213
+ expect(result.missing).not.toContain("BINANCE_API_KEY");
214
+ } finally {
215
+ rmSync(dir, { recursive: true, force: true });
216
+ }
217
+ });
218
+
219
+ it("handles comments and blank lines in .dev.vars", async () => {
220
+ const dir = tmpDir();
221
+ try {
222
+ writeFileSync(
223
+ join(dir, ".dev.vars"),
224
+ "# This is a comment\nAPI_SERVICE_KEY=real-key\n\n# Another comment\nBINANCE_API_KEY=another-key\nBINANCE_API_SECRET=third-key\n"
225
+ );
226
+
227
+ const svc = await createService();
228
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
229
+ (svc as any).config.workers["trade-worker"].path = dir;
230
+
231
+ const result = await svc.checkLocalSecrets("trade-worker");
232
+ expect(result.allSet).toBe(true);
233
+ expect(result.missing).toEqual([]);
234
+ } finally {
235
+ rmSync(dir, { recursive: true, force: true });
236
+ }
237
+ });
238
+
239
+ it("returns empty result for unknown worker", async () => {
240
+ const svc = await createService();
241
+ const result = await svc.checkLocalSecrets("unknown-worker");
242
+ expect(result.worker).toBe("unknown-worker");
243
+ expect(result.secrets).toEqual([]);
244
+ expect(result.allSet).toBe(false);
245
+ });
246
+ });
247
+
248
+ // -- generateDevVars ------------------------------------------------------
249
+
250
+ describe("generateDevVars", () => {
251
+ it("creates .dev.vars with placeholder entries", async () => {
252
+ const dir = tmpDir();
253
+ try {
254
+ const svc = await createService();
255
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
256
+ (svc as any).config.workers["telegram-worker"].path = dir;
257
+
258
+ const result = await svc.generateDevVars("telegram-worker");
259
+ expect(result.success).toBe(true);
260
+ expect(result.data).toBe(join(dir, ".dev.vars"));
261
+
262
+ // Verify file content
263
+ const content = await Bun.file(join(dir, ".dev.vars")).text();
264
+ expect(content).toContain(
265
+ "TELEGRAM_BOT_TOKEN=placeholder_telegram_bot_token"
266
+ );
267
+ } finally {
268
+ rmSync(dir, { recursive: true, force: true });
269
+ }
270
+ });
271
+
272
+ it("writes empty file for worker with no secrets", async () => {
273
+ const dir = tmpDir();
274
+ try {
275
+ const svc = await createService();
276
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
277
+ (svc as any).config.workers["no-secrets-worker"].path = dir;
278
+
279
+ const result = await svc.generateDevVars("no-secrets-worker");
280
+ expect(result.success).toBe(true);
281
+ expect(result.data).toBe(join(dir, ".dev.vars"));
282
+
283
+ const content = await Bun.file(join(dir, ".dev.vars")).text();
284
+ expect(content).toBe("");
285
+ } finally {
286
+ rmSync(dir, { recursive: true, force: true });
287
+ }
288
+ });
289
+
290
+ it("returns error for unknown worker", async () => {
291
+ const svc = await createService();
292
+ const result = await svc.generateDevVars("unknown-worker");
293
+ expect(result.success).toBe(false);
294
+ expect(result.error).toContain("not found in config");
295
+ });
296
+
297
+ it("overwrites existing .dev.vars", async () => {
298
+ const dir = tmpDir();
299
+ try {
300
+ writeFileSync(join(dir, ".dev.vars"), "OLD_KEY=old_value\n");
301
+
302
+ const svc = await createService();
303
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
304
+ (svc as any).config.workers["telegram-worker"].path = dir;
305
+
306
+ await svc.generateDevVars("telegram-worker");
307
+
308
+ const content = await Bun.file(join(dir, ".dev.vars")).text();
309
+ expect(content).toContain(
310
+ "TELEGRAM_BOT_TOKEN=placeholder_telegram_bot_token"
311
+ );
312
+ expect(content).not.toContain("OLD_KEY");
313
+ } finally {
314
+ rmSync(dir, { recursive: true, force: true });
315
+ }
316
+ });
317
+ });
318
+
319
+ // -- syncToCloudflare -----------------------------------------------------
320
+
321
+ describe("syncToCloudflare", () => {
322
+ it("pushes secrets via wrangler when valid values are in .dev.vars", async () => {
323
+ const dir = tmpDir();
324
+ try {
325
+ writeFileSync(
326
+ join(dir, ".dev.vars"),
327
+ "TELEGRAM_BOT_TOKEN=my-real-token\n"
328
+ );
329
+
330
+ const svc = await createService();
331
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
332
+ (svc as any).config.workers["telegram-worker"].path = dir;
333
+
334
+ // Stub execWranglerSecretPut to avoid real wrangler calls.
335
+ const called: Array<[string, string]> = [];
336
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
337
+ (svc as any).execWranglerSecretPut = mock(
338
+ async (_path: string, name: string, value: string) => {
339
+ called.push([name, value]);
340
+ }
341
+ );
342
+
343
+ const result = await svc.syncToCloudflare("telegram-worker");
344
+ expect(result.success).toBe(true);
345
+ expect(result.data).toEqual(["TELEGRAM_BOT_TOKEN"]);
346
+ expect(called).toEqual([["TELEGRAM_BOT_TOKEN", "my-real-token"]]);
347
+ } finally {
348
+ rmSync(dir, { recursive: true, force: true });
349
+ }
350
+ });
351
+
352
+ it("skips secrets that are placeholder values", async () => {
353
+ const dir = tmpDir();
354
+ try {
355
+ writeFileSync(
356
+ join(dir, ".dev.vars"),
357
+ "API_SERVICE_KEY=placeholder_api_service_key\nBINANCE_API_KEY=real-binance-key\nBINANCE_API_SECRET=generate_something\n"
358
+ );
359
+
360
+ const svc = await createService();
361
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
362
+ (svc as any).config.workers["trade-worker"].path = dir;
363
+
364
+ const called: Array<[string, string]> = [];
365
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
366
+ (svc as any).execWranglerSecretPut = mock(
367
+ async (_path: string, name: string, value: string) => {
368
+ called.push([name, value]);
369
+ }
370
+ );
371
+
372
+ const result = await svc.syncToCloudflare("trade-worker");
373
+ // Only BINANCE_API_KEY was synced
374
+ expect(result.success).toBe(false); // partial success = false
375
+ expect(result.data).toEqual(["BINANCE_API_KEY"]);
376
+ expect(result.error).toContain("API_SERVICE_KEY");
377
+ expect(result.error).toContain("BINANCE_API_SECRET");
378
+ expect(called).toEqual([["BINANCE_API_KEY", "real-binance-key"]]);
379
+ } finally {
380
+ rmSync(dir, { recursive: true, force: true });
381
+ }
382
+ });
383
+
384
+ it("returns error when no .dev.vars exists and all secrets are missing", async () => {
385
+ const dir = tmpDir();
386
+ try {
387
+ const svc = await createService();
388
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
389
+ (svc as any).config.workers["trade-worker"].path = dir;
390
+
391
+ const called: Array<[string, string]> = [];
392
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
393
+ (svc as any).execWranglerSecretPut = mock(
394
+ async (_path: string, name: string, value: string) => {
395
+ called.push([name, value]);
396
+ }
397
+ );
398
+
399
+ const result = await svc.syncToCloudflare("trade-worker");
400
+ expect(result.success).toBe(false);
401
+ expect(result.data).toBeUndefined();
402
+ expect(result.error).toContain("API_SERVICE_KEY");
403
+ expect(result.error).toContain("BINANCE_API_KEY");
404
+ expect(result.error).toContain("BINANCE_API_SECRET");
405
+ expect(called).toHaveLength(0);
406
+ } finally {
407
+ rmSync(dir, { recursive: true, force: true });
408
+ }
409
+ });
410
+
411
+ it("returns error for unknown worker", async () => {
412
+ const svc = await createService();
413
+ const result = await svc.syncToCloudflare("unknown-worker");
414
+ expect(result.success).toBe(false);
415
+ expect(result.error).toContain("not found in config");
416
+ });
417
+
418
+ it("handles wrangler failures gracefully", async () => {
419
+ const dir = tmpDir();
420
+ try {
421
+ writeFileSync(join(dir, ".dev.vars"), "TELEGRAM_BOT_TOKEN=my-token\n");
422
+
423
+ const svc = await createService();
424
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
425
+ (svc as any).config.workers["telegram-worker"].path = dir;
426
+
427
+ // Stub execWranglerSecretPut to throw
428
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
429
+ (svc as any).execWranglerSecretPut = mock(async () => {
430
+ throw new Error("wrangler command failed");
431
+ });
432
+
433
+ const result = await svc.syncToCloudflare("telegram-worker");
434
+ expect(result.success).toBe(false);
435
+ expect(result.data).toBeUndefined();
436
+ expect(result.error).toContain("wrangler command failed");
437
+ } finally {
438
+ rmSync(dir, { recursive: true, force: true });
439
+ }
440
+ });
441
+
442
+ it("works for worker with no secrets", async () => {
443
+ const dir = tmpDir();
444
+ try {
445
+ const svc = await createService();
446
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
447
+ (svc as any).config.workers["no-secrets-worker"].path = dir;
448
+
449
+ const result = await svc.syncToCloudflare("no-secrets-worker");
450
+ expect(result.success).toBe(true);
451
+ expect(result.data).toEqual([]);
452
+ } finally {
453
+ rmSync(dir, { recursive: true, force: true });
454
+ }
455
+ });
456
+ });
457
+
458
+ // -- Edge cases -----------------------------------------------------------
459
+
460
+ describe("edge cases", () => {
461
+ it("throws when config file does not exist", async () => {
462
+ await expect(
463
+ SecretsService.create("/nonexistent/wrangler.jsonc")
464
+ ).rejects.toThrow("Config file not found");
465
+ });
466
+
467
+ it("handles worker at path with trailing/leading whitespace in .dev.vars", async () => {
468
+ const dir = tmpDir();
469
+ try {
470
+ writeFileSync(
471
+ join(dir, ".dev.vars"),
472
+ " TELEGRAM_BOT_TOKEN = my-token \n"
473
+ );
474
+
475
+ const svc = await createService();
476
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
477
+ (svc as any).config.workers["telegram-worker"].path = dir;
478
+
479
+ const result = await svc.checkLocalSecrets("telegram-worker");
480
+ expect(result.allSet).toBe(true);
481
+ } finally {
482
+ rmSync(dir, { recursive: true, force: true });
483
+ }
484
+ });
485
+ });
486
+ });