@mycadet/cli 0.12.11 → 0.13.0-dev.202610171626.d97b8b8
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 +2 -2
- 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 +2 -2
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/__tests__/ingress.test.ts +2 -2
- 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 +2 -2
- package/node_modules/@mycadet/service-contracts/src/__tests__/ingress.test.ts +2 -2
- package/node_modules/@mycadet/service-contracts/src/channels.ts +0 -1
- package/node_modules/@mycadet/service-contracts/src/ingress.ts +2 -2
- 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-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 +2 -2
- package/src/__tests__/gateway-token.test.ts +267 -0
- package/src/__tests__/host-teleport-docker.test.ts +371 -0
- package/src/__tests__/host-teleport.test.ts +405 -0
- package/src/__tests__/nginx-ingress.test.ts +536 -23
- 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 +6 -6
- 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 +3 -3
- 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.test.ts +10 -2
- 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 +51 -17
- package/src/commands/gateway/token.ts +77 -23
- package/src/commands/pair.ts +2 -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 +48 -7
- package/src/commands/upgrade.ts +78 -156
- 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 +23 -18
- package/src/lib/backup-ops.ts +37 -251
- package/src/lib/cloud-client.ts +10 -13
- package/src/lib/cloud-login.ts +2 -1
- 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 +59 -22
- package/src/lib/github-repository.ts +2 -0
- package/src/lib/guardian-token.ts +1 -23
- package/src/lib/hatch-local.ts +17 -1
- 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 +1 -1
- package/src/lib/local.ts +4 -2
- package/src/lib/nginx-ingress.ts +454 -79
- 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 +1 -0
- package/src/lib/upgrade-lifecycle.ts +63 -141
- package/src/lib/web-dist.ts +79 -1
- 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
|
@@ -91,7 +91,12 @@ mock.module("../lib/tunnels/gateway-notify.js", () => ({
|
|
|
91
91
|
const EDGE_PORT = 18080;
|
|
92
92
|
|
|
93
93
|
const ensureTunnelEdgeMock = mock<typeof nginxIngress.ensureTunnelEdge>(
|
|
94
|
-
async () => ({
|
|
94
|
+
async () => ({
|
|
95
|
+
port: EDGE_PORT,
|
|
96
|
+
started: true,
|
|
97
|
+
includesWebApp: true,
|
|
98
|
+
shellMode: "web",
|
|
99
|
+
}),
|
|
95
100
|
);
|
|
96
101
|
const getNginxVersionMock = mock<typeof nginxIngress.getNginxVersion>(
|
|
97
102
|
() => "nginx version (test)",
|
|
@@ -132,7 +137,7 @@ function makeCloudEntry(assistantId = "cloud-1"): AssistantEntry {
|
|
|
132
137
|
return {
|
|
133
138
|
assistantId,
|
|
134
139
|
runtimeUrl: `https://runtime.example.com/${assistantId}`,
|
|
135
|
-
cloud: "
|
|
140
|
+
cloud: "cloud",
|
|
136
141
|
};
|
|
137
142
|
}
|
|
138
143
|
|
|
@@ -289,6 +294,7 @@ describe("tunnel edge targeting", () => {
|
|
|
289
294
|
port: EDGE_PORT,
|
|
290
295
|
started: true,
|
|
291
296
|
includesWebApp: true,
|
|
297
|
+
shellMode: "web",
|
|
292
298
|
});
|
|
293
299
|
});
|
|
294
300
|
|
|
@@ -803,6 +809,7 @@ describe("tunnel edge targeting", () => {
|
|
|
803
809
|
port: EDGE_PORT,
|
|
804
810
|
started: false,
|
|
805
811
|
includesWebApp: false,
|
|
812
|
+
shellMode: "web",
|
|
806
813
|
});
|
|
807
814
|
|
|
808
815
|
const logs = await runTunnelCapturingLogs();
|
|
@@ -1744,6 +1751,7 @@ describe("tunnel background mode", () => {
|
|
|
1744
1751
|
port: EDGE_PORT,
|
|
1745
1752
|
started: true,
|
|
1746
1753
|
includesWebApp: true,
|
|
1754
|
+
shellMode: "web",
|
|
1747
1755
|
});
|
|
1748
1756
|
const configHome = mkdtempSync(join(tmpdir(), "cadet-tunnel-xdg-"));
|
|
1749
1757
|
tempDirs.push(configHome);
|
|
@@ -55,7 +55,7 @@ mock.module("../lib/assistant-config", () => ({
|
|
|
55
55
|
}));
|
|
56
56
|
|
|
57
57
|
const createBackupMock = mock<typeof backupOps.createBackup>(
|
|
58
|
-
async () => "/tmp/local-pre-upgrade.
|
|
58
|
+
async () => "/tmp/local-pre-upgrade.tar.gz",
|
|
59
59
|
);
|
|
60
60
|
const pruneOldBackupsMock = mock<typeof backupOps.pruneOldBackups>(() => {});
|
|
61
61
|
const restoreBackupMock = mock<typeof backupOps.restoreBackup>(
|
|
@@ -238,7 +238,7 @@ beforeEach(() => {
|
|
|
238
238
|
loadAllAssistantsMock.mockReturnValue([entry]);
|
|
239
239
|
saveAssistantEntryMock.mockReset();
|
|
240
240
|
createBackupMock.mockReset();
|
|
241
|
-
createBackupMock.mockResolvedValue("/tmp/local-pre-upgrade.
|
|
241
|
+
createBackupMock.mockResolvedValue("/tmp/local-pre-upgrade.tar.gz");
|
|
242
242
|
pruneOldBackupsMock.mockReset();
|
|
243
243
|
pruneOldBackupsMock.mockReturnValue(undefined);
|
|
244
244
|
restoreBackupMock.mockReset();
|
|
@@ -372,7 +372,7 @@ describe("cadet upgrade local", () => {
|
|
|
372
372
|
await upgrade();
|
|
373
373
|
|
|
374
374
|
expect(loopbackSafeFetchMock).toHaveBeenCalledWith(
|
|
375
|
-
"http://127.0.0.1:7830/healthz
|
|
375
|
+
"http://127.0.0.1:7830/healthz",
|
|
376
376
|
expect.any(Object),
|
|
377
377
|
);
|
|
378
378
|
expect(createBackupMock).toHaveBeenCalledWith(
|
|
@@ -382,14 +382,6 @@ describe("cadet upgrade local", () => {
|
|
|
382
382
|
prefix: "local-assistant-pre-upgrade",
|
|
383
383
|
}),
|
|
384
384
|
);
|
|
385
|
-
expect(saveAssistantEntryMock).toHaveBeenCalledWith(
|
|
386
|
-
expect.objectContaining({
|
|
387
|
-
previousVersion: "v0.8.11",
|
|
388
|
-
previousDbMigrationVersion: 12,
|
|
389
|
-
previousWorkspaceMigrationId: "011-test",
|
|
390
|
-
preUpgradeBackupPath: "/tmp/local-pre-upgrade.vbundle",
|
|
391
|
-
}),
|
|
392
|
-
);
|
|
393
385
|
expect(stopLocalProcessesMock).toHaveBeenCalledWith(
|
|
394
386
|
expect.objectContaining({
|
|
395
387
|
instanceDir: tempDir,
|
|
@@ -446,6 +438,24 @@ describe("cadet upgrade local", () => {
|
|
|
446
438
|
expect(consoleLogSpy.mock.calls.flat().join("\n")).toContain("upgraded to");
|
|
447
439
|
});
|
|
448
440
|
|
|
441
|
+
test("failed safety backup aborts before runtime installation or service replacement", async () => {
|
|
442
|
+
createBackupMock.mockResolvedValue(null);
|
|
443
|
+
await expect(upgrade()).rejects.toThrow("process.exit(1)");
|
|
444
|
+
expect(ensureLocalRuntimeMock).not.toHaveBeenCalled();
|
|
445
|
+
expect(stopLocalProcessesMock).not.toHaveBeenCalled();
|
|
446
|
+
expect(startLocalDaemonMock).not.toHaveBeenCalled();
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
test("failed target startup restores prior runtime selection and all roots before restarting", async () => {
|
|
450
|
+
startLocalDaemonMock.mockRejectedValueOnce(new Error("target startup failed"));
|
|
451
|
+
await expect(upgrade()).rejects.toThrow("process.exit(1)");
|
|
452
|
+
expect(restoreBackupMock).toHaveBeenCalledWith("http://127.0.0.1:7830", "local-assistant", "/tmp/local-pre-upgrade.tar.gz", { resume: false });
|
|
453
|
+
expect(startLocalDaemonMock).toHaveBeenCalledTimes(2);
|
|
454
|
+
expect(startCesMock).toHaveBeenCalledTimes(2);
|
|
455
|
+
expect(stopLocalProcessesMock).toHaveBeenCalledTimes(2);
|
|
456
|
+
expect(waitForReadyMock).toHaveBeenCalled();
|
|
457
|
+
});
|
|
458
|
+
|
|
449
459
|
test("skips restart when the local assistant is already on the target version", async () => {
|
|
450
460
|
loopbackSafeFetchMock.mockResolvedValue({
|
|
451
461
|
ok: true,
|
|
@@ -156,10 +156,10 @@ describe("buildReplayState", () => {
|
|
|
156
156
|
expect(state.signingKey).toBe("sign-key");
|
|
157
157
|
});
|
|
158
158
|
|
|
159
|
-
test("generates
|
|
159
|
+
test("generates a service token but preserves file-backed signing identity when env is absent", () => {
|
|
160
160
|
const state = buildReplayState({}, {});
|
|
161
161
|
expect(state.bootstrapSecret).toBeUndefined();
|
|
162
162
|
expect(state.cesServiceToken).toMatch(/^[0-9a-f]{64}$/);
|
|
163
|
-
expect(state.signingKey).
|
|
163
|
+
expect(state.signingKey).toBeUndefined();
|
|
164
164
|
});
|
|
165
165
|
});
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { afterAll, beforeEach, expect, spyOn, test } from "bun:test";
|
|
2
|
+
import * as backups from "../lib/backup-ops.js";
|
|
3
|
+
import * as docker from "../lib/docker.js";
|
|
4
|
+
import * as lifecycle from "../lib/upgrade-lifecycle.js";
|
|
5
|
+
import * as config from "../lib/assistant-config.js";
|
|
6
|
+
import { mockCommands } from "./commands-mock.js";
|
|
7
|
+
import * as loopback from "../lib/loopback-fetch.js";
|
|
8
|
+
|
|
9
|
+
const capture = spyOn(backups, "createBackup");
|
|
10
|
+
const images = spyOn(docker, "captureImageRefs");
|
|
11
|
+
const pull = spyOn(docker, "pullImage");
|
|
12
|
+
const wake = spyOn(docker, "wakeContainers");
|
|
13
|
+
const save = spyOn(config, "saveAssistantEntry");
|
|
14
|
+
const restore = spyOn(backups, "restoreBackup");
|
|
15
|
+
const sleep = spyOn(docker, "sleepContainers");
|
|
16
|
+
const stop = spyOn(docker, "stopContainers");
|
|
17
|
+
const start = spyOn(docker, "startContainers");
|
|
18
|
+
const fetch = spyOn(loopback, "loopbackSafeFetch");
|
|
19
|
+
const res = docker.dockerResourceNames("recovery");
|
|
20
|
+
const containerOptions = {
|
|
21
|
+
gatewayPort: 7830,
|
|
22
|
+
assistantPort: 3001,
|
|
23
|
+
imageTags: {
|
|
24
|
+
assistant: "assistant:old",
|
|
25
|
+
gateway: "gateway:old",
|
|
26
|
+
"credential-executor": "ces:old",
|
|
27
|
+
},
|
|
28
|
+
instanceName: "recovery",
|
|
29
|
+
res,
|
|
30
|
+
};
|
|
31
|
+
const events: string[] = [];
|
|
32
|
+
beforeEach(() => {
|
|
33
|
+
events.length = 0;
|
|
34
|
+
capture.mockReset();
|
|
35
|
+
images.mockReset();
|
|
36
|
+
pull.mockReset();
|
|
37
|
+
wake.mockReset();
|
|
38
|
+
save.mockReset();
|
|
39
|
+
save.mockImplementation(() => {});
|
|
40
|
+
restore.mockReset();
|
|
41
|
+
sleep.mockReset();
|
|
42
|
+
stop.mockReset();
|
|
43
|
+
start.mockReset();
|
|
44
|
+
fetch.mockReset();
|
|
45
|
+
restore.mockImplementation(async () => {
|
|
46
|
+
events.push("restore-roots");
|
|
47
|
+
return true;
|
|
48
|
+
});
|
|
49
|
+
sleep.mockImplementation(async () => {
|
|
50
|
+
events.push("sleep");
|
|
51
|
+
});
|
|
52
|
+
stop.mockImplementation(async () => {
|
|
53
|
+
events.push("remove");
|
|
54
|
+
});
|
|
55
|
+
start.mockImplementation(async () => {
|
|
56
|
+
events.push("start-old-version");
|
|
57
|
+
});
|
|
58
|
+
fetch.mockImplementation(async () => {
|
|
59
|
+
events.push("check-ready");
|
|
60
|
+
return Response.json({ ready: true });
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
afterAll(() => {
|
|
64
|
+
for (const spy of [
|
|
65
|
+
restore,
|
|
66
|
+
sleep,
|
|
67
|
+
stop,
|
|
68
|
+
start,
|
|
69
|
+
fetch,
|
|
70
|
+
capture,
|
|
71
|
+
images,
|
|
72
|
+
pull,
|
|
73
|
+
wake,
|
|
74
|
+
save,
|
|
75
|
+
])
|
|
76
|
+
spy.mockRestore();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test("failed-state recovery restores tar roots while stopped before restarting old services", async () => {
|
|
80
|
+
expect(
|
|
81
|
+
await lifecycle.attemptFailedStateRestore({
|
|
82
|
+
runtimeUrl: "http://127.0.0.1:1",
|
|
83
|
+
assistantId: "recovery",
|
|
84
|
+
backupPath: "pre-upgrade.tar.gz",
|
|
85
|
+
backupLabel: "pre-upgrade",
|
|
86
|
+
res,
|
|
87
|
+
containerOptions,
|
|
88
|
+
}),
|
|
89
|
+
).toEqual({ restored: true, ready: true });
|
|
90
|
+
expect(restore).toHaveBeenCalledWith(
|
|
91
|
+
"http://127.0.0.1:1",
|
|
92
|
+
"recovery",
|
|
93
|
+
"pre-upgrade.tar.gz",
|
|
94
|
+
{ resume: false },
|
|
95
|
+
);
|
|
96
|
+
expect(events).toEqual([
|
|
97
|
+
"restore-roots",
|
|
98
|
+
"sleep",
|
|
99
|
+
"remove",
|
|
100
|
+
"start-old-version",
|
|
101
|
+
"check-ready",
|
|
102
|
+
]);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("failed root restore does not restart a partial installation", async () => {
|
|
106
|
+
restore.mockResolvedValue(false);
|
|
107
|
+
expect(
|
|
108
|
+
await lifecycle.attemptFailedStateRestore({
|
|
109
|
+
runtimeUrl: "http://127.0.0.1:1",
|
|
110
|
+
assistantId: "recovery",
|
|
111
|
+
backupPath: "pre-upgrade.tar.gz",
|
|
112
|
+
backupLabel: "pre-upgrade",
|
|
113
|
+
res,
|
|
114
|
+
containerOptions,
|
|
115
|
+
}),
|
|
116
|
+
).toEqual({ restored: false, ready: false });
|
|
117
|
+
expect(start).not.toHaveBeenCalled();
|
|
118
|
+
expect(stop).not.toHaveBeenCalled();
|
|
119
|
+
expect(fetch).not.toHaveBeenCalled();
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test("versioned restore installs roots into stopped target containers before first startup", async () => {
|
|
123
|
+
const commands = mockCommands();
|
|
124
|
+
commands.on(
|
|
125
|
+
"docker",
|
|
126
|
+
(args) => args[0] === "inspect",
|
|
127
|
+
() => ({
|
|
128
|
+
stdout: JSON.stringify(["ACTOR_TOKEN_SIGNING_KEY=" + "01".repeat(32)]),
|
|
129
|
+
}),
|
|
130
|
+
);
|
|
131
|
+
const entry: config.AssistantEntry = {
|
|
132
|
+
assistantId: "recovery",
|
|
133
|
+
cloud: "docker",
|
|
134
|
+
runtimeUrl: "http://127.0.0.1:1",
|
|
135
|
+
};
|
|
136
|
+
capture.mockImplementation(async () => {
|
|
137
|
+
events.push("capture-safety");
|
|
138
|
+
return "/tmp/pre-rollback.tar.gz";
|
|
139
|
+
});
|
|
140
|
+
images.mockResolvedValue({
|
|
141
|
+
assistant: "assistant:current",
|
|
142
|
+
gateway: "gateway:current",
|
|
143
|
+
"credential-executor": "ces:current",
|
|
144
|
+
});
|
|
145
|
+
pull.mockResolvedValue(undefined);
|
|
146
|
+
start.mockImplementation(async (options) => {
|
|
147
|
+
expect(options.createOnly).toBe(true);
|
|
148
|
+
events.push("create-stopped-target");
|
|
149
|
+
});
|
|
150
|
+
wake.mockImplementation(async () => {
|
|
151
|
+
events.push("wake-restored-target");
|
|
152
|
+
});
|
|
153
|
+
fetch.mockImplementation(async (url) => {
|
|
154
|
+
if (String(url).includes("/healthz"))
|
|
155
|
+
return Response.json({ version: "0.12.11" });
|
|
156
|
+
if (String(url).includes("/readyz")) {
|
|
157
|
+
events.push("check-ready");
|
|
158
|
+
return Response.json({ ready: true });
|
|
159
|
+
}
|
|
160
|
+
return Response.json({});
|
|
161
|
+
});
|
|
162
|
+
try {
|
|
163
|
+
await lifecycle.performDockerRollback(entry, {
|
|
164
|
+
targetVersion: "0.12.10",
|
|
165
|
+
backupPath: "/tmp/source.tar.gz",
|
|
166
|
+
});
|
|
167
|
+
expect(restore).toHaveBeenCalledWith(
|
|
168
|
+
entry.runtimeUrl,
|
|
169
|
+
entry.assistantId,
|
|
170
|
+
"/tmp/source.tar.gz",
|
|
171
|
+
{ resume: false },
|
|
172
|
+
);
|
|
173
|
+
expect(events).toEqual([
|
|
174
|
+
"capture-safety",
|
|
175
|
+
"sleep",
|
|
176
|
+
"remove",
|
|
177
|
+
"create-stopped-target",
|
|
178
|
+
"restore-roots",
|
|
179
|
+
"wake-restored-target",
|
|
180
|
+
"check-ready",
|
|
181
|
+
]);
|
|
182
|
+
} finally {
|
|
183
|
+
commands.restore();
|
|
184
|
+
}
|
|
185
|
+
});
|
|
@@ -173,7 +173,7 @@ mock.module("../lib/ingress-config.js", () => ({
|
|
|
173
173
|
}));
|
|
174
174
|
|
|
175
175
|
const ensureTunnelEdgeMock = mock<typeof nginxIngress.ensureTunnelEdge>(
|
|
176
|
-
async () => ({ port: 7840, started: true, includesWebApp: true }),
|
|
176
|
+
async () => ({ port: 7840, started: true, includesWebApp: true, shellMode: "web" }),
|
|
177
177
|
);
|
|
178
178
|
const isIngressRunningMock = mock<typeof nginxIngress.isIngressRunning>(
|
|
179
179
|
() => false,
|
|
@@ -305,6 +305,7 @@ beforeEach(() => {
|
|
|
305
305
|
port: 7840,
|
|
306
306
|
started: true,
|
|
307
307
|
includesWebApp: true,
|
|
308
|
+
shellMode: "web",
|
|
308
309
|
});
|
|
309
310
|
isIngressRunningMock.mockReset();
|
|
310
311
|
isIngressRunningMock.mockReturnValue(false);
|
|
@@ -1073,6 +1074,7 @@ describe("cadet wake — tunnel edge restore", () => {
|
|
|
1073
1074
|
port: 7840,
|
|
1074
1075
|
started: false,
|
|
1075
1076
|
includesWebApp: true,
|
|
1077
|
+
shellMode: "web",
|
|
1076
1078
|
});
|
|
1077
1079
|
process.argv = ["bun", "cadet", "wake", "docker-1"];
|
|
1078
1080
|
|
|
@@ -1579,6 +1581,7 @@ describe("cadet wake — tunnel edge restore", () => {
|
|
|
1579
1581
|
port: 7841,
|
|
1580
1582
|
started: false,
|
|
1581
1583
|
includesWebApp: true,
|
|
1584
|
+
shellMode: "web",
|
|
1582
1585
|
});
|
|
1583
1586
|
|
|
1584
1587
|
await wake();
|
|
@@ -227,3 +227,28 @@ test("preserves PID tracking when process termination fails", async () => {
|
|
|
227
227
|
expect(isProcessAlive(pidFile)).toEqual({ alive: true, pid: process.pid });
|
|
228
228
|
rmSync(dir, { recursive: true, force: true });
|
|
229
229
|
});
|
|
230
|
+
|
|
231
|
+
test("graceful-only stop times out without SIGKILL or losing PID identity", async () => {
|
|
232
|
+
const originalKill = process.kill;
|
|
233
|
+
const calls: Array<NodeJS.Signals | number | undefined> = [];
|
|
234
|
+
process.kill = ((_pid: number, signal?: NodeJS.Signals | number) => {
|
|
235
|
+
calls.push(signal);
|
|
236
|
+
return true;
|
|
237
|
+
}) as typeof process.kill;
|
|
238
|
+
try {
|
|
239
|
+
expect(
|
|
240
|
+
await stopProcess(
|
|
241
|
+
4812,
|
|
242
|
+
"backup service",
|
|
243
|
+
0,
|
|
244
|
+
"linux",
|
|
245
|
+
undefined,
|
|
246
|
+
() => "cadet-daemon",
|
|
247
|
+
true,
|
|
248
|
+
),
|
|
249
|
+
).toBe(false);
|
|
250
|
+
expect(calls).toEqual([0, "SIGTERM", 0]);
|
|
251
|
+
} finally {
|
|
252
|
+
process.kill = originalKill;
|
|
253
|
+
}
|
|
254
|
+
});
|