@mycadet/assistant 0.12.11 → 0.13.0
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/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/__tests__/ingress.test.ts +2 -2
- package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/ingress.ts +2 -2
- package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/__tests__/ingress.test.ts +2 -2
- package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/ingress.ts +2 -2
- package/node_modules/@mycadet/gateway-client/src/gateway-ipc-contracts.ts +0 -20
- package/node_modules/@mycadet/service-contracts/src/__tests__/ingress.test.ts +2 -2
- package/node_modules/@mycadet/service-contracts/src/ingress.ts +2 -2
- package/openapi.yaml +2 -708
- package/package.json +2 -3
- package/scripts/sync-web-search-catalog.ts +1 -2
- package/src/__tests__/cloud-profile-guard.test.ts +2 -2
- package/src/__tests__/config-schema.test.ts +6 -38
- package/src/__tests__/conversation-queue.test.ts +45 -6
- package/src/__tests__/db-readiness-http-gate.test.ts +0 -6
- package/src/__tests__/persist-onboarding-artifacts.test.ts +0 -1
- package/src/__tests__/persona-resolver.test.ts +0 -51
- package/src/__tests__/provider-cloud-proxy-integration.test.ts +2 -110
- package/src/__tests__/run-due-schedules.test.ts +0 -4
- package/src/__tests__/schedule-store.test.ts +0 -12
- package/src/__tests__/stt-stream-session.test.ts +4 -4
- package/src/__tests__/telephony-tts-guard.test.ts +2 -50
- package/src/__tests__/voice-config-update.test.ts +12 -167
- package/src/__tests__/web-search-catalog-parity.test.ts +0 -2
- package/src/__tests__/workspace-migration-130-speech-mode-to-provider.test.ts +9 -7
- package/src/__tests__/workspace-migration-132-web-search-mode-to-provider.test.ts +6 -6
- package/src/calls/__tests__/voice-session-bridge.test.ts +1 -4
- package/src/calls/resolve-call-tts-provider.ts +6 -25
- package/src/calls/telephony-credential-preflight.ts +0 -10
- package/src/calls/telephony-tts-capability.ts +1 -17
- package/src/cli/commands/tts.ts +1 -2
- package/src/cli/index.help.ts +0 -2
- package/src/cli/program.ts +0 -2
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +4 -42
- package/src/config/schemas/__tests__/stt.test.ts +10 -14
- package/src/config/schemas/services.ts +5 -6
- package/src/config/schemas/stt.ts +1 -3
- package/src/config/schemas/tts.ts +1 -30
- package/src/daemon/daemon-readiness.ts +4 -29
- package/src/daemon/lifecycle.ts +0 -31
- package/src/daemon/providers-setup.ts +0 -8
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +0 -1
- package/src/heartbeat/heartbeat-service.ts +1 -66
- package/src/ipc/__tests__/db-readiness-ipc-gate.test.ts +1 -9
- package/src/live-voice/__tests__/live-voice-credential-preflight.test.ts +0 -44
- package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +4 -6
- package/src/live-voice/__tests__/live-voice-stt.test.ts +1 -1
- package/src/live-voice/live-voice-credential-preflight.ts +9 -33
- package/src/live-voice/live-voice-session.ts +10 -6
- package/src/live-voice/live-voice-tts.ts +1 -8
- package/src/plugins/defaults/memory/worker.ts +3 -3
- package/src/prompts/persona-resolver.ts +0 -35
- package/src/providers/__tests__/search-provider-catalog.test.ts +5 -8
- package/src/providers/__tests__/voice-error-copy.test.ts +9 -10
- package/src/providers/search-provider-catalog.ts +7 -39
- package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +3 -47
- package/src/providers/speech-to-text/__tests__/resolve.test.ts +6 -372
- package/src/providers/speech-to-text/provider-catalog.ts +2 -73
- package/src/providers/speech-to-text/resolve.ts +3 -120
- package/src/runtime/http-router-types.ts +1 -3
- package/src/runtime/http-server.ts +1 -1
- package/src/runtime/routes/__tests__/tts-routes.test.ts +2 -6
- package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +7 -7
- package/src/runtime/routes/index.ts +0 -4
- package/src/runtime/routes/live-voice-routes.test.ts +4 -11
- package/src/runtime/routes/live-voice-routes.ts +8 -11
- package/src/runtime/routes/secret-routes.ts +0 -6
- package/src/runtime/routes/tts-routes.ts +0 -38
- package/src/schedule/__tests__/plugin-schedule-reconciler.test.ts +1 -7
- package/src/schedule/__tests__/worker-mcp-readiness.test.ts +0 -4
- package/src/schedule/schedule-store.ts +0 -7
- package/src/schedule/scheduler.ts +0 -5
- package/src/schedule/worker.ts +1 -1
- package/src/stt/__tests__/daemon-batch-transcriber.test.ts +2 -10
- package/src/stt/daemon-batch-transcriber.ts +4 -38
- package/src/stt/types.ts +1 -9
- package/src/tools/network/__tests__/web-search.test.ts +3 -250
- package/src/tools/network/web-search.ts +4 -165
- package/src/tts/__tests__/provider-resolution.test.ts +0 -5
- package/src/tts/provider-catalog.ts +0 -4
- package/src/tts/types.ts +0 -1
- package/src/__tests__/cloud-speech-defaults.test.ts +0 -330
- package/src/__tests__/migration-cross-version-compatibility.test.ts +0 -1708
- package/src/__tests__/migration-export-http.test.ts +0 -557
- package/src/__tests__/migration-export-streaming.test.ts +0 -350
- package/src/__tests__/migration-export-to-gcs.test.ts +0 -706
- package/src/__tests__/migration-import-commit-http.test.ts +0 -1176
- package/src/__tests__/migration-import-from-gcs.test.ts +0 -534
- package/src/__tests__/migration-import-from-path.test.ts +0 -324
- package/src/__tests__/migration-import-from-url.test.ts +0 -588
- package/src/__tests__/migration-import-preflight-http.test.ts +0 -739
- package/src/__tests__/migration-jobs-status.test.ts +0 -139
- package/src/__tests__/migration-parity-persistence.test.ts +0 -1891
- package/src/__tests__/migration-transport.test.ts +0 -1089
- package/src/__tests__/migration-validate-http.test.ts +0 -634
- package/src/__tests__/migration-wizard.test.ts +0 -1395
- package/src/__tests__/rebind-secrets-screen.test.ts +0 -876
- package/src/__tests__/sanitize-config-for-transfer.test.ts +0 -259
- package/src/__tests__/transfer-progress-screen.test.ts +0 -1217
- package/src/__tests__/validation-results-screen.test.ts +0 -1130
- package/src/__tests__/vbundle-pax-and-symlink.test.ts +0 -147
- package/src/background-wake/background-wake-routes.test.ts +0 -906
- package/src/background-wake/cloud-client.test.ts +0 -328
- package/src/background-wake/cloud-client.ts +0 -196
- package/src/background-wake/next-wake.test.ts +0 -335
- package/src/background-wake/next-wake.ts +0 -188
- package/src/background-wake/publisher.ts +0 -93
- package/src/background-wake/wake-intent-hooks.test.ts +0 -234
- package/src/cli/commands/__tests__/backup.test.ts +0 -363
- package/src/cli/commands/backup.help.ts +0 -180
- package/src/cli/commands/backup.ts +0 -398
- package/src/cloud/cloud-speech.test.ts +0 -335
- package/src/cloud/cloud-speech.ts +0 -300
- package/src/config/cloud-speech-defaults.ts +0 -300
- package/src/config/sanitize-for-transfer.ts +0 -111
- package/src/live-voice/__tests__/live-voice-cloud-speech-fallback.test.ts +0 -250
- package/src/providers/speech-to-text/__tests__/cadet-cloud-realtime.test.ts +0 -457
- package/src/providers/speech-to-text/__tests__/cadet-cloud.test.ts +0 -271
- package/src/providers/speech-to-text/__tests__/cadet-speech-relay-connection.test.ts +0 -126
- package/src/providers/speech-to-text/cadet-cloud-flux-realtime.ts +0 -309
- package/src/providers/speech-to-text/cadet-cloud-realtime.ts +0 -315
- package/src/providers/speech-to-text/cadet-cloud.ts +0 -108
- package/src/providers/speech-to-text/cadet-speech-relay-connection.ts +0 -176
- package/src/runtime/migrations/__tests__/gcs-signed-url.test.ts +0 -254
- package/src/runtime/migrations/__tests__/job-registry.test.ts +0 -346
- package/src/runtime/migrations/__tests__/origin-mode.test.ts +0 -62
- package/src/runtime/migrations/__tests__/rebind-secrets-credentials.test.ts +0 -184
- package/src/runtime/migrations/__tests__/staged-import-path.test.ts +0 -110
- package/src/runtime/migrations/__tests__/v1-test-helpers.ts +0 -112
- package/src/runtime/migrations/__tests__/vbundle-builder-credentials.test.ts +0 -285
- package/src/runtime/migrations/__tests__/vbundle-builder-fd-leak.test.ts +0 -126
- package/src/runtime/migrations/__tests__/vbundle-builder-v1-shape.test.ts +0 -253
- package/src/runtime/migrations/__tests__/vbundle-import-credentials.test.ts +0 -208
- package/src/runtime/migrations/__tests__/vbundle-import-parity.test.ts +0 -485
- package/src/runtime/migrations/__tests__/vbundle-import-policy.test.ts +0 -349
- package/src/runtime/migrations/__tests__/vbundle-import-version-compat.test.ts +0 -196
- package/src/runtime/migrations/__tests__/vbundle-legacy-user-md.test.ts +0 -551
- package/src/runtime/migrations/__tests__/vbundle-metadata-merge-integration.test.ts +0 -428
- package/src/runtime/migrations/__tests__/vbundle-metadata-merge.test.ts +0 -220
- package/src/runtime/migrations/__tests__/vbundle-streaming-importer.test.ts +0 -1823
- package/src/runtime/migrations/__tests__/vbundle-streaming-validator.test.ts +0 -598
- package/src/runtime/migrations/__tests__/vbundle-symlink-importer.test.ts +0 -463
- package/src/runtime/migrations/__tests__/vbundle-symlink-streaming-importer.test.ts +0 -538
- package/src/runtime/migrations/__tests__/vbundle-symlink-streaming.test.ts +0 -515
- package/src/runtime/migrations/__tests__/vbundle-symlink-tar.test.ts +0 -453
- package/src/runtime/migrations/__tests__/vbundle-symlink-walker.test.ts +0 -319
- package/src/runtime/migrations/__tests__/vbundle-tar-stream.test.ts +0 -240
- package/src/runtime/migrations/__tests__/vbundle-validator-v1-schema.test.ts +0 -473
- package/src/runtime/migrations/gcs-signed-url.ts +0 -190
- package/src/runtime/migrations/job-registry.ts +0 -297
- package/src/runtime/migrations/migration-transport.ts +0 -612
- package/src/runtime/migrations/migration-wizard.ts +0 -747
- package/src/runtime/migrations/origin-mode.ts +0 -42
- package/src/runtime/migrations/rebind-secrets-screen.ts +0 -412
- package/src/runtime/migrations/staged-import-path.ts +0 -116
- package/src/runtime/migrations/transfer-progress-screen.ts +0 -323
- package/src/runtime/migrations/validation-results-screen.ts +0 -469
- package/src/runtime/migrations/vbundle-builder.ts +0 -1382
- package/src/runtime/migrations/vbundle-import-analyzer.ts +0 -405
- package/src/runtime/migrations/vbundle-import-policy.ts +0 -241
- package/src/runtime/migrations/vbundle-importer.ts +0 -826
- package/src/runtime/migrations/vbundle-metadata-merge.ts +0 -113
- package/src/runtime/migrations/vbundle-streaming-importer.ts +0 -2844
- package/src/runtime/migrations/vbundle-streaming-validator.ts +0 -426
- package/src/runtime/migrations/vbundle-tar-stream.ts +0 -244
- package/src/runtime/migrations/vbundle-validator.ts +0 -724
- package/src/runtime/routes/__tests__/migration-cadet-metadata-reconcile.test.ts +0 -227
- package/src/runtime/routes/__tests__/migration-export-secrets-redacted.test.ts +0 -54
- package/src/runtime/routes/__tests__/migration-import-allowed-hosts-env.test.ts +0 -70
- package/src/runtime/routes/__tests__/migration-import-credential-filter.test.ts +0 -257
- package/src/runtime/routes/background-wake-routes.ts +0 -387
- package/src/runtime/routes/migration-routes.ts +0 -2584
- package/src/tools/network/__tests__/cloud-search-proxy.test.ts +0 -282
- package/src/tools/network/cloud-search-proxy.ts +0 -211
- package/src/tts/__tests__/cadet-provider.test.ts +0 -253
- package/src/tts/providers/cadet-provider.ts +0 -244
- package/src/types/tar-stream.d.ts +0 -66
|
@@ -1,234 +0,0 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
2
|
-
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
3
|
-
|
|
4
|
-
import type { BackgroundWakeIntentClientResult } from "./cloud-client.js";
|
|
5
|
-
import type { BackgroundWakeIntent } from "./next-wake.js";
|
|
6
|
-
|
|
7
|
-
const mockPublishBackgroundWakeIntent = mock(
|
|
8
|
-
async (): Promise<BackgroundWakeIntentClientResult> => ({
|
|
9
|
-
status: "published",
|
|
10
|
-
}),
|
|
11
|
-
);
|
|
12
|
-
const mockClearBackgroundWakeIntent = mock(
|
|
13
|
-
async (): Promise<BackgroundWakeIntentClientResult> => ({
|
|
14
|
-
status: "cleared",
|
|
15
|
-
}),
|
|
16
|
-
);
|
|
17
|
-
let computedIntent: BackgroundWakeIntent | null = intentFixture();
|
|
18
|
-
|
|
19
|
-
mock.module("./next-wake.js", () => ({
|
|
20
|
-
computeNextBackgroundWakeIntent: () => computedIntent,
|
|
21
|
-
}));
|
|
22
|
-
|
|
23
|
-
mock.module("./cloud-client.js", () => ({
|
|
24
|
-
publishBackgroundWakeIntent: mockPublishBackgroundWakeIntent,
|
|
25
|
-
clearBackgroundWakeIntent: mockClearBackgroundWakeIntent,
|
|
26
|
-
}));
|
|
27
|
-
|
|
28
|
-
// Replace `publishSchedulesChanged` while keeping the rest of the module REAL
|
|
29
|
-
// — a partial mock that drops the other exports breaks any transitively loaded
|
|
30
|
-
// module that imports them (the conversation write paths reach this module
|
|
31
|
-
// through the hook pipeline).
|
|
32
|
-
const actualSyncEvents =
|
|
33
|
-
await import("../runtime/sync/resource-sync-events.js");
|
|
34
|
-
mock.module("../runtime/sync/resource-sync-events.js", () => ({
|
|
35
|
-
...actualSyncEvents,
|
|
36
|
-
publishSchedulesChanged: () => {},
|
|
37
|
-
}));
|
|
38
|
-
|
|
39
|
-
const {
|
|
40
|
-
flushBackgroundWakeIntentRefreshForTest,
|
|
41
|
-
refreshBackgroundWakeIntent,
|
|
42
|
-
resetBackgroundWakeIntentPublisherForTest,
|
|
43
|
-
} = await import("./publisher.js");
|
|
44
|
-
const { initializeDb } = await import("../persistence/db-init.js");
|
|
45
|
-
const { getDb } = await import("../persistence/db-connection.js");
|
|
46
|
-
const { createSchedule, deleteSchedule, updateSchedule } =
|
|
47
|
-
await import("../schedule/schedule-store.js");
|
|
48
|
-
|
|
49
|
-
await initializeDb();
|
|
50
|
-
|
|
51
|
-
describe("background wake intent publisher hooks", () => {
|
|
52
|
-
beforeEach(() => {
|
|
53
|
-
computedIntent = intentFixture();
|
|
54
|
-
mockPublishBackgroundWakeIntent.mockClear();
|
|
55
|
-
mockClearBackgroundWakeIntent.mockClear();
|
|
56
|
-
mockPublishBackgroundWakeIntent.mockImplementation(async () => ({
|
|
57
|
-
status: "published" as const,
|
|
58
|
-
}));
|
|
59
|
-
mockClearBackgroundWakeIntent.mockImplementation(async () => ({
|
|
60
|
-
status: "cleared" as const,
|
|
61
|
-
}));
|
|
62
|
-
resetBackgroundWakeIntentPublisherForTest();
|
|
63
|
-
const db = getDb();
|
|
64
|
-
db.run("DELETE FROM cron_runs");
|
|
65
|
-
db.run("DELETE FROM cron_jobs");
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
test("debounces repeated refreshes into one publish", async () => {
|
|
69
|
-
refreshBackgroundWakeIntent("first");
|
|
70
|
-
refreshBackgroundWakeIntent("second");
|
|
71
|
-
refreshBackgroundWakeIntent("third");
|
|
72
|
-
|
|
73
|
-
await flushBackgroundWakeIntentRefreshForTest();
|
|
74
|
-
|
|
75
|
-
expect(mockPublishBackgroundWakeIntent).toHaveBeenCalledTimes(1);
|
|
76
|
-
expect(mockClearBackgroundWakeIntent).not.toHaveBeenCalled();
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
test("clears without throwing when no local intent exists", async () => {
|
|
80
|
-
computedIntent = null;
|
|
81
|
-
|
|
82
|
-
refreshBackgroundWakeIntent("no-intent");
|
|
83
|
-
await flushBackgroundWakeIntentRefreshForTest();
|
|
84
|
-
|
|
85
|
-
expect(mockClearBackgroundWakeIntent).toHaveBeenCalledTimes(1);
|
|
86
|
-
expect(mockClearBackgroundWakeIntent).toHaveBeenCalledWith(null);
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
test("keeps last intent snapshot when clear is skipped so it can retry", async () => {
|
|
90
|
-
const publishedIntent = intentFixture({
|
|
91
|
-
computedAt: 1_800_000_000_000,
|
|
92
|
-
sourceGeneration: "bw1:stable-source",
|
|
93
|
-
});
|
|
94
|
-
computedIntent = publishedIntent;
|
|
95
|
-
|
|
96
|
-
refreshBackgroundWakeIntent("publish");
|
|
97
|
-
await flushBackgroundWakeIntentRefreshForTest();
|
|
98
|
-
|
|
99
|
-
computedIntent = null;
|
|
100
|
-
mockClearBackgroundWakeIntent.mockImplementationOnce(async () => ({
|
|
101
|
-
status: "skipped" as const,
|
|
102
|
-
reason: "missing_platform_client" as const,
|
|
103
|
-
}));
|
|
104
|
-
|
|
105
|
-
refreshBackgroundWakeIntent("clear-skipped");
|
|
106
|
-
await flushBackgroundWakeIntentRefreshForTest();
|
|
107
|
-
refreshBackgroundWakeIntent("clear-retry");
|
|
108
|
-
await flushBackgroundWakeIntentRefreshForTest();
|
|
109
|
-
|
|
110
|
-
expect(mockClearBackgroundWakeIntent).toHaveBeenCalledTimes(2);
|
|
111
|
-
expect(mockClearBackgroundWakeIntent).toHaveBeenNthCalledWith(
|
|
112
|
-
1,
|
|
113
|
-
publishedIntent,
|
|
114
|
-
);
|
|
115
|
-
expect(mockClearBackgroundWakeIntent).toHaveBeenNthCalledWith(
|
|
116
|
-
2,
|
|
117
|
-
publishedIntent,
|
|
118
|
-
);
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
test("does not throw into callers when platform publish fails", async () => {
|
|
122
|
-
mockPublishBackgroundWakeIntent.mockImplementation(async () => {
|
|
123
|
-
throw new Error("network down");
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
refreshBackgroundWakeIntent("publish-failure");
|
|
127
|
-
await expect(
|
|
128
|
-
flushBackgroundWakeIntentRefreshForTest(),
|
|
129
|
-
).resolves.toBeUndefined();
|
|
130
|
-
|
|
131
|
-
expect(mockPublishBackgroundWakeIntent).toHaveBeenCalledTimes(1);
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
test("schedule create, update, and delete mutations refresh the wake intent", async () => {
|
|
135
|
-
const job = await createSchedule({
|
|
136
|
-
name: "Wake hook",
|
|
137
|
-
cronExpression: "* * * * *",
|
|
138
|
-
message: "wake",
|
|
139
|
-
syntax: "cron",
|
|
140
|
-
});
|
|
141
|
-
await flushQueuedWakeRefresh();
|
|
142
|
-
expect(mockPublishBackgroundWakeIntent).toHaveBeenCalledTimes(1);
|
|
143
|
-
|
|
144
|
-
await updateSchedule(job.id, { name: "Wake hook updated" });
|
|
145
|
-
await flushQueuedWakeRefresh();
|
|
146
|
-
expect(mockPublishBackgroundWakeIntent).toHaveBeenCalledTimes(2);
|
|
147
|
-
|
|
148
|
-
expect(await deleteSchedule(job.id)).toBe(true);
|
|
149
|
-
await flushQueuedWakeRefresh();
|
|
150
|
-
expect(mockPublishBackgroundWakeIntent).toHaveBeenCalledTimes(3);
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
test("heartbeat scheduling and run-completion paths queue wake intent refreshes", () => {
|
|
154
|
-
const heartbeatSource = readFileSync(
|
|
155
|
-
new URL("../heartbeat/heartbeat-service.ts", import.meta.url),
|
|
156
|
-
"utf-8",
|
|
157
|
-
);
|
|
158
|
-
|
|
159
|
-
expect(heartbeatSource).toContain(
|
|
160
|
-
'refreshBackgroundWakeIntentSoon("heartbeat-disabled")',
|
|
161
|
-
);
|
|
162
|
-
expect(heartbeatSource).toContain(
|
|
163
|
-
'refreshBackgroundWakeIntentSoon("heartbeat-cron-scheduled")',
|
|
164
|
-
);
|
|
165
|
-
expect(heartbeatSource).toContain(
|
|
166
|
-
'refreshBackgroundWakeIntentSoon("heartbeat-interval-scheduled")',
|
|
167
|
-
);
|
|
168
|
-
expect(heartbeatSource).toContain(
|
|
169
|
-
'refreshBackgroundWakeIntentSoon("heartbeat-reconfigured")',
|
|
170
|
-
);
|
|
171
|
-
expect(heartbeatSource).toContain(
|
|
172
|
-
'refreshBackgroundWakeIntentSoon("heartbeat-run-complete")',
|
|
173
|
-
);
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
test("scheduler startup publishes the daemon-startup wake intent", () => {
|
|
177
|
-
const schedulerSource = readFileSync(
|
|
178
|
-
new URL("../schedule/scheduler.ts", import.meta.url),
|
|
179
|
-
"utf-8",
|
|
180
|
-
);
|
|
181
|
-
const lifecycleSource = readFileSync(
|
|
182
|
-
new URL("../daemon/lifecycle.ts", import.meta.url),
|
|
183
|
-
"utf-8",
|
|
184
|
-
);
|
|
185
|
-
|
|
186
|
-
// The daemon-startup intent is published from the end of startScheduler(),
|
|
187
|
-
// so it lands once the scheduler is live and its schedules are visible to
|
|
188
|
-
// computeNextBackgroundWakeIntent. Heartbeat startup republishes with the
|
|
189
|
-
// live heartbeat timing via its own "heartbeat-*" refreshes.
|
|
190
|
-
expect(schedulerSource).toContain(
|
|
191
|
-
'refreshBackgroundWakeIntent("daemon-startup")',
|
|
192
|
-
);
|
|
193
|
-
// Lifecycle no longer publishes the intent directly.
|
|
194
|
-
expect(lifecycleSource).not.toContain("refreshBackgroundWakeIntent");
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
test("shutdown paths do not clear background wake intents", () => {
|
|
198
|
-
const lifecycleSource = readFileSync(
|
|
199
|
-
new URL("../daemon/lifecycle.ts", import.meta.url),
|
|
200
|
-
"utf-8",
|
|
201
|
-
);
|
|
202
|
-
const shutdownSource = readFileSync(
|
|
203
|
-
new URL("../daemon/shutdown-handlers.ts", import.meta.url),
|
|
204
|
-
"utf-8",
|
|
205
|
-
);
|
|
206
|
-
|
|
207
|
-
expect(lifecycleSource).not.toContain("clearBackgroundWakeIntent");
|
|
208
|
-
expect(shutdownSource).not.toContain("clearBackgroundWakeIntent");
|
|
209
|
-
expect(shutdownSource).not.toContain("background-wake/cloud-client");
|
|
210
|
-
});
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
async function flushQueuedWakeRefresh(): Promise<void> {
|
|
214
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
215
|
-
await flushBackgroundWakeIntentRefreshForTest();
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
function intentFixture(
|
|
219
|
-
overrides: Partial<BackgroundWakeIntent> = {},
|
|
220
|
-
): BackgroundWakeIntent {
|
|
221
|
-
const nextWakeAt = 1_800_000_060_000;
|
|
222
|
-
return {
|
|
223
|
-
nextWakeAt,
|
|
224
|
-
actualNextDueAt: nextWakeAt,
|
|
225
|
-
reason: "schedule",
|
|
226
|
-
sourceGeneration: "bw1:source",
|
|
227
|
-
computedAt: nextWakeAt - 60_000,
|
|
228
|
-
sourcePayload: {
|
|
229
|
-
heartbeat: null,
|
|
230
|
-
schedules: [],
|
|
231
|
-
},
|
|
232
|
-
...overrides,
|
|
233
|
-
};
|
|
234
|
-
}
|
|
@@ -1,363 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for the `assistant backup` CLI command tree.
|
|
3
|
-
*
|
|
4
|
-
* Uses the IPC mock pattern: cliIpcCall is stubbed out so tests assert the
|
|
5
|
-
* correct method + params are sent without touching the daemon or filesystem.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
9
|
-
|
|
10
|
-
import { Command } from "commander";
|
|
11
|
-
|
|
12
|
-
// ---------------------------------------------------------------------------
|
|
13
|
-
// Mock state
|
|
14
|
-
// ---------------------------------------------------------------------------
|
|
15
|
-
|
|
16
|
-
/** The last `cliIpcCall` invocation captured for assertions. */
|
|
17
|
-
let lastIpcCall: { method: string; params?: any } | null = null;
|
|
18
|
-
|
|
19
|
-
/** The result that cliIpcCall will return. */
|
|
20
|
-
let mockIpcResult: { ok: boolean; result?: unknown; error?: string } = {
|
|
21
|
-
ok: true,
|
|
22
|
-
result: {},
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
/** Captured log output for assertion. */
|
|
26
|
-
let logOutput: string[] = [];
|
|
27
|
-
|
|
28
|
-
// ---------------------------------------------------------------------------
|
|
29
|
-
// Mocks
|
|
30
|
-
// ---------------------------------------------------------------------------
|
|
31
|
-
|
|
32
|
-
mock.module("../../../ipc/cli-client.js", () => ({
|
|
33
|
-
cliIpcCall: async (method: string, params?: any) => {
|
|
34
|
-
lastIpcCall = { method, params };
|
|
35
|
-
return mockIpcResult;
|
|
36
|
-
},
|
|
37
|
-
exitFromIpcResult: (r: any) => {
|
|
38
|
-
process.exitCode = 1;
|
|
39
|
-
throw new Error(r.error ?? "ipc error");
|
|
40
|
-
},
|
|
41
|
-
}));
|
|
42
|
-
|
|
43
|
-
const capture = (...args: unknown[]) => {
|
|
44
|
-
logOutput.push(args.map(String).join(" "));
|
|
45
|
-
};
|
|
46
|
-
const fakeLogger = {
|
|
47
|
-
info: capture,
|
|
48
|
-
warn: capture,
|
|
49
|
-
error: capture,
|
|
50
|
-
debug: () => {},
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
mock.module("../../logger.js", () => ({
|
|
54
|
-
log: fakeLogger,
|
|
55
|
-
getCliLogger: () => fakeLogger,
|
|
56
|
-
}));
|
|
57
|
-
|
|
58
|
-
// ---------------------------------------------------------------------------
|
|
59
|
-
// Import module under test (after mocks)
|
|
60
|
-
// ---------------------------------------------------------------------------
|
|
61
|
-
|
|
62
|
-
const { registerBackupCommand } = await import("../backup.js");
|
|
63
|
-
|
|
64
|
-
// ---------------------------------------------------------------------------
|
|
65
|
-
// Test helpers
|
|
66
|
-
// ---------------------------------------------------------------------------
|
|
67
|
-
|
|
68
|
-
function buildProgram(): Command {
|
|
69
|
-
const program = new Command();
|
|
70
|
-
program.exitOverride();
|
|
71
|
-
program.configureOutput({
|
|
72
|
-
writeErr: () => {},
|
|
73
|
-
writeOut: () => {},
|
|
74
|
-
});
|
|
75
|
-
registerBackupCommand(program);
|
|
76
|
-
return program;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
async function runCommand(args: string[]): Promise<{ exitCode: number }> {
|
|
80
|
-
process.exitCode = 0;
|
|
81
|
-
try {
|
|
82
|
-
const program = buildProgram();
|
|
83
|
-
await program.parseAsync(["node", "assistant", ...args]);
|
|
84
|
-
} catch {
|
|
85
|
-
if (process.exitCode === 0) {
|
|
86
|
-
process.exitCode = 1;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
const exitCode = process.exitCode ?? 0;
|
|
90
|
-
process.exitCode = 0;
|
|
91
|
-
return { exitCode };
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// ---------------------------------------------------------------------------
|
|
95
|
-
// Setup
|
|
96
|
-
// ---------------------------------------------------------------------------
|
|
97
|
-
|
|
98
|
-
beforeEach(() => {
|
|
99
|
-
lastIpcCall = null;
|
|
100
|
-
mockIpcResult = { ok: true, result: {} };
|
|
101
|
-
logOutput = [];
|
|
102
|
-
process.exitCode = 0;
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
// ---------------------------------------------------------------------------
|
|
106
|
-
// enable
|
|
107
|
-
// ---------------------------------------------------------------------------
|
|
108
|
-
|
|
109
|
-
describe("backup enable", () => {
|
|
110
|
-
test("basic enable sends backup_enable with no extra params", async () => {
|
|
111
|
-
mockIpcResult = {
|
|
112
|
-
ok: true,
|
|
113
|
-
result: {
|
|
114
|
-
enabled: true,
|
|
115
|
-
intervalHours: 6,
|
|
116
|
-
retention: 3,
|
|
117
|
-
offsite: { enabled: true },
|
|
118
|
-
},
|
|
119
|
-
};
|
|
120
|
-
const { exitCode } = await runCommand(["backup", "enable"]);
|
|
121
|
-
expect(exitCode).toBe(0);
|
|
122
|
-
expect(lastIpcCall!.method).toBe("backup_enable");
|
|
123
|
-
// Should not have intervalHours or retention if not specified
|
|
124
|
-
expect(
|
|
125
|
-
(lastIpcCall!.params?.body as Record<string, unknown>)?.intervalHours,
|
|
126
|
-
).toBeUndefined();
|
|
127
|
-
expect(
|
|
128
|
-
(lastIpcCall!.params?.body as Record<string, unknown>)?.retention,
|
|
129
|
-
).toBeUndefined();
|
|
130
|
-
expect(
|
|
131
|
-
(lastIpcCall!.params?.body as Record<string, unknown>)?.offsiteEnabled,
|
|
132
|
-
).toBeUndefined();
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
test("--interval 12 --retention 7 sends correct params", async () => {
|
|
136
|
-
mockIpcResult = {
|
|
137
|
-
ok: true,
|
|
138
|
-
result: {
|
|
139
|
-
enabled: true,
|
|
140
|
-
intervalHours: 12,
|
|
141
|
-
retention: 7,
|
|
142
|
-
offsite: { enabled: true },
|
|
143
|
-
},
|
|
144
|
-
};
|
|
145
|
-
const { exitCode } = await runCommand([
|
|
146
|
-
"backup",
|
|
147
|
-
"enable",
|
|
148
|
-
"--interval",
|
|
149
|
-
"12",
|
|
150
|
-
"--retention",
|
|
151
|
-
"7",
|
|
152
|
-
]);
|
|
153
|
-
expect(exitCode).toBe(0);
|
|
154
|
-
expect(lastIpcCall!.method).toBe("backup_enable");
|
|
155
|
-
expect(lastIpcCall!.params).toEqual({
|
|
156
|
-
body: { intervalHours: 12, retention: 7 },
|
|
157
|
-
});
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
test("--no-offsite sends offsiteEnabled: false", async () => {
|
|
161
|
-
mockIpcResult = {
|
|
162
|
-
ok: true,
|
|
163
|
-
result: {
|
|
164
|
-
enabled: true,
|
|
165
|
-
intervalHours: 6,
|
|
166
|
-
retention: 3,
|
|
167
|
-
offsite: { enabled: false },
|
|
168
|
-
},
|
|
169
|
-
};
|
|
170
|
-
const { exitCode } = await runCommand(["backup", "enable", "--no-offsite"]);
|
|
171
|
-
expect(exitCode).toBe(0);
|
|
172
|
-
expect(lastIpcCall!.method).toBe("backup_enable");
|
|
173
|
-
expect(
|
|
174
|
-
(lastIpcCall!.params?.body as Record<string, unknown>)?.offsiteEnabled,
|
|
175
|
-
).toBe(false);
|
|
176
|
-
});
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
// ---------------------------------------------------------------------------
|
|
180
|
-
// disable
|
|
181
|
-
// ---------------------------------------------------------------------------
|
|
182
|
-
|
|
183
|
-
describe("backup disable", () => {
|
|
184
|
-
test("sends backup_disable", async () => {
|
|
185
|
-
const { exitCode } = await runCommand(["backup", "disable"]);
|
|
186
|
-
expect(exitCode).toBe(0);
|
|
187
|
-
expect(lastIpcCall!.method).toBe("backup_disable");
|
|
188
|
-
});
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
// ---------------------------------------------------------------------------
|
|
192
|
-
// destinations list
|
|
193
|
-
// ---------------------------------------------------------------------------
|
|
194
|
-
|
|
195
|
-
describe("backup destinations list", () => {
|
|
196
|
-
test("sends backup_destinations_list", async () => {
|
|
197
|
-
mockIpcResult = { ok: true, result: { destinations: [] } };
|
|
198
|
-
const { exitCode } = await runCommand(["backup", "destinations", "list"]);
|
|
199
|
-
expect(exitCode).toBe(0);
|
|
200
|
-
expect(lastIpcCall!.method).toBe("backup_destinations_list");
|
|
201
|
-
});
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
// ---------------------------------------------------------------------------
|
|
205
|
-
// destinations add
|
|
206
|
-
// ---------------------------------------------------------------------------
|
|
207
|
-
|
|
208
|
-
describe("backup destinations add", () => {
|
|
209
|
-
test("sends backup_destinations_add with encrypt: true by default", async () => {
|
|
210
|
-
mockIpcResult = {
|
|
211
|
-
ok: true,
|
|
212
|
-
result: { destinations: [{ path: "/tmp/foo", encrypt: true }] },
|
|
213
|
-
};
|
|
214
|
-
const { exitCode } = await runCommand([
|
|
215
|
-
"backup",
|
|
216
|
-
"destinations",
|
|
217
|
-
"add",
|
|
218
|
-
"/tmp/foo",
|
|
219
|
-
]);
|
|
220
|
-
expect(exitCode).toBe(0);
|
|
221
|
-
expect(lastIpcCall!.method).toBe("backup_destinations_add");
|
|
222
|
-
expect(lastIpcCall!.params).toEqual({
|
|
223
|
-
body: { path: "/tmp/foo", encrypt: true },
|
|
224
|
-
});
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
test("--plaintext sends encrypt: false", async () => {
|
|
228
|
-
mockIpcResult = {
|
|
229
|
-
ok: true,
|
|
230
|
-
result: { destinations: [{ path: "/tmp/foo", encrypt: false }] },
|
|
231
|
-
};
|
|
232
|
-
const { exitCode } = await runCommand([
|
|
233
|
-
"backup",
|
|
234
|
-
"destinations",
|
|
235
|
-
"add",
|
|
236
|
-
"/tmp/foo",
|
|
237
|
-
"--plaintext",
|
|
238
|
-
]);
|
|
239
|
-
expect(exitCode).toBe(0);
|
|
240
|
-
expect(lastIpcCall!.method).toBe("backup_destinations_add");
|
|
241
|
-
expect(lastIpcCall!.params).toEqual({
|
|
242
|
-
body: { path: "/tmp/foo", encrypt: false },
|
|
243
|
-
});
|
|
244
|
-
});
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
// ---------------------------------------------------------------------------
|
|
248
|
-
// destinations remove
|
|
249
|
-
// ---------------------------------------------------------------------------
|
|
250
|
-
|
|
251
|
-
describe("backup destinations remove", () => {
|
|
252
|
-
test("sends backup_destinations_remove with correct path", async () => {
|
|
253
|
-
mockIpcResult = { ok: true, result: { destinations: [] } };
|
|
254
|
-
const { exitCode } = await runCommand([
|
|
255
|
-
"backup",
|
|
256
|
-
"destinations",
|
|
257
|
-
"remove",
|
|
258
|
-
"/tmp/foo",
|
|
259
|
-
]);
|
|
260
|
-
expect(exitCode).toBe(0);
|
|
261
|
-
expect(lastIpcCall!.method).toBe("backup_destinations_remove");
|
|
262
|
-
expect((lastIpcCall!.params?.body as Record<string, unknown>)?.path).toBe(
|
|
263
|
-
"/tmp/foo",
|
|
264
|
-
);
|
|
265
|
-
});
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
// ---------------------------------------------------------------------------
|
|
269
|
-
// destinations set-encrypt
|
|
270
|
-
// ---------------------------------------------------------------------------
|
|
271
|
-
|
|
272
|
-
describe("backup destinations set-encrypt", () => {
|
|
273
|
-
test("sends backup_destinations_set_encrypt with encrypt: true", async () => {
|
|
274
|
-
mockIpcResult = {
|
|
275
|
-
ok: true,
|
|
276
|
-
result: { destination: { path: "/tmp/foo", encrypt: true } },
|
|
277
|
-
};
|
|
278
|
-
const { exitCode } = await runCommand([
|
|
279
|
-
"backup",
|
|
280
|
-
"destinations",
|
|
281
|
-
"set-encrypt",
|
|
282
|
-
"/tmp/foo",
|
|
283
|
-
"true",
|
|
284
|
-
]);
|
|
285
|
-
expect(exitCode).toBe(0);
|
|
286
|
-
expect(lastIpcCall!.method).toBe("backup_destinations_set_encrypt");
|
|
287
|
-
expect(lastIpcCall!.params).toEqual({
|
|
288
|
-
body: { path: "/tmp/foo", encrypt: true },
|
|
289
|
-
});
|
|
290
|
-
});
|
|
291
|
-
|
|
292
|
-
test("invalid value exits with code 1 without calling IPC", async () => {
|
|
293
|
-
const { exitCode } = await runCommand([
|
|
294
|
-
"backup",
|
|
295
|
-
"destinations",
|
|
296
|
-
"set-encrypt",
|
|
297
|
-
"/tmp/foo",
|
|
298
|
-
"yes",
|
|
299
|
-
]);
|
|
300
|
-
expect(exitCode).toBe(1);
|
|
301
|
-
// IPC should not have been called since we validated locally
|
|
302
|
-
expect(lastIpcCall).toBeNull();
|
|
303
|
-
});
|
|
304
|
-
});
|
|
305
|
-
|
|
306
|
-
// ---------------------------------------------------------------------------
|
|
307
|
-
// status
|
|
308
|
-
// ---------------------------------------------------------------------------
|
|
309
|
-
|
|
310
|
-
describe("backup status", () => {
|
|
311
|
-
test("successful status call exits with 0", async () => {
|
|
312
|
-
mockIpcResult = {
|
|
313
|
-
ok: true,
|
|
314
|
-
result: {
|
|
315
|
-
enabled: true,
|
|
316
|
-
intervalHours: 6,
|
|
317
|
-
retention: 3,
|
|
318
|
-
lastRunAt: null,
|
|
319
|
-
nextRunAt: null,
|
|
320
|
-
localDir: "/tmp/local",
|
|
321
|
-
localSnapshotCount: 0,
|
|
322
|
-
offsiteEnabled: false,
|
|
323
|
-
offsite: [],
|
|
324
|
-
},
|
|
325
|
-
};
|
|
326
|
-
const { exitCode } = await runCommand(["backup", "status"]);
|
|
327
|
-
expect(exitCode).toBe(0);
|
|
328
|
-
expect(lastIpcCall!.method).toBe("backup_status");
|
|
329
|
-
});
|
|
330
|
-
});
|
|
331
|
-
|
|
332
|
-
// ---------------------------------------------------------------------------
|
|
333
|
-
// list
|
|
334
|
-
// ---------------------------------------------------------------------------
|
|
335
|
-
|
|
336
|
-
describe("backup list", () => {
|
|
337
|
-
test("sends backups_list", async () => {
|
|
338
|
-
mockIpcResult = {
|
|
339
|
-
ok: true,
|
|
340
|
-
result: {
|
|
341
|
-
local: [],
|
|
342
|
-
offsite: [],
|
|
343
|
-
offsiteEnabled: false,
|
|
344
|
-
nextRunAt: null,
|
|
345
|
-
},
|
|
346
|
-
};
|
|
347
|
-
const { exitCode } = await runCommand(["backup", "list"]);
|
|
348
|
-
expect(exitCode).toBe(0);
|
|
349
|
-
expect(lastIpcCall!.method).toBe("backups_list");
|
|
350
|
-
});
|
|
351
|
-
});
|
|
352
|
-
|
|
353
|
-
// ---------------------------------------------------------------------------
|
|
354
|
-
// IPC error path
|
|
355
|
-
// ---------------------------------------------------------------------------
|
|
356
|
-
|
|
357
|
-
describe("IPC error handling", () => {
|
|
358
|
-
test("IPC error causes process.exitCode = 1", async () => {
|
|
359
|
-
mockIpcResult = { ok: false, error: "daemon not running" };
|
|
360
|
-
const { exitCode } = await runCommand(["backup", "disable"]);
|
|
361
|
-
expect(exitCode).toBe(1);
|
|
362
|
-
});
|
|
363
|
-
});
|