@jango-blockchained/hoox-cli 0.5.1 → 0.5.6

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 (106) hide show
  1. package/bin/hoox.js +3 -4
  2. package/dist/index.js +32545 -0
  3. package/package.json +10 -15
  4. package/src/commands/check/check-command.test.ts +0 -476
  5. package/src/commands/check/check-command.ts +0 -1144
  6. package/src/commands/check/index.ts +0 -10
  7. package/src/commands/check/prerequisites-command.test.ts +0 -19
  8. package/src/commands/check/prerequisites-command.ts +0 -96
  9. package/src/commands/check/types.ts +0 -103
  10. package/src/commands/clone/clone-command.test.ts +0 -442
  11. package/src/commands/clone/clone-command.ts +0 -431
  12. package/src/commands/clone/index.ts +0 -1
  13. package/src/commands/config/config-command.test.ts +0 -583
  14. package/src/commands/config/config-command.ts +0 -897
  15. package/src/commands/config/env-command.test.ts +0 -43
  16. package/src/commands/config/env-command.ts +0 -295
  17. package/src/commands/config/index.ts +0 -3
  18. package/src/commands/config/kv-command.test.ts +0 -14
  19. package/src/commands/config/kv-command.ts +0 -290
  20. package/src/commands/dashboard/dashboard-command.test.ts +0 -47
  21. package/src/commands/dashboard/dashboard-command.ts +0 -128
  22. package/src/commands/dashboard/index.ts +0 -1
  23. package/src/commands/db/db-command.test.ts +0 -21
  24. package/src/commands/db/db-command.ts +0 -312
  25. package/src/commands/db/index.ts +0 -1
  26. package/src/commands/deploy/deploy-command.test.ts +0 -304
  27. package/src/commands/deploy/deploy-command.ts +0 -1135
  28. package/src/commands/deploy/index.ts +0 -2
  29. package/src/commands/deploy/telegram-service.ts +0 -76
  30. package/src/commands/deploy/types.ts +0 -34
  31. package/src/commands/dev/dev-command.test.ts +0 -383
  32. package/src/commands/dev/dev-command.ts +0 -398
  33. package/src/commands/dev/index.ts +0 -1
  34. package/src/commands/infra/index.ts +0 -5
  35. package/src/commands/infra/infra-command.test.ts +0 -719
  36. package/src/commands/infra/infra-command.ts +0 -974
  37. package/src/commands/infra/types.ts +0 -23
  38. package/src/commands/init/__tests__/cli-provisioner.test.ts +0 -17
  39. package/src/commands/init/cli-provisioner.ts +0 -71
  40. package/src/commands/init/index.ts +0 -1
  41. package/src/commands/init/init-command.test.ts +0 -876
  42. package/src/commands/init/init-command.ts +0 -593
  43. package/src/commands/init/types.ts +0 -23
  44. package/src/commands/logs/index.ts +0 -1
  45. package/src/commands/logs/logs-command.test.ts +0 -294
  46. package/src/commands/logs/logs-command.ts +0 -452
  47. package/src/commands/monitor/index.ts +0 -2
  48. package/src/commands/monitor/monitor-command.test.ts +0 -263
  49. package/src/commands/monitor/monitor-command.ts +0 -269
  50. package/src/commands/monitor/monitor-service.ts +0 -65
  51. package/src/commands/monitor/types.ts +0 -13
  52. package/src/commands/repair/index.ts +0 -2
  53. package/src/commands/repair/repair-command.test.ts +0 -224
  54. package/src/commands/repair/repair-command.ts +0 -220
  55. package/src/commands/repair/repair-service.ts +0 -138
  56. package/src/commands/repair/types.ts +0 -13
  57. package/src/commands/test/index.ts +0 -2
  58. package/src/commands/test/test-command.test.ts +0 -319
  59. package/src/commands/test/test-command.ts +0 -406
  60. package/src/commands/tui/index.ts +0 -1
  61. package/src/commands/tui/tui-command.ts +0 -87
  62. package/src/commands/update/index.ts +0 -1
  63. package/src/commands/update/update-command.ts +0 -168
  64. package/src/commands/waf/index.ts +0 -2
  65. package/src/commands/waf/types.ts +0 -48
  66. package/src/commands/waf/waf-command.test.ts +0 -506
  67. package/src/commands/waf/waf-command.ts +0 -548
  68. package/src/index.ts +0 -269
  69. package/src/services/cloudflare/cloudflare-service.test.ts +0 -658
  70. package/src/services/cloudflare/cloudflare-service.ts +0 -433
  71. package/src/services/cloudflare/index.ts +0 -2
  72. package/src/services/cloudflare/types.ts +0 -30
  73. package/src/services/config/config-service.test.ts +0 -395
  74. package/src/services/config/config-service.ts +0 -207
  75. package/src/services/config/index.ts +0 -9
  76. package/src/services/config/types.ts +0 -66
  77. package/src/services/db/db-service.test.ts +0 -51
  78. package/src/services/db/db-service.ts +0 -131
  79. package/src/services/db/index.ts +0 -1
  80. package/src/services/docker/docker-service.ts +0 -155
  81. package/src/services/docker/index.ts +0 -1
  82. package/src/services/env/env-service.test.ts +0 -236
  83. package/src/services/env/env-service.ts +0 -386
  84. package/src/services/env/index.ts +0 -1
  85. package/src/services/kv/index.ts +0 -1
  86. package/src/services/kv/kv-sync-service.test.ts +0 -38
  87. package/src/services/kv/kv-sync-service.ts +0 -234
  88. package/src/services/prerequisites/index.ts +0 -1
  89. package/src/services/prerequisites/prerequisites-service.test.ts +0 -89
  90. package/src/services/prerequisites/prerequisites-service.ts +0 -377
  91. package/src/services/prerequisites/types.ts +0 -48
  92. package/src/services/secrets/index.ts +0 -11
  93. package/src/services/secrets/secrets-service.test.ts +0 -495
  94. package/src/services/secrets/secrets-service.ts +0 -287
  95. package/src/services/secrets/types.ts +0 -50
  96. package/src/services/update/index.ts +0 -2
  97. package/src/services/update/update-service.test.ts +0 -76
  98. package/src/services/update/update-service.ts +0 -193
  99. package/src/ui/banner.ts +0 -141
  100. package/src/ui/index.ts +0 -8
  101. package/src/ui/menu.ts +0 -490
  102. package/src/utils/errors.test.ts +0 -69
  103. package/src/utils/errors.ts +0 -23
  104. package/src/utils/formatters.test.ts +0 -180
  105. package/src/utils/formatters.ts +0 -269
  106. package/src/utils/theme.ts +0 -94
@@ -1,48 +0,0 @@
1
- /**
2
- * Waf command type definitions.
3
- */
4
-
5
- /** WAF status summary returned by `hoox waf status`. */
6
- export interface WafStatus {
7
- /** Whether WAF protection is enabled on the zone. */
8
- enabled: boolean;
9
- /** WAF mode (e.g. "on", "off", "simulate"). */
10
- mode: string;
11
- /** Number of active firewall rules on the zone. */
12
- activeRulesCount: number;
13
- /** Approximate count of recent blocked requests (last 24h). */
14
- recentBlocks: number;
15
- /** Cloudflare zone ID. */
16
- zoneId: string;
17
- /** Cloudflare zone name (domain). */
18
- zoneName: string;
19
- }
20
-
21
- /** A Cloudflare WAF / firewall rule. */
22
- export interface WafRule {
23
- /** Unique rule identifier. */
24
- id: string;
25
- /** Human-readable description of the rule. */
26
- description: string;
27
- /** Rule mode: "block", "challenge", "js_challenge", "allow", "log". */
28
- mode: string;
29
- /** Cloudflare Filters expression that triggers the rule. */
30
- expression: string;
31
- /** ISO 8601 creation timestamp. */
32
- created_at: string;
33
- /** ISO 8601 last-modified timestamp. */
34
- updated_at: string;
35
- }
36
-
37
- /** WAF toggle mode. */
38
- export type WafMode = "on" | "off";
39
-
40
- /** Input for creating a new WAF rule. */
41
- export interface WafRuleInput {
42
- /** Rule type: "ip-allowlist", "ip-blocklist", "rate-limit", "custom". */
43
- type: "ip-allowlist" | "ip-blocklist" | "rate-limit" | "custom";
44
- /** Value associated with the rule type (IP address, requests/period, etc.). */
45
- value: string;
46
- /** Optional human-readable description. */
47
- description?: string;
48
- }
@@ -1,506 +0,0 @@
1
- /**
2
- * Unit tests for `hoox waf` command.
3
- *
4
- * Mocks CloudflareService.zonesList() and global fetch() for Cloudflare API
5
- * calls. Tests verify command action functions produce correct output and
6
- * handle errors gracefully.
7
- */
8
-
9
- import {
10
- afterEach,
11
- beforeEach,
12
- describe,
13
- expect,
14
- it,
15
- mock,
16
- spyOn,
17
- } from "bun:test";
18
- import { Command } from "commander";
19
- import { registerWafCommand } from "./waf-command.js";
20
- import { CloudflareService } from "../../services/cloudflare/cloudflare-service.js";
21
- import type { WranglerResult } from "../../services/cloudflare/types.js";
22
-
23
- // ---------------------------------------------------------------------------
24
- // Helpers
25
- // ---------------------------------------------------------------------------
26
-
27
- const realFetch = globalThis.fetch;
28
- const realEnv = { ...process.env };
29
-
30
- /** Mock CloudflareService.zonesList to return a known zone. */
31
- function mockZonesList(output: string, ok = true, error?: string): void {
32
- const result: WranglerResult<string> = ok
33
- ? { ok: true, value: output }
34
- : { ok: false, error: error ?? "zone list failed" };
35
-
36
- spyOn(CloudflareService.prototype, "zonesList").mockImplementation(() =>
37
- Promise.resolve(result)
38
- );
39
- }
40
-
41
- /**
42
- * Sets up a mock fetch that returns a CF-style JSON response.
43
- * Returns the list of calls made to fetch for assertions.
44
- */
45
- function mockCfFetch(
46
- responseMap: Record<string, unknown>
47
- ): Array<{ url: string; method: string; body?: string }> {
48
- const calls: Array<{ url: string; method: string; body?: string }> = [];
49
-
50
- const fetchMock = mock(
51
- async (url: string, init?: Record<string, unknown>) => {
52
- void init; // Mark as used
53
- // Find matching response by path partial match
54
- let matched: unknown = null;
55
- for (const [pathPattern, response] of Object.entries(responseMap)) {
56
- if ((url as string).includes(pathPattern)) {
57
- matched = response;
58
- break;
59
- }
60
- }
61
-
62
- calls.push({
63
- url: url as string,
64
- method: (init?.method ?? "GET") as string,
65
- body: init?.body as string | undefined,
66
- });
67
-
68
- if (
69
- matched &&
70
- typeof matched === "object" &&
71
- matched !== null &&
72
- "error" in (matched as Record<string, unknown>)
73
- ) {
74
- // Error response
75
- return {
76
- ok: false,
77
- status: 400,
78
- json: async () => ({
79
- success: false,
80
- errors: [{ message: (matched as Record<string, string>).error }],
81
- }),
82
- };
83
- }
84
-
85
- return {
86
- ok: true,
87
- status: 200,
88
- json: async () => ({
89
- success: true,
90
- result: matched,
91
- }),
92
- };
93
- }
94
- );
95
-
96
- (globalThis as unknown as Record<string, unknown>).fetch = fetchMock;
97
- return calls;
98
- }
99
-
100
- // ---------------------------------------------------------------------------
101
- // Setup / Teardown
102
- // ---------------------------------------------------------------------------
103
-
104
- beforeEach(() => {
105
- // Set required env vars for CF API calls
106
- process.env.CLOUDFLARE_API_TOKEN = "test-token";
107
- process.env.CLOUDFLARE_ACCOUNT_ID = "test-account-id";
108
- process.env.CLOUDFLARE_ZONE_ID = ""; // force zonesList path
109
- });
110
-
111
- afterEach(() => {
112
- (globalThis as unknown as Record<string, unknown>).fetch = realFetch;
113
- process.env = { ...realEnv };
114
- mock.restore();
115
- });
116
-
117
- // ---------------------------------------------------------------------------
118
- // Helper: build a Commander program with waf registered
119
- // ---------------------------------------------------------------------------
120
-
121
- function makeProgram(): Command {
122
- const program = new Command()
123
- .name("hoox")
124
- .option("--json", "JSON output")
125
- .option("--quiet", "Minimal output");
126
-
127
- registerWafCommand(program);
128
- return program;
129
- }
130
-
131
- /** Run a command string (e.g. "waf status --json") and capture stdout. */
132
- async function runCommand(
133
- args: string
134
- ): Promise<{ stdout: string; exitCode: number }> {
135
- const program = makeProgram();
136
-
137
- let stdout = "";
138
- const writeSpy = spyOn(process.stdout, "write").mockImplementation(
139
- (chunk: unknown) => {
140
- stdout += typeof chunk === "string" ? chunk : String(chunk ?? "");
141
- return true;
142
- }
143
- );
144
-
145
- // We don't want process.exit() to kill the test runner
146
- const exitSpy = spyOn(process, "exit").mockImplementation(
147
- (() => {}) as never
148
- );
149
-
150
- try {
151
- await program.parseAsync([...args.split(" ")], { from: "user" });
152
- } catch {
153
- // Some commands may fail — that's fine, we test output
154
- }
155
-
156
- writeSpy.mockRestore();
157
- exitSpy.mockRestore();
158
-
159
- return { stdout, exitCode: 0 };
160
- }
161
-
162
- // ---------------------------------------------------------------------------
163
- // Tests
164
- // ---------------------------------------------------------------------------
165
-
166
- describe("waf command", () => {
167
- // -- waf status ------------------------------------------------------------
168
-
169
- describe("status", () => {
170
- it("shows WAF status when enabled", async () => {
171
- mockZonesList("example.com (abc123def456)");
172
- mockCfFetch({
173
- "/settings/waf": { id: "waf", value: "on" },
174
- "/firewall/rules": [
175
- {
176
- id: "rule-1",
177
- description: "Block bad IPs",
178
- action: "block",
179
- filter: { id: "f1", expression: "(ip.src eq 1.2.3.4)" },
180
- created_on: "2026-01-01T00:00:00Z",
181
- modified_on: "2026-01-02T00:00:00Z",
182
- },
183
- ],
184
- "/analytics/dashboard": { totals: { threats: 42 } },
185
- });
186
-
187
- const { stdout } = await runCommand("waf status");
188
-
189
- expect(stdout).toContain("example.com");
190
- expect(stdout).toContain("ENABLED");
191
- expect(stdout).toContain("1 active");
192
- expect(stdout).toContain("42");
193
- });
194
-
195
- it("shows WAF status when disabled", async () => {
196
- mockZonesList("example.com (abc123def456)");
197
- mockCfFetch({
198
- "/settings/waf": { id: "waf", value: "off" },
199
- "/firewall/rules": [],
200
- "/analytics/dashboard": { totals: { threats: 0 } },
201
- });
202
-
203
- const { stdout } = await runCommand("waf status");
204
-
205
- expect(stdout).toContain("DISABLED");
206
- expect(stdout).toContain("0 active");
207
- expect(stdout).toContain("0");
208
- });
209
-
210
- it("outputs JSON when --json flag is used", async () => {
211
- mockZonesList("example.com (abc123def456)");
212
- mockCfFetch({
213
- "/settings/waf": { id: "waf", value: "on" },
214
- "/firewall/rules": [],
215
- "/analytics/dashboard": { totals: { threats: 0 } },
216
- });
217
-
218
- const { stdout } = await runCommand("waf status --json");
219
-
220
- const parsed = JSON.parse(stdout.trim());
221
- expect(parsed).toHaveProperty("enabled", true);
222
- expect(parsed).toHaveProperty("zoneId", "abc123def456");
223
- expect(parsed).toHaveProperty("zoneName", "example.com");
224
- });
225
-
226
- it("throws when zonesList fails", async () => {
227
- mockZonesList("", false, "auth error");
228
-
229
- const { stdout } = await runCommand("waf status");
230
-
231
- expect(stdout).toContain("Failed to list zones");
232
- });
233
-
234
- it("uses CLOUDFLARE_ZONE_ID env var when set", async () => {
235
- process.env.CLOUDFLARE_ZONE_ID = "direct-zone-id";
236
- mockCfFetch({
237
- "/settings/waf": { id: "waf", value: "on" },
238
- "/firewall/rules": [],
239
- "/analytics/dashboard": { totals: { threats: 0 } },
240
- });
241
-
242
- const { stdout } = await runCommand("waf status");
243
-
244
- expect(stdout).toContain("direct-zone-id");
245
- });
246
- });
247
-
248
- // -- waf rules list --------------------------------------------------------
249
-
250
- describe("rules list", () => {
251
- it("lists firewall rules in human-readable format", async () => {
252
- mockZonesList("example.com (zone-1)");
253
- mockCfFetch({
254
- "/firewall/rules": [
255
- {
256
- id: "rule-1-is-a-very-long-id",
257
- description: "IP blocklist rule",
258
- action: "block",
259
- filter: {
260
- id: "f1",
261
- expression: "(ip.src eq 10.0.0.1)",
262
- },
263
- created_on: "2026-01-01T00:00:00Z",
264
- modified_on: "2026-01-02T00:00:00Z",
265
- },
266
- {
267
- id: "rule-2-short",
268
- description: "",
269
- action: "allow",
270
- filter: {
271
- id: "f2",
272
- expression: '(http.request.uri.path contains "/api")',
273
- },
274
- created_on: "2026-01-03T00:00:00Z",
275
- modified_on: "2026-01-04T00:00:00Z",
276
- },
277
- ],
278
- });
279
-
280
- const { stdout } = await runCommand("waf rules list");
281
-
282
- expect(stdout).toContain("IP blocklist rule");
283
- expect(stdout).toContain("block");
284
- expect(stdout).toContain("allow");
285
- expect(stdout).toContain("(ip.src eq 10.0.0.1)");
286
- });
287
-
288
- it("outputs JSON when --json flag is used", async () => {
289
- mockZonesList("example.com (zone-1)");
290
- mockCfFetch({
291
- "/firewall/rules": [
292
- {
293
- id: "rule-1",
294
- description: "Test rule",
295
- action: "block",
296
- filter: { id: "f1", expression: "(ip.src eq 1.1.1.1)" },
297
- created_on: "2026-01-01T00:00:00Z",
298
- modified_on: "2026-01-01T00:00:00Z",
299
- },
300
- ],
301
- });
302
-
303
- const { stdout } = await runCommand("waf rules list --json");
304
-
305
- const parsed = JSON.parse(stdout.trim());
306
- expect(Array.isArray(parsed)).toBe(true);
307
- expect(parsed[0]).toHaveProperty("id", "rule-1");
308
- });
309
-
310
- it("shows empty message when no rules exist", async () => {
311
- mockZonesList("example.com (zone-1)");
312
- mockCfFetch({ "/firewall/rules": [] });
313
-
314
- const { stdout } = await runCommand("waf rules list");
315
-
316
- expect(stdout).toContain("No firewall rules");
317
- });
318
-
319
- it("throws when rules fetch fails", async () => {
320
- mockZonesList("example.com (zone-1)");
321
- mockCfFetch({
322
- "/firewall/rules": { error: "API error" },
323
- });
324
-
325
- const { stdout } = await runCommand("waf rules list");
326
-
327
- expect(stdout).toContain("API error");
328
- });
329
- });
330
-
331
- // -- waf rules add ---------------------------------------------------------
332
-
333
- describe("rules add", () => {
334
- it("adds an IP allowlist rule", async () => {
335
- mockZonesList("example.com (zone-1)");
336
- mockCfFetch({
337
- "/filters": { id: "filter-1" },
338
- // NOTE: the firewall/rules endpoint is matched but returns array
339
- "/firewall/rules": [
340
- {
341
- id: "new-rule-1",
342
- description: "ip-allowlist rule for 192.168.1.1",
343
- action: "allow",
344
- filter: { id: "filter-1", expression: "(ip.src eq 192.168.1.1)" },
345
- created_on: "2026-01-01T00:00:00Z",
346
- modified_on: "2026-01-01T00:00:00Z",
347
- },
348
- ],
349
- });
350
-
351
- const { stdout } = await runCommand(
352
- "waf rules add ip-allowlist 192.168.1.1"
353
- );
354
-
355
- expect(stdout).toContain("WAF rule added");
356
- expect(stdout).toContain("192.168.1.1");
357
- });
358
-
359
- it("rejects invalid rule types", async () => {
360
- mockZonesList("example.com (zone-1)");
361
-
362
- const { stdout } = await runCommand("waf rules add invalid-type 1.2.3.4");
363
-
364
- expect(stdout).toContain("Invalid rule type");
365
- });
366
-
367
- it("throws when filter creation fails", async () => {
368
- mockZonesList("example.com (zone-1)");
369
- mockCfFetch({
370
- "/filters": { error: "Filter creation failed" },
371
- });
372
-
373
- const { stdout } = await runCommand(
374
- "waf rules add ip-blocklist 10.0.0.1"
375
- );
376
-
377
- expect(stdout).toContain("Filter creation failed");
378
- });
379
- });
380
-
381
- // -- waf rules remove ------------------------------------------------------
382
-
383
- describe("rules remove", () => {
384
- it("removes a WAF rule by ID", async () => {
385
- mockZonesList("example.com (zone-1)");
386
- mockCfFetch({
387
- "/firewall/rules/rule-to-delete": { id: "rule-to-delete" },
388
- });
389
-
390
- const { stdout } = await runCommand("waf rules remove rule-to-delete");
391
-
392
- expect(stdout).toContain("removed");
393
- expect(stdout).toContain("rule-to-delete");
394
- });
395
-
396
- it("throws when rule deletion fails", async () => {
397
- mockZonesList("example.com (zone-1)");
398
- mockCfFetch({
399
- "/firewall/rules/bad-id": { error: "Rule not found" },
400
- });
401
-
402
- const { stdout } = await runCommand("waf rules remove bad-id");
403
-
404
- expect(stdout).toContain("Rule not found");
405
- });
406
- });
407
-
408
- // -- waf mode --------------------------------------------------------------
409
-
410
- describe("mode", () => {
411
- it("enables WAF on the zone", async () => {
412
- mockZonesList("example.com (zone-1)");
413
- mockCfFetch({
414
- "/settings/waf": { id: "waf", value: "on" },
415
- });
416
-
417
- const { stdout } = await runCommand("waf mode enable");
418
-
419
- expect(stdout).toContain("enabled");
420
- expect(stdout).toContain("example.com");
421
- });
422
-
423
- it("disables WAF on the zone", async () => {
424
- mockZonesList("example.com (zone-1)");
425
- mockCfFetch({
426
- "/settings/waf": { id: "waf", value: "off" },
427
- });
428
-
429
- const { stdout } = await runCommand("waf mode disable");
430
-
431
- expect(stdout).toContain("disabled");
432
- expect(stdout).toContain("example.com");
433
- });
434
-
435
- it("throws when mode update fails", async () => {
436
- mockZonesList("example.com (zone-1)");
437
- mockCfFetch({
438
- "/settings/waf": { error: "Permission denied" },
439
- });
440
-
441
- const { stdout } = await runCommand("waf mode enable");
442
-
443
- expect(stdout).toContain("Permission denied");
444
- });
445
- });
446
-
447
- // -- Error handling --------------------------------------------------------
448
-
449
- describe("error handling", () => {
450
- it("throws when CLOUDFLARE_API_TOKEN is missing", async () => {
451
- delete process.env.CLOUDFLARE_API_TOKEN;
452
- mockZonesList("example.com (zone-1)");
453
-
454
- const { stdout } = await runCommand("waf status");
455
-
456
- expect(stdout).toContain("CLOUDFLARE_API_TOKEN");
457
- });
458
-
459
- it("throws when CLOUDFLARE_ACCOUNT_ID is missing", async () => {
460
- delete process.env.CLOUDFLARE_ACCOUNT_ID;
461
- mockZonesList("example.com (zone-1)");
462
-
463
- const { stdout } = await runCommand("waf status");
464
-
465
- expect(stdout).toContain("CLOUDFLARE_ACCOUNT_ID");
466
- });
467
- });
468
-
469
- // -- Subcommand structure --------------------------------------------------
470
-
471
- describe("subcommand structure", () => {
472
- it("registers waf as a command", () => {
473
- const program = makeProgram();
474
- const wafCmd = program.commands.find((c) => c.name() === "waf");
475
- expect(wafCmd).toBeDefined();
476
-
477
- const subNames = (wafCmd as Command).commands.map((c) => c.name());
478
- expect(subNames).toContain("status");
479
- expect(subNames).toContain("rules");
480
- expect(subNames).toContain("mode");
481
- });
482
-
483
- it("registers rules subcommands", () => {
484
- const program = makeProgram();
485
- const wafCmd = program.commands.find((c) => c.name() === "waf")!;
486
- const rulesCmd = (wafCmd as Command).commands.find(
487
- (c) => c.name() === "rules"
488
- )!;
489
- const ruleSubNames = (rulesCmd as Command).commands.map((c) => c.name());
490
- expect(ruleSubNames).toContain("list");
491
- expect(ruleSubNames).toContain("add");
492
- expect(ruleSubNames).toContain("remove");
493
- });
494
-
495
- it("registers mode subcommands", () => {
496
- const program = makeProgram();
497
- const wafCmd = program.commands.find((c) => c.name() === "waf")!;
498
- const modeCmd = (wafCmd as Command).commands.find(
499
- (c) => c.name() === "mode"
500
- )!;
501
- const modeSubNames = (modeCmd as Command).commands.map((c) => c.name());
502
- expect(modeSubNames).toContain("enable");
503
- expect(modeSubNames).toContain("disable");
504
- });
505
- });
506
- });