@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,1217 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for the transfer progress screen view model.
|
|
3
|
-
*
|
|
4
|
-
* Covers:
|
|
5
|
-
* - Disabled state: screen not accessible when earlier steps incomplete
|
|
6
|
-
* - Exporting state: export phase in progress
|
|
7
|
-
* - Polling state: managed async export job polling
|
|
8
|
-
* - Importing state: import phase in progress
|
|
9
|
-
* - Success state: full import summary with details
|
|
10
|
-
* - Error states: retryable vs non-retryable, failed phase inference
|
|
11
|
-
* - Retry behavior: reset and re-execute after error
|
|
12
|
-
* - Back navigation: return to preflight-review step
|
|
13
|
-
* - Continue action: proceed to rebind-secrets step
|
|
14
|
-
* - Transfer flow execution: runtime (sync) and managed (async) flows
|
|
15
|
-
* - State persistence/resume: derive correct screen state after resume
|
|
16
|
-
* - State change callbacks: UI reactivity during transfer
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
import { describe, expect, test } from "bun:test";
|
|
20
|
-
|
|
21
|
-
import type {
|
|
22
|
-
ExportManagedResult,
|
|
23
|
-
ImportCommitResponse,
|
|
24
|
-
ImportPreflightResponse,
|
|
25
|
-
TransportConfig,
|
|
26
|
-
ValidateResponse,
|
|
27
|
-
} from "../runtime/migrations/migration-transport.js";
|
|
28
|
-
import type {
|
|
29
|
-
MigrationWizardState,
|
|
30
|
-
StepExecutorOptions,
|
|
31
|
-
WizardStep,
|
|
32
|
-
} from "../runtime/migrations/migration-wizard.js";
|
|
33
|
-
import {
|
|
34
|
-
completeRebindSecrets,
|
|
35
|
-
createWizardState,
|
|
36
|
-
deserializeWizardState,
|
|
37
|
-
prepareForResume,
|
|
38
|
-
selectDirection,
|
|
39
|
-
serializeWizardState,
|
|
40
|
-
setBundleUploaded,
|
|
41
|
-
} from "../runtime/migrations/migration-wizard.js";
|
|
42
|
-
import type { TransferScreenState } from "../runtime/migrations/transfer-progress-screen.js";
|
|
43
|
-
import {
|
|
44
|
-
deriveTransferScreenState,
|
|
45
|
-
executeTransferFlow,
|
|
46
|
-
goBackToPreflight,
|
|
47
|
-
isTransferScreenAccessible,
|
|
48
|
-
retryTransferFlow,
|
|
49
|
-
} from "../runtime/migrations/transfer-progress-screen.js";
|
|
50
|
-
|
|
51
|
-
// ---------------------------------------------------------------------------
|
|
52
|
-
// Test helpers
|
|
53
|
-
// ---------------------------------------------------------------------------
|
|
54
|
-
|
|
55
|
-
function mockFetch(
|
|
56
|
-
status: number,
|
|
57
|
-
body: unknown,
|
|
58
|
-
headers?: Record<string, string>,
|
|
59
|
-
): typeof fetch {
|
|
60
|
-
return (async () => {
|
|
61
|
-
const responseHeaders = new Headers(headers);
|
|
62
|
-
if (
|
|
63
|
-
typeof body === "object" &&
|
|
64
|
-
body !== undefined &&
|
|
65
|
-
!(body instanceof ArrayBuffer)
|
|
66
|
-
) {
|
|
67
|
-
responseHeaders.set("Content-Type", "application/json");
|
|
68
|
-
return new Response(JSON.stringify(body), {
|
|
69
|
-
status,
|
|
70
|
-
headers: responseHeaders,
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
if (body instanceof ArrayBuffer) {
|
|
74
|
-
return new Response(body, { status, headers: responseHeaders });
|
|
75
|
-
}
|
|
76
|
-
return new Response(String(body), { status, headers: responseHeaders });
|
|
77
|
-
}) as unknown as typeof fetch;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function runtimeConfig(overrides?: Partial<TransportConfig>): TransportConfig {
|
|
81
|
-
return {
|
|
82
|
-
baseURL: "http://localhost:7821",
|
|
83
|
-
target: "runtime",
|
|
84
|
-
authHeader: "Bearer test-jwt",
|
|
85
|
-
fetchFn: mockFetch(200, {}),
|
|
86
|
-
...overrides,
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function managedConfig(overrides?: Partial<TransportConfig>): TransportConfig {
|
|
91
|
-
return {
|
|
92
|
-
baseURL: "https://cloud.mycadet.ai",
|
|
93
|
-
target: "managed",
|
|
94
|
-
authHeader: "session-token-abc",
|
|
95
|
-
fetchFn: mockFetch(200, {}),
|
|
96
|
-
...overrides,
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function makeExecutorOptions(
|
|
101
|
-
overrides?: Partial<StepExecutorOptions>,
|
|
102
|
-
): StepExecutorOptions {
|
|
103
|
-
return {
|
|
104
|
-
sourceConfig: runtimeConfig(),
|
|
105
|
-
destConfig: runtimeConfig(),
|
|
106
|
-
bundleData: new ArrayBuffer(16),
|
|
107
|
-
...overrides,
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
function makeValidateSuccess(): ValidateResponse {
|
|
112
|
-
return {
|
|
113
|
-
is_valid: true,
|
|
114
|
-
errors: [],
|
|
115
|
-
manifest: {
|
|
116
|
-
schema_version: 1,
|
|
117
|
-
bundle_id: "00000000-0000-4000-8000-000000000000",
|
|
118
|
-
created_at: "2026-03-01T00:00:00Z",
|
|
119
|
-
assistant: { id: "self", name: "Test", runtime_version: "0.0.0-test" },
|
|
120
|
-
origin: { mode: "self-hosted-local" },
|
|
121
|
-
compatibility: {
|
|
122
|
-
min_runtime_version: "0.0.0-test",
|
|
123
|
-
max_runtime_version: null,
|
|
124
|
-
},
|
|
125
|
-
contents: [{ path: "config.json", sha256: "abc123", size_bytes: 1024 }],
|
|
126
|
-
checksum: "manifest-hash",
|
|
127
|
-
secrets_redacted: false,
|
|
128
|
-
export_options: {
|
|
129
|
-
include_logs: false,
|
|
130
|
-
include_browser_state: false,
|
|
131
|
-
include_memory_vectors: false,
|
|
132
|
-
},
|
|
133
|
-
},
|
|
134
|
-
};
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
function makePreflightSuccess(): ImportPreflightResponse {
|
|
138
|
-
return {
|
|
139
|
-
can_import: true,
|
|
140
|
-
summary: {
|
|
141
|
-
total_files: 3,
|
|
142
|
-
files_to_create: 1,
|
|
143
|
-
files_to_overwrite: 1,
|
|
144
|
-
files_unchanged: 1,
|
|
145
|
-
files_to_skip: 0,
|
|
146
|
-
},
|
|
147
|
-
files: [
|
|
148
|
-
{
|
|
149
|
-
path: "config.json",
|
|
150
|
-
action: "overwrite",
|
|
151
|
-
bundle_size: 1024,
|
|
152
|
-
current_size: 800,
|
|
153
|
-
bundle_sha256: "abc123",
|
|
154
|
-
current_sha256: "old123",
|
|
155
|
-
},
|
|
156
|
-
],
|
|
157
|
-
conflicts: [],
|
|
158
|
-
manifest: {
|
|
159
|
-
schema_version: 1,
|
|
160
|
-
bundle_id: "00000000-0000-4000-8000-000000000000",
|
|
161
|
-
created_at: "2026-03-01T00:00:00Z",
|
|
162
|
-
assistant: { id: "self", name: "Test", runtime_version: "0.0.0-test" },
|
|
163
|
-
origin: { mode: "self-hosted-local" },
|
|
164
|
-
compatibility: {
|
|
165
|
-
min_runtime_version: "0.0.0-test",
|
|
166
|
-
max_runtime_version: null,
|
|
167
|
-
},
|
|
168
|
-
contents: [{ path: "config.json", sha256: "abc123", size_bytes: 1024 }],
|
|
169
|
-
checksum: "manifest-hash",
|
|
170
|
-
secrets_redacted: false,
|
|
171
|
-
export_options: {
|
|
172
|
-
include_logs: false,
|
|
173
|
-
include_browser_state: false,
|
|
174
|
-
include_memory_vectors: false,
|
|
175
|
-
},
|
|
176
|
-
},
|
|
177
|
-
};
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
function makeImportSuccess(): ImportCommitResponse {
|
|
181
|
-
return {
|
|
182
|
-
success: true,
|
|
183
|
-
summary: {
|
|
184
|
-
total_files: 3,
|
|
185
|
-
files_created: 1,
|
|
186
|
-
files_overwritten: 1,
|
|
187
|
-
files_skipped: 1,
|
|
188
|
-
backups_created: 1,
|
|
189
|
-
},
|
|
190
|
-
files: [
|
|
191
|
-
{
|
|
192
|
-
path: "config.json",
|
|
193
|
-
disk_path: "/data/config.json",
|
|
194
|
-
action: "overwritten",
|
|
195
|
-
size: 1024,
|
|
196
|
-
sha256: "abc123",
|
|
197
|
-
backup_path: "/data/config.json.bak",
|
|
198
|
-
},
|
|
199
|
-
],
|
|
200
|
-
manifest: {
|
|
201
|
-
schema_version: 1,
|
|
202
|
-
bundle_id: "00000000-0000-4000-8000-000000000000",
|
|
203
|
-
created_at: "2026-03-01T00:00:00Z",
|
|
204
|
-
assistant: { id: "self", name: "Test", runtime_version: "0.0.0-test" },
|
|
205
|
-
origin: { mode: "self-hosted-local" },
|
|
206
|
-
compatibility: {
|
|
207
|
-
min_runtime_version: "0.0.0-test",
|
|
208
|
-
max_runtime_version: null,
|
|
209
|
-
},
|
|
210
|
-
contents: [{ path: "config.json", sha256: "abc123", size_bytes: 1024 }],
|
|
211
|
-
checksum: "manifest-hash",
|
|
212
|
-
secrets_redacted: false,
|
|
213
|
-
export_options: {
|
|
214
|
-
include_logs: false,
|
|
215
|
-
include_browser_state: false,
|
|
216
|
-
include_memory_vectors: false,
|
|
217
|
-
},
|
|
218
|
-
},
|
|
219
|
-
warnings: ["Backup created for config.json"],
|
|
220
|
-
};
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
/** Advance state to a specific step for testing. */
|
|
224
|
-
function advanceTo(step: WizardStep): MigrationWizardState {
|
|
225
|
-
let state = createWizardState();
|
|
226
|
-
const stepOrder: WizardStep[] = [
|
|
227
|
-
"select-direction",
|
|
228
|
-
"upload-bundle",
|
|
229
|
-
"validate",
|
|
230
|
-
"preflight-review",
|
|
231
|
-
"transfer",
|
|
232
|
-
"rebind-secrets",
|
|
233
|
-
"complete",
|
|
234
|
-
];
|
|
235
|
-
const targetIdx = stepOrder.indexOf(step);
|
|
236
|
-
|
|
237
|
-
if (targetIdx >= 1) {
|
|
238
|
-
state = selectDirection(state, "managed-to-self-hosted");
|
|
239
|
-
}
|
|
240
|
-
if (targetIdx >= 2) {
|
|
241
|
-
state = setBundleUploaded(state);
|
|
242
|
-
}
|
|
243
|
-
if (targetIdx >= 3) {
|
|
244
|
-
state = {
|
|
245
|
-
...state,
|
|
246
|
-
steps: { ...state.steps, validate: { status: "success" } },
|
|
247
|
-
validateResult: makeValidateSuccess(),
|
|
248
|
-
currentStep: "preflight-review",
|
|
249
|
-
};
|
|
250
|
-
}
|
|
251
|
-
if (targetIdx >= 4) {
|
|
252
|
-
state = {
|
|
253
|
-
...state,
|
|
254
|
-
steps: { ...state.steps, "preflight-review": { status: "success" } },
|
|
255
|
-
preflightResult: makePreflightSuccess(),
|
|
256
|
-
currentStep: "transfer",
|
|
257
|
-
};
|
|
258
|
-
}
|
|
259
|
-
if (targetIdx >= 5) {
|
|
260
|
-
state = {
|
|
261
|
-
...state,
|
|
262
|
-
steps: { ...state.steps, transfer: { status: "success" } },
|
|
263
|
-
importResult: makeImportSuccess(),
|
|
264
|
-
currentStep: "rebind-secrets",
|
|
265
|
-
};
|
|
266
|
-
}
|
|
267
|
-
if (targetIdx >= 6) {
|
|
268
|
-
state = completeRebindSecrets(state);
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
return state;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
// ---------------------------------------------------------------------------
|
|
275
|
-
// Disabled state
|
|
276
|
-
// ---------------------------------------------------------------------------
|
|
277
|
-
|
|
278
|
-
describe("deriveTransferScreenState -- disabled", () => {
|
|
279
|
-
test("returns disabled when on select-direction step", () => {
|
|
280
|
-
const state = createWizardState();
|
|
281
|
-
const screen = deriveTransferScreenState(state);
|
|
282
|
-
expect(screen.phase).toBe("disabled");
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
test("returns disabled when on upload-bundle step", () => {
|
|
286
|
-
const state = advanceTo("upload-bundle");
|
|
287
|
-
const screen = deriveTransferScreenState(state);
|
|
288
|
-
expect(screen.phase).toBe("disabled");
|
|
289
|
-
});
|
|
290
|
-
|
|
291
|
-
test("returns disabled when on validate step", () => {
|
|
292
|
-
const state = advanceTo("validate");
|
|
293
|
-
const screen = deriveTransferScreenState(state);
|
|
294
|
-
expect(screen.phase).toBe("disabled");
|
|
295
|
-
});
|
|
296
|
-
|
|
297
|
-
test("returns disabled when on preflight-review step (not yet completed)", () => {
|
|
298
|
-
const state = advanceTo("preflight-review");
|
|
299
|
-
const screen = deriveTransferScreenState(state);
|
|
300
|
-
expect(screen.phase).toBe("disabled");
|
|
301
|
-
});
|
|
302
|
-
|
|
303
|
-
test("isTransferScreenAccessible returns false for early steps", () => {
|
|
304
|
-
expect(isTransferScreenAccessible(createWizardState())).toBe(false);
|
|
305
|
-
});
|
|
306
|
-
|
|
307
|
-
test("isTransferScreenAccessible returns false for validate step", () => {
|
|
308
|
-
const state = advanceTo("validate");
|
|
309
|
-
expect(isTransferScreenAccessible(state)).toBe(false);
|
|
310
|
-
});
|
|
311
|
-
|
|
312
|
-
test("isTransferScreenAccessible returns true for transfer step", () => {
|
|
313
|
-
const state = advanceTo("transfer");
|
|
314
|
-
expect(isTransferScreenAccessible(state)).toBe(true);
|
|
315
|
-
});
|
|
316
|
-
});
|
|
317
|
-
|
|
318
|
-
// ---------------------------------------------------------------------------
|
|
319
|
-
// Importing state (default phase -- import-only flow)
|
|
320
|
-
// ---------------------------------------------------------------------------
|
|
321
|
-
|
|
322
|
-
describe("deriveTransferScreenState -- importing (default)", () => {
|
|
323
|
-
test("shows importing when transfer step is idle (preparing)", () => {
|
|
324
|
-
const state = advanceTo("transfer");
|
|
325
|
-
const screen = deriveTransferScreenState(state);
|
|
326
|
-
expect(screen.phase).toBe("importing");
|
|
327
|
-
if (screen.phase === "importing") {
|
|
328
|
-
expect(screen.message).toContain("Importing");
|
|
329
|
-
}
|
|
330
|
-
});
|
|
331
|
-
|
|
332
|
-
test("shows importing when transfer step is loading with no export result", () => {
|
|
333
|
-
let state = advanceTo("transfer");
|
|
334
|
-
state = {
|
|
335
|
-
...state,
|
|
336
|
-
steps: { ...state.steps, transfer: { status: "loading" } },
|
|
337
|
-
};
|
|
338
|
-
const screen = deriveTransferScreenState(state);
|
|
339
|
-
expect(screen.phase).toBe("importing");
|
|
340
|
-
if (screen.phase === "importing") {
|
|
341
|
-
expect(screen.message).toContain("Importing");
|
|
342
|
-
}
|
|
343
|
-
});
|
|
344
|
-
});
|
|
345
|
-
|
|
346
|
-
// ---------------------------------------------------------------------------
|
|
347
|
-
// Polling state
|
|
348
|
-
// ---------------------------------------------------------------------------
|
|
349
|
-
|
|
350
|
-
describe("deriveTransferScreenState -- polling", () => {
|
|
351
|
-
test("shows polling when managed export result exists with pending status", () => {
|
|
352
|
-
let state = advanceTo("transfer");
|
|
353
|
-
state = {
|
|
354
|
-
...state,
|
|
355
|
-
steps: { ...state.steps, transfer: { status: "loading" } },
|
|
356
|
-
exportResult: {
|
|
357
|
-
ok: true,
|
|
358
|
-
jobId: "exp-123",
|
|
359
|
-
status: "pending",
|
|
360
|
-
} as ExportManagedResult,
|
|
361
|
-
};
|
|
362
|
-
const screen = deriveTransferScreenState(state);
|
|
363
|
-
expect(screen.phase).toBe("polling");
|
|
364
|
-
if (screen.phase === "polling") {
|
|
365
|
-
expect(screen.jobId).toBe("exp-123");
|
|
366
|
-
expect(screen.message).toContain("export job");
|
|
367
|
-
}
|
|
368
|
-
});
|
|
369
|
-
|
|
370
|
-
test("shows polling when managed export result exists with processing status", () => {
|
|
371
|
-
let state = advanceTo("transfer");
|
|
372
|
-
state = {
|
|
373
|
-
...state,
|
|
374
|
-
steps: { ...state.steps, transfer: { status: "loading" } },
|
|
375
|
-
exportResult: {
|
|
376
|
-
ok: true,
|
|
377
|
-
jobId: "exp-456",
|
|
378
|
-
status: "processing",
|
|
379
|
-
} as ExportManagedResult,
|
|
380
|
-
};
|
|
381
|
-
const screen = deriveTransferScreenState(state);
|
|
382
|
-
expect(screen.phase).toBe("polling");
|
|
383
|
-
if (screen.phase === "polling") {
|
|
384
|
-
expect(screen.jobId).toBe("exp-456");
|
|
385
|
-
}
|
|
386
|
-
});
|
|
387
|
-
});
|
|
388
|
-
|
|
389
|
-
// ---------------------------------------------------------------------------
|
|
390
|
-
// Importing state
|
|
391
|
-
// ---------------------------------------------------------------------------
|
|
392
|
-
|
|
393
|
-
describe("deriveTransferScreenState -- importing", () => {
|
|
394
|
-
test("shows importing when runtime export result exists but no import result", () => {
|
|
395
|
-
let state = advanceTo("transfer");
|
|
396
|
-
state = {
|
|
397
|
-
...state,
|
|
398
|
-
steps: { ...state.steps, transfer: { status: "loading" } },
|
|
399
|
-
exportResult: {
|
|
400
|
-
ok: true,
|
|
401
|
-
filename: "export.vbundle",
|
|
402
|
-
schemaVersion: 1,
|
|
403
|
-
checksum: "abc",
|
|
404
|
-
},
|
|
405
|
-
};
|
|
406
|
-
const screen = deriveTransferScreenState(state);
|
|
407
|
-
expect(screen.phase).toBe("importing");
|
|
408
|
-
if (screen.phase === "importing") {
|
|
409
|
-
expect(screen.message).toContain("Importing");
|
|
410
|
-
}
|
|
411
|
-
});
|
|
412
|
-
|
|
413
|
-
test("shows importing when managed export completed and import in progress", () => {
|
|
414
|
-
let state = advanceTo("transfer");
|
|
415
|
-
state = {
|
|
416
|
-
...state,
|
|
417
|
-
steps: { ...state.steps, transfer: { status: "loading" } },
|
|
418
|
-
exportResult: {
|
|
419
|
-
ok: true,
|
|
420
|
-
jobId: "exp-789",
|
|
421
|
-
status: "complete",
|
|
422
|
-
} as ExportManagedResult,
|
|
423
|
-
};
|
|
424
|
-
const screen = deriveTransferScreenState(state);
|
|
425
|
-
expect(screen.phase).toBe("importing");
|
|
426
|
-
if (screen.phase === "importing") {
|
|
427
|
-
expect(screen.message).toContain("Importing");
|
|
428
|
-
}
|
|
429
|
-
});
|
|
430
|
-
});
|
|
431
|
-
|
|
432
|
-
// ---------------------------------------------------------------------------
|
|
433
|
-
// Success state
|
|
434
|
-
// ---------------------------------------------------------------------------
|
|
435
|
-
|
|
436
|
-
describe("deriveTransferScreenState -- success", () => {
|
|
437
|
-
test("shows success with import summary after transfer completes", () => {
|
|
438
|
-
const state = advanceTo("rebind-secrets");
|
|
439
|
-
const screen = deriveTransferScreenState(state);
|
|
440
|
-
expect(screen.phase).toBe("success");
|
|
441
|
-
if (screen.phase === "success") {
|
|
442
|
-
expect(screen.importSummary.totalFiles).toBe(3);
|
|
443
|
-
expect(screen.importSummary.filesCreated).toBe(1);
|
|
444
|
-
expect(screen.importSummary.filesOverwritten).toBe(1);
|
|
445
|
-
expect(screen.importSummary.filesSkipped).toBe(1);
|
|
446
|
-
expect(screen.importSummary.backupsCreated).toBe(1);
|
|
447
|
-
expect(screen.importSummary.warnings).toHaveLength(1);
|
|
448
|
-
expect(screen.importSummary.warnings[0]).toContain("Backup");
|
|
449
|
-
}
|
|
450
|
-
});
|
|
451
|
-
|
|
452
|
-
test("canContinue is true when rebind-secrets step is accessible", () => {
|
|
453
|
-
const state = advanceTo("rebind-secrets");
|
|
454
|
-
const screen = deriveTransferScreenState(state);
|
|
455
|
-
if (screen.phase === "success") {
|
|
456
|
-
expect(screen.canContinue).toBe(true);
|
|
457
|
-
}
|
|
458
|
-
});
|
|
459
|
-
|
|
460
|
-
test("success state persists when viewing from later steps", () => {
|
|
461
|
-
const state = advanceTo("complete");
|
|
462
|
-
const screen = deriveTransferScreenState(state);
|
|
463
|
-
expect(screen.phase).toBe("success");
|
|
464
|
-
});
|
|
465
|
-
|
|
466
|
-
test("shows success with empty summary when importResult is missing (defensive)", () => {
|
|
467
|
-
let state = advanceTo("transfer");
|
|
468
|
-
state = {
|
|
469
|
-
...state,
|
|
470
|
-
steps: { ...state.steps, transfer: { status: "success" } },
|
|
471
|
-
currentStep: "rebind-secrets",
|
|
472
|
-
// No importResult
|
|
473
|
-
};
|
|
474
|
-
const screen = deriveTransferScreenState(state);
|
|
475
|
-
expect(screen.phase).toBe("success");
|
|
476
|
-
if (screen.phase === "success") {
|
|
477
|
-
expect(screen.importSummary.totalFiles).toBe(0);
|
|
478
|
-
}
|
|
479
|
-
});
|
|
480
|
-
});
|
|
481
|
-
|
|
482
|
-
// ---------------------------------------------------------------------------
|
|
483
|
-
// Error states
|
|
484
|
-
// ---------------------------------------------------------------------------
|
|
485
|
-
|
|
486
|
-
describe("deriveTransferScreenState -- error", () => {
|
|
487
|
-
test("shows error when transfer step fails with retryable error", () => {
|
|
488
|
-
let state = advanceTo("transfer");
|
|
489
|
-
state = {
|
|
490
|
-
...state,
|
|
491
|
-
steps: {
|
|
492
|
-
...state.steps,
|
|
493
|
-
transfer: {
|
|
494
|
-
status: "error",
|
|
495
|
-
error: {
|
|
496
|
-
message: "export: HTTP 500",
|
|
497
|
-
code: "HTTP_500",
|
|
498
|
-
retryable: true,
|
|
499
|
-
},
|
|
500
|
-
},
|
|
501
|
-
},
|
|
502
|
-
};
|
|
503
|
-
const screen = deriveTransferScreenState(state);
|
|
504
|
-
expect(screen.phase).toBe("error");
|
|
505
|
-
if (screen.phase === "error") {
|
|
506
|
-
expect(screen.error.message).toContain("HTTP 500");
|
|
507
|
-
expect(screen.canRetry).toBe(true);
|
|
508
|
-
}
|
|
509
|
-
});
|
|
510
|
-
|
|
511
|
-
test("shows error when transfer step fails with non-retryable error", () => {
|
|
512
|
-
let state = advanceTo("transfer");
|
|
513
|
-
state = {
|
|
514
|
-
...state,
|
|
515
|
-
steps: {
|
|
516
|
-
...state.steps,
|
|
517
|
-
transfer: {
|
|
518
|
-
status: "error",
|
|
519
|
-
error: {
|
|
520
|
-
message: "import: HTTP 400",
|
|
521
|
-
code: "HTTP_400",
|
|
522
|
-
retryable: false,
|
|
523
|
-
},
|
|
524
|
-
},
|
|
525
|
-
},
|
|
526
|
-
};
|
|
527
|
-
const screen = deriveTransferScreenState(state);
|
|
528
|
-
expect(screen.phase).toBe("error");
|
|
529
|
-
if (screen.phase === "error") {
|
|
530
|
-
expect(screen.canRetry).toBe(false);
|
|
531
|
-
}
|
|
532
|
-
});
|
|
533
|
-
|
|
534
|
-
test("infers failed phase as import when no export result exists (import-only flow)", () => {
|
|
535
|
-
let state = advanceTo("transfer");
|
|
536
|
-
state = {
|
|
537
|
-
...state,
|
|
538
|
-
steps: {
|
|
539
|
-
...state.steps,
|
|
540
|
-
transfer: {
|
|
541
|
-
status: "error",
|
|
542
|
-
error: {
|
|
543
|
-
message: "import: HTTP 503",
|
|
544
|
-
code: "HTTP_503",
|
|
545
|
-
retryable: true,
|
|
546
|
-
},
|
|
547
|
-
},
|
|
548
|
-
},
|
|
549
|
-
};
|
|
550
|
-
const screen = deriveTransferScreenState(state);
|
|
551
|
-
if (screen.phase === "error") {
|
|
552
|
-
expect(screen.failedPhase).toBe("import");
|
|
553
|
-
}
|
|
554
|
-
});
|
|
555
|
-
|
|
556
|
-
test("infers failed phase as export for EXPORT_JOB_FAILED code", () => {
|
|
557
|
-
let state = advanceTo("transfer");
|
|
558
|
-
state = {
|
|
559
|
-
...state,
|
|
560
|
-
steps: {
|
|
561
|
-
...state.steps,
|
|
562
|
-
transfer: {
|
|
563
|
-
status: "error",
|
|
564
|
-
error: {
|
|
565
|
-
message: "Export job failed: Timeout",
|
|
566
|
-
code: "EXPORT_JOB_FAILED",
|
|
567
|
-
retryable: true,
|
|
568
|
-
},
|
|
569
|
-
},
|
|
570
|
-
},
|
|
571
|
-
exportResult: {
|
|
572
|
-
ok: true,
|
|
573
|
-
jobId: "exp-fail",
|
|
574
|
-
status: "pending",
|
|
575
|
-
} as ExportManagedResult,
|
|
576
|
-
};
|
|
577
|
-
const screen = deriveTransferScreenState(state);
|
|
578
|
-
if (screen.phase === "error") {
|
|
579
|
-
expect(screen.failedPhase).toBe("export");
|
|
580
|
-
}
|
|
581
|
-
});
|
|
582
|
-
|
|
583
|
-
test("infers failed phase as export for NO_DOWNLOAD_URL code", () => {
|
|
584
|
-
let state = advanceTo("transfer");
|
|
585
|
-
state = {
|
|
586
|
-
...state,
|
|
587
|
-
steps: {
|
|
588
|
-
...state.steps,
|
|
589
|
-
transfer: {
|
|
590
|
-
status: "error",
|
|
591
|
-
error: {
|
|
592
|
-
message: "Export completed but no download URL was provided",
|
|
593
|
-
code: "NO_DOWNLOAD_URL",
|
|
594
|
-
retryable: true,
|
|
595
|
-
},
|
|
596
|
-
},
|
|
597
|
-
},
|
|
598
|
-
exportResult: {
|
|
599
|
-
ok: true,
|
|
600
|
-
jobId: "exp-no-url",
|
|
601
|
-
status: "complete",
|
|
602
|
-
} as ExportManagedResult,
|
|
603
|
-
};
|
|
604
|
-
const screen = deriveTransferScreenState(state);
|
|
605
|
-
if (screen.phase === "error") {
|
|
606
|
-
expect(screen.failedPhase).toBe("export");
|
|
607
|
-
}
|
|
608
|
-
});
|
|
609
|
-
|
|
610
|
-
test("infers failed phase as import when import result exists and failed", () => {
|
|
611
|
-
let state = advanceTo("transfer");
|
|
612
|
-
state = {
|
|
613
|
-
...state,
|
|
614
|
-
steps: {
|
|
615
|
-
...state.steps,
|
|
616
|
-
transfer: {
|
|
617
|
-
status: "error",
|
|
618
|
-
error: {
|
|
619
|
-
message: "Disk full",
|
|
620
|
-
code: "write_failed",
|
|
621
|
-
retryable: true,
|
|
622
|
-
},
|
|
623
|
-
},
|
|
624
|
-
},
|
|
625
|
-
exportResult: {
|
|
626
|
-
ok: true,
|
|
627
|
-
filename: "export.vbundle",
|
|
628
|
-
schemaVersion: 1,
|
|
629
|
-
checksum: "abc",
|
|
630
|
-
},
|
|
631
|
-
importResult: {
|
|
632
|
-
success: false,
|
|
633
|
-
reason: "write_failed",
|
|
634
|
-
message: "Disk full",
|
|
635
|
-
},
|
|
636
|
-
};
|
|
637
|
-
const screen = deriveTransferScreenState(state);
|
|
638
|
-
if (screen.phase === "error") {
|
|
639
|
-
expect(screen.failedPhase).toBe("import");
|
|
640
|
-
}
|
|
641
|
-
});
|
|
642
|
-
|
|
643
|
-
test("infers failed phase as poll for managed export transport error during download", () => {
|
|
644
|
-
let state = advanceTo("transfer");
|
|
645
|
-
state = {
|
|
646
|
-
...state,
|
|
647
|
-
steps: {
|
|
648
|
-
...state.steps,
|
|
649
|
-
transfer: {
|
|
650
|
-
status: "error",
|
|
651
|
-
error: {
|
|
652
|
-
message: "Failed to download export: HTTP 404",
|
|
653
|
-
code: "HTTP_404",
|
|
654
|
-
retryable: false,
|
|
655
|
-
},
|
|
656
|
-
},
|
|
657
|
-
},
|
|
658
|
-
exportResult: {
|
|
659
|
-
ok: true,
|
|
660
|
-
jobId: "exp-download-fail",
|
|
661
|
-
status: "complete",
|
|
662
|
-
} as ExportManagedResult,
|
|
663
|
-
// No importResult -- download failed before import
|
|
664
|
-
};
|
|
665
|
-
const screen = deriveTransferScreenState(state);
|
|
666
|
-
if (screen.phase === "error") {
|
|
667
|
-
expect(screen.failedPhase).toBe("poll");
|
|
668
|
-
}
|
|
669
|
-
});
|
|
670
|
-
|
|
671
|
-
test("shows default error when step has error status but no error details", () => {
|
|
672
|
-
let state = advanceTo("transfer");
|
|
673
|
-
state = {
|
|
674
|
-
...state,
|
|
675
|
-
steps: {
|
|
676
|
-
...state.steps,
|
|
677
|
-
transfer: { status: "error" },
|
|
678
|
-
},
|
|
679
|
-
};
|
|
680
|
-
const screen = deriveTransferScreenState(state);
|
|
681
|
-
expect(screen.phase).toBe("error");
|
|
682
|
-
if (screen.phase === "error") {
|
|
683
|
-
expect(screen.error.message).toBe("Transfer failed");
|
|
684
|
-
expect(screen.error.retryable).toBe(false);
|
|
685
|
-
expect(screen.canRetry).toBe(false);
|
|
686
|
-
}
|
|
687
|
-
});
|
|
688
|
-
});
|
|
689
|
-
|
|
690
|
-
// ---------------------------------------------------------------------------
|
|
691
|
-
// Retry behavior
|
|
692
|
-
// ---------------------------------------------------------------------------
|
|
693
|
-
|
|
694
|
-
describe("retryTransferFlow", () => {
|
|
695
|
-
test("retries transfer step after export error", async () => {
|
|
696
|
-
let state = advanceTo("transfer");
|
|
697
|
-
const importResponse = makeImportSuccess();
|
|
698
|
-
|
|
699
|
-
// Simulate a failed export
|
|
700
|
-
state = {
|
|
701
|
-
...state,
|
|
702
|
-
steps: {
|
|
703
|
-
...state.steps,
|
|
704
|
-
transfer: {
|
|
705
|
-
status: "error",
|
|
706
|
-
error: {
|
|
707
|
-
message: "export: HTTP 500",
|
|
708
|
-
code: "HTTP_500",
|
|
709
|
-
retryable: true,
|
|
710
|
-
},
|
|
711
|
-
},
|
|
712
|
-
},
|
|
713
|
-
};
|
|
714
|
-
|
|
715
|
-
const archiveBytes = new ArrayBuffer(32);
|
|
716
|
-
const sourceFetch = (async () => {
|
|
717
|
-
return new Response(archiveBytes, {
|
|
718
|
-
status: 200,
|
|
719
|
-
headers: {
|
|
720
|
-
"Content-Disposition": 'attachment; filename="export.vbundle"',
|
|
721
|
-
"X-Vbundle-Schema-Version": "1",
|
|
722
|
-
"X-Vbundle-Manifest-Sha256": "abc",
|
|
723
|
-
},
|
|
724
|
-
});
|
|
725
|
-
}) as unknown as typeof fetch;
|
|
726
|
-
|
|
727
|
-
const destFetch = (async () => {
|
|
728
|
-
return new Response(JSON.stringify(importResponse), {
|
|
729
|
-
status: 200,
|
|
730
|
-
headers: { "Content-Type": "application/json" },
|
|
731
|
-
});
|
|
732
|
-
}) as unknown as typeof fetch;
|
|
733
|
-
|
|
734
|
-
const options = makeExecutorOptions({
|
|
735
|
-
sourceConfig: runtimeConfig({ fetchFn: sourceFetch }),
|
|
736
|
-
destConfig: runtimeConfig({ fetchFn: destFetch }),
|
|
737
|
-
});
|
|
738
|
-
|
|
739
|
-
const result = await retryTransferFlow(state, options);
|
|
740
|
-
expect(result.steps.transfer.status).toBe("success");
|
|
741
|
-
expect(result.currentStep).toBe("rebind-secrets");
|
|
742
|
-
});
|
|
743
|
-
|
|
744
|
-
test("throws when trying to retry non-retryable error", () => {
|
|
745
|
-
let state = advanceTo("transfer");
|
|
746
|
-
state = {
|
|
747
|
-
...state,
|
|
748
|
-
steps: {
|
|
749
|
-
...state.steps,
|
|
750
|
-
transfer: {
|
|
751
|
-
status: "error",
|
|
752
|
-
error: {
|
|
753
|
-
message: "Fatal error",
|
|
754
|
-
retryable: false,
|
|
755
|
-
},
|
|
756
|
-
},
|
|
757
|
-
},
|
|
758
|
-
};
|
|
759
|
-
|
|
760
|
-
const options = makeExecutorOptions();
|
|
761
|
-
expect(retryTransferFlow(state, options)).rejects.toThrow(
|
|
762
|
-
"not in a retryable error state",
|
|
763
|
-
);
|
|
764
|
-
});
|
|
765
|
-
|
|
766
|
-
test("emits state change on reset before retry", async () => {
|
|
767
|
-
let state = advanceTo("transfer");
|
|
768
|
-
const importResponse = makeImportSuccess();
|
|
769
|
-
|
|
770
|
-
state = {
|
|
771
|
-
...state,
|
|
772
|
-
steps: {
|
|
773
|
-
...state.steps,
|
|
774
|
-
transfer: {
|
|
775
|
-
status: "error",
|
|
776
|
-
error: {
|
|
777
|
-
message: "export: HTTP 500",
|
|
778
|
-
code: "HTTP_500",
|
|
779
|
-
retryable: true,
|
|
780
|
-
},
|
|
781
|
-
},
|
|
782
|
-
},
|
|
783
|
-
};
|
|
784
|
-
|
|
785
|
-
const archiveBytes = new ArrayBuffer(32);
|
|
786
|
-
const sourceFetch = (async () => {
|
|
787
|
-
return new Response(archiveBytes, {
|
|
788
|
-
status: 200,
|
|
789
|
-
headers: {
|
|
790
|
-
"Content-Disposition": 'attachment; filename="export.vbundle"',
|
|
791
|
-
"X-Vbundle-Schema-Version": "1",
|
|
792
|
-
"X-Vbundle-Manifest-Sha256": "abc",
|
|
793
|
-
},
|
|
794
|
-
});
|
|
795
|
-
}) as unknown as typeof fetch;
|
|
796
|
-
|
|
797
|
-
const destFetch = (async () => {
|
|
798
|
-
return new Response(JSON.stringify(importResponse), {
|
|
799
|
-
status: 200,
|
|
800
|
-
headers: { "Content-Type": "application/json" },
|
|
801
|
-
});
|
|
802
|
-
}) as unknown as typeof fetch;
|
|
803
|
-
|
|
804
|
-
const stateChanges: MigrationWizardState[] = [];
|
|
805
|
-
const options = makeExecutorOptions({
|
|
806
|
-
sourceConfig: runtimeConfig({ fetchFn: sourceFetch }),
|
|
807
|
-
destConfig: runtimeConfig({ fetchFn: destFetch }),
|
|
808
|
-
onStateChange: (s) => stateChanges.push(s),
|
|
809
|
-
});
|
|
810
|
-
|
|
811
|
-
await retryTransferFlow(state, options);
|
|
812
|
-
|
|
813
|
-
// The first state change should be the reset (idle status)
|
|
814
|
-
expect(stateChanges.length).toBeGreaterThan(0);
|
|
815
|
-
expect(stateChanges[0].steps.transfer.status).toBe("idle");
|
|
816
|
-
});
|
|
817
|
-
});
|
|
818
|
-
|
|
819
|
-
// ---------------------------------------------------------------------------
|
|
820
|
-
// Transfer flow execution
|
|
821
|
-
// ---------------------------------------------------------------------------
|
|
822
|
-
|
|
823
|
-
describe("executeTransferFlow", () => {
|
|
824
|
-
test("runtime export + import -- success", async () => {
|
|
825
|
-
const archiveBytes = new ArrayBuffer(32);
|
|
826
|
-
const importResponse = makeImportSuccess();
|
|
827
|
-
|
|
828
|
-
const sourceFetch = (async () => {
|
|
829
|
-
return new Response(archiveBytes, {
|
|
830
|
-
status: 200,
|
|
831
|
-
headers: {
|
|
832
|
-
"Content-Disposition": 'attachment; filename="export.vbundle"',
|
|
833
|
-
"X-Vbundle-Schema-Version": "1",
|
|
834
|
-
"X-Vbundle-Manifest-Sha256": "abc",
|
|
835
|
-
},
|
|
836
|
-
});
|
|
837
|
-
}) as unknown as typeof fetch;
|
|
838
|
-
|
|
839
|
-
const destFetch = (async () => {
|
|
840
|
-
return new Response(JSON.stringify(importResponse), {
|
|
841
|
-
status: 200,
|
|
842
|
-
headers: { "Content-Type": "application/json" },
|
|
843
|
-
});
|
|
844
|
-
}) as unknown as typeof fetch;
|
|
845
|
-
|
|
846
|
-
const state = advanceTo("transfer");
|
|
847
|
-
const options = makeExecutorOptions({
|
|
848
|
-
sourceConfig: runtimeConfig({ fetchFn: sourceFetch }),
|
|
849
|
-
destConfig: runtimeConfig({ fetchFn: destFetch }),
|
|
850
|
-
});
|
|
851
|
-
|
|
852
|
-
const result = await executeTransferFlow(state, options);
|
|
853
|
-
expect(result.steps.transfer.status).toBe("success");
|
|
854
|
-
expect(result.currentStep).toBe("rebind-secrets");
|
|
855
|
-
|
|
856
|
-
const screen = deriveTransferScreenState(result);
|
|
857
|
-
expect(screen.phase).toBe("success");
|
|
858
|
-
if (screen.phase === "success") {
|
|
859
|
-
expect(screen.importSummary.totalFiles).toBe(3);
|
|
860
|
-
}
|
|
861
|
-
});
|
|
862
|
-
|
|
863
|
-
test("managed export + poll + import -- success", async () => {
|
|
864
|
-
let fetchCallCount = 0;
|
|
865
|
-
const importResponse = makeImportSuccess();
|
|
866
|
-
|
|
867
|
-
const sourceFetch = (async (url: string | URL | Request) => {
|
|
868
|
-
fetchCallCount++;
|
|
869
|
-
const urlStr = String(url);
|
|
870
|
-
if (urlStr.endsWith("/export/")) {
|
|
871
|
-
return new Response(
|
|
872
|
-
JSON.stringify({ job_id: "exp-1", status: "pending" }),
|
|
873
|
-
{
|
|
874
|
-
status: 200,
|
|
875
|
-
headers: { "Content-Type": "application/json" },
|
|
876
|
-
},
|
|
877
|
-
);
|
|
878
|
-
}
|
|
879
|
-
if (urlStr.includes("/export/") && urlStr.includes("/status/")) {
|
|
880
|
-
if (fetchCallCount <= 3) {
|
|
881
|
-
return new Response(
|
|
882
|
-
JSON.stringify({ status: "processing", job_id: "exp-1" }),
|
|
883
|
-
{
|
|
884
|
-
status: 200,
|
|
885
|
-
headers: { "Content-Type": "application/json" },
|
|
886
|
-
},
|
|
887
|
-
);
|
|
888
|
-
}
|
|
889
|
-
return new Response(
|
|
890
|
-
JSON.stringify({
|
|
891
|
-
status: "complete",
|
|
892
|
-
job_id: "exp-1",
|
|
893
|
-
download_url: "https://cdn.example.com/export.vbundle",
|
|
894
|
-
}),
|
|
895
|
-
{
|
|
896
|
-
status: 200,
|
|
897
|
-
headers: { "Content-Type": "application/json" },
|
|
898
|
-
},
|
|
899
|
-
);
|
|
900
|
-
}
|
|
901
|
-
if (urlStr === "https://cdn.example.com/export.vbundle") {
|
|
902
|
-
return new Response(new ArrayBuffer(32), { status: 200 });
|
|
903
|
-
}
|
|
904
|
-
return new Response("Not found", { status: 404 });
|
|
905
|
-
}) as unknown as typeof fetch;
|
|
906
|
-
|
|
907
|
-
const destFetch = (async () => {
|
|
908
|
-
return new Response(JSON.stringify(importResponse), {
|
|
909
|
-
status: 200,
|
|
910
|
-
headers: { "Content-Type": "application/json" },
|
|
911
|
-
});
|
|
912
|
-
}) as unknown as typeof fetch;
|
|
913
|
-
|
|
914
|
-
const state = advanceTo("transfer");
|
|
915
|
-
const options = makeExecutorOptions({
|
|
916
|
-
sourceConfig: managedConfig({ fetchFn: sourceFetch }),
|
|
917
|
-
destConfig: runtimeConfig({ fetchFn: destFetch }),
|
|
918
|
-
});
|
|
919
|
-
|
|
920
|
-
const result = await executeTransferFlow(state, options);
|
|
921
|
-
expect(result.steps.transfer.status).toBe("success");
|
|
922
|
-
expect(result.currentStep).toBe("rebind-secrets");
|
|
923
|
-
});
|
|
924
|
-
|
|
925
|
-
test("transfer failure sets error state with import phase", async () => {
|
|
926
|
-
const state = advanceTo("transfer");
|
|
927
|
-
const options = makeExecutorOptions({
|
|
928
|
-
sourceConfig: runtimeConfig({ fetchFn: mockFetch(500, "Server Error") }),
|
|
929
|
-
});
|
|
930
|
-
|
|
931
|
-
const result = await executeTransferFlow(state, options);
|
|
932
|
-
expect(result.steps.transfer.status).toBe("error");
|
|
933
|
-
|
|
934
|
-
const screen = deriveTransferScreenState(result);
|
|
935
|
-
expect(screen.phase).toBe("error");
|
|
936
|
-
if (screen.phase === "error") {
|
|
937
|
-
expect(screen.canRetry).toBe(true);
|
|
938
|
-
expect(screen.failedPhase).toBe("import");
|
|
939
|
-
}
|
|
940
|
-
});
|
|
941
|
-
|
|
942
|
-
test("import failure sets error state with import reason", async () => {
|
|
943
|
-
const archiveBytes = new ArrayBuffer(32);
|
|
944
|
-
const importResponse: ImportCommitResponse = {
|
|
945
|
-
success: false,
|
|
946
|
-
reason: "write_failed",
|
|
947
|
-
message: "Disk full",
|
|
948
|
-
};
|
|
949
|
-
|
|
950
|
-
const sourceFetch = (async () => {
|
|
951
|
-
return new Response(archiveBytes, {
|
|
952
|
-
status: 200,
|
|
953
|
-
headers: {
|
|
954
|
-
"Content-Disposition": 'attachment; filename="export.vbundle"',
|
|
955
|
-
"X-Vbundle-Schema-Version": "1",
|
|
956
|
-
"X-Vbundle-Manifest-Sha256": "abc",
|
|
957
|
-
},
|
|
958
|
-
});
|
|
959
|
-
}) as unknown as typeof fetch;
|
|
960
|
-
|
|
961
|
-
const destFetch = (async () => {
|
|
962
|
-
return new Response(JSON.stringify(importResponse), {
|
|
963
|
-
status: 200,
|
|
964
|
-
headers: { "Content-Type": "application/json" },
|
|
965
|
-
});
|
|
966
|
-
}) as unknown as typeof fetch;
|
|
967
|
-
|
|
968
|
-
const state = advanceTo("transfer");
|
|
969
|
-
const options = makeExecutorOptions({
|
|
970
|
-
sourceConfig: runtimeConfig({ fetchFn: sourceFetch }),
|
|
971
|
-
destConfig: runtimeConfig({ fetchFn: destFetch }),
|
|
972
|
-
});
|
|
973
|
-
|
|
974
|
-
const result = await executeTransferFlow(state, options);
|
|
975
|
-
expect(result.steps.transfer.status).toBe("error");
|
|
976
|
-
expect(result.steps.transfer.error?.message).toContain("Disk full");
|
|
977
|
-
});
|
|
978
|
-
});
|
|
979
|
-
|
|
980
|
-
// ---------------------------------------------------------------------------
|
|
981
|
-
// Back navigation
|
|
982
|
-
// ---------------------------------------------------------------------------
|
|
983
|
-
|
|
984
|
-
describe("goBackToPreflight", () => {
|
|
985
|
-
test("navigates back to preflight-review from transfer", () => {
|
|
986
|
-
const state = advanceTo("transfer");
|
|
987
|
-
const result = goBackToPreflight(state);
|
|
988
|
-
expect(result.currentStep).toBe("preflight-review");
|
|
989
|
-
expect(result.steps.transfer.status).toBe("idle");
|
|
990
|
-
});
|
|
991
|
-
|
|
992
|
-
test("clears export and import results when going back", () => {
|
|
993
|
-
let state = advanceTo("transfer");
|
|
994
|
-
state = {
|
|
995
|
-
...state,
|
|
996
|
-
exportResult: {
|
|
997
|
-
ok: true,
|
|
998
|
-
filename: "export.vbundle",
|
|
999
|
-
schemaVersion: 1,
|
|
1000
|
-
checksum: "abc",
|
|
1001
|
-
},
|
|
1002
|
-
importResult: makeImportSuccess(),
|
|
1003
|
-
};
|
|
1004
|
-
const result = goBackToPreflight(state);
|
|
1005
|
-
expect(result.exportResult).toBeUndefined();
|
|
1006
|
-
expect(result.importResult).toBeUndefined();
|
|
1007
|
-
});
|
|
1008
|
-
|
|
1009
|
-
test("preserves validation results but clears preflight when going back", () => {
|
|
1010
|
-
let state = advanceTo("transfer");
|
|
1011
|
-
state = {
|
|
1012
|
-
...state,
|
|
1013
|
-
validateResult: makeValidateSuccess(),
|
|
1014
|
-
preflightResult: makePreflightSuccess(),
|
|
1015
|
-
};
|
|
1016
|
-
const result = goBackToPreflight(state);
|
|
1017
|
-
// Validation results are preserved since we're only going back to preflight
|
|
1018
|
-
expect(result.validateResult).toBeDefined();
|
|
1019
|
-
// Preflight results are cleared because going back resets the target step
|
|
1020
|
-
expect(result.preflightResult).toBeUndefined();
|
|
1021
|
-
});
|
|
1022
|
-
});
|
|
1023
|
-
|
|
1024
|
-
// ---------------------------------------------------------------------------
|
|
1025
|
-
// State change callbacks
|
|
1026
|
-
// ---------------------------------------------------------------------------
|
|
1027
|
-
|
|
1028
|
-
describe("executeTransferFlow -- onStateChange callbacks", () => {
|
|
1029
|
-
test("emits state changes during transfer flow", async () => {
|
|
1030
|
-
const archiveBytes = new ArrayBuffer(32);
|
|
1031
|
-
const importResponse = makeImportSuccess();
|
|
1032
|
-
|
|
1033
|
-
const sourceFetch = (async () => {
|
|
1034
|
-
return new Response(archiveBytes, {
|
|
1035
|
-
status: 200,
|
|
1036
|
-
headers: {
|
|
1037
|
-
"Content-Disposition": 'attachment; filename="export.vbundle"',
|
|
1038
|
-
"X-Vbundle-Schema-Version": "1",
|
|
1039
|
-
"X-Vbundle-Manifest-Sha256": "abc",
|
|
1040
|
-
},
|
|
1041
|
-
});
|
|
1042
|
-
}) as unknown as typeof fetch;
|
|
1043
|
-
|
|
1044
|
-
const destFetch = (async () => {
|
|
1045
|
-
return new Response(JSON.stringify(importResponse), {
|
|
1046
|
-
status: 200,
|
|
1047
|
-
headers: { "Content-Type": "application/json" },
|
|
1048
|
-
});
|
|
1049
|
-
}) as unknown as typeof fetch;
|
|
1050
|
-
|
|
1051
|
-
const state = advanceTo("transfer");
|
|
1052
|
-
const stateChanges: TransferScreenState[] = [];
|
|
1053
|
-
const options = makeExecutorOptions({
|
|
1054
|
-
sourceConfig: runtimeConfig({ fetchFn: sourceFetch }),
|
|
1055
|
-
destConfig: runtimeConfig({ fetchFn: destFetch }),
|
|
1056
|
-
onStateChange: (s) => stateChanges.push(deriveTransferScreenState(s)),
|
|
1057
|
-
});
|
|
1058
|
-
|
|
1059
|
-
await executeTransferFlow(state, options);
|
|
1060
|
-
|
|
1061
|
-
// Should have gone through at least the loading state
|
|
1062
|
-
const phases = stateChanges.map((s) => s.phase);
|
|
1063
|
-
expect(phases).toContain("importing");
|
|
1064
|
-
});
|
|
1065
|
-
});
|
|
1066
|
-
|
|
1067
|
-
// ---------------------------------------------------------------------------
|
|
1068
|
-
// State persistence and resume
|
|
1069
|
-
// ---------------------------------------------------------------------------
|
|
1070
|
-
|
|
1071
|
-
describe("transfer screen state persistence/resume", () => {
|
|
1072
|
-
test("derives correct state after serialize + deserialize at transfer step", () => {
|
|
1073
|
-
const state = advanceTo("transfer");
|
|
1074
|
-
const json = serializeWizardState(state);
|
|
1075
|
-
const restored = deserializeWizardState(json);
|
|
1076
|
-
expect(restored).toBeDefined();
|
|
1077
|
-
|
|
1078
|
-
// After deserialization, hasBundleData is false, so prepareForResume
|
|
1079
|
-
// should redirect to upload-bundle
|
|
1080
|
-
const resumed = prepareForResume(restored!);
|
|
1081
|
-
const screen = deriveTransferScreenState(resumed);
|
|
1082
|
-
expect(screen.phase).toBe("disabled");
|
|
1083
|
-
expect(resumed.currentStep).toBe("upload-bundle");
|
|
1084
|
-
});
|
|
1085
|
-
|
|
1086
|
-
test("derives correct state after resume at transfer step with loading status", () => {
|
|
1087
|
-
let state = advanceTo("transfer");
|
|
1088
|
-
state = {
|
|
1089
|
-
...state,
|
|
1090
|
-
steps: { ...state.steps, transfer: { status: "loading" } },
|
|
1091
|
-
};
|
|
1092
|
-
|
|
1093
|
-
const json = serializeWizardState(state);
|
|
1094
|
-
const restored = deserializeWizardState(json);
|
|
1095
|
-
expect(restored).toBeDefined();
|
|
1096
|
-
|
|
1097
|
-
// prepareForResume resets loading to idle and redirects to upload-bundle
|
|
1098
|
-
// because hasBundleData is false after deserialization
|
|
1099
|
-
const resumed = prepareForResume(restored!);
|
|
1100
|
-
expect(resumed.currentStep).toBe("upload-bundle");
|
|
1101
|
-
});
|
|
1102
|
-
|
|
1103
|
-
test("success results persist across serialize/deserialize", () => {
|
|
1104
|
-
let state = advanceTo("rebind-secrets");
|
|
1105
|
-
state = {
|
|
1106
|
-
...state,
|
|
1107
|
-
importResult: makeImportSuccess(),
|
|
1108
|
-
};
|
|
1109
|
-
|
|
1110
|
-
const json = serializeWizardState(state);
|
|
1111
|
-
const restored = deserializeWizardState(json);
|
|
1112
|
-
expect(restored).toBeDefined();
|
|
1113
|
-
|
|
1114
|
-
const screen = deriveTransferScreenState(restored!);
|
|
1115
|
-
expect(screen.phase).toBe("success");
|
|
1116
|
-
if (screen.phase === "success") {
|
|
1117
|
-
expect(screen.importSummary.totalFiles).toBe(3);
|
|
1118
|
-
}
|
|
1119
|
-
});
|
|
1120
|
-
|
|
1121
|
-
test("error state is preserved after serialize/deserialize", () => {
|
|
1122
|
-
let state = advanceTo("transfer");
|
|
1123
|
-
state = {
|
|
1124
|
-
...state,
|
|
1125
|
-
steps: {
|
|
1126
|
-
...state.steps,
|
|
1127
|
-
transfer: {
|
|
1128
|
-
status: "error",
|
|
1129
|
-
error: {
|
|
1130
|
-
message: "export: HTTP 500",
|
|
1131
|
-
code: "HTTP_500",
|
|
1132
|
-
retryable: true,
|
|
1133
|
-
},
|
|
1134
|
-
},
|
|
1135
|
-
},
|
|
1136
|
-
};
|
|
1137
|
-
|
|
1138
|
-
const json = serializeWizardState(state);
|
|
1139
|
-
const restored = deserializeWizardState(json);
|
|
1140
|
-
expect(restored).toBeDefined();
|
|
1141
|
-
|
|
1142
|
-
// The raw deserialized state (before resume) preserves the error
|
|
1143
|
-
const screen = deriveTransferScreenState(restored!);
|
|
1144
|
-
expect(screen.phase).toBe("error");
|
|
1145
|
-
if (screen.phase === "error") {
|
|
1146
|
-
expect(screen.error.message).toContain("HTTP 500");
|
|
1147
|
-
expect(screen.canRetry).toBe(true);
|
|
1148
|
-
}
|
|
1149
|
-
});
|
|
1150
|
-
});
|
|
1151
|
-
|
|
1152
|
-
// ---------------------------------------------------------------------------
|
|
1153
|
-
// Edge cases
|
|
1154
|
-
// ---------------------------------------------------------------------------
|
|
1155
|
-
|
|
1156
|
-
describe("transfer screen edge cases", () => {
|
|
1157
|
-
test("handles transfer step at complete state -- success visible", () => {
|
|
1158
|
-
const state = advanceTo("complete");
|
|
1159
|
-
const screen = deriveTransferScreenState(state);
|
|
1160
|
-
expect(screen.phase).toBe("success");
|
|
1161
|
-
});
|
|
1162
|
-
|
|
1163
|
-
test("handles transfer error from wrong step gracefully", () => {
|
|
1164
|
-
const state = advanceTo("validate");
|
|
1165
|
-
// Attempting to derive transfer screen from validate step
|
|
1166
|
-
const screen = deriveTransferScreenState(state);
|
|
1167
|
-
expect(screen.phase).toBe("disabled");
|
|
1168
|
-
});
|
|
1169
|
-
|
|
1170
|
-
test("runtime flow does not show polling phase", async () => {
|
|
1171
|
-
const archiveBytes = new ArrayBuffer(32);
|
|
1172
|
-
const importResponse = makeImportSuccess();
|
|
1173
|
-
|
|
1174
|
-
const sourceFetch = (async () => {
|
|
1175
|
-
return new Response(archiveBytes, {
|
|
1176
|
-
status: 200,
|
|
1177
|
-
headers: {
|
|
1178
|
-
"Content-Disposition": 'attachment; filename="export.vbundle"',
|
|
1179
|
-
"X-Vbundle-Schema-Version": "1",
|
|
1180
|
-
"X-Vbundle-Manifest-Sha256": "abc",
|
|
1181
|
-
},
|
|
1182
|
-
});
|
|
1183
|
-
}) as unknown as typeof fetch;
|
|
1184
|
-
|
|
1185
|
-
const destFetch = (async () => {
|
|
1186
|
-
return new Response(JSON.stringify(importResponse), {
|
|
1187
|
-
status: 200,
|
|
1188
|
-
headers: { "Content-Type": "application/json" },
|
|
1189
|
-
});
|
|
1190
|
-
}) as unknown as typeof fetch;
|
|
1191
|
-
|
|
1192
|
-
const state = advanceTo("transfer");
|
|
1193
|
-
const stateChanges: TransferScreenState[] = [];
|
|
1194
|
-
const options = makeExecutorOptions({
|
|
1195
|
-
sourceConfig: runtimeConfig({ fetchFn: sourceFetch }),
|
|
1196
|
-
destConfig: runtimeConfig({ fetchFn: destFetch }),
|
|
1197
|
-
onStateChange: (s) => stateChanges.push(deriveTransferScreenState(s)),
|
|
1198
|
-
});
|
|
1199
|
-
|
|
1200
|
-
await executeTransferFlow(state, options);
|
|
1201
|
-
|
|
1202
|
-
// Runtime flow should not have any polling phases
|
|
1203
|
-
const phases = stateChanges.map((s) => s.phase);
|
|
1204
|
-
expect(phases).not.toContain("polling");
|
|
1205
|
-
});
|
|
1206
|
-
|
|
1207
|
-
test("transfer flow throws when called from wrong step", async () => {
|
|
1208
|
-
const state = advanceTo("validate");
|
|
1209
|
-
const options = makeExecutorOptions();
|
|
1210
|
-
try {
|
|
1211
|
-
await executeTransferFlow(state, options);
|
|
1212
|
-
expect(true).toBe(false);
|
|
1213
|
-
} catch (err) {
|
|
1214
|
-
expect((err as Error).message).toContain("wrong step");
|
|
1215
|
-
}
|
|
1216
|
-
});
|
|
1217
|
-
});
|