@mycadet/cli 0.12.7 → 0.12.9
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 +2 -2
- package/knip.json +1 -1
- package/node_modules/@mycadet/assistant-api/events/cloud-disconnected.ts +21 -0
- package/node_modules/@mycadet/assistant-api/events/host-bash.ts +1 -1
- package/node_modules/@mycadet/assistant-api/events/host-cu.ts +1 -1
- package/node_modules/@mycadet/assistant-api/events/host-file.ts +1 -1
- package/node_modules/@mycadet/assistant-api/events/notification-intent.ts +1 -1
- package/node_modules/@mycadet/assistant-api/events/show-cloud-login.ts +18 -0
- package/node_modules/@mycadet/assistant-api/index.ts +11 -11
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/package.json +2 -2
- package/node_modules/@mycadet/{service-contracts/src/platform-credential.ts → assistant-api/node_modules/@mycadet/service-contracts/src/cloud-credential.ts} +9 -9
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/handles.ts +3 -3
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/index.ts +1 -1
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/ingress.ts +11 -5
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/remote-web-pairing.ts +1 -1
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/rpc.ts +14 -14
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/trust-rules.ts +5 -5
- package/node_modules/@mycadet/assistant-api/responses/home.ts +1 -1
- package/node_modules/@mycadet/assistant-api/surfaces.ts +4 -4
- package/node_modules/@mycadet/avatar-manifest/src/__tests__/notification-avatar.test.ts +1 -1
- package/node_modules/@mycadet/avatar-manifest/src/notification-avatar.ts +2 -2
- package/node_modules/@mycadet/environments/src/seeds.ts +6 -6
- package/node_modules/@mycadet/environments/src/shell.test.ts +1 -1
- package/node_modules/@mycadet/environments/src/types.ts +6 -6
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/avatar-manifest/src/__tests__/notification-avatar.test.ts +1 -1
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/avatar-manifest/src/notification-avatar.ts +2 -2
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/environments/src/seeds.ts +6 -6
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/environments/src/shell.test.ts +1 -1
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/environments/src/types.ts +6 -6
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/package.json +2 -2
- package/node_modules/@mycadet/{assistant-api/node_modules/@mycadet/service-contracts/src/platform-credential.ts → local-mode/node_modules/@mycadet/service-contracts/src/cloud-credential.ts} +9 -9
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/handles.ts +3 -3
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/index.ts +1 -1
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/ingress.ts +11 -5
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/remote-web-pairing.ts +1 -1
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/rpc.ts +14 -14
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/trust-rules.ts +5 -5
- package/node_modules/@mycadet/local-mode/src/__tests__/pair.test.ts +5 -5
- package/node_modules/@mycadet/local-mode/src/__tests__/retire.test.ts +1 -1
- package/node_modules/@mycadet/local-mode/src/config.ts +2 -2
- package/node_modules/@mycadet/local-mode/src/index.ts +1 -1
- package/node_modules/@mycadet/local-mode/src/lockfile-contract.test.ts +3 -3
- package/node_modules/@mycadet/local-mode/src/lockfile-contract.ts +10 -5
- package/node_modules/@mycadet/local-mode/src/lockfile.test.ts +15 -15
- package/node_modules/@mycadet/local-mode/src/lockfile.ts +9 -9
- package/node_modules/@mycadet/local-mode/src/pair.ts +2 -2
- package/node_modules/@mycadet/local-mode/src/retire.ts +3 -3
- package/node_modules/@mycadet/local-mode/src/teleport-backup-policy.ts +2 -2
- package/node_modules/@mycadet/local-mode/src/unpair.ts +2 -2
- package/node_modules/@mycadet/service-contracts/package.json +2 -2
- package/node_modules/@mycadet/{local-mode/node_modules/@mycadet/service-contracts/src/platform-credential.ts → service-contracts/src/cloud-credential.ts} +9 -9
- package/node_modules/@mycadet/service-contracts/src/handles.ts +3 -3
- package/node_modules/@mycadet/service-contracts/src/index.ts +1 -1
- package/node_modules/@mycadet/service-contracts/src/ingress.ts +11 -5
- package/node_modules/@mycadet/service-contracts/src/remote-web-pairing.ts +1 -1
- package/node_modules/@mycadet/service-contracts/src/rpc.ts +14 -14
- package/node_modules/@mycadet/service-contracts/src/trust-rules.ts +5 -5
- package/package.json +3 -2
- package/src/__tests__/assistant-api-key-resolution.test.ts +6 -6
- package/src/__tests__/{assistant-client-platform-auth.test.ts → assistant-client-cloud-auth.test.ts} +10 -10
- package/src/__tests__/assistant-client-refresh.test.ts +2 -2
- package/src/__tests__/backup.test.ts +86 -91
- package/src/__tests__/client-token.test.ts +1 -1
- package/src/__tests__/client-tui-refresh.test.ts +1 -1
- package/src/__tests__/client-web-config.test.ts +2 -2
- package/src/__tests__/{platform-client.test.ts → cloud-client.test.ts} +59 -59
- package/src/__tests__/{platform-releases.test.ts → cloud-releases.test.ts} +28 -18
- package/src/__tests__/{cloudflare-tunnel.test.ts → cloudflare.test.ts} +1 -1
- package/src/__tests__/connect-import.test.ts +55 -39
- package/src/__tests__/debug.test.ts +38 -38
- package/src/__tests__/ensure-binary.test.ts +182 -0
- package/src/__tests__/guardian-token.test.ts +149 -56
- package/src/__tests__/hatch-provider-secrets.test.ts +2 -2
- package/src/__tests__/helpers/guardian-token.ts +22 -0
- package/src/__tests__/helpers/http.ts +10 -0
- package/src/__tests__/nginx-ingress.test.ts +21 -20
- package/src/__tests__/ngrok-spawn.test.ts +49 -0
- package/src/__tests__/orphan-detection.test.ts +1 -1
- package/src/__tests__/pair.test.ts +1 -8
- package/src/__tests__/{ps-platform-status.test.ts → ps-cloud-status.test.ts} +79 -26
- package/src/__tests__/ps-table.test.ts +79 -0
- package/src/__tests__/{recover.test.ts → restore.test.ts} +29 -28
- package/src/__tests__/retire.test.ts +58 -199
- package/src/__tests__/root-process-lockfile.test.ts +99 -0
- package/src/__tests__/sleep.test.ts +19 -19
- package/src/__tests__/statefulset.test.ts +8 -8
- package/src/__tests__/{tailscale-tunnel.test.ts → tailscale.test.ts} +2 -2
- package/src/__tests__/teleport-backup.test.ts +3 -3
- package/src/__tests__/teleport.test.ts +181 -197
- package/src/__tests__/tunnel.test.ts +257 -222
- package/src/__tests__/uninstall.test.ts +13 -4
- package/src/__tests__/upgrade-local.test.ts +3 -3
- package/src/__tests__/upgrade-replay-env.test.ts +12 -12
- package/src/__tests__/wake.test.ts +260 -43
- package/src/__tests__/workos-pkce.test.ts +2 -2
- package/src/commands/backup.ts +24 -24
- package/src/commands/client.ts +76 -76
- package/src/commands/completions.test.ts +104 -0
- package/src/commands/completions.ts +87 -0
- package/src/commands/debug.ts +20 -20
- package/src/commands/exec.ts +12 -12
- package/src/commands/flags.ts +2 -2
- package/src/commands/hatch.ts +23 -23
- package/src/commands/login.ts +22 -22
- package/src/commands/logs.ts +8 -8
- package/src/commands/ps.ts +244 -86
- package/src/commands/restart.ts +30 -0
- package/src/commands/restore.ts +140 -22
- package/src/commands/retire.ts +48 -150
- package/src/commands/rollback.ts +20 -26
- package/src/commands/setup.ts +2 -2
- package/src/commands/sleep.ts +2 -2
- package/src/commands/teleport.ts +109 -109
- package/src/commands/terminal.ts +158 -18
- package/src/commands/tunnel.ts +138 -157
- package/src/commands/uninstall.ts +57 -9
- package/src/commands/unpair.ts +3 -3
- package/src/commands/upgrade.ts +30 -54
- package/src/commands/use.ts +1 -1
- package/src/commands/wake.ts +166 -50
- package/src/index.ts +18 -9
- package/src/lib/__tests__/{platform-client-signed-url.test.ts → cloud-client-signed-url.test.ts} +16 -16
- package/src/lib/__tests__/docker.test.ts +168 -20
- package/src/lib/__tests__/job-polling.test.ts +2 -2
- package/src/lib/__tests__/local-runtime-client.test.ts +4 -4
- package/src/lib/__tests__/runtime-url.test.ts +1 -1
- package/src/lib/assistant-api-key-resolution.ts +5 -5
- package/src/lib/assistant-client.ts +8 -8
- package/src/lib/assistant-config.ts +58 -12
- package/src/lib/{platform-client.ts → cloud-client.ts} +107 -98
- package/src/lib/cloud-login.ts +24 -24
- package/src/lib/{platform-releases.ts → cloud-releases.ts} +11 -9
- package/src/lib/cloud-values.ts +3 -0
- package/src/lib/config-utils.ts +1 -1
- package/src/lib/detached-process.ts +1 -1
- package/src/lib/docker.ts +260 -53
- package/src/lib/ensure-binary.ts +171 -0
- package/src/lib/environments/__tests__/paths.test.ts +2 -2
- package/src/lib/environments/__tests__/resolve.test.ts +26 -26
- package/src/lib/environments/resolve.ts +8 -8
- package/src/lib/flag-args.test.ts +1 -1
- package/src/lib/flag-args.ts +1 -1
- package/src/lib/guardian-token.ts +37 -5
- package/src/lib/hatch-local.ts +19 -23
- package/src/lib/health-check.ts +6 -6
- package/src/lib/ingress-config.ts +8 -35
- package/src/lib/job-polling.ts +2 -2
- package/src/lib/live-voice/connection.ts +12 -12
- package/src/lib/live-voice/tunnel.test.ts +11 -11
- package/src/lib/live-voice/tunnel.ts +15 -15
- package/src/lib/local-runtime-client.ts +20 -20
- package/src/lib/local.ts +10 -31
- package/src/lib/nginx-ingress.ts +42 -15
- package/src/lib/pair.ts +1 -1
- package/src/lib/process.ts +1 -1
- package/src/lib/provider-secrets.ts +3 -3
- package/src/lib/retire-apple-container.ts +1 -1
- package/src/lib/runtime-url.ts +8 -8
- package/src/lib/statefulset.ts +2 -2
- package/src/lib/sync-cloud-assistants.ts +19 -19
- package/src/lib/teleport-backup.ts +4 -4
- package/src/lib/terminal-client.ts +17 -17
- package/src/lib/terminal-session.ts +24 -24
- package/src/lib/{tunnel-edge.ts → tunnel.ts} +199 -11
- package/src/lib/tunnels/cadet.ts +29 -0
- package/src/lib/{cloudflare-tunnel.ts → tunnels/cloudflare.ts} +36 -15
- package/src/lib/tunnels/gateway-notify.ts +61 -0
- package/src/lib/{ngrok.ts → tunnels/ngrok.ts} +350 -89
- package/src/lib/{tailscale-tunnel.ts → tunnels/tailscale.ts} +53 -9
- package/src/lib/upgrade-lifecycle.ts +8 -21
- package/src/lib/upgrade-preflight.ts +2 -2
- package/src/lib/workos-pkce.ts +5 -5
- package/node_modules/@mycadet/assistant-api/events/platform-disconnected.ts +0 -21
- package/node_modules/@mycadet/assistant-api/events/show-platform-login.ts +0 -20
- package/src/commands/recover.ts +0 -131
- package/src/commands/roadmap.ts +0 -459
- package/src/commands/ssh.ts +0 -144
- package/src/commands/workflows.ts +0 -305
- package/src/lib/managed-cloud.ts +0 -3
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
describe,
|
|
6
6
|
expect,
|
|
7
7
|
mock,
|
|
8
|
+
type Mock,
|
|
8
9
|
spyOn,
|
|
9
10
|
test,
|
|
10
11
|
} from "bun:test";
|
|
@@ -30,26 +31,26 @@ import * as backupOps from "../lib/backup-ops.js";
|
|
|
30
31
|
import * as guardianToken from "../lib/guardian-token.js";
|
|
31
32
|
import * as localRuntimeClient from "../lib/local-runtime-client.js";
|
|
32
33
|
import { MigrationInProgressError } from "../lib/local-runtime-client.js";
|
|
33
|
-
import * as
|
|
34
|
+
import * as cloudClient from "../lib/cloud-client.js";
|
|
34
35
|
|
|
35
36
|
const findAssistantByNameMock = spyOn(
|
|
36
37
|
assistantConfig,
|
|
37
38
|
"findAssistantByName",
|
|
38
39
|
).mockReturnValue(null);
|
|
39
40
|
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
"
|
|
41
|
+
const readCloudTokenMock = spyOn(
|
|
42
|
+
cloudClient,
|
|
43
|
+
"readCloudToken",
|
|
43
44
|
).mockReturnValue("platform-token");
|
|
44
45
|
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
"
|
|
46
|
+
const getCloudUrlMock = spyOn(
|
|
47
|
+
cloudClient,
|
|
48
|
+
"getCloudUrl",
|
|
48
49
|
).mockReturnValue("https://cloud.mycadet.ai");
|
|
49
50
|
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
"
|
|
51
|
+
const cloudRequestSignedUrlMock = spyOn(
|
|
52
|
+
cloudClient,
|
|
53
|
+
"cloudRequestSignedUrl",
|
|
53
54
|
).mockImplementation(async (params) => ({
|
|
54
55
|
url:
|
|
55
56
|
params.operation === "upload"
|
|
@@ -79,6 +80,7 @@ const localRuntimePollJobStatusMock = spyOn(
|
|
|
79
80
|
result: { manifest_sha256: "abc123def456" },
|
|
80
81
|
});
|
|
81
82
|
|
|
83
|
+
/** A complete-but-fake guardian token; the SUT only reads the fields it sets. */
|
|
82
84
|
// Mode 1 (runtime-direct local backup) uses guardian tokens. Don't exercise
|
|
83
85
|
// it here, but the spies need to exist so the module under test can import
|
|
84
86
|
// them without surprises. Saved to variables so afterAll can restore them —
|
|
@@ -87,31 +89,25 @@ const localRuntimePollJobStatusMock = spyOn(
|
|
|
87
89
|
const loadGuardianTokenSpy = spyOn(
|
|
88
90
|
guardianToken,
|
|
89
91
|
"loadGuardianToken",
|
|
90
|
-
).mockReturnValue({
|
|
91
|
-
accessToken: "local-token",
|
|
92
|
-
accessTokenExpiresAt: new Date(Date.now() + 60_000).toISOString(),
|
|
93
|
-
} as unknown as ReturnType<typeof guardianToken.loadGuardianToken>);
|
|
92
|
+
).mockReturnValue(makeGuardianTokenData({ accessToken: "local-token" }));
|
|
94
93
|
const leaseGuardianTokenSpy = spyOn(
|
|
95
94
|
guardianToken,
|
|
96
95
|
"leaseGuardianToken",
|
|
97
|
-
).mockResolvedValue({
|
|
98
|
-
accessToken: "leased-token",
|
|
99
|
-
accessTokenExpiresAt: new Date(Date.now() + 60_000).toISOString(),
|
|
100
|
-
} as unknown as Awaited<ReturnType<typeof guardianToken.leaseGuardianToken>>);
|
|
96
|
+
).mockResolvedValue(makeGuardianTokenData({ accessToken: "leased-token" }));
|
|
101
97
|
|
|
102
98
|
const getBackupsDirMock = spyOn(backupOps, "getBackupsDir").mockReturnValue(
|
|
103
99
|
"/tmp/backups-default",
|
|
104
100
|
);
|
|
105
101
|
|
|
106
102
|
const mkdirSyncMock = spyOn(fs, "mkdirSync").mockImplementation(
|
|
107
|
-
(
|
|
103
|
+
() => undefined,
|
|
108
104
|
);
|
|
109
105
|
const writeFileSyncMock = spyOn(fs, "writeFileSync").mockImplementation(
|
|
110
106
|
() => undefined,
|
|
111
107
|
);
|
|
112
108
|
|
|
113
109
|
let originalFetch: typeof globalThis.fetch;
|
|
114
|
-
let exitMock:
|
|
110
|
+
let exitMock: Mock<(code?: number | string | undefined) => never>;
|
|
115
111
|
|
|
116
112
|
const CADET_ENTRY = {
|
|
117
113
|
assistantId: "11111111-2222-3333-4444-555555555555",
|
|
@@ -126,19 +122,19 @@ function setArgv(...rest: string[]) {
|
|
|
126
122
|
|
|
127
123
|
beforeEach(() => {
|
|
128
124
|
originalFetch = globalThis.fetch;
|
|
129
|
-
exitMock = mock((code?: number) => {
|
|
125
|
+
exitMock = mock((code?: number | string | undefined): never => {
|
|
130
126
|
throw new Error(`process.exit:${code}`);
|
|
131
127
|
});
|
|
132
|
-
process.exit = exitMock
|
|
128
|
+
process.exit = exitMock;
|
|
133
129
|
|
|
134
130
|
findAssistantByNameMock.mockReset();
|
|
135
131
|
findAssistantByNameMock.mockReturnValue(null);
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
132
|
+
readCloudTokenMock.mockReset();
|
|
133
|
+
readCloudTokenMock.mockReturnValue("platform-token");
|
|
134
|
+
getCloudUrlMock.mockReset();
|
|
135
|
+
getCloudUrlMock.mockReturnValue("https://cloud.mycadet.ai");
|
|
136
|
+
cloudRequestSignedUrlMock.mockReset();
|
|
137
|
+
cloudRequestSignedUrlMock.mockImplementation(async (params) => ({
|
|
142
138
|
url:
|
|
143
139
|
params.operation === "upload"
|
|
144
140
|
? "https://storage.googleapis.com/bucket/signed-upload"
|
|
@@ -162,17 +158,15 @@ beforeEach(() => {
|
|
|
162
158
|
getBackupsDirMock.mockReset();
|
|
163
159
|
getBackupsDirMock.mockReturnValue("/tmp/backups-default");
|
|
164
160
|
loadGuardianTokenSpy.mockReset();
|
|
165
|
-
loadGuardianTokenSpy.mockReturnValue(
|
|
166
|
-
accessToken: "local-token",
|
|
167
|
-
|
|
168
|
-
} as unknown as ReturnType<typeof guardianToken.loadGuardianToken>);
|
|
161
|
+
loadGuardianTokenSpy.mockReturnValue(
|
|
162
|
+
makeGuardianTokenData({ accessToken: "local-token" }),
|
|
163
|
+
);
|
|
169
164
|
leaseGuardianTokenSpy.mockReset();
|
|
170
|
-
leaseGuardianTokenSpy.mockResolvedValue(
|
|
171
|
-
accessToken: "leased-token",
|
|
172
|
-
|
|
173
|
-
} as unknown as Awaited<ReturnType<typeof guardianToken.leaseGuardianToken>>);
|
|
165
|
+
leaseGuardianTokenSpy.mockResolvedValue(
|
|
166
|
+
makeGuardianTokenData({ accessToken: "leased-token" }),
|
|
167
|
+
);
|
|
174
168
|
mkdirSyncMock.mockReset();
|
|
175
|
-
mkdirSyncMock.mockImplementation((
|
|
169
|
+
mkdirSyncMock.mockImplementation(() => undefined);
|
|
176
170
|
writeFileSyncMock.mockReset();
|
|
177
171
|
writeFileSyncMock.mockImplementation(() => undefined);
|
|
178
172
|
});
|
|
@@ -185,9 +179,9 @@ afterAll(() => {
|
|
|
185
179
|
// Restore module-level spies so they don't bleed into other test files
|
|
186
180
|
// when bun test runs the whole suite.
|
|
187
181
|
findAssistantByNameMock.mockRestore();
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
182
|
+
readCloudTokenMock.mockRestore();
|
|
183
|
+
getCloudUrlMock.mockRestore();
|
|
184
|
+
cloudRequestSignedUrlMock.mockRestore();
|
|
191
185
|
localRuntimeExportToGcsMock.mockRestore();
|
|
192
186
|
localRuntimeIdentityMock.mockRestore();
|
|
193
187
|
localRuntimePollJobStatusMock.mockRestore();
|
|
@@ -200,20 +194,26 @@ afterAll(() => {
|
|
|
200
194
|
});
|
|
201
195
|
|
|
202
196
|
import { backup } from "../commands/backup.js";
|
|
197
|
+
import { withPreconnect } from "./helpers/http.js";
|
|
198
|
+
import { makeGuardianTokenData } from "./helpers/guardian-token.js";
|
|
203
199
|
|
|
204
200
|
// ---------------------------------------------------------------------------
|
|
205
201
|
// Helper: simulated GCS download response
|
|
206
202
|
// ---------------------------------------------------------------------------
|
|
207
|
-
function mockGcsDownload(
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
203
|
+
function mockGcsDownload(
|
|
204
|
+
body: Uint8Array<ArrayBuffer>,
|
|
205
|
+
ok = true,
|
|
206
|
+
status = 200,
|
|
207
|
+
) {
|
|
208
|
+
const gcsFetch = mock(async () => {
|
|
209
|
+
const responseBody: BodyInit = ok ? new Blob([body]) : "boom";
|
|
212
210
|
return new Response(responseBody, {
|
|
213
211
|
status,
|
|
214
212
|
statusText: ok ? "OK" : "Error",
|
|
215
213
|
});
|
|
216
|
-
})
|
|
214
|
+
});
|
|
215
|
+
globalThis.fetch = withPreconnect(gcsFetch);
|
|
216
|
+
return gcsFetch;
|
|
217
217
|
}
|
|
218
218
|
|
|
219
219
|
describe("cadet backup <local>: guardian bootstrap secret", () => {
|
|
@@ -228,11 +228,9 @@ describe("cadet backup <local>: guardian bootstrap secret", () => {
|
|
|
228
228
|
loadGuardianTokenSpy.mockReturnValue(null);
|
|
229
229
|
setArgv("my-local", "--output", "/tmp/local-backup.vbundle");
|
|
230
230
|
|
|
231
|
-
globalThis.fetch =
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
});
|
|
235
|
-
}) as unknown as typeof globalThis.fetch;
|
|
231
|
+
globalThis.fetch = withPreconnect(
|
|
232
|
+
async () => new Response(new Uint8Array([1, 2, 3]), { status: 200 }),
|
|
233
|
+
);
|
|
236
234
|
|
|
237
235
|
await backup();
|
|
238
236
|
|
|
@@ -294,9 +292,9 @@ describe("cadet backup: --export-timeout flag", () => {
|
|
|
294
292
|
"/tmp/local-backup.vbundle",
|
|
295
293
|
);
|
|
296
294
|
|
|
297
|
-
globalThis.fetch =
|
|
298
|
-
|
|
299
|
-
|
|
295
|
+
globalThis.fetch = withPreconnect(
|
|
296
|
+
async () => new Response(new Uint8Array([1, 2, 3]), { status: 200 }),
|
|
297
|
+
);
|
|
300
298
|
|
|
301
299
|
await backup();
|
|
302
300
|
|
|
@@ -307,18 +305,18 @@ describe("cadet backup: --export-timeout flag", () => {
|
|
|
307
305
|
});
|
|
308
306
|
});
|
|
309
307
|
|
|
310
|
-
describe("cadet backup <
|
|
308
|
+
describe("cadet backup <cloud-managed>: GCS happy path", () => {
|
|
311
309
|
test("requests upload URL → kicks off runtime export → polls → downloads from GCS → writes file", async () => {
|
|
312
310
|
findAssistantByNameMock.mockReturnValue(CADET_ENTRY);
|
|
313
311
|
setArgv("my-platform");
|
|
314
312
|
|
|
315
313
|
const bytes = new Uint8Array([1, 2, 3, 4]);
|
|
316
|
-
mockGcsDownload(bytes);
|
|
314
|
+
const gcsFetch = mockGcsDownload(bytes);
|
|
317
315
|
|
|
318
316
|
await backup();
|
|
319
317
|
|
|
320
|
-
// Upload-URL request to the
|
|
321
|
-
expect(
|
|
318
|
+
// Upload-URL request to the cloud.
|
|
319
|
+
expect(cloudRequestSignedUrlMock).toHaveBeenCalledWith(
|
|
322
320
|
expect.objectContaining({
|
|
323
321
|
operation: "upload",
|
|
324
322
|
minRuntimeVersion: "0.6.5",
|
|
@@ -357,7 +355,7 @@ describe("cadet backup <platform-managed>: GCS happy path", () => {
|
|
|
357
355
|
// bundle to disk for offline storage; there is no target runtime to
|
|
358
356
|
// gate against, and an older CLI must be able to download newer
|
|
359
357
|
// assistant backups.
|
|
360
|
-
expect(
|
|
358
|
+
expect(cloudRequestSignedUrlMock).toHaveBeenCalledWith(
|
|
361
359
|
expect.objectContaining({
|
|
362
360
|
operation: "download",
|
|
363
361
|
bundleKey: "uploads/org-1/bundle-abc.vbundle",
|
|
@@ -365,14 +363,13 @@ describe("cadet backup <platform-managed>: GCS happy path", () => {
|
|
|
365
363
|
"platform-token",
|
|
366
364
|
"https://cloud.mycadet.ai",
|
|
367
365
|
);
|
|
368
|
-
const downloadCall =
|
|
369
|
-
(c) =>
|
|
366
|
+
const downloadCall = cloudRequestSignedUrlMock.mock.calls.find(
|
|
367
|
+
(c) => c[0].operation === "download",
|
|
370
368
|
);
|
|
371
369
|
expect(downloadCall).toBeDefined();
|
|
372
370
|
expect(downloadCall![0]).not.toHaveProperty("targetRuntimeVersion");
|
|
373
371
|
|
|
374
372
|
// GCS fetch went directly to the signed download URL with no auth.
|
|
375
|
-
const gcsFetch = globalThis.fetch as unknown as ReturnType<typeof mock>;
|
|
376
373
|
expect(gcsFetch).toHaveBeenCalledWith(
|
|
377
374
|
"https://storage.googleapis.com/bucket/signed-download",
|
|
378
375
|
);
|
|
@@ -382,7 +379,7 @@ describe("cadet backup <platform-managed>: GCS happy path", () => {
|
|
|
382
379
|
const [outputPath, written] = writeFileSyncMock.mock.calls[0]!;
|
|
383
380
|
expect(written).toEqual(bytes);
|
|
384
381
|
expect(typeof outputPath).toBe("string");
|
|
385
|
-
expect(outputPath
|
|
382
|
+
expect(outputPath).toMatch(
|
|
386
383
|
/\/tmp\/backups-default\/my-platform-.*\.vbundle$/,
|
|
387
384
|
);
|
|
388
385
|
expect(mkdirSyncMock).toHaveBeenCalled();
|
|
@@ -411,22 +408,20 @@ describe("cadet backup <platform-managed>: GCS happy path", () => {
|
|
|
411
408
|
await backup();
|
|
412
409
|
|
|
413
410
|
const [outputPath] = writeFileSyncMock.mock.calls[0]!;
|
|
414
|
-
expect(outputPath
|
|
415
|
-
|
|
416
|
-
);
|
|
417
|
-
expect(outputPath as string).toMatch(/\.vbundle$/);
|
|
411
|
+
expect(outputPath).toMatch(/^\/tmp\/backups-default\/my-platform-/);
|
|
412
|
+
expect(outputPath).toMatch(/\.vbundle$/);
|
|
418
413
|
});
|
|
419
414
|
|
|
420
|
-
test("signed-URL requests target entry.runtimeUrl, not
|
|
415
|
+
test("signed-URL requests target entry.runtimeUrl, not getCloudUrl() — regression for staging/dev assistants", async () => {
|
|
421
416
|
// Assistant lives on a non-default platform instance (e.g. staging).
|
|
422
|
-
// `
|
|
417
|
+
// `getCloudUrl()` still returns the default — picking it up for
|
|
423
418
|
// signed URLs would target the wrong GCS bucket.
|
|
424
419
|
const stagingEntry = {
|
|
425
420
|
...CADET_ENTRY,
|
|
426
421
|
runtimeUrl: "https://staging-cloud.mycadet.ai",
|
|
427
422
|
};
|
|
428
423
|
findAssistantByNameMock.mockReturnValue(stagingEntry);
|
|
429
|
-
|
|
424
|
+
getCloudUrlMock.mockReturnValue("https://cloud.mycadet.ai");
|
|
430
425
|
setArgv("my-platform");
|
|
431
426
|
|
|
432
427
|
mockGcsDownload(new Uint8Array([9]));
|
|
@@ -434,9 +429,9 @@ describe("cadet backup <platform-managed>: GCS happy path", () => {
|
|
|
434
429
|
await backup();
|
|
435
430
|
|
|
436
431
|
// Both upload and download URL requests are pinned to the entry's
|
|
437
|
-
// runtimeUrl. The signed URLs returned by the
|
|
432
|
+
// runtimeUrl. The signed URLs returned by the cloud target the
|
|
438
433
|
// GCS bucket the runtime can reach, not the default platform's.
|
|
439
|
-
expect(
|
|
434
|
+
expect(cloudRequestSignedUrlMock).toHaveBeenCalledWith(
|
|
440
435
|
expect.objectContaining({
|
|
441
436
|
operation: "upload",
|
|
442
437
|
minRuntimeVersion: "0.6.5",
|
|
@@ -445,13 +440,13 @@ describe("cadet backup <platform-managed>: GCS happy path", () => {
|
|
|
445
440
|
"platform-token",
|
|
446
441
|
"https://staging-cloud.mycadet.ai",
|
|
447
442
|
);
|
|
448
|
-
expect(
|
|
443
|
+
expect(cloudRequestSignedUrlMock).toHaveBeenCalledWith(
|
|
449
444
|
expect.objectContaining({ operation: "download" }),
|
|
450
445
|
"platform-token",
|
|
451
446
|
"https://staging-cloud.mycadet.ai",
|
|
452
447
|
);
|
|
453
448
|
// No call should have used the default platform URL.
|
|
454
|
-
const calls =
|
|
449
|
+
const calls = cloudRequestSignedUrlMock.mock.calls;
|
|
455
450
|
for (const call of calls) {
|
|
456
451
|
expect(call[2]).toBe("https://staging-cloud.mycadet.ai");
|
|
457
452
|
}
|
|
@@ -471,20 +466,20 @@ describe("cadet backup <platform-managed>: GCS happy path", () => {
|
|
|
471
466
|
status: "complete",
|
|
472
467
|
result: {},
|
|
473
468
|
}));
|
|
474
|
-
// Make
|
|
469
|
+
// Make readCloudToken return a fresh value on the second call,
|
|
475
470
|
// mimicking the "user re-ran `cadet login` in another terminal"
|
|
476
471
|
// scenario. The helper's pollJobUntilDone calls refreshOn401 only
|
|
477
472
|
// when its own request 401s — for the test we drive the refresh
|
|
478
473
|
// directly by overriding the mock to surface a fresh token at the
|
|
479
474
|
// download-step boundary.
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
475
|
+
readCloudTokenMock.mockReset();
|
|
476
|
+
readCloudTokenMock.mockReturnValueOnce("platform-token-old");
|
|
477
|
+
readCloudTokenMock.mockReturnValue("platform-token-new");
|
|
483
478
|
|
|
484
479
|
// Hook into pollJobUntilDone via overriding poll to intercept the
|
|
485
480
|
// refresh call. Easier: just verify the second-arg token to the
|
|
486
481
|
// download signed-URL request equals the one we'll inject by
|
|
487
|
-
// letting backup re-read the
|
|
482
|
+
// letting backup re-read the cloud token mid-flight. The current
|
|
488
483
|
// implementation only re-reads inside pollJobUntilDone's
|
|
489
484
|
// `refreshOn401`, so we simulate a refresh by overriding poll to
|
|
490
485
|
// throw-and-recover. Instead we directly assert the regression
|
|
@@ -499,13 +494,13 @@ describe("cadet backup <platform-managed>: GCS happy path", () => {
|
|
|
499
494
|
// All four token-bearing platform calls (upload signed-URL, runtime
|
|
500
495
|
// export-to-gcs kickoff, poll, download signed-URL) must use the
|
|
501
496
|
// same token string. If the download step fell back to the captured
|
|
502
|
-
// `
|
|
497
|
+
// `cloudToken` parameter instead of `exportPlatformToken`, a
|
|
503
498
|
// future poll-loop refresh would silently break this invariant.
|
|
504
|
-
const uploadCallToken =
|
|
505
|
-
(c) =>
|
|
499
|
+
const uploadCallToken = cloudRequestSignedUrlMock.mock.calls.find(
|
|
500
|
+
(c) => c[0].operation === "upload",
|
|
506
501
|
)![1];
|
|
507
|
-
const downloadCallToken =
|
|
508
|
-
(c) =>
|
|
502
|
+
const downloadCallToken = cloudRequestSignedUrlMock.mock.calls.find(
|
|
503
|
+
(c) => c[0].operation === "download",
|
|
509
504
|
)![1];
|
|
510
505
|
expect(downloadCallToken).toBe(uploadCallToken);
|
|
511
506
|
const kickoffToken = localRuntimeExportToGcsMock.mock.calls[0]![1];
|
|
@@ -515,10 +510,10 @@ describe("cadet backup <platform-managed>: GCS happy path", () => {
|
|
|
515
510
|
});
|
|
516
511
|
});
|
|
517
512
|
|
|
518
|
-
describe("cadet backup <
|
|
513
|
+
describe("cadet backup <cloud-managed>: failure cases", () => {
|
|
519
514
|
test("not logged in (no platform token) exits with 'Run cadet login'", async () => {
|
|
520
515
|
findAssistantByNameMock.mockReturnValue(CADET_ENTRY);
|
|
521
|
-
|
|
516
|
+
readCloudTokenMock.mockReturnValue(null);
|
|
522
517
|
setArgv("my-platform");
|
|
523
518
|
|
|
524
519
|
const consoleErrorSpy = spyOn(console, "error").mockImplementation(
|
|
@@ -611,7 +606,7 @@ describe("cadet backup <platform-managed>: failure cases", () => {
|
|
|
611
606
|
|
|
612
607
|
// NOTE: The `VersionMismatchError handling` describe block was removed when
|
|
613
608
|
// backup stopped sending `targetRuntimeVersion` on the download signed-URL
|
|
614
|
-
// request — without that field the
|
|
609
|
+
// request — without that field the cloud doesn't run the version gate,
|
|
615
610
|
// so 422 `version_mismatch` is no longer reachable from this code path.
|
|
616
611
|
|
|
617
612
|
// ---------------------------------------------------------------------------
|
|
@@ -628,7 +623,7 @@ describe("upload signed-URL records source runtime version (not CLI version)", (
|
|
|
628
623
|
callOrder.push("identity");
|
|
629
624
|
return { version: "0.5.9" };
|
|
630
625
|
});
|
|
631
|
-
|
|
626
|
+
cloudRequestSignedUrlMock.mockImplementationOnce(async (params) => {
|
|
632
627
|
callOrder.push("signed-url");
|
|
633
628
|
return {
|
|
634
629
|
url: "https://storage.googleapis.com/bucket/signed-upload",
|
|
@@ -644,7 +639,7 @@ describe("upload signed-URL records source runtime version (not CLI version)", (
|
|
|
644
639
|
expect(callOrder[0]).toBe("identity");
|
|
645
640
|
expect(callOrder[1]).toBe("signed-url");
|
|
646
641
|
|
|
647
|
-
expect(
|
|
642
|
+
expect(cloudRequestSignedUrlMock).toHaveBeenCalledWith(
|
|
648
643
|
expect.objectContaining({
|
|
649
644
|
operation: "upload",
|
|
650
645
|
minRuntimeVersion: "0.5.9",
|
|
@@ -655,7 +650,7 @@ describe("upload signed-URL records source runtime version (not CLI version)", (
|
|
|
655
650
|
);
|
|
656
651
|
});
|
|
657
652
|
|
|
658
|
-
test("identity is fetched against the
|
|
653
|
+
test("identity is fetched against the cloud-managed runtime entry with the cloud token", async () => {
|
|
659
654
|
findAssistantByNameMock.mockReturnValue(CADET_ENTRY);
|
|
660
655
|
setArgv("my-platform");
|
|
661
656
|
|
|
@@ -688,7 +683,7 @@ describe("upload signed-URL records source runtime version (not CLI version)", (
|
|
|
688
683
|
await expect(backup()).rejects.toThrow("process.exit:1");
|
|
689
684
|
|
|
690
685
|
// Signed-URL must NOT have been requested.
|
|
691
|
-
expect(
|
|
686
|
+
expect(cloudRequestSignedUrlMock).not.toHaveBeenCalled();
|
|
692
687
|
expect(localRuntimeExportToGcsMock).not.toHaveBeenCalled();
|
|
693
688
|
|
|
694
689
|
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
@@ -90,7 +90,7 @@ describe("client --token (ephemeral)", () => {
|
|
|
90
90
|
expect(parsed.runtimeUrl).toBe(REMOTE_URL);
|
|
91
91
|
expect(parsed.assistantId).toBe("self"); // DAEMON_INTERNAL_ASSISTANT_ID
|
|
92
92
|
expect(parsed.bearerToken).toBe("test-jwt-token");
|
|
93
|
-
expect(parsed.
|
|
93
|
+
expect(parsed.cloudToken).toBeUndefined();
|
|
94
94
|
});
|
|
95
95
|
|
|
96
96
|
test("--assistant-id overrides the default 'self' segment", () => {
|
|
@@ -166,7 +166,7 @@ describe("resolveFreshBearerToken", () => {
|
|
|
166
166
|
expect(refresh.hit()).toBe(false);
|
|
167
167
|
});
|
|
168
168
|
|
|
169
|
-
test("never refreshes on the
|
|
169
|
+
test("never refreshes on the cloud session-auth path", async () => {
|
|
170
170
|
seed({ accessToken: "old-acc", refreshToken: "ref", refreshAfter: past() });
|
|
171
171
|
const refresh = stubRefresh(true);
|
|
172
172
|
|
|
@@ -12,8 +12,8 @@ import { buildWebInterfaceConfig } from "../commands/client.js";
|
|
|
12
12
|
|
|
13
13
|
const BASE = {
|
|
14
14
|
webUrl: "https://app.example.com",
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
cloudUrl: "https://platform.example.com",
|
|
16
|
+
disableCloud: true,
|
|
17
17
|
};
|
|
18
18
|
|
|
19
19
|
describe("cadet client web __config", () => {
|