@mycadet/cli 0.12.10 → 0.13.0-dev.202610171617.9ea363e
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/README.md +13 -0
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/__tests__/ingress.test.ts +10 -10
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/channels.ts +0 -1
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/ingress.ts +3 -3
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/__tests__/ingress.test.ts +10 -10
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/channels.ts +0 -1
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/ingress.ts +3 -3
- package/node_modules/@mycadet/service-contracts/src/__tests__/ingress.test.ts +10 -10
- package/node_modules/@mycadet/service-contracts/src/channels.ts +0 -1
- package/node_modules/@mycadet/service-contracts/src/ingress.ts +3 -3
- package/package.json +8 -3
- package/src/__tests__/assistant-client-cloud-auth.test.ts +2 -2
- package/src/__tests__/backup-ops-matching.test.ts +15 -13
- package/src/__tests__/backup-ops.test.ts +105 -0
- package/src/__tests__/backup.test.ts +114 -673
- package/src/__tests__/cloud-login-poll.test.ts +69 -0
- package/src/__tests__/cloud-releases.test.ts +82 -0
- package/src/__tests__/commands-mock.ts +101 -0
- package/src/__tests__/debug.test.ts +17 -91
- package/src/__tests__/devices.test.ts +64 -2
- package/src/__tests__/ensure-binary.test.ts +52 -0
- package/src/__tests__/gateway-token.test.ts +267 -0
- package/src/__tests__/hatch-next-steps.test.ts +38 -0
- package/src/__tests__/host-teleport-docker.test.ts +371 -0
- package/src/__tests__/host-teleport.test.ts +405 -0
- package/src/__tests__/multi-local.test.ts +3 -3
- package/src/__tests__/nginx-ingress.test.ts +587 -36
- package/src/__tests__/orphan-detection.test.ts +1 -1
- package/src/__tests__/paired-lifecycle.test.ts +2 -2
- package/src/__tests__/ps-cloud-status.test.ts +8 -7
- package/src/__tests__/root-backup-docker.test.ts +294 -0
- package/src/__tests__/root-backup.test.ts +527 -0
- package/src/__tests__/root-restore-command.test.ts +147 -0
- package/src/__tests__/root-signing-key.test.ts +50 -0
- package/src/__tests__/setup.test.ts +17 -7
- package/src/__tests__/shell-mode.test.ts +83 -0
- package/src/__tests__/sleep.test.ts +11 -0
- package/src/__tests__/teleport.test.ts +68 -2996
- package/src/__tests__/tunnel-startup-summary.test.ts +110 -0
- package/src/__tests__/tunnel-stop.test.ts +226 -0
- package/src/__tests__/tunnel.test.ts +22 -4
- package/src/__tests__/upgrade-local.test.ts +21 -11
- package/src/__tests__/upgrade-replay-env.test.ts +2 -2
- package/src/__tests__/upgrade-root-recovery.test.ts +185 -0
- package/src/__tests__/wake.test.ts +4 -1
- package/src/__tests__/windows-lifecycle.test.ts +25 -0
- package/src/commands/backup.ts +44 -399
- package/src/commands/client.ts +3 -3
- package/src/commands/completions.ts +0 -2
- package/src/commands/debug.ts +75 -131
- package/src/commands/devices.ts +117 -47
- package/src/commands/gateway/token.ts +77 -23
- package/src/commands/hatch.ts +10 -52
- package/src/commands/pair.ts +9 -3
- package/src/commands/ps.ts +4 -2
- package/src/commands/restore.ts +77 -444
- package/src/commands/retire.ts +2 -3
- package/src/commands/sleep.ts +15 -2
- package/src/commands/teleport.ts +47 -1559
- package/src/commands/tunnel.ts +91 -11
- package/src/commands/upgrade.ts +84 -157
- package/src/commands/wake.ts +1 -1
- package/src/index.ts +0 -4
- package/src/lib/__tests__/http-client-version.test.ts +29 -0
- package/src/lib/__tests__/runtime-url.test.ts +3 -75
- package/src/lib/__tests__/web-dist.test.ts +27 -1
- package/src/lib/assistant-client.ts +1 -1
- package/src/lib/assistant-config.ts +43 -23
- package/src/lib/backup-ops.ts +37 -251
- package/src/lib/cloud-client.ts +10 -13
- package/src/lib/cloud-login-poll.ts +75 -0
- package/src/lib/cloud-login.ts +5 -2
- package/src/lib/cloud-releases.ts +54 -10
- package/src/lib/cloud-values.ts +1 -1
- package/src/lib/confirm-action.ts +2 -2
- package/src/lib/docker.ts +65 -34
- package/src/lib/ensure-binary.ts +65 -6
- package/src/lib/github-repository.ts +2 -0
- package/src/lib/guardian-token.ts +1 -23
- package/src/lib/hatch-local.ts +18 -52
- package/src/lib/hatch-next-steps.ts +15 -2
- package/src/lib/host-teleport.ts +506 -0
- package/src/lib/http-client.ts +25 -0
- package/src/lib/ingress-config.ts +31 -0
- package/src/lib/live-voice/tunnel.test.ts +1 -1
- package/src/lib/live-voice/tunnel.ts +3 -3
- package/src/lib/local.ts +10 -8
- package/src/lib/nginx-ingress.ts +572 -95
- package/src/lib/process.ts +13 -0
- package/src/lib/root-backup.ts +917 -0
- package/src/lib/root-signing-key.ts +31 -0
- package/src/lib/runtime-url.ts +0 -29
- package/src/lib/teleport-backup.ts +3 -16
- package/src/lib/tunnel.ts +87 -34
- package/src/lib/tunnels/cadet.ts +12 -1
- package/src/lib/tunnels/cloudflare.ts +36 -1
- package/src/lib/tunnels/ngrok.ts +16 -2
- package/src/lib/tunnels/process-lifecycle.ts +26 -0
- package/src/lib/tunnels/tailscale.ts +79 -1
- package/src/lib/upgrade-lifecycle.ts +63 -141
- package/src/lib/web-dist.ts +79 -1
- package/src/lib/xdg-log.ts +11 -0
- package/src/__tests__/bundle-staging.test.ts +0 -268
- package/src/__tests__/unpair.test.ts +0 -160
- package/src/commands/rollback.ts +0 -479
- package/src/commands/unpair.ts +0 -122
- package/src/lib/__tests__/job-polling.test.ts +0 -278
- package/src/lib/__tests__/local-runtime-client.test.ts +0 -666
- package/src/lib/bundle-staging.ts +0 -220
- package/src/lib/job-polling.ts +0 -195
- package/src/lib/local-runtime-client.ts +0 -374
|
@@ -107,7 +107,7 @@ describe("getKnownPidsFromAssistants", () => {
|
|
|
107
107
|
const entry: AssistantEntry = {
|
|
108
108
|
assistantId: "managed-1",
|
|
109
109
|
runtimeUrl: "https://cloud.mycadet.ai/foo",
|
|
110
|
-
cloud: "
|
|
110
|
+
cloud: "cloud",
|
|
111
111
|
};
|
|
112
112
|
const pids = getKnownPidsFromAssistants([entry]);
|
|
113
113
|
expect(pids.size).toBe(0);
|
|
@@ -102,13 +102,13 @@ describe("paired lifecycle guards", () => {
|
|
|
102
102
|
expect(errors).not.toContain("only works with local and docker");
|
|
103
103
|
});
|
|
104
104
|
|
|
105
|
-
test("retire refuses a paired entry and points to
|
|
105
|
+
test("retire refuses a paired entry and points to the host", async () => {
|
|
106
106
|
process.argv = ["bun", "cadet", "retire", "px", "--yes"];
|
|
107
107
|
const { exited, errors } = await runGuarded(retire);
|
|
108
108
|
|
|
109
109
|
expect(exited).toBe(true);
|
|
110
110
|
expect(errors).toContain("paired from another machine");
|
|
111
|
-
expect(errors).toContain("cadet
|
|
111
|
+
expect(errors).toContain("cadet devices");
|
|
112
112
|
// It must NOT fall through to the generic "Unknown cloud type" path.
|
|
113
113
|
expect(errors).not.toContain("Unknown cloud type");
|
|
114
114
|
});
|
|
@@ -230,13 +230,13 @@ describe("cadet ps — platform status line", () => {
|
|
|
230
230
|
assistantId: "assistant-123",
|
|
231
231
|
name: "Alice",
|
|
232
232
|
runtimeUrl: "https://platform.example",
|
|
233
|
-
cloud: "
|
|
233
|
+
cloud: "cloud",
|
|
234
234
|
},
|
|
235
235
|
{
|
|
236
236
|
assistantId: "assistant-456",
|
|
237
237
|
name: "Bob",
|
|
238
238
|
runtimeUrl: "https://platform.example/b",
|
|
239
|
-
cloud: "
|
|
239
|
+
cloud: "cloud",
|
|
240
240
|
},
|
|
241
241
|
]);
|
|
242
242
|
getActiveAssistantMock.mockReturnValue(null);
|
|
@@ -262,7 +262,8 @@ describe("cadet ps — platform status line", () => {
|
|
|
262
262
|
// children of an assistant row…
|
|
263
263
|
expect(output).toContain("ngrok");
|
|
264
264
|
expect(output).not.toContain("└ ngrok");
|
|
265
|
-
expect(output).toContain("nginx
|
|
265
|
+
expect(output).toContain("nginx");
|
|
266
|
+
expect(output).toContain("port ");
|
|
266
267
|
// …the type column distinguishes the three process kinds…
|
|
267
268
|
expect(output).toContain("🧑🚀 Assistant");
|
|
268
269
|
expect(output).toContain("🚇 Tunnel");
|
|
@@ -277,7 +278,7 @@ describe("cadet ps — platform status line", () => {
|
|
|
277
278
|
assistantId: "assistant-123",
|
|
278
279
|
name: "Alice",
|
|
279
280
|
runtimeUrl: "https://platform.example",
|
|
280
|
-
cloud: "
|
|
281
|
+
cloud: "cloud",
|
|
281
282
|
},
|
|
282
283
|
]);
|
|
283
284
|
getActiveAssistantMock.mockReturnValue("assistant-123");
|
|
@@ -286,7 +287,7 @@ describe("cadet ps — platform status line", () => {
|
|
|
286
287
|
|
|
287
288
|
const output = stdout.join("\n");
|
|
288
289
|
expect(output).toContain("* Alice");
|
|
289
|
-
expect(output).toContain("remote:
|
|
290
|
+
expect(output).toContain("remote: cloud");
|
|
290
291
|
expect(output).toContain("https://platform.example");
|
|
291
292
|
expect(output).not.toContain("* assistant-123");
|
|
292
293
|
});
|
|
@@ -297,13 +298,13 @@ describe("cadet ps — platform status line", () => {
|
|
|
297
298
|
assistantId: "assistant-123",
|
|
298
299
|
name: "Alice",
|
|
299
300
|
runtimeUrl: "https://platform.example/a",
|
|
300
|
-
cloud: "
|
|
301
|
+
cloud: "cloud",
|
|
301
302
|
},
|
|
302
303
|
{
|
|
303
304
|
assistantId: "assistant-456",
|
|
304
305
|
name: "Bob",
|
|
305
306
|
runtimeUrl: "https://platform.example/b",
|
|
306
|
-
cloud: "
|
|
307
|
+
cloud: "cloud",
|
|
307
308
|
},
|
|
308
309
|
]);
|
|
309
310
|
getActiveAssistantMock.mockReturnValue("assistant-123");
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import { afterEach, beforeEach, expect, test } from "bun:test";
|
|
2
|
+
import { cp, mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { Readable } from "node:stream";
|
|
6
|
+
import { pipeline } from "node:stream/promises";
|
|
7
|
+
import * as tar from "tar";
|
|
8
|
+
import {
|
|
9
|
+
DockerRootStorage,
|
|
10
|
+
packRoot,
|
|
11
|
+
restoreRootBackup,
|
|
12
|
+
stageRootArchive,
|
|
13
|
+
STATE_ROOTS,
|
|
14
|
+
} from "../lib/root-backup.js";
|
|
15
|
+
import {
|
|
16
|
+
dockerResourceNames,
|
|
17
|
+
sleepContainers,
|
|
18
|
+
startContainers,
|
|
19
|
+
} from "../lib/docker.js";
|
|
20
|
+
import type { AssistantEntry } from "../lib/assistant-config.js";
|
|
21
|
+
import { mockCommands } from "./commands-mock.js";
|
|
22
|
+
|
|
23
|
+
const entry: AssistantEntry = {
|
|
24
|
+
assistantId: "docker-backup",
|
|
25
|
+
cloud: "docker",
|
|
26
|
+
runtimeUrl: "http://127.0.0.1:1",
|
|
27
|
+
};
|
|
28
|
+
const names = dockerResourceNames(entry.assistantId);
|
|
29
|
+
const containers = [
|
|
30
|
+
names.assistantContainer,
|
|
31
|
+
names.gatewayContainer,
|
|
32
|
+
names.cesContainer,
|
|
33
|
+
];
|
|
34
|
+
const rootContainers = {
|
|
35
|
+
workspace: names.assistantContainer,
|
|
36
|
+
"gateway-security": names.gatewayContainer,
|
|
37
|
+
"ces-security": names.cesContainer,
|
|
38
|
+
"ces-data": names.cesContainer,
|
|
39
|
+
};
|
|
40
|
+
let directory: string;
|
|
41
|
+
let commands: ReturnType<typeof mockCommands>;
|
|
42
|
+
const running = new Map<string, boolean>();
|
|
43
|
+
let writes = 0;
|
|
44
|
+
let failWrite = 0;
|
|
45
|
+
|
|
46
|
+
beforeEach(async () => {
|
|
47
|
+
directory = await mkdtemp(join(tmpdir(), "docker-root-mock-"));
|
|
48
|
+
commands = mockCommands();
|
|
49
|
+
writes = 0;
|
|
50
|
+
failWrite = 0;
|
|
51
|
+
for (const container of containers) running.set(container, true);
|
|
52
|
+
for (const root of STATE_ROOTS) {
|
|
53
|
+
await mkdir(join(directory, root), { mode: 0o700 });
|
|
54
|
+
await writeFile(join(directory, root, "value"), "original", {
|
|
55
|
+
mode: 0o600,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
await writeFile(
|
|
59
|
+
join(directory, "gateway-security", "actor-token-signing-key"),
|
|
60
|
+
Buffer.alloc(32, 1),
|
|
61
|
+
{ mode: 0o600 },
|
|
62
|
+
);
|
|
63
|
+
commands.on("docker", ["info"], {});
|
|
64
|
+
commands.on(
|
|
65
|
+
"docker",
|
|
66
|
+
(args) => args[0] === "inspect",
|
|
67
|
+
({ args }) => {
|
|
68
|
+
const container = args.at(-1)!;
|
|
69
|
+
if (args.includes("--format"))
|
|
70
|
+
return {
|
|
71
|
+
stdout:
|
|
72
|
+
args[2] === "{{.State.Running}}"
|
|
73
|
+
? String(running.get(container))
|
|
74
|
+
: running.get(container)
|
|
75
|
+
? "running"
|
|
76
|
+
: "exited",
|
|
77
|
+
};
|
|
78
|
+
return {
|
|
79
|
+
stdout: JSON.stringify([
|
|
80
|
+
{
|
|
81
|
+
Image: "sha256:service-image",
|
|
82
|
+
State: {
|
|
83
|
+
Running: running.get(container),
|
|
84
|
+
Status: running.get(container) ? "running" : "exited",
|
|
85
|
+
},
|
|
86
|
+
Config: {
|
|
87
|
+
Env: ["RUNTIME_HTTP_PORT=3001", "GATEWAY_PORT=7830"],
|
|
88
|
+
User: "1001:1001",
|
|
89
|
+
},
|
|
90
|
+
Mounts: STATE_ROOTS.filter(
|
|
91
|
+
(root) => rootContainers[root] === container,
|
|
92
|
+
).map((root) => ({
|
|
93
|
+
Type: "volume",
|
|
94
|
+
Name: `remote-${root}`,
|
|
95
|
+
Source: `/remote/${root}`,
|
|
96
|
+
Destination: `/${root}`,
|
|
97
|
+
RW: true,
|
|
98
|
+
})),
|
|
99
|
+
},
|
|
100
|
+
]),
|
|
101
|
+
};
|
|
102
|
+
},
|
|
103
|
+
);
|
|
104
|
+
commands.on(
|
|
105
|
+
"docker",
|
|
106
|
+
(args) => args[0] === "kill",
|
|
107
|
+
({ args }) => {
|
|
108
|
+
expect(args[1]).toBe("--signal=SIGTERM");
|
|
109
|
+
running.set(args.at(-1)!, false);
|
|
110
|
+
return {};
|
|
111
|
+
},
|
|
112
|
+
);
|
|
113
|
+
commands.on(
|
|
114
|
+
"docker",
|
|
115
|
+
(args) => args[0] === "start",
|
|
116
|
+
({ args }) => {
|
|
117
|
+
running.set(args[1]!, true);
|
|
118
|
+
return {};
|
|
119
|
+
},
|
|
120
|
+
);
|
|
121
|
+
commands.on(
|
|
122
|
+
"docker",
|
|
123
|
+
(args) => args[0] === "cp",
|
|
124
|
+
async ({ args }) => {
|
|
125
|
+
const root = args[1]!.split(":/")[1]!.split("/")[0]!;
|
|
126
|
+
await cp(join(directory, root), args[2]!, { recursive: true });
|
|
127
|
+
return {};
|
|
128
|
+
},
|
|
129
|
+
);
|
|
130
|
+
commands.on(
|
|
131
|
+
"docker",
|
|
132
|
+
(args) => args[0] === "run" && args.includes("-i"),
|
|
133
|
+
async ({ args, input }) => {
|
|
134
|
+
writes++;
|
|
135
|
+
const mount = args[args.indexOf("--mount") + 1]!;
|
|
136
|
+
const root = mount.split("src=remote-")[1]!.split(",")[0]!;
|
|
137
|
+
expect(args).toContain("--network=none");
|
|
138
|
+
expect(args).toContain("--user=0");
|
|
139
|
+
expect(args).toContain("1001:1001");
|
|
140
|
+
expect(mount).not.toContain(directory);
|
|
141
|
+
expect(args[args.indexOf("-ec") + 1]).toContain('chown -R "$1" /restore');
|
|
142
|
+
await rm(join(directory, root), { recursive: true });
|
|
143
|
+
await mkdir(join(directory, root));
|
|
144
|
+
if (writes === failWrite)
|
|
145
|
+
return { code: 1, stderr: "injected volume replacement failure" };
|
|
146
|
+
await pipeline(
|
|
147
|
+
Readable.from(input),
|
|
148
|
+
tar.x({ cwd: join(directory, root), preserveOwner: false }),
|
|
149
|
+
);
|
|
150
|
+
return {};
|
|
151
|
+
},
|
|
152
|
+
);
|
|
153
|
+
});
|
|
154
|
+
afterEach(async () => {
|
|
155
|
+
commands.restore();
|
|
156
|
+
await rm(directory, { recursive: true, force: true });
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
async function archive() {
|
|
160
|
+
const capture = join(directory, "capture");
|
|
161
|
+
await mkdir(capture);
|
|
162
|
+
const storage = new DockerRootStorage(entry);
|
|
163
|
+
await sleepContainers(names, { timeoutMs: 100 });
|
|
164
|
+
await storage.capture(capture);
|
|
165
|
+
const file = join(directory, "backup.tar.gz");
|
|
166
|
+
await packRoot(capture, file);
|
|
167
|
+
return { storage, file };
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
test("Docker subprocess round-trip preserves every root, private files and clears stale entries", async () => {
|
|
171
|
+
const { storage, file } = await archive();
|
|
172
|
+
const stage = await stageRootArchive(file, directory);
|
|
173
|
+
for (const root of STATE_ROOTS)
|
|
174
|
+
await writeFile(join(directory, root, "stale"), "delete");
|
|
175
|
+
await storage.replace(join(stage, "state"));
|
|
176
|
+
for (const root of STATE_ROOTS) {
|
|
177
|
+
expect(await readFile(join(directory, root, "value"), "utf8")).toBe(
|
|
178
|
+
"original",
|
|
179
|
+
);
|
|
180
|
+
await expect(readFile(join(directory, root, "stale"))).rejects.toThrow();
|
|
181
|
+
}
|
|
182
|
+
expect(writes).toBe(4);
|
|
183
|
+
expect(commands.calls.filter((call) => call.args[0] === "kill")).toHaveLength(
|
|
184
|
+
3,
|
|
185
|
+
);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test("failure during Docker replacement recovers prior roots before restarting", async () => {
|
|
189
|
+
const { storage, file } = await archive();
|
|
190
|
+
for (const container of containers) running.set(container, true);
|
|
191
|
+
for (const root of STATE_ROOTS)
|
|
192
|
+
await writeFile(join(directory, root, "value"), "destination");
|
|
193
|
+
failWrite = 2;
|
|
194
|
+
const recovery = join(directory, "recovery.tar.gz");
|
|
195
|
+
await expect(
|
|
196
|
+
restoreRootBackup(
|
|
197
|
+
entry,
|
|
198
|
+
file,
|
|
199
|
+
{ recoveryPath: recovery },
|
|
200
|
+
{
|
|
201
|
+
storage,
|
|
202
|
+
sleep: () => sleepContainers(names, { timeoutMs: 100 }),
|
|
203
|
+
wake: async () => {
|
|
204
|
+
for (const container of containers) running.set(container, true);
|
|
205
|
+
},
|
|
206
|
+
save: () => {},
|
|
207
|
+
},
|
|
208
|
+
),
|
|
209
|
+
).rejects.toThrow("injected volume replacement failure");
|
|
210
|
+
expect(await storage.running()).toBe(true);
|
|
211
|
+
const previous = await stageRootArchive(recovery, directory);
|
|
212
|
+
for (const root of STATE_ROOTS) {
|
|
213
|
+
expect(await readFile(join(directory, root, "value"), "utf8")).toBe(
|
|
214
|
+
"destination",
|
|
215
|
+
);
|
|
216
|
+
expect(await readFile(join(previous, "state", root, "value"), "utf8")).toBe(
|
|
217
|
+
"destination",
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
test("restart probes gateway and assistant on their own service ports", async () => {
|
|
223
|
+
const storage = new DockerRootStorage(entry);
|
|
224
|
+
expect(await storage.running()).toBe(true);
|
|
225
|
+
for (const container of containers) running.set(container, false);
|
|
226
|
+
commands.on(
|
|
227
|
+
"docker",
|
|
228
|
+
(args) => args[0] === "exec",
|
|
229
|
+
() => ({ stdout: "ready" }),
|
|
230
|
+
);
|
|
231
|
+
await storage.resume();
|
|
232
|
+
const probes = commands.calls.filter((call) => call.args[0] === "exec");
|
|
233
|
+
expect(
|
|
234
|
+
probes.find((call) => call.args[1] === names.gatewayContainer)?.args.at(-1),
|
|
235
|
+
).toBe("http://127.0.0.1:7830/healthz");
|
|
236
|
+
expect(
|
|
237
|
+
probes
|
|
238
|
+
.find((call) => call.args[1] === names.assistantContainer)
|
|
239
|
+
?.args.at(-1),
|
|
240
|
+
).toBe("http://127.0.0.1:3001/readyz");
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
test("default Docker sleep aborts a graceful timeout without forcing termination", async () => {
|
|
244
|
+
commands.on(
|
|
245
|
+
"docker",
|
|
246
|
+
(args) => args[0] === "kill",
|
|
247
|
+
() => ({}),
|
|
248
|
+
);
|
|
249
|
+
await expect(sleepContainers(names, { timeoutMs: 0 })).rejects.toThrow(
|
|
250
|
+
"did not stop gracefully",
|
|
251
|
+
);
|
|
252
|
+
expect(running.get(names.assistantContainer)).toBe(true);
|
|
253
|
+
expect(commands.calls.some((call) => call.args[0] === "stop")).toBe(false);
|
|
254
|
+
expect(
|
|
255
|
+
commands.calls
|
|
256
|
+
.filter((call) => call.args[0] === "kill")
|
|
257
|
+
.every((call) => call.args.includes("--signal=SIGTERM")),
|
|
258
|
+
).toBe(true);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
test("create-only provisioning creates every target container without starting services", async () => {
|
|
262
|
+
commands.on(
|
|
263
|
+
"docker",
|
|
264
|
+
(args) => args[0] === "create",
|
|
265
|
+
({ args }) => {
|
|
266
|
+
running.set(args[args.indexOf("--name") + 1]!, false);
|
|
267
|
+
return { stdout: "created" };
|
|
268
|
+
},
|
|
269
|
+
);
|
|
270
|
+
await startContainers(
|
|
271
|
+
{
|
|
272
|
+
createOnly: true,
|
|
273
|
+
gatewayPort: 7830,
|
|
274
|
+
assistantPort: 3001,
|
|
275
|
+
imageTags: {
|
|
276
|
+
assistant: "assistant:target",
|
|
277
|
+
gateway: "gateway:target",
|
|
278
|
+
"credential-executor": "ces:target",
|
|
279
|
+
},
|
|
280
|
+
instanceName: entry.assistantId,
|
|
281
|
+
res: names,
|
|
282
|
+
},
|
|
283
|
+
() => {},
|
|
284
|
+
);
|
|
285
|
+
const creates = commands.calls.filter((call) => call.args[0] === "create");
|
|
286
|
+
expect(creates).toHaveLength(3);
|
|
287
|
+
expect(creates.every((call) => !call.args.includes("-d"))).toBe(true);
|
|
288
|
+
expect(
|
|
289
|
+
commands.calls.some(
|
|
290
|
+
(call) => call.args[0] === "start" || call.args[0] === "run",
|
|
291
|
+
),
|
|
292
|
+
).toBe(false);
|
|
293
|
+
expect([...running.values()].every((value) => !value)).toBe(true);
|
|
294
|
+
});
|