@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.
- package/bin/hoox.js +3 -4
- package/dist/index.js +32545 -0
- package/package.json +10 -15
- package/src/commands/check/check-command.test.ts +0 -476
- package/src/commands/check/check-command.ts +0 -1144
- package/src/commands/check/index.ts +0 -10
- package/src/commands/check/prerequisites-command.test.ts +0 -19
- package/src/commands/check/prerequisites-command.ts +0 -96
- package/src/commands/check/types.ts +0 -103
- package/src/commands/clone/clone-command.test.ts +0 -442
- package/src/commands/clone/clone-command.ts +0 -431
- package/src/commands/clone/index.ts +0 -1
- package/src/commands/config/config-command.test.ts +0 -583
- package/src/commands/config/config-command.ts +0 -897
- package/src/commands/config/env-command.test.ts +0 -43
- package/src/commands/config/env-command.ts +0 -295
- package/src/commands/config/index.ts +0 -3
- package/src/commands/config/kv-command.test.ts +0 -14
- package/src/commands/config/kv-command.ts +0 -290
- package/src/commands/dashboard/dashboard-command.test.ts +0 -47
- package/src/commands/dashboard/dashboard-command.ts +0 -128
- package/src/commands/dashboard/index.ts +0 -1
- package/src/commands/db/db-command.test.ts +0 -21
- package/src/commands/db/db-command.ts +0 -312
- package/src/commands/db/index.ts +0 -1
- package/src/commands/deploy/deploy-command.test.ts +0 -304
- package/src/commands/deploy/deploy-command.ts +0 -1135
- package/src/commands/deploy/index.ts +0 -2
- package/src/commands/deploy/telegram-service.ts +0 -76
- package/src/commands/deploy/types.ts +0 -34
- package/src/commands/dev/dev-command.test.ts +0 -383
- package/src/commands/dev/dev-command.ts +0 -398
- package/src/commands/dev/index.ts +0 -1
- package/src/commands/infra/index.ts +0 -5
- package/src/commands/infra/infra-command.test.ts +0 -719
- package/src/commands/infra/infra-command.ts +0 -974
- package/src/commands/infra/types.ts +0 -23
- package/src/commands/init/__tests__/cli-provisioner.test.ts +0 -17
- package/src/commands/init/cli-provisioner.ts +0 -71
- package/src/commands/init/index.ts +0 -1
- package/src/commands/init/init-command.test.ts +0 -876
- package/src/commands/init/init-command.ts +0 -593
- package/src/commands/init/types.ts +0 -23
- package/src/commands/logs/index.ts +0 -1
- package/src/commands/logs/logs-command.test.ts +0 -294
- package/src/commands/logs/logs-command.ts +0 -452
- package/src/commands/monitor/index.ts +0 -2
- package/src/commands/monitor/monitor-command.test.ts +0 -263
- package/src/commands/monitor/monitor-command.ts +0 -269
- package/src/commands/monitor/monitor-service.ts +0 -65
- package/src/commands/monitor/types.ts +0 -13
- package/src/commands/repair/index.ts +0 -2
- package/src/commands/repair/repair-command.test.ts +0 -224
- package/src/commands/repair/repair-command.ts +0 -220
- package/src/commands/repair/repair-service.ts +0 -138
- package/src/commands/repair/types.ts +0 -13
- package/src/commands/test/index.ts +0 -2
- package/src/commands/test/test-command.test.ts +0 -319
- package/src/commands/test/test-command.ts +0 -406
- package/src/commands/tui/index.ts +0 -1
- package/src/commands/tui/tui-command.ts +0 -87
- package/src/commands/update/index.ts +0 -1
- package/src/commands/update/update-command.ts +0 -168
- package/src/commands/waf/index.ts +0 -2
- package/src/commands/waf/types.ts +0 -48
- package/src/commands/waf/waf-command.test.ts +0 -506
- package/src/commands/waf/waf-command.ts +0 -548
- package/src/index.ts +0 -269
- package/src/services/cloudflare/cloudflare-service.test.ts +0 -658
- package/src/services/cloudflare/cloudflare-service.ts +0 -433
- package/src/services/cloudflare/index.ts +0 -2
- package/src/services/cloudflare/types.ts +0 -30
- package/src/services/config/config-service.test.ts +0 -395
- package/src/services/config/config-service.ts +0 -207
- package/src/services/config/index.ts +0 -9
- package/src/services/config/types.ts +0 -66
- package/src/services/db/db-service.test.ts +0 -51
- package/src/services/db/db-service.ts +0 -131
- package/src/services/db/index.ts +0 -1
- package/src/services/docker/docker-service.ts +0 -155
- package/src/services/docker/index.ts +0 -1
- package/src/services/env/env-service.test.ts +0 -236
- package/src/services/env/env-service.ts +0 -386
- package/src/services/env/index.ts +0 -1
- package/src/services/kv/index.ts +0 -1
- package/src/services/kv/kv-sync-service.test.ts +0 -38
- package/src/services/kv/kv-sync-service.ts +0 -234
- package/src/services/prerequisites/index.ts +0 -1
- package/src/services/prerequisites/prerequisites-service.test.ts +0 -89
- package/src/services/prerequisites/prerequisites-service.ts +0 -377
- package/src/services/prerequisites/types.ts +0 -48
- package/src/services/secrets/index.ts +0 -11
- package/src/services/secrets/secrets-service.test.ts +0 -495
- package/src/services/secrets/secrets-service.ts +0 -287
- package/src/services/secrets/types.ts +0 -50
- package/src/services/update/index.ts +0 -2
- package/src/services/update/update-service.test.ts +0 -76
- package/src/services/update/update-service.ts +0 -193
- package/src/ui/banner.ts +0 -141
- package/src/ui/index.ts +0 -8
- package/src/ui/menu.ts +0 -490
- package/src/utils/errors.test.ts +0 -69
- package/src/utils/errors.ts +0 -23
- package/src/utils/formatters.test.ts +0 -180
- package/src/utils/formatters.ts +0 -269
- package/src/utils/theme.ts +0 -94
|
@@ -1,719 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unit tests for the `hoox infra` command group.
|
|
3
|
-
*
|
|
4
|
-
* Tests each subcommand handler directly by injecting mock CloudflareService
|
|
5
|
-
* and ConfigService instances. Output is captured via process.stdout.write mock.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { afterEach, beforeEach, describe, expect, it, mock } from "bun:test";
|
|
9
|
-
import {
|
|
10
|
-
doD1List,
|
|
11
|
-
doD1Create,
|
|
12
|
-
doD1Delete,
|
|
13
|
-
doKvList,
|
|
14
|
-
doKvCreate,
|
|
15
|
-
doKvDelete,
|
|
16
|
-
doR2List,
|
|
17
|
-
doR2Create,
|
|
18
|
-
doR2Delete,
|
|
19
|
-
doQueueList,
|
|
20
|
-
doQueueCreate,
|
|
21
|
-
doQueueDelete,
|
|
22
|
-
doProvision,
|
|
23
|
-
displayListResult,
|
|
24
|
-
handleCreate,
|
|
25
|
-
handleDelete,
|
|
26
|
-
registerInfraCommand,
|
|
27
|
-
} from "./infra-command.js";
|
|
28
|
-
import type { InfraOptions } from "./types.js";
|
|
29
|
-
import type { WranglerResult } from "../../services/cloudflare/types.js";
|
|
30
|
-
import { Command } from "commander";
|
|
31
|
-
|
|
32
|
-
// ---------------------------------------------------------------------------
|
|
33
|
-
// Output capture helper
|
|
34
|
-
// ---------------------------------------------------------------------------
|
|
35
|
-
|
|
36
|
-
function captureStdout(): { output: () => string; restore: () => void } {
|
|
37
|
-
const chunks: string[] = [];
|
|
38
|
-
const originalWrite = process.stdout.write.bind(process.stdout);
|
|
39
|
-
const writeMock = mock((chunk: string | Buffer) => {
|
|
40
|
-
chunks.push(typeof chunk === "string" ? chunk : chunk.toString());
|
|
41
|
-
return true;
|
|
42
|
-
});
|
|
43
|
-
process.stdout.write = writeMock as unknown as typeof process.stdout.write;
|
|
44
|
-
|
|
45
|
-
return {
|
|
46
|
-
output: () => chunks.join(""),
|
|
47
|
-
restore: () => {
|
|
48
|
-
process.stdout.write = originalWrite;
|
|
49
|
-
},
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// ---------------------------------------------------------------------------
|
|
54
|
-
// Mock CloudflareService builder
|
|
55
|
-
// ---------------------------------------------------------------------------
|
|
56
|
-
|
|
57
|
-
/** Successful wrangler result with data. */
|
|
58
|
-
function okResult<T>(value: T): WranglerResult<T> {
|
|
59
|
-
return { ok: true, value } as WranglerResult<T>;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/** Failed wrangler result with error message. */
|
|
63
|
-
function errResult(error: string): WranglerResult<string> {
|
|
64
|
-
return { ok: false, error };
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/** Default JSON list output for D1. */
|
|
68
|
-
const D1_LIST_JSON = JSON.stringify([
|
|
69
|
-
{ name: "test-db", uuid: "abc-123", version: "production", num_tables: 3 },
|
|
70
|
-
{ name: "other-db", uuid: "def-456", version: "production", num_tables: 1 },
|
|
71
|
-
]);
|
|
72
|
-
|
|
73
|
-
/** Default JSON list output for KV. */
|
|
74
|
-
const KV_LIST_JSON = JSON.stringify([
|
|
75
|
-
{ id: "kv-abc", title: "config-kv", supports_url_encoding: true },
|
|
76
|
-
]);
|
|
77
|
-
|
|
78
|
-
/** Default JSON list output for R2. */
|
|
79
|
-
const R2_LIST_JSON = JSON.stringify([
|
|
80
|
-
{ name: "my-bucket", creation_date: "2024-01-15T00:00:00Z" },
|
|
81
|
-
]);
|
|
82
|
-
|
|
83
|
-
/** Default JSON list output for Queues. */
|
|
84
|
-
const QUEUE_LIST_JSON = JSON.stringify([
|
|
85
|
-
{ queue_name: "trade-queue", created_on: "2024-06-01T00:00:00Z" },
|
|
86
|
-
]);
|
|
87
|
-
|
|
88
|
-
interface MockCloudflareService {
|
|
89
|
-
d1List: () => Promise<WranglerResult<string>>;
|
|
90
|
-
d1Create: (name: string) => Promise<WranglerResult<string>>;
|
|
91
|
-
d1Delete: (name: string) => Promise<WranglerResult<string>>;
|
|
92
|
-
kvList: () => Promise<WranglerResult<string>>;
|
|
93
|
-
kvCreate: (name: string) => Promise<WranglerResult<string>>;
|
|
94
|
-
kvDelete: (id: string) => Promise<WranglerResult<string>>;
|
|
95
|
-
r2List: () => Promise<WranglerResult<string>>;
|
|
96
|
-
r2Create: (name: string) => Promise<WranglerResult<string>>;
|
|
97
|
-
r2Delete: (name: string) => Promise<WranglerResult<string>>;
|
|
98
|
-
queueList: () => Promise<WranglerResult<string>>;
|
|
99
|
-
queueCreate: (name: string) => Promise<WranglerResult<string>>;
|
|
100
|
-
queueDelete: (name: string) => Promise<WranglerResult<string>>;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function createMockCloudflare(
|
|
104
|
-
overrides?: Partial<MockCloudflareService>
|
|
105
|
-
): MockCloudflareService {
|
|
106
|
-
return {
|
|
107
|
-
d1List: async () => okResult(D1_LIST_JSON),
|
|
108
|
-
d1Create: async (name: string) => okResult(`Created ${name}`),
|
|
109
|
-
d1Delete: async (name: string) => okResult(`Deleted ${name}`),
|
|
110
|
-
kvList: async () => okResult(KV_LIST_JSON),
|
|
111
|
-
kvCreate: async (name: string) => okResult(`Created ${name}`),
|
|
112
|
-
kvDelete: async (id: string) => okResult(`Deleted ${id}`),
|
|
113
|
-
r2List: async () => okResult(R2_LIST_JSON),
|
|
114
|
-
r2Create: async (name: string) => okResult(`Created ${name}`),
|
|
115
|
-
r2Delete: async (name: string) => okResult(`Deleted ${name}`),
|
|
116
|
-
queueList: async () => okResult(QUEUE_LIST_JSON),
|
|
117
|
-
queueCreate: async (name: string) => okResult(`Created ${name}`),
|
|
118
|
-
queueDelete: async (name: string) => okResult(`Deleted ${name}`),
|
|
119
|
-
...overrides,
|
|
120
|
-
};
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// ---------------------------------------------------------------------------
|
|
124
|
-
// Tests
|
|
125
|
-
// ---------------------------------------------------------------------------
|
|
126
|
-
|
|
127
|
-
describe("infra-command", () => {
|
|
128
|
-
let capture: ReturnType<typeof captureStdout>;
|
|
129
|
-
const humanOpts: InfraOptions = { json: false, quiet: false };
|
|
130
|
-
const jsonOpts: InfraOptions = { json: true, quiet: false };
|
|
131
|
-
const quietOpts: InfraOptions = { json: false, quiet: true };
|
|
132
|
-
|
|
133
|
-
beforeEach(() => {
|
|
134
|
-
capture = captureStdout();
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
afterEach(() => {
|
|
138
|
-
capture.restore();
|
|
139
|
-
mock.restore();
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
// -- registerInfraCommand -------------------------------------------------
|
|
143
|
-
|
|
144
|
-
describe("registerInfraCommand", () => {
|
|
145
|
-
it("registers infra command with subcommands on a program", () => {
|
|
146
|
-
const program = new Command();
|
|
147
|
-
program.exitOverride();
|
|
148
|
-
|
|
149
|
-
registerInfraCommand(program);
|
|
150
|
-
|
|
151
|
-
// Verify the infra command exists
|
|
152
|
-
const infraCmd = program.commands.find((c) => c.name() === "infra");
|
|
153
|
-
expect(infraCmd).toBeDefined();
|
|
154
|
-
|
|
155
|
-
// Verify subcommands exist
|
|
156
|
-
const subNames = infraCmd!.commands.map((c) => c.name());
|
|
157
|
-
expect(subNames).toContain("provision");
|
|
158
|
-
expect(subNames).toContain("d1");
|
|
159
|
-
expect(subNames).toContain("kv");
|
|
160
|
-
expect(subNames).toContain("r2");
|
|
161
|
-
expect(subNames).toContain("queues");
|
|
162
|
-
|
|
163
|
-
// Verify d1 subcommands
|
|
164
|
-
const d1Cmd = infraCmd!.commands.find((c) => c.name() === "d1");
|
|
165
|
-
expect(d1Cmd).toBeDefined();
|
|
166
|
-
const d1Subs = d1Cmd!.commands.map((c) => c.name());
|
|
167
|
-
expect(d1Subs).toContain("list");
|
|
168
|
-
expect(d1Subs).toContain("create");
|
|
169
|
-
expect(d1Subs).toContain("delete");
|
|
170
|
-
});
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
// -- displayListResult ----------------------------------------------------
|
|
174
|
-
|
|
175
|
-
describe("displayListResult", () => {
|
|
176
|
-
it("displays JSON array as a table in human mode", () => {
|
|
177
|
-
const result = okResult(D1_LIST_JSON);
|
|
178
|
-
displayListResult(result, humanOpts, ["name", "uuid"]);
|
|
179
|
-
|
|
180
|
-
const out = capture.output();
|
|
181
|
-
expect(out).toContain("test-db");
|
|
182
|
-
expect(out).toContain("abc-123");
|
|
183
|
-
expect(out).toContain("other-db");
|
|
184
|
-
expect(out).toContain("def-456");
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
it("outputs raw JSON array in json mode", () => {
|
|
188
|
-
const result = okResult(D1_LIST_JSON);
|
|
189
|
-
displayListResult(result, jsonOpts, ["name", "uuid"]);
|
|
190
|
-
|
|
191
|
-
const out = capture.output();
|
|
192
|
-
const parsed = JSON.parse(out);
|
|
193
|
-
expect(Array.isArray(parsed)).toBe(true);
|
|
194
|
-
expect(parsed[0].name).toBe("test-db");
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
it("outputs nothing in quiet mode", () => {
|
|
198
|
-
const result = okResult(D1_LIST_JSON);
|
|
199
|
-
displayListResult(result, quietOpts, ["name"]);
|
|
200
|
-
|
|
201
|
-
const out = capture.output();
|
|
202
|
-
expect(out).toBe("");
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
it("falls back to raw text when output is not JSON", () => {
|
|
206
|
-
const result = okResult("Raw text output from wrangler");
|
|
207
|
-
displayListResult(result, humanOpts);
|
|
208
|
-
|
|
209
|
-
const out = capture.output();
|
|
210
|
-
expect(out).toContain("Raw text output from wrangler");
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
it("displays error when result is not ok", () => {
|
|
214
|
-
const result = errResult("Authentication failed");
|
|
215
|
-
displayListResult(result, humanOpts);
|
|
216
|
-
|
|
217
|
-
const out = capture.output();
|
|
218
|
-
expect(out).toContain("Authentication failed");
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
it("displays (empty) for empty array", () => {
|
|
222
|
-
const result = okResult("[]");
|
|
223
|
-
displayListResult(result, humanOpts);
|
|
224
|
-
// Falls through to raw text since try/catch catches empty array
|
|
225
|
-
// (our helper only attempts table for non-empty arrays)
|
|
226
|
-
const out = capture.output();
|
|
227
|
-
expect(out).toContain("[]");
|
|
228
|
-
});
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
// -- handleCreate ---------------------------------------------------------
|
|
232
|
-
|
|
233
|
-
describe("handleCreate", () => {
|
|
234
|
-
it("shows success message on creation", async () => {
|
|
235
|
-
await handleCreate(
|
|
236
|
-
"my-resource",
|
|
237
|
-
"D1 database",
|
|
238
|
-
async () => okResult("created"),
|
|
239
|
-
humanOpts
|
|
240
|
-
);
|
|
241
|
-
|
|
242
|
-
const out = capture.output();
|
|
243
|
-
expect(out).toContain("Creating D1 database:");
|
|
244
|
-
expect(out).toContain("my-resource");
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
it("shows error message on failure", async () => {
|
|
248
|
-
await handleCreate(
|
|
249
|
-
"bad-resource",
|
|
250
|
-
"D1 database",
|
|
251
|
-
async () => errResult("Already exists"),
|
|
252
|
-
humanOpts
|
|
253
|
-
);
|
|
254
|
-
|
|
255
|
-
const out = capture.output();
|
|
256
|
-
expect(out).toContain("Already exists");
|
|
257
|
-
});
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
// -- handleDelete ---------------------------------------------------------
|
|
261
|
-
|
|
262
|
-
describe("handleDelete", () => {
|
|
263
|
-
it("shows success message on deletion", async () => {
|
|
264
|
-
await handleDelete(
|
|
265
|
-
"my-resource",
|
|
266
|
-
"D1 database",
|
|
267
|
-
async () => okResult("deleted"),
|
|
268
|
-
humanOpts
|
|
269
|
-
);
|
|
270
|
-
|
|
271
|
-
const out = capture.output();
|
|
272
|
-
expect(out).toContain("Deleting D1 database:");
|
|
273
|
-
expect(out).toContain("my-resource");
|
|
274
|
-
});
|
|
275
|
-
|
|
276
|
-
it("shows error message on failure", async () => {
|
|
277
|
-
await handleDelete(
|
|
278
|
-
"missing",
|
|
279
|
-
"D1 database",
|
|
280
|
-
async () => errResult("Not found"),
|
|
281
|
-
humanOpts
|
|
282
|
-
);
|
|
283
|
-
|
|
284
|
-
const out = capture.output();
|
|
285
|
-
expect(out).toContain("Not found");
|
|
286
|
-
});
|
|
287
|
-
});
|
|
288
|
-
|
|
289
|
-
// -- D1 handlers ----------------------------------------------------------
|
|
290
|
-
|
|
291
|
-
describe("doD1List", () => {
|
|
292
|
-
it("shows D1 databases as a table", async () => {
|
|
293
|
-
const mockCf = createMockCloudflare();
|
|
294
|
-
await doD1List(
|
|
295
|
-
humanOpts,
|
|
296
|
-
mockCf as unknown as import("../../services/cloudflare/cloudflare-service.js").CloudflareService
|
|
297
|
-
);
|
|
298
|
-
|
|
299
|
-
const out = capture.output();
|
|
300
|
-
expect(out).toContain("test-db");
|
|
301
|
-
expect(out).toContain("other-db");
|
|
302
|
-
});
|
|
303
|
-
});
|
|
304
|
-
|
|
305
|
-
describe("doD1Create", () => {
|
|
306
|
-
it("creates a D1 database and shows success", async () => {
|
|
307
|
-
const mockCf = createMockCloudflare();
|
|
308
|
-
await doD1Create(
|
|
309
|
-
"new-db",
|
|
310
|
-
humanOpts,
|
|
311
|
-
mockCf as unknown as import("../../services/cloudflare/cloudflare-service.js").CloudflareService
|
|
312
|
-
);
|
|
313
|
-
|
|
314
|
-
const out = capture.output();
|
|
315
|
-
expect(out).toContain("new-db");
|
|
316
|
-
});
|
|
317
|
-
});
|
|
318
|
-
|
|
319
|
-
describe("doD1Delete", () => {
|
|
320
|
-
it("deletes a D1 database and shows success", async () => {
|
|
321
|
-
const mockCf = createMockCloudflare();
|
|
322
|
-
await doD1Delete(
|
|
323
|
-
"old-db",
|
|
324
|
-
humanOpts,
|
|
325
|
-
mockCf as unknown as import("../../services/cloudflare/cloudflare-service.js").CloudflareService
|
|
326
|
-
);
|
|
327
|
-
|
|
328
|
-
const out = capture.output();
|
|
329
|
-
expect(out).toContain("old-db");
|
|
330
|
-
});
|
|
331
|
-
});
|
|
332
|
-
|
|
333
|
-
// -- KV handlers ----------------------------------------------------------
|
|
334
|
-
|
|
335
|
-
describe("doKvList", () => {
|
|
336
|
-
it("shows KV namespaces", async () => {
|
|
337
|
-
const mockCf = createMockCloudflare();
|
|
338
|
-
await doKvList(
|
|
339
|
-
humanOpts,
|
|
340
|
-
mockCf as unknown as import("../../services/cloudflare/cloudflare-service.js").CloudflareService
|
|
341
|
-
);
|
|
342
|
-
|
|
343
|
-
const out = capture.output();
|
|
344
|
-
expect(out).toContain("config-kv");
|
|
345
|
-
});
|
|
346
|
-
});
|
|
347
|
-
|
|
348
|
-
describe("doKvCreate", () => {
|
|
349
|
-
it("creates a KV namespace", async () => {
|
|
350
|
-
const mockCf = createMockCloudflare();
|
|
351
|
-
await doKvCreate(
|
|
352
|
-
"my-kv",
|
|
353
|
-
humanOpts,
|
|
354
|
-
mockCf as unknown as import("../../services/cloudflare/cloudflare-service.js").CloudflareService
|
|
355
|
-
);
|
|
356
|
-
|
|
357
|
-
const out = capture.output();
|
|
358
|
-
expect(out).toContain("my-kv");
|
|
359
|
-
});
|
|
360
|
-
});
|
|
361
|
-
|
|
362
|
-
describe("doKvDelete", () => {
|
|
363
|
-
it("deletes a KV namespace by ID", async () => {
|
|
364
|
-
const mockCf = createMockCloudflare();
|
|
365
|
-
await doKvDelete(
|
|
366
|
-
"kv-abc",
|
|
367
|
-
humanOpts,
|
|
368
|
-
mockCf as unknown as import("../../services/cloudflare/cloudflare-service.js").CloudflareService
|
|
369
|
-
);
|
|
370
|
-
|
|
371
|
-
const out = capture.output();
|
|
372
|
-
expect(out).toContain("kv-abc");
|
|
373
|
-
});
|
|
374
|
-
});
|
|
375
|
-
|
|
376
|
-
// -- R2 handlers ----------------------------------------------------------
|
|
377
|
-
|
|
378
|
-
describe("doR2List", () => {
|
|
379
|
-
it("shows R2 buckets", async () => {
|
|
380
|
-
const mockCf = createMockCloudflare();
|
|
381
|
-
await doR2List(
|
|
382
|
-
humanOpts,
|
|
383
|
-
mockCf as unknown as import("../../services/cloudflare/cloudflare-service.js").CloudflareService
|
|
384
|
-
);
|
|
385
|
-
|
|
386
|
-
const out = capture.output();
|
|
387
|
-
expect(out).toContain("my-bucket");
|
|
388
|
-
});
|
|
389
|
-
});
|
|
390
|
-
|
|
391
|
-
describe("doR2Create", () => {
|
|
392
|
-
it("creates an R2 bucket", async () => {
|
|
393
|
-
const mockCf = createMockCloudflare();
|
|
394
|
-
await doR2Create(
|
|
395
|
-
"new-bucket",
|
|
396
|
-
humanOpts,
|
|
397
|
-
mockCf as unknown as import("../../services/cloudflare/cloudflare-service.js").CloudflareService
|
|
398
|
-
);
|
|
399
|
-
|
|
400
|
-
const out = capture.output();
|
|
401
|
-
expect(out).toContain("new-bucket");
|
|
402
|
-
});
|
|
403
|
-
});
|
|
404
|
-
|
|
405
|
-
describe("doR2Delete", () => {
|
|
406
|
-
it("deletes an R2 bucket", async () => {
|
|
407
|
-
const mockCf = createMockCloudflare();
|
|
408
|
-
await doR2Delete(
|
|
409
|
-
"old-bucket",
|
|
410
|
-
humanOpts,
|
|
411
|
-
mockCf as unknown as import("../../services/cloudflare/cloudflare-service.js").CloudflareService
|
|
412
|
-
);
|
|
413
|
-
|
|
414
|
-
const out = capture.output();
|
|
415
|
-
expect(out).toContain("old-bucket");
|
|
416
|
-
});
|
|
417
|
-
});
|
|
418
|
-
|
|
419
|
-
// -- Queue handlers -------------------------------------------------------
|
|
420
|
-
|
|
421
|
-
describe("doQueueList", () => {
|
|
422
|
-
it("shows Queues", async () => {
|
|
423
|
-
const mockCf = createMockCloudflare();
|
|
424
|
-
await doQueueList(
|
|
425
|
-
humanOpts,
|
|
426
|
-
mockCf as unknown as import("../../services/cloudflare/cloudflare-service.js").CloudflareService
|
|
427
|
-
);
|
|
428
|
-
|
|
429
|
-
const out = capture.output();
|
|
430
|
-
expect(out).toContain("trade-queue");
|
|
431
|
-
});
|
|
432
|
-
});
|
|
433
|
-
|
|
434
|
-
describe("doQueueCreate", () => {
|
|
435
|
-
it("creates a Queue", async () => {
|
|
436
|
-
const mockCf = createMockCloudflare();
|
|
437
|
-
await doQueueCreate(
|
|
438
|
-
"new-queue",
|
|
439
|
-
humanOpts,
|
|
440
|
-
mockCf as unknown as import("../../services/cloudflare/cloudflare-service.js").CloudflareService
|
|
441
|
-
);
|
|
442
|
-
|
|
443
|
-
const out = capture.output();
|
|
444
|
-
expect(out).toContain("new-queue");
|
|
445
|
-
});
|
|
446
|
-
});
|
|
447
|
-
|
|
448
|
-
describe("doQueueDelete", () => {
|
|
449
|
-
it("deletes a Queue", async () => {
|
|
450
|
-
const mockCf = createMockCloudflare();
|
|
451
|
-
await doQueueDelete(
|
|
452
|
-
"old-queue",
|
|
453
|
-
humanOpts,
|
|
454
|
-
mockCf as unknown as import("../../services/cloudflare/cloudflare-service.js").CloudflareService
|
|
455
|
-
);
|
|
456
|
-
|
|
457
|
-
const out = capture.output();
|
|
458
|
-
expect(out).toContain("old-queue");
|
|
459
|
-
});
|
|
460
|
-
});
|
|
461
|
-
|
|
462
|
-
// -- JSON output mode -----------------------------------------------------
|
|
463
|
-
|
|
464
|
-
describe("JSON output mode", () => {
|
|
465
|
-
it("outputs D1 list as JSON", async () => {
|
|
466
|
-
const mockCf = createMockCloudflare();
|
|
467
|
-
await doD1List(
|
|
468
|
-
jsonOpts,
|
|
469
|
-
mockCf as unknown as import("../../services/cloudflare/cloudflare-service.js").CloudflareService
|
|
470
|
-
);
|
|
471
|
-
|
|
472
|
-
const out = capture.output();
|
|
473
|
-
const parsed = JSON.parse(out);
|
|
474
|
-
expect(Array.isArray(parsed)).toBe(true);
|
|
475
|
-
expect(parsed[0].name).toBe("test-db");
|
|
476
|
-
});
|
|
477
|
-
|
|
478
|
-
it("outputs KV list as JSON", async () => {
|
|
479
|
-
const mockCf = createMockCloudflare();
|
|
480
|
-
await doKvList(
|
|
481
|
-
jsonOpts,
|
|
482
|
-
mockCf as unknown as import("../../services/cloudflare/cloudflare-service.js").CloudflareService
|
|
483
|
-
);
|
|
484
|
-
|
|
485
|
-
const out = capture.output();
|
|
486
|
-
const parsed = JSON.parse(out);
|
|
487
|
-
expect(Array.isArray(parsed)).toBe(true);
|
|
488
|
-
expect(parsed[0].id).toBe("kv-abc");
|
|
489
|
-
});
|
|
490
|
-
});
|
|
491
|
-
|
|
492
|
-
// -- Quiet mode -----------------------------------------------------------
|
|
493
|
-
|
|
494
|
-
describe("Quiet mode", () => {
|
|
495
|
-
it("produces no output for list in quiet mode", async () => {
|
|
496
|
-
const mockCf = createMockCloudflare();
|
|
497
|
-
await doD1List(
|
|
498
|
-
quietOpts,
|
|
499
|
-
mockCf as unknown as import("../../services/cloudflare/cloudflare-service.js").CloudflareService
|
|
500
|
-
);
|
|
501
|
-
|
|
502
|
-
expect(capture.output()).toBe("");
|
|
503
|
-
});
|
|
504
|
-
});
|
|
505
|
-
|
|
506
|
-
// -- Error handling -------------------------------------------------------
|
|
507
|
-
|
|
508
|
-
describe("error propagation", () => {
|
|
509
|
-
it("shows error for failed D1 list", async () => {
|
|
510
|
-
const mockCf = createMockCloudflare({
|
|
511
|
-
d1List: async () => errResult("wrangler not authenticated"),
|
|
512
|
-
});
|
|
513
|
-
await doD1List(
|
|
514
|
-
humanOpts,
|
|
515
|
-
mockCf as unknown as import("../../services/cloudflare/cloudflare-service.js").CloudflareService
|
|
516
|
-
);
|
|
517
|
-
|
|
518
|
-
const out = capture.output();
|
|
519
|
-
expect(out).toContain("wrangler not authenticated");
|
|
520
|
-
});
|
|
521
|
-
|
|
522
|
-
it("shows error for failed D1 create", async () => {
|
|
523
|
-
const mockCf = createMockCloudflare({
|
|
524
|
-
d1Create: async () => errResult("database already exists"),
|
|
525
|
-
});
|
|
526
|
-
await doD1Create(
|
|
527
|
-
"dup-db",
|
|
528
|
-
humanOpts,
|
|
529
|
-
mockCf as unknown as import("../../services/cloudflare/cloudflare-service.js").CloudflareService
|
|
530
|
-
);
|
|
531
|
-
|
|
532
|
-
const out = capture.output();
|
|
533
|
-
expect(out).toContain("database already exists");
|
|
534
|
-
});
|
|
535
|
-
|
|
536
|
-
it("shows error for failed queue delete", async () => {
|
|
537
|
-
const mockCf = createMockCloudflare({
|
|
538
|
-
queueDelete: async () => errResult("queue not found"),
|
|
539
|
-
});
|
|
540
|
-
await doQueueDelete(
|
|
541
|
-
"ghost",
|
|
542
|
-
humanOpts,
|
|
543
|
-
mockCf as unknown as import("../../services/cloudflare/cloudflare-service.js").CloudflareService
|
|
544
|
-
);
|
|
545
|
-
|
|
546
|
-
const out = capture.output();
|
|
547
|
-
expect(out).toContain("queue not found");
|
|
548
|
-
});
|
|
549
|
-
});
|
|
550
|
-
|
|
551
|
-
// -- Provision ------------------------------------------------------------
|
|
552
|
-
|
|
553
|
-
describe("doProvision", () => {
|
|
554
|
-
// Save references to real implementations for restoration
|
|
555
|
-
const realFile = Bun.file;
|
|
556
|
-
|
|
557
|
-
afterEach(() => {
|
|
558
|
-
(Bun as unknown as Record<string, unknown>).file = realFile;
|
|
559
|
-
});
|
|
560
|
-
|
|
561
|
-
it("returns empty result when no enabled workers", async () => {
|
|
562
|
-
const mockConfig = {
|
|
563
|
-
load: async () => ({ global: {}, workers: {} }),
|
|
564
|
-
listEnabledWorkers: () => [] as string[],
|
|
565
|
-
getWorker: () => undefined,
|
|
566
|
-
};
|
|
567
|
-
|
|
568
|
-
const mockCf = createMockCloudflare();
|
|
569
|
-
const result = await doProvision(
|
|
570
|
-
humanOpts,
|
|
571
|
-
mockCf as unknown as import("../../services/cloudflare/cloudflare-service.js").CloudflareService,
|
|
572
|
-
mockConfig as unknown as import("../../services/config/config-service.js").ConfigService
|
|
573
|
-
);
|
|
574
|
-
|
|
575
|
-
expect(result.items).toEqual([]);
|
|
576
|
-
expect(result.summary.total).toBe(0);
|
|
577
|
-
});
|
|
578
|
-
|
|
579
|
-
it("handles config load failure gracefully", async () => {
|
|
580
|
-
const mockConfig = {
|
|
581
|
-
load: async () => {
|
|
582
|
-
throw new Error("Config file not found");
|
|
583
|
-
},
|
|
584
|
-
listEnabledWorkers: () => [] as string[],
|
|
585
|
-
getWorker: () => undefined,
|
|
586
|
-
};
|
|
587
|
-
|
|
588
|
-
const mockCf = createMockCloudflare();
|
|
589
|
-
const result = await doProvision(
|
|
590
|
-
humanOpts,
|
|
591
|
-
mockCf as unknown as import("../../services/cloudflare/cloudflare-service.js").CloudflareService,
|
|
592
|
-
mockConfig as unknown as import("../../services/config/config-service.js").ConfigService
|
|
593
|
-
);
|
|
594
|
-
|
|
595
|
-
expect(result.items).toEqual([]);
|
|
596
|
-
expect(result.summary.total).toBe(0);
|
|
597
|
-
|
|
598
|
-
const out = capture.output();
|
|
599
|
-
expect(out).toContain("Config file not found");
|
|
600
|
-
});
|
|
601
|
-
|
|
602
|
-
it("provisions D1 databases from worker wrangler.jsonc", async () => {
|
|
603
|
-
const workerPath = "/fake/workers/test-worker";
|
|
604
|
-
|
|
605
|
-
const mockConfig = {
|
|
606
|
-
load: async () => ({
|
|
607
|
-
global: {},
|
|
608
|
-
workers: { "test-worker": { enabled: true, path: workerPath } },
|
|
609
|
-
}),
|
|
610
|
-
listEnabledWorkers: () => ["test-worker"],
|
|
611
|
-
getWorker: (name: string) =>
|
|
612
|
-
name === "test-worker"
|
|
613
|
-
? { enabled: true, path: workerPath }
|
|
614
|
-
: undefined,
|
|
615
|
-
};
|
|
616
|
-
|
|
617
|
-
const wranglerConfig = JSON.stringify({
|
|
618
|
-
d1_databases: [
|
|
619
|
-
{ binding: "DB", database_name: "my-db", database_id: "xxx" },
|
|
620
|
-
],
|
|
621
|
-
});
|
|
622
|
-
|
|
623
|
-
// Mock Bun.file to return our fake wrangler.jsonc
|
|
624
|
-
(Bun as unknown as Record<string, unknown>).file = mock((p: string) => ({
|
|
625
|
-
exists: async () => p === `${workerPath}/wrangler.jsonc`,
|
|
626
|
-
text: async () => wranglerConfig,
|
|
627
|
-
}));
|
|
628
|
-
|
|
629
|
-
const mockCf = createMockCloudflare({
|
|
630
|
-
d1Create: async () => okResult("Created my-db"),
|
|
631
|
-
});
|
|
632
|
-
|
|
633
|
-
const result = await doProvision(
|
|
634
|
-
humanOpts,
|
|
635
|
-
mockCf as unknown as import("../../services/cloudflare/cloudflare-service.js").CloudflareService,
|
|
636
|
-
mockConfig as unknown as import("../../services/config/config-service.js").ConfigService
|
|
637
|
-
);
|
|
638
|
-
|
|
639
|
-
expect(result.items.length).toBeGreaterThan(0);
|
|
640
|
-
const d1Item = result.items.find(
|
|
641
|
-
(i) => i.name === "my-db" && i.type === "d1"
|
|
642
|
-
);
|
|
643
|
-
expect(d1Item).toBeDefined();
|
|
644
|
-
expect(d1Item!.status).toBe("created");
|
|
645
|
-
});
|
|
646
|
-
|
|
647
|
-
it("marks resources as exists when wrangler reports already exists", async () => {
|
|
648
|
-
const workerPath = "/fake/workers/existing-worker";
|
|
649
|
-
|
|
650
|
-
const mockConfig = {
|
|
651
|
-
load: async () => ({
|
|
652
|
-
global: {},
|
|
653
|
-
workers: { "existing-worker": { enabled: true, path: workerPath } },
|
|
654
|
-
}),
|
|
655
|
-
listEnabledWorkers: () => ["existing-worker"],
|
|
656
|
-
getWorker: (name: string) =>
|
|
657
|
-
name === "existing-worker"
|
|
658
|
-
? { enabled: true, path: workerPath }
|
|
659
|
-
: undefined,
|
|
660
|
-
};
|
|
661
|
-
|
|
662
|
-
const wranglerConfig = JSON.stringify({
|
|
663
|
-
r2_buckets: [{ binding: "FILES", bucket_name: "storage" }],
|
|
664
|
-
});
|
|
665
|
-
|
|
666
|
-
(Bun as unknown as Record<string, unknown>).file = mock((p: string) => ({
|
|
667
|
-
exists: async () => p === `${workerPath}/wrangler.jsonc`,
|
|
668
|
-
text: async () => wranglerConfig,
|
|
669
|
-
}));
|
|
670
|
-
|
|
671
|
-
const mockCf = createMockCloudflare({
|
|
672
|
-
r2Create: async () => errResult("bucket already exists"),
|
|
673
|
-
});
|
|
674
|
-
|
|
675
|
-
const result = await doProvision(
|
|
676
|
-
humanOpts,
|
|
677
|
-
mockCf as unknown as import("../../services/cloudflare/cloudflare-service.js").CloudflareService,
|
|
678
|
-
mockConfig as unknown as import("../../services/config/config-service.js").ConfigService
|
|
679
|
-
);
|
|
680
|
-
|
|
681
|
-
const r2Item = result.items.find(
|
|
682
|
-
(i) => i.name === "storage" && i.type === "r2"
|
|
683
|
-
);
|
|
684
|
-
expect(r2Item).toBeDefined();
|
|
685
|
-
expect(r2Item!.status).toBe("exists");
|
|
686
|
-
});
|
|
687
|
-
|
|
688
|
-
it("handles workers without wrangler.jsonc gracefully", async () => {
|
|
689
|
-
const workerPath = "/fake/workers/no-config-worker";
|
|
690
|
-
|
|
691
|
-
const mockConfig = {
|
|
692
|
-
load: async () => ({
|
|
693
|
-
global: {},
|
|
694
|
-
workers: { "no-config-worker": { enabled: true, path: workerPath } },
|
|
695
|
-
}),
|
|
696
|
-
listEnabledWorkers: () => ["no-config-worker"],
|
|
697
|
-
getWorker: (name: string) =>
|
|
698
|
-
name === "no-config-worker"
|
|
699
|
-
? { enabled: true, path: workerPath }
|
|
700
|
-
: undefined,
|
|
701
|
-
};
|
|
702
|
-
|
|
703
|
-
(Bun as unknown as Record<string, unknown>).file = mock(() => ({
|
|
704
|
-
exists: async () => false,
|
|
705
|
-
text: async () => "",
|
|
706
|
-
}));
|
|
707
|
-
|
|
708
|
-
const mockCf = createMockCloudflare();
|
|
709
|
-
const result = await doProvision(
|
|
710
|
-
humanOpts,
|
|
711
|
-
mockCf as unknown as import("../../services/cloudflare/cloudflare-service.js").CloudflareService,
|
|
712
|
-
mockConfig as unknown as import("../../services/config/config-service.js").ConfigService
|
|
713
|
-
);
|
|
714
|
-
|
|
715
|
-
// No wrangler.jsonc → no items to provision
|
|
716
|
-
expect(result.items).toEqual([]);
|
|
717
|
-
});
|
|
718
|
-
});
|
|
719
|
-
});
|