@mycadet/assistant 0.12.10 → 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 +10 -10
- package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/ingress.ts +3 -3
- package/node_modules/@mycadet/ces-client/src/http-credentials.ts +18 -1
- package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/__tests__/ingress.test.ts +10 -10
- package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/ingress.ts +3 -3
- package/node_modules/@mycadet/gateway-client/src/gateway-ipc-contracts.ts +0 -20
- package/node_modules/@mycadet/service-contracts/src/__tests__/ingress.test.ts +10 -10
- package/node_modules/@mycadet/service-contracts/src/ingress.ts +3 -3
- 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 +41 -5
- 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/llm.ts +3 -5
- 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__/conversation-query-routes.test.ts +13 -11
- package/src/runtime/routes/__tests__/tts-routes.test.ts +2 -6
- package/src/runtime/routes/conversation-query-routes.ts +0 -96
- 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,906 +0,0 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
|
-
|
|
3
|
-
import type { HeartbeatService } from "../heartbeat/heartbeat-service.js";
|
|
4
|
-
import type {
|
|
5
|
-
SchedulerDueWorkResult,
|
|
6
|
-
SchedulerHandle,
|
|
7
|
-
} from "../schedule/scheduler.js";
|
|
8
|
-
import type { BackgroundWakeIntent } from "./next-wake.js";
|
|
9
|
-
|
|
10
|
-
// The drain-due route resolves the scheduler and heartbeat from their
|
|
11
|
-
// singletons; mock those accessors so each test can inject the doubles it
|
|
12
|
-
// asserts against. next-wake.ts also reads HeartbeatService.getInstance(), so
|
|
13
|
-
// the heartbeat mock exposes that too.
|
|
14
|
-
type BackgroundWakeRuntime = {
|
|
15
|
-
scheduler: Pick<SchedulerHandle, "runOnce" | "runDueWorkOnce">;
|
|
16
|
-
heartbeat: Pick<HeartbeatService, "nextRunAt" | "runCloudWakeIfDue">;
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
let mockScheduler: BackgroundWakeRuntime["scheduler"] | null = null;
|
|
20
|
-
let mockHeartbeat: BackgroundWakeRuntime["heartbeat"] | null = null;
|
|
21
|
-
|
|
22
|
-
mock.module("../schedule/scheduler.js", () => ({
|
|
23
|
-
getScheduler: () => mockScheduler,
|
|
24
|
-
}));
|
|
25
|
-
mock.module("../heartbeat/heartbeat-service.js", () => ({
|
|
26
|
-
getHeartbeatService: () => mockHeartbeat,
|
|
27
|
-
HeartbeatService: { getInstance: () => mockHeartbeat },
|
|
28
|
-
}));
|
|
29
|
-
|
|
30
|
-
function setBackgroundWakeRuntime(runtime: BackgroundWakeRuntime): void {
|
|
31
|
-
mockScheduler = runtime.scheduler;
|
|
32
|
-
mockHeartbeat = runtime.heartbeat;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function clearBackgroundWakeRuntime(): void {
|
|
36
|
-
mockScheduler = null;
|
|
37
|
-
mockHeartbeat = null;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
type MockIntent = BackgroundWakeIntent;
|
|
41
|
-
type MockCompletionPayload = {
|
|
42
|
-
leaseId: string;
|
|
43
|
-
status: "completed" | "failed" | "expired";
|
|
44
|
-
error?: string;
|
|
45
|
-
nextIntent?: MockIntent | null;
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
let computedIntent: MockIntent | null;
|
|
49
|
-
let computeCalls: number;
|
|
50
|
-
const mockRenewBackgroundWakeLease = mock(async (_leaseId: string) => ({
|
|
51
|
-
status: "renewed" as const,
|
|
52
|
-
httpStatus: 200,
|
|
53
|
-
}));
|
|
54
|
-
const mockCompleteBackgroundWakeLease = mock(
|
|
55
|
-
async (_args: MockCompletionPayload) => ({
|
|
56
|
-
status: "completed" as const,
|
|
57
|
-
httpStatus: 200,
|
|
58
|
-
}),
|
|
59
|
-
);
|
|
60
|
-
|
|
61
|
-
const {
|
|
62
|
-
ROUTES,
|
|
63
|
-
flushBackgroundWakeDrainsForTest,
|
|
64
|
-
setBackgroundWakeIntentComputerForTest,
|
|
65
|
-
setBackgroundWakeLeaseClientForTest,
|
|
66
|
-
} = await import("../runtime/routes/background-wake-routes.js");
|
|
67
|
-
|
|
68
|
-
function findHandler(operationId: string) {
|
|
69
|
-
const route = ROUTES.find(
|
|
70
|
-
(candidate) => candidate.operationId === operationId,
|
|
71
|
-
);
|
|
72
|
-
if (!route) {
|
|
73
|
-
throw new Error(`Route ${operationId} not found`);
|
|
74
|
-
}
|
|
75
|
-
return route.handler;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const NOW = 1_800_000_000_000;
|
|
79
|
-
|
|
80
|
-
function schedulerResultFixture(
|
|
81
|
-
overrides: Partial<SchedulerDueWorkResult> = {},
|
|
82
|
-
): SchedulerDueWorkResult {
|
|
83
|
-
return {
|
|
84
|
-
claimed: 0,
|
|
85
|
-
completed: 0,
|
|
86
|
-
failed: 0,
|
|
87
|
-
skipped: 0,
|
|
88
|
-
...overrides,
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function firstCallArg(fn: {
|
|
93
|
-
mock: { calls: unknown[] };
|
|
94
|
-
}): Record<string, unknown> {
|
|
95
|
-
const call = fn.mock.calls[0] as unknown[] | undefined;
|
|
96
|
-
return (call?.[0] ?? {}) as Record<string, unknown>;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
function acceptedResponse(
|
|
100
|
-
overrides: Partial<{
|
|
101
|
-
leaseId: string;
|
|
102
|
-
reason: string;
|
|
103
|
-
sourceGeneration: string;
|
|
104
|
-
startedAt: number;
|
|
105
|
-
deadlineAt: number;
|
|
106
|
-
}> = {},
|
|
107
|
-
) {
|
|
108
|
-
return {
|
|
109
|
-
accepted: true,
|
|
110
|
-
leaseId: "lease-123",
|
|
111
|
-
reason: "schedule",
|
|
112
|
-
sourceGeneration: "source-generation",
|
|
113
|
-
startedAt: NOW,
|
|
114
|
-
deadlineAt: NOW + 30_000,
|
|
115
|
-
...overrides,
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function lastCompletionPayload(): MockCompletionPayload {
|
|
120
|
-
const calls = mockCompleteBackgroundWakeLease.mock.calls;
|
|
121
|
-
const call = calls[calls.length - 1] as unknown[] | undefined;
|
|
122
|
-
if (!call) {
|
|
123
|
-
throw new Error("completeBackgroundWakeLease was not called");
|
|
124
|
-
}
|
|
125
|
-
return call[0] as MockCompletionPayload;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
describe("background wake runtime routes", () => {
|
|
129
|
-
beforeEach(() => {
|
|
130
|
-
clearBackgroundWakeRuntime();
|
|
131
|
-
mockRenewBackgroundWakeLease.mockClear();
|
|
132
|
-
mockCompleteBackgroundWakeLease.mockClear();
|
|
133
|
-
mockRenewBackgroundWakeLease.mockImplementation(
|
|
134
|
-
async (_leaseId: string) => ({
|
|
135
|
-
status: "renewed" as const,
|
|
136
|
-
httpStatus: 200,
|
|
137
|
-
}),
|
|
138
|
-
);
|
|
139
|
-
mockCompleteBackgroundWakeLease.mockImplementation(
|
|
140
|
-
async (_args: MockCompletionPayload) => ({
|
|
141
|
-
status: "completed" as const,
|
|
142
|
-
httpStatus: 200,
|
|
143
|
-
}),
|
|
144
|
-
);
|
|
145
|
-
setBackgroundWakeLeaseClientForTest({
|
|
146
|
-
renew: mockRenewBackgroundWakeLease,
|
|
147
|
-
complete: mockCompleteBackgroundWakeLease,
|
|
148
|
-
});
|
|
149
|
-
computeCalls = 0;
|
|
150
|
-
computedIntent = intentFixture({ nextWakeAt: Date.now() + 10 * 60_000 });
|
|
151
|
-
setBackgroundWakeIntentComputerForTest(() => {
|
|
152
|
-
computeCalls += 1;
|
|
153
|
-
return computedIntent;
|
|
154
|
-
});
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
afterEach(async () => {
|
|
158
|
-
await flushBackgroundWakeDrainsForTest();
|
|
159
|
-
setBackgroundWakeIntentComputerForTest(null);
|
|
160
|
-
setBackgroundWakeLeaseClientForTest(null);
|
|
161
|
-
clearBackgroundWakeRuntime();
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
test("intent route returns the current computed wake intent", async () => {
|
|
165
|
-
const handler = findHandler("getBackgroundWakeIntent");
|
|
166
|
-
|
|
167
|
-
expect(await handler({})).toEqual({ intent: computedIntent });
|
|
168
|
-
expect(computeCalls).toBe(1);
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
test("intent route returns null when no wake intent exists", async () => {
|
|
172
|
-
computedIntent = null;
|
|
173
|
-
const handler = findHandler("getBackgroundWakeIntent");
|
|
174
|
-
|
|
175
|
-
expect(await handler({})).toEqual({ intent: null });
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
test("prepare-sleep defers when the next wake is inside the local window", async () => {
|
|
179
|
-
computedIntent = intentFixture({ nextWakeAt: Date.now() + 30_000 });
|
|
180
|
-
const handler = findHandler("prepareBackgroundWakeSleep");
|
|
181
|
-
|
|
182
|
-
expect(await handler({})).toEqual({
|
|
183
|
-
intent: computedIntent,
|
|
184
|
-
deferSleep: true,
|
|
185
|
-
});
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
test("prepare-sleep allows sleep when the next wake is outside the local window", async () => {
|
|
189
|
-
computedIntent = intentFixture({ nextWakeAt: Date.now() + 5 * 60_000 });
|
|
190
|
-
const handler = findHandler("prepareBackgroundWakeSleep");
|
|
191
|
-
|
|
192
|
-
expect(await handler({})).toEqual({
|
|
193
|
-
intent: computedIntent,
|
|
194
|
-
deferSleep: false,
|
|
195
|
-
});
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
test("drain-due fails when the scheduler or heartbeat is unavailable", async () => {
|
|
199
|
-
const handler = findHandler("drainDueBackgroundWake");
|
|
200
|
-
|
|
201
|
-
await expect(handler({ body: drainBodyFixture() })).rejects.toThrow(
|
|
202
|
-
"Background wake runtime is not available",
|
|
203
|
-
);
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
test("drain-due invokes due heartbeat and scheduler work", async () => {
|
|
207
|
-
const dueAt = Date.now() - 1;
|
|
208
|
-
const recomputedIntent = intentFixture({
|
|
209
|
-
nextWakeAt: Date.now() + 60_000,
|
|
210
|
-
sourceGeneration: "next-generation",
|
|
211
|
-
});
|
|
212
|
-
computedIntent = intentFixture({
|
|
213
|
-
nextWakeAt: dueAt,
|
|
214
|
-
actualNextDueAt: dueAt,
|
|
215
|
-
reason: "mixed",
|
|
216
|
-
});
|
|
217
|
-
const heartbeatRunCloud = mock(async () => ({
|
|
218
|
-
due: true,
|
|
219
|
-
completed: 1,
|
|
220
|
-
skipped: 0,
|
|
221
|
-
}));
|
|
222
|
-
const schedulerRunDue = mock(async () => {
|
|
223
|
-
computedIntent = recomputedIntent;
|
|
224
|
-
return schedulerResultFixture({ claimed: 2, completed: 2 });
|
|
225
|
-
});
|
|
226
|
-
setBackgroundWakeRuntime({
|
|
227
|
-
heartbeat: {
|
|
228
|
-
nextRunAt: dueAt,
|
|
229
|
-
runCloudWakeIfDue: heartbeatRunCloud,
|
|
230
|
-
},
|
|
231
|
-
scheduler: {
|
|
232
|
-
runOnce: mock(async () => 2),
|
|
233
|
-
runDueWorkOnce: schedulerRunDue,
|
|
234
|
-
},
|
|
235
|
-
});
|
|
236
|
-
const handler = findHandler("drainDueBackgroundWake");
|
|
237
|
-
|
|
238
|
-
const response = await handler({
|
|
239
|
-
body: drainBodyFixture({ reason: "mixed" }),
|
|
240
|
-
});
|
|
241
|
-
await flushBackgroundWakeDrainsForTest();
|
|
242
|
-
|
|
243
|
-
expect(heartbeatRunCloud).toHaveBeenCalledTimes(1);
|
|
244
|
-
expect(heartbeatRunCloud).toHaveBeenCalledWith(
|
|
245
|
-
expect.objectContaining({ scheduledFor: NOW }),
|
|
246
|
-
);
|
|
247
|
-
expect(firstCallArg(heartbeatRunCloud)).not.toHaveProperty("assumeDue");
|
|
248
|
-
expect(schedulerRunDue).toHaveBeenCalledTimes(1);
|
|
249
|
-
expect(response).toEqual(acceptedResponse({ reason: "mixed" }));
|
|
250
|
-
expect(lastCompletionPayload()).toEqual({
|
|
251
|
-
leaseId: "lease-123",
|
|
252
|
-
status: "completed",
|
|
253
|
-
nextIntent: recomputedIntent,
|
|
254
|
-
});
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
test("drain-due skips heartbeat when it is not due and reports no scheduler work", async () => {
|
|
258
|
-
const heartbeatRunCloud = mock(async () => ({
|
|
259
|
-
due: true,
|
|
260
|
-
completed: 1,
|
|
261
|
-
skipped: 0,
|
|
262
|
-
}));
|
|
263
|
-
const schedulerRunDue = mock(async () => schedulerResultFixture());
|
|
264
|
-
setBackgroundWakeRuntime({
|
|
265
|
-
heartbeat: {
|
|
266
|
-
nextRunAt: Date.now() + 5 * 60_000,
|
|
267
|
-
runCloudWakeIfDue: heartbeatRunCloud,
|
|
268
|
-
},
|
|
269
|
-
scheduler: {
|
|
270
|
-
runOnce: mock(async () => 0),
|
|
271
|
-
runDueWorkOnce: schedulerRunDue,
|
|
272
|
-
},
|
|
273
|
-
});
|
|
274
|
-
const handler = findHandler("drainDueBackgroundWake");
|
|
275
|
-
|
|
276
|
-
const response = await handler({ body: drainBodyFixture() });
|
|
277
|
-
await flushBackgroundWakeDrainsForTest();
|
|
278
|
-
|
|
279
|
-
expect(heartbeatRunCloud).not.toHaveBeenCalled();
|
|
280
|
-
expect(schedulerRunDue).toHaveBeenCalledTimes(1);
|
|
281
|
-
expect(response).toEqual(acceptedResponse());
|
|
282
|
-
expect(lastCompletionPayload()).toMatchObject({
|
|
283
|
-
leaseId: "lease-123",
|
|
284
|
-
status: "completed",
|
|
285
|
-
});
|
|
286
|
-
});
|
|
287
|
-
|
|
288
|
-
test("drain-due recomputes the next intent after due work drains", async () => {
|
|
289
|
-
const recomputedIntent = intentFixture({
|
|
290
|
-
nextWakeAt: NOW + 2 * 60_000,
|
|
291
|
-
sourceGeneration: "after-drain",
|
|
292
|
-
});
|
|
293
|
-
const schedulerRunOnce = mock(async () => {
|
|
294
|
-
computedIntent = recomputedIntent;
|
|
295
|
-
return schedulerResultFixture({ claimed: 1, completed: 1 });
|
|
296
|
-
});
|
|
297
|
-
setBackgroundWakeRuntime({
|
|
298
|
-
heartbeat: {
|
|
299
|
-
nextRunAt: null,
|
|
300
|
-
runCloudWakeIfDue: mock(async () => ({
|
|
301
|
-
due: false,
|
|
302
|
-
completed: 0,
|
|
303
|
-
skipped: 0,
|
|
304
|
-
})),
|
|
305
|
-
},
|
|
306
|
-
scheduler: {
|
|
307
|
-
runOnce: mock(async () => 1),
|
|
308
|
-
runDueWorkOnce: schedulerRunOnce,
|
|
309
|
-
},
|
|
310
|
-
});
|
|
311
|
-
const handler = findHandler("drainDueBackgroundWake");
|
|
312
|
-
|
|
313
|
-
const response = await handler({ body: drainBodyFixture() });
|
|
314
|
-
await flushBackgroundWakeDrainsForTest();
|
|
315
|
-
|
|
316
|
-
expect(response).toEqual(acceptedResponse());
|
|
317
|
-
expect(lastCompletionPayload().nextIntent).toEqual(recomputedIntent);
|
|
318
|
-
});
|
|
319
|
-
|
|
320
|
-
test("drain-due runs scheduler backlog on heartbeat-only drains", async () => {
|
|
321
|
-
const dueAt = Date.now() - 1;
|
|
322
|
-
const recomputedIntent = intentFixture({
|
|
323
|
-
nextWakeAt: Date.now() + 60_000,
|
|
324
|
-
sourceGeneration: "after-heartbeat-drain",
|
|
325
|
-
});
|
|
326
|
-
computedIntent = intentFixture({
|
|
327
|
-
nextWakeAt: dueAt,
|
|
328
|
-
actualNextDueAt: dueAt,
|
|
329
|
-
reason: "heartbeat",
|
|
330
|
-
});
|
|
331
|
-
const heartbeatRunCloud = mock(async () => ({
|
|
332
|
-
due: true,
|
|
333
|
-
completed: 1,
|
|
334
|
-
skipped: 0,
|
|
335
|
-
}));
|
|
336
|
-
const schedulerRunDue = mock(async () => {
|
|
337
|
-
computedIntent = recomputedIntent;
|
|
338
|
-
return schedulerResultFixture({ claimed: 1, completed: 1 });
|
|
339
|
-
});
|
|
340
|
-
setBackgroundWakeRuntime({
|
|
341
|
-
heartbeat: {
|
|
342
|
-
nextRunAt: dueAt,
|
|
343
|
-
runCloudWakeIfDue: heartbeatRunCloud,
|
|
344
|
-
},
|
|
345
|
-
scheduler: {
|
|
346
|
-
runOnce: mock(async () => 1),
|
|
347
|
-
runDueWorkOnce: schedulerRunDue,
|
|
348
|
-
},
|
|
349
|
-
});
|
|
350
|
-
const handler = findHandler("drainDueBackgroundWake");
|
|
351
|
-
|
|
352
|
-
const response = await handler({
|
|
353
|
-
body: drainBodyFixture({ reason: "heartbeat" }),
|
|
354
|
-
});
|
|
355
|
-
await flushBackgroundWakeDrainsForTest();
|
|
356
|
-
|
|
357
|
-
expect(heartbeatRunCloud).toHaveBeenCalledWith(
|
|
358
|
-
expect.objectContaining({
|
|
359
|
-
scheduledFor: NOW,
|
|
360
|
-
}),
|
|
361
|
-
);
|
|
362
|
-
expect(firstCallArg(heartbeatRunCloud)).not.toHaveProperty("assumeDue");
|
|
363
|
-
expect(schedulerRunDue).toHaveBeenCalledTimes(1);
|
|
364
|
-
expect(response).toEqual(acceptedResponse({ reason: "heartbeat" }));
|
|
365
|
-
expect(lastCompletionPayload()).toMatchObject({
|
|
366
|
-
leaseId: "lease-123",
|
|
367
|
-
status: "completed",
|
|
368
|
-
nextIntent: recomputedIntent,
|
|
369
|
-
});
|
|
370
|
-
});
|
|
371
|
-
|
|
372
|
-
test("drain-due does not rerun heartbeat for stale heartbeat drain requests", async () => {
|
|
373
|
-
const heartbeatRunCloud = mock(async () => ({
|
|
374
|
-
due: true,
|
|
375
|
-
completed: 1,
|
|
376
|
-
skipped: 0,
|
|
377
|
-
}));
|
|
378
|
-
const schedulerRunDue = mock(async () => schedulerResultFixture());
|
|
379
|
-
setBackgroundWakeRuntime({
|
|
380
|
-
heartbeat: {
|
|
381
|
-
nextRunAt: Date.now() + 30 * 60_000,
|
|
382
|
-
runCloudWakeIfDue: heartbeatRunCloud,
|
|
383
|
-
},
|
|
384
|
-
scheduler: {
|
|
385
|
-
runOnce: mock(async () => 0),
|
|
386
|
-
runDueWorkOnce: schedulerRunDue,
|
|
387
|
-
},
|
|
388
|
-
});
|
|
389
|
-
const handler = findHandler("drainDueBackgroundWake");
|
|
390
|
-
|
|
391
|
-
const response = await handler({
|
|
392
|
-
body: drainBodyFixture({ reason: "heartbeat" }),
|
|
393
|
-
});
|
|
394
|
-
await flushBackgroundWakeDrainsForTest();
|
|
395
|
-
|
|
396
|
-
expect(heartbeatRunCloud).not.toHaveBeenCalled();
|
|
397
|
-
expect(schedulerRunDue).not.toHaveBeenCalled();
|
|
398
|
-
expect(response).toEqual(acceptedResponse({ reason: "heartbeat" }));
|
|
399
|
-
expect(lastCompletionPayload()).toMatchObject({
|
|
400
|
-
leaseId: "lease-123",
|
|
401
|
-
status: "completed",
|
|
402
|
-
});
|
|
403
|
-
});
|
|
404
|
-
|
|
405
|
-
test("drain-due runs heartbeat when the local intent is due at wake", async () => {
|
|
406
|
-
const dueAt = Date.now() - 1;
|
|
407
|
-
computedIntent = intentFixture({
|
|
408
|
-
nextWakeAt: dueAt,
|
|
409
|
-
actualNextDueAt: dueAt,
|
|
410
|
-
reason: "heartbeat",
|
|
411
|
-
});
|
|
412
|
-
const heartbeatRunCloud = mock(async () => ({
|
|
413
|
-
due: true,
|
|
414
|
-
completed: 1,
|
|
415
|
-
skipped: 0,
|
|
416
|
-
}));
|
|
417
|
-
setBackgroundWakeRuntime({
|
|
418
|
-
heartbeat: {
|
|
419
|
-
nextRunAt: dueAt,
|
|
420
|
-
runCloudWakeIfDue: heartbeatRunCloud,
|
|
421
|
-
},
|
|
422
|
-
scheduler: {
|
|
423
|
-
runOnce: mock(async () => 0),
|
|
424
|
-
runDueWorkOnce: mock(async () => schedulerResultFixture()),
|
|
425
|
-
},
|
|
426
|
-
});
|
|
427
|
-
const handler = findHandler("drainDueBackgroundWake");
|
|
428
|
-
|
|
429
|
-
const response = await handler({
|
|
430
|
-
body: drainBodyFixture({ reason: "refresh" }),
|
|
431
|
-
});
|
|
432
|
-
await flushBackgroundWakeDrainsForTest();
|
|
433
|
-
|
|
434
|
-
expect(heartbeatRunCloud).toHaveBeenCalledWith(
|
|
435
|
-
expect.objectContaining({ scheduledFor: NOW }),
|
|
436
|
-
);
|
|
437
|
-
expect(firstCallArg(heartbeatRunCloud)).not.toHaveProperty("assumeDue");
|
|
438
|
-
expect(response).toEqual(acceptedResponse({ reason: "refresh" }));
|
|
439
|
-
});
|
|
440
|
-
|
|
441
|
-
test("drain-due runs scheduler when schedule work is due at wake", async () => {
|
|
442
|
-
const dueAt = Date.now() - 1;
|
|
443
|
-
computedIntent = intentFixture({
|
|
444
|
-
nextWakeAt: dueAt,
|
|
445
|
-
actualNextDueAt: dueAt,
|
|
446
|
-
reason: "schedule",
|
|
447
|
-
});
|
|
448
|
-
const schedulerRunDue = mock(async () =>
|
|
449
|
-
schedulerResultFixture({ claimed: 1, completed: 1 }),
|
|
450
|
-
);
|
|
451
|
-
setBackgroundWakeRuntime({
|
|
452
|
-
heartbeat: {
|
|
453
|
-
nextRunAt: Date.now() + 60_000,
|
|
454
|
-
runCloudWakeIfDue: mock(async () => ({
|
|
455
|
-
due: false,
|
|
456
|
-
completed: 0,
|
|
457
|
-
skipped: 0,
|
|
458
|
-
})),
|
|
459
|
-
},
|
|
460
|
-
scheduler: {
|
|
461
|
-
runOnce: mock(async () => 1),
|
|
462
|
-
runDueWorkOnce: schedulerRunDue,
|
|
463
|
-
},
|
|
464
|
-
});
|
|
465
|
-
const handler = findHandler("drainDueBackgroundWake");
|
|
466
|
-
|
|
467
|
-
const response = await handler({
|
|
468
|
-
body: drainBodyFixture({ reason: "refresh" }),
|
|
469
|
-
});
|
|
470
|
-
await flushBackgroundWakeDrainsForTest();
|
|
471
|
-
|
|
472
|
-
expect(schedulerRunDue).toHaveBeenCalledTimes(1);
|
|
473
|
-
expect(response).toEqual(acceptedResponse({ reason: "refresh" }));
|
|
474
|
-
expect(lastCompletionPayload()).toMatchObject({
|
|
475
|
-
leaseId: "lease-123",
|
|
476
|
-
status: "completed",
|
|
477
|
-
});
|
|
478
|
-
});
|
|
479
|
-
|
|
480
|
-
test("drain-due runs due heartbeat even when schedule is the earliest intent source", async () => {
|
|
481
|
-
const now = Date.now();
|
|
482
|
-
const scheduleDueAt = now - 60_000;
|
|
483
|
-
const heartbeatDueAt = now - 1_000;
|
|
484
|
-
const recomputedIntent = intentFixture({
|
|
485
|
-
nextWakeAt: now + 60_000,
|
|
486
|
-
sourceGeneration: "after-mixed-backlog",
|
|
487
|
-
});
|
|
488
|
-
computedIntent = intentFixture({
|
|
489
|
-
nextWakeAt: scheduleDueAt,
|
|
490
|
-
actualNextDueAt: scheduleDueAt,
|
|
491
|
-
reason: "schedule",
|
|
492
|
-
});
|
|
493
|
-
const heartbeatRunCloud = mock(async () => ({
|
|
494
|
-
due: true,
|
|
495
|
-
completed: 1,
|
|
496
|
-
skipped: 0,
|
|
497
|
-
}));
|
|
498
|
-
const schedulerRunDue = mock(async () => {
|
|
499
|
-
computedIntent = recomputedIntent;
|
|
500
|
-
return schedulerResultFixture({ claimed: 1, completed: 1 });
|
|
501
|
-
});
|
|
502
|
-
setBackgroundWakeRuntime({
|
|
503
|
-
heartbeat: {
|
|
504
|
-
nextRunAt: heartbeatDueAt,
|
|
505
|
-
runCloudWakeIfDue: heartbeatRunCloud,
|
|
506
|
-
},
|
|
507
|
-
scheduler: {
|
|
508
|
-
runOnce: mock(async () => 1),
|
|
509
|
-
runDueWorkOnce: schedulerRunDue,
|
|
510
|
-
},
|
|
511
|
-
});
|
|
512
|
-
const handler = findHandler("drainDueBackgroundWake");
|
|
513
|
-
|
|
514
|
-
const response = await handler({
|
|
515
|
-
body: drainBodyFixture({ reason: "schedule" }),
|
|
516
|
-
});
|
|
517
|
-
await flushBackgroundWakeDrainsForTest();
|
|
518
|
-
|
|
519
|
-
expect(heartbeatRunCloud).toHaveBeenCalledWith(
|
|
520
|
-
expect.objectContaining({ scheduledFor: NOW }),
|
|
521
|
-
);
|
|
522
|
-
expect(schedulerRunDue).toHaveBeenCalledTimes(1);
|
|
523
|
-
expect(response).toEqual(acceptedResponse({ reason: "schedule" }));
|
|
524
|
-
expect(lastCompletionPayload()).toMatchObject({
|
|
525
|
-
leaseId: "lease-123",
|
|
526
|
-
status: "completed",
|
|
527
|
-
nextIntent: recomputedIntent,
|
|
528
|
-
});
|
|
529
|
-
});
|
|
530
|
-
|
|
531
|
-
test("drain-due does no work for refresh-only wakes with no due intent", async () => {
|
|
532
|
-
const heartbeatRunCloud = mock(async () => ({
|
|
533
|
-
due: true,
|
|
534
|
-
completed: 1,
|
|
535
|
-
skipped: 0,
|
|
536
|
-
}));
|
|
537
|
-
const schedulerRunDue = mock(async () =>
|
|
538
|
-
schedulerResultFixture({ completed: 1 }),
|
|
539
|
-
);
|
|
540
|
-
setBackgroundWakeRuntime({
|
|
541
|
-
heartbeat: {
|
|
542
|
-
nextRunAt: Date.now() + 60_000,
|
|
543
|
-
runCloudWakeIfDue: heartbeatRunCloud,
|
|
544
|
-
},
|
|
545
|
-
scheduler: {
|
|
546
|
-
runOnce: mock(async () => 1),
|
|
547
|
-
runDueWorkOnce: schedulerRunDue,
|
|
548
|
-
},
|
|
549
|
-
});
|
|
550
|
-
const handler = findHandler("drainDueBackgroundWake");
|
|
551
|
-
|
|
552
|
-
const response = await handler({
|
|
553
|
-
body: drainBodyFixture({ reason: "refresh" }),
|
|
554
|
-
});
|
|
555
|
-
await flushBackgroundWakeDrainsForTest();
|
|
556
|
-
|
|
557
|
-
expect(heartbeatRunCloud).not.toHaveBeenCalled();
|
|
558
|
-
expect(schedulerRunDue).not.toHaveBeenCalled();
|
|
559
|
-
expect(response).toEqual(acceptedResponse({ reason: "refresh" }));
|
|
560
|
-
expect(lastCompletionPayload()).toMatchObject({
|
|
561
|
-
leaseId: "lease-123",
|
|
562
|
-
status: "completed",
|
|
563
|
-
});
|
|
564
|
-
});
|
|
565
|
-
|
|
566
|
-
test("drain-due honors deadline exhaustion before starting heartbeat work", async () => {
|
|
567
|
-
const dueAt = Date.now() - 1;
|
|
568
|
-
computedIntent = intentFixture({
|
|
569
|
-
nextWakeAt: dueAt,
|
|
570
|
-
actualNextDueAt: dueAt,
|
|
571
|
-
reason: "mixed",
|
|
572
|
-
});
|
|
573
|
-
const heartbeatRunCloud = mock(async () => ({
|
|
574
|
-
due: true,
|
|
575
|
-
completed: 1,
|
|
576
|
-
skipped: 0,
|
|
577
|
-
}));
|
|
578
|
-
const schedulerRunDue = mock(async () =>
|
|
579
|
-
schedulerResultFixture({ skipped: 2 }),
|
|
580
|
-
);
|
|
581
|
-
setBackgroundWakeRuntime({
|
|
582
|
-
heartbeat: {
|
|
583
|
-
nextRunAt: dueAt,
|
|
584
|
-
runCloudWakeIfDue: heartbeatRunCloud,
|
|
585
|
-
},
|
|
586
|
-
scheduler: {
|
|
587
|
-
runOnce: mock(async () => 0),
|
|
588
|
-
runDueWorkOnce: schedulerRunDue,
|
|
589
|
-
},
|
|
590
|
-
});
|
|
591
|
-
const handler = findHandler("drainDueBackgroundWake");
|
|
592
|
-
|
|
593
|
-
const response = await handler({
|
|
594
|
-
body: drainBodyFixture({
|
|
595
|
-
reason: "mixed",
|
|
596
|
-
deadlineAt: Date.now() + 1_000,
|
|
597
|
-
}),
|
|
598
|
-
});
|
|
599
|
-
await flushBackgroundWakeDrainsForTest();
|
|
600
|
-
|
|
601
|
-
expect(heartbeatRunCloud).not.toHaveBeenCalled();
|
|
602
|
-
expect(schedulerRunDue).toHaveBeenCalledWith(
|
|
603
|
-
expect.objectContaining({ minStartBudgetMs: 5_000 }),
|
|
604
|
-
);
|
|
605
|
-
expect(response).toMatchObject({
|
|
606
|
-
accepted: true,
|
|
607
|
-
reason: "mixed",
|
|
608
|
-
});
|
|
609
|
-
expect(lastCompletionPayload()).toMatchObject({
|
|
610
|
-
leaseId: "lease-123",
|
|
611
|
-
status: "completed",
|
|
612
|
-
});
|
|
613
|
-
});
|
|
614
|
-
|
|
615
|
-
test("drain-due completes expired without starting when the lease deadline already elapsed", async () => {
|
|
616
|
-
const dueAt = Date.now() - 1;
|
|
617
|
-
computedIntent = intentFixture({
|
|
618
|
-
nextWakeAt: dueAt,
|
|
619
|
-
actualNextDueAt: dueAt,
|
|
620
|
-
reason: "mixed",
|
|
621
|
-
});
|
|
622
|
-
const heartbeatRunCloud = mock(async () => ({
|
|
623
|
-
due: true,
|
|
624
|
-
completed: 1,
|
|
625
|
-
skipped: 0,
|
|
626
|
-
}));
|
|
627
|
-
const schedulerRunDue = mock(async () =>
|
|
628
|
-
schedulerResultFixture({ claimed: 1, completed: 1 }),
|
|
629
|
-
);
|
|
630
|
-
setBackgroundWakeRuntime({
|
|
631
|
-
heartbeat: {
|
|
632
|
-
nextRunAt: dueAt,
|
|
633
|
-
runCloudWakeIfDue: heartbeatRunCloud,
|
|
634
|
-
},
|
|
635
|
-
scheduler: {
|
|
636
|
-
runOnce: mock(async () => 1),
|
|
637
|
-
runDueWorkOnce: schedulerRunDue,
|
|
638
|
-
},
|
|
639
|
-
});
|
|
640
|
-
const handler = findHandler("drainDueBackgroundWake");
|
|
641
|
-
|
|
642
|
-
const response = await handler({
|
|
643
|
-
body: drainBodyFixture({
|
|
644
|
-
reason: "mixed",
|
|
645
|
-
deadlineAt: Date.now() - 1,
|
|
646
|
-
}),
|
|
647
|
-
});
|
|
648
|
-
await flushBackgroundWakeDrainsForTest();
|
|
649
|
-
|
|
650
|
-
expect(heartbeatRunCloud).not.toHaveBeenCalled();
|
|
651
|
-
expect(schedulerRunDue).not.toHaveBeenCalled();
|
|
652
|
-
expect(response).toMatchObject({
|
|
653
|
-
accepted: true,
|
|
654
|
-
reason: "mixed",
|
|
655
|
-
});
|
|
656
|
-
expect(lastCompletionPayload()).toMatchObject({
|
|
657
|
-
leaseId: "lease-123",
|
|
658
|
-
status: "expired",
|
|
659
|
-
error: "background wake lease deadline elapsed before starting work",
|
|
660
|
-
});
|
|
661
|
-
});
|
|
662
|
-
|
|
663
|
-
test("drain-due keeps a lease active until slow work settles after the original deadline", async () => {
|
|
664
|
-
const schedulerResolvers: Array<() => void> = [];
|
|
665
|
-
const schedulerRunDue = mock(async () => {
|
|
666
|
-
await new Promise<void>((resolve) => schedulerResolvers.push(resolve));
|
|
667
|
-
return schedulerResultFixture({ claimed: 1, completed: 1 });
|
|
668
|
-
});
|
|
669
|
-
setBackgroundWakeRuntime({
|
|
670
|
-
heartbeat: {
|
|
671
|
-
nextRunAt: Date.now() + 60_000,
|
|
672
|
-
runCloudWakeIfDue: mock(async () => ({
|
|
673
|
-
due: false,
|
|
674
|
-
completed: 0,
|
|
675
|
-
skipped: 0,
|
|
676
|
-
})),
|
|
677
|
-
},
|
|
678
|
-
scheduler: {
|
|
679
|
-
runOnce: mock(async () => 1),
|
|
680
|
-
runDueWorkOnce: schedulerRunDue,
|
|
681
|
-
},
|
|
682
|
-
});
|
|
683
|
-
const handler = findHandler("drainDueBackgroundWake");
|
|
684
|
-
// Keep the deadline far enough ahead that the synchronous start-check always
|
|
685
|
-
// sees it as live; the wait below outlasts it, so completion is verified to
|
|
686
|
-
// hold until the work settles rather than fire when the deadline elapses.
|
|
687
|
-
const deadlineAt = Date.now() + 200;
|
|
688
|
-
|
|
689
|
-
const firstResponse = await handler({
|
|
690
|
-
body: drainBodyFixture({
|
|
691
|
-
leaseId: "lease-long-running",
|
|
692
|
-
deadlineAt,
|
|
693
|
-
}),
|
|
694
|
-
});
|
|
695
|
-
|
|
696
|
-
try {
|
|
697
|
-
await new Promise((resolve) => setTimeout(resolve, 280));
|
|
698
|
-
expect(mockCompleteBackgroundWakeLease).not.toHaveBeenCalled();
|
|
699
|
-
|
|
700
|
-
const duplicateResponse = await handler({
|
|
701
|
-
body: drainBodyFixture({
|
|
702
|
-
leaseId: "lease-long-running",
|
|
703
|
-
deadlineAt,
|
|
704
|
-
}),
|
|
705
|
-
});
|
|
706
|
-
|
|
707
|
-
expect(firstResponse).toMatchObject({
|
|
708
|
-
accepted: true,
|
|
709
|
-
leaseId: "lease-long-running",
|
|
710
|
-
});
|
|
711
|
-
expect(duplicateResponse).toMatchObject({
|
|
712
|
-
accepted: true,
|
|
713
|
-
leaseId: "lease-long-running",
|
|
714
|
-
});
|
|
715
|
-
expect(schedulerRunDue).toHaveBeenCalledTimes(1);
|
|
716
|
-
} finally {
|
|
717
|
-
for (const resolve of schedulerResolvers) {
|
|
718
|
-
resolve();
|
|
719
|
-
}
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
await flushBackgroundWakeDrainsForTest();
|
|
723
|
-
expect(schedulerRunDue).toHaveBeenCalledTimes(1);
|
|
724
|
-
expect(mockCompleteBackgroundWakeLease).toHaveBeenCalledTimes(1);
|
|
725
|
-
expect(lastCompletionPayload()).toMatchObject({
|
|
726
|
-
leaseId: "lease-long-running",
|
|
727
|
-
status: "completed",
|
|
728
|
-
});
|
|
729
|
-
});
|
|
730
|
-
|
|
731
|
-
test("drain-due runs scheduler work even when pending work remains", async () => {
|
|
732
|
-
const schedulerRunDue = mock(async () =>
|
|
733
|
-
schedulerResultFixture({ claimed: 1, completed: 1 }),
|
|
734
|
-
);
|
|
735
|
-
setBackgroundWakeRuntime({
|
|
736
|
-
heartbeat: {
|
|
737
|
-
nextRunAt: Date.now() + 60_000,
|
|
738
|
-
runCloudWakeIfDue: mock(async () => ({
|
|
739
|
-
due: false,
|
|
740
|
-
completed: 0,
|
|
741
|
-
skipped: 0,
|
|
742
|
-
})),
|
|
743
|
-
},
|
|
744
|
-
scheduler: {
|
|
745
|
-
runOnce: mock(async () => 1),
|
|
746
|
-
runDueWorkOnce: schedulerRunDue,
|
|
747
|
-
},
|
|
748
|
-
});
|
|
749
|
-
const handler = findHandler("drainDueBackgroundWake");
|
|
750
|
-
|
|
751
|
-
const response = await handler({ body: drainBodyFixture() });
|
|
752
|
-
await flushBackgroundWakeDrainsForTest();
|
|
753
|
-
|
|
754
|
-
expect(schedulerRunDue).toHaveBeenCalledTimes(1);
|
|
755
|
-
expect(response).toEqual(acceptedResponse());
|
|
756
|
-
expect(lastCompletionPayload()).toMatchObject({
|
|
757
|
-
leaseId: "lease-123",
|
|
758
|
-
status: "completed",
|
|
759
|
-
});
|
|
760
|
-
});
|
|
761
|
-
|
|
762
|
-
test("drain-due completes failed when due work throws", async () => {
|
|
763
|
-
const schedulerRunDue = mock(async () => {
|
|
764
|
-
throw new Error("scheduler exploded");
|
|
765
|
-
});
|
|
766
|
-
setBackgroundWakeRuntime({
|
|
767
|
-
heartbeat: {
|
|
768
|
-
nextRunAt: null,
|
|
769
|
-
runCloudWakeIfDue: mock(async () => ({
|
|
770
|
-
due: false,
|
|
771
|
-
completed: 0,
|
|
772
|
-
skipped: 0,
|
|
773
|
-
})),
|
|
774
|
-
},
|
|
775
|
-
scheduler: {
|
|
776
|
-
runOnce: mock(async () => 0),
|
|
777
|
-
runDueWorkOnce: schedulerRunDue,
|
|
778
|
-
},
|
|
779
|
-
});
|
|
780
|
-
const handler = findHandler("drainDueBackgroundWake");
|
|
781
|
-
|
|
782
|
-
const response = await handler({ body: drainBodyFixture() });
|
|
783
|
-
await flushBackgroundWakeDrainsForTest();
|
|
784
|
-
|
|
785
|
-
expect(response).toEqual(acceptedResponse());
|
|
786
|
-
expect(lastCompletionPayload()).toMatchObject({
|
|
787
|
-
leaseId: "lease-123",
|
|
788
|
-
status: "failed",
|
|
789
|
-
error: "scheduler exploded",
|
|
790
|
-
});
|
|
791
|
-
});
|
|
792
|
-
|
|
793
|
-
test("drain-due does not reclassify successful work when completion reporting fails", async () => {
|
|
794
|
-
const completionError = new Error("platform unavailable");
|
|
795
|
-
mockCompleteBackgroundWakeLease.mockImplementation(async (args) => {
|
|
796
|
-
if (args.status === "completed" || args.status === "expired") {
|
|
797
|
-
throw completionError;
|
|
798
|
-
}
|
|
799
|
-
return { status: "completed" as const, httpStatus: 200 };
|
|
800
|
-
});
|
|
801
|
-
const schedulerRunDue = mock(async () => schedulerResultFixture());
|
|
802
|
-
setBackgroundWakeRuntime({
|
|
803
|
-
heartbeat: {
|
|
804
|
-
nextRunAt: null,
|
|
805
|
-
runCloudWakeIfDue: mock(async () => ({
|
|
806
|
-
due: false,
|
|
807
|
-
completed: 0,
|
|
808
|
-
skipped: 0,
|
|
809
|
-
})),
|
|
810
|
-
},
|
|
811
|
-
scheduler: {
|
|
812
|
-
runOnce: mock(async () => 0),
|
|
813
|
-
runDueWorkOnce: schedulerRunDue,
|
|
814
|
-
},
|
|
815
|
-
});
|
|
816
|
-
const handler = findHandler("drainDueBackgroundWake");
|
|
817
|
-
|
|
818
|
-
const response = await handler({ body: drainBodyFixture() });
|
|
819
|
-
await flushBackgroundWakeDrainsForTest();
|
|
820
|
-
|
|
821
|
-
expect(response).toEqual(acceptedResponse());
|
|
822
|
-
expect(mockCompleteBackgroundWakeLease).toHaveBeenCalledTimes(1);
|
|
823
|
-
expect(lastCompletionPayload()).toMatchObject({
|
|
824
|
-
leaseId: "lease-123",
|
|
825
|
-
status: "completed",
|
|
826
|
-
});
|
|
827
|
-
});
|
|
828
|
-
|
|
829
|
-
test("drain-due accepts vembda snake_case payload aliases", async () => {
|
|
830
|
-
const schedulerRunDue = mock(async () => schedulerResultFixture());
|
|
831
|
-
setBackgroundWakeRuntime({
|
|
832
|
-
heartbeat: {
|
|
833
|
-
nextRunAt: null,
|
|
834
|
-
runCloudWakeIfDue: mock(async () => ({
|
|
835
|
-
due: false,
|
|
836
|
-
completed: 0,
|
|
837
|
-
skipped: 0,
|
|
838
|
-
})),
|
|
839
|
-
},
|
|
840
|
-
scheduler: {
|
|
841
|
-
runOnce: mock(async () => 0),
|
|
842
|
-
runDueWorkOnce: schedulerRunDue,
|
|
843
|
-
},
|
|
844
|
-
});
|
|
845
|
-
const handler = findHandler("drainDueBackgroundWake");
|
|
846
|
-
|
|
847
|
-
const response = await handler({
|
|
848
|
-
body: {
|
|
849
|
-
lease_id: "lease-snake",
|
|
850
|
-
reason: "refresh",
|
|
851
|
-
source_generation: "generation-snake",
|
|
852
|
-
started_at: new Date(NOW).toISOString(),
|
|
853
|
-
deadline_at: String(NOW + 30_000),
|
|
854
|
-
},
|
|
855
|
-
});
|
|
856
|
-
await flushBackgroundWakeDrainsForTest();
|
|
857
|
-
|
|
858
|
-
expect(response).toMatchObject({
|
|
859
|
-
accepted: true,
|
|
860
|
-
leaseId: "lease-snake",
|
|
861
|
-
reason: "refresh",
|
|
862
|
-
sourceGeneration: "generation-snake",
|
|
863
|
-
startedAt: NOW,
|
|
864
|
-
deadlineAt: NOW + 30_000,
|
|
865
|
-
});
|
|
866
|
-
expect(lastCompletionPayload()).toMatchObject({
|
|
867
|
-
leaseId: "lease-snake",
|
|
868
|
-
status: "completed",
|
|
869
|
-
});
|
|
870
|
-
});
|
|
871
|
-
});
|
|
872
|
-
|
|
873
|
-
function drainBodyFixture(
|
|
874
|
-
overrides: Partial<{
|
|
875
|
-
leaseId: string;
|
|
876
|
-
reason: string;
|
|
877
|
-
sourceGeneration: string;
|
|
878
|
-
startedAt: number;
|
|
879
|
-
deadlineAt: number;
|
|
880
|
-
}> = {},
|
|
881
|
-
) {
|
|
882
|
-
return {
|
|
883
|
-
leaseId: "lease-123",
|
|
884
|
-
reason: "schedule",
|
|
885
|
-
sourceGeneration: "source-generation",
|
|
886
|
-
startedAt: NOW,
|
|
887
|
-
deadlineAt: NOW + 30_000,
|
|
888
|
-
...overrides,
|
|
889
|
-
};
|
|
890
|
-
}
|
|
891
|
-
|
|
892
|
-
function intentFixture(overrides: Partial<MockIntent>): MockIntent {
|
|
893
|
-
const nextWakeAt = overrides.nextWakeAt ?? NOW + 60_000;
|
|
894
|
-
return {
|
|
895
|
-
nextWakeAt,
|
|
896
|
-
actualNextDueAt: nextWakeAt,
|
|
897
|
-
reason: "schedule",
|
|
898
|
-
sourceGeneration: "source-generation",
|
|
899
|
-
computedAt: NOW - 1_000,
|
|
900
|
-
sourcePayload: {
|
|
901
|
-
heartbeat: null,
|
|
902
|
-
schedules: [],
|
|
903
|
-
},
|
|
904
|
-
...overrides,
|
|
905
|
-
};
|
|
906
|
-
}
|