@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,254 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for the GCS signed-URL validator.
|
|
3
|
-
*
|
|
4
|
-
* Covers:
|
|
5
|
-
* - A valid V4-signed URL (`X-Goog-Signature=...`) is accepted and the
|
|
6
|
-
* returned host/path match the input.
|
|
7
|
-
* - A valid V2-signed URL (`Signature=...`) is accepted.
|
|
8
|
-
* - `http://` is rejected with reason `scheme`.
|
|
9
|
-
* - A non-GCS host is rejected with reason `host`.
|
|
10
|
-
* - A GCS URL with no signature query param is rejected with reason
|
|
11
|
-
* `missing_signature`; SigV4 (`X-Amz-Signature`) is accepted only for
|
|
12
|
-
* hosts on a non-default allowlist, and unsigned URLs are rejected
|
|
13
|
-
* everywhere.
|
|
14
|
-
* - A GCS URL with a non-default explicit port (e.g. `:1234`, `:80`) is
|
|
15
|
-
* rejected with reason `port`. A URL with `:443` is accepted because
|
|
16
|
-
* WHATWG URL normalizes the default HTTPS port to an empty port
|
|
17
|
-
* string.
|
|
18
|
-
* - Path traversal (`/bucket/../foo`) is rejected with reason
|
|
19
|
-
* `path_traversal`, even though the URL parser would normalize it.
|
|
20
|
-
* - Encoded-slash traversal (`%2e%2e%2fother`), backslash-separator
|
|
21
|
-
* traversal (`\..\secret`), uppercase percent encoding, and an
|
|
22
|
-
* encoded dot-dot at the start of the path are all rejected.
|
|
23
|
-
* - A malformed URL string is rejected with reason `invalid_url`.
|
|
24
|
-
*/
|
|
25
|
-
|
|
26
|
-
import { describe, expect, test } from "bun:test";
|
|
27
|
-
|
|
28
|
-
import { validateGcsSignedUrl } from "../gcs-signed-url.js";
|
|
29
|
-
|
|
30
|
-
describe("validateGcsSignedUrl", () => {
|
|
31
|
-
test("accepts a V4-signed URL", () => {
|
|
32
|
-
const url =
|
|
33
|
-
"https://storage.googleapis.com/my-bucket/path/to/object.tgz" +
|
|
34
|
-
"?X-Goog-Algorithm=GOOG4-RSA-SHA256" +
|
|
35
|
-
"&X-Goog-Credential=service-account%40project.iam.gserviceaccount.com" +
|
|
36
|
-
"&X-Goog-Date=20260420T000000Z" +
|
|
37
|
-
"&X-Goog-Expires=3600" +
|
|
38
|
-
"&X-Goog-SignedHeaders=host" +
|
|
39
|
-
"&X-Goog-Signature=deadbeef";
|
|
40
|
-
|
|
41
|
-
const result = validateGcsSignedUrl(url);
|
|
42
|
-
expect(result.ok).toBe(true);
|
|
43
|
-
if (result.ok) {
|
|
44
|
-
expect(result.host).toBe("storage.googleapis.com");
|
|
45
|
-
expect(result.path).toBe("/my-bucket/path/to/object.tgz");
|
|
46
|
-
}
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
test("accepts a V2-signed URL", () => {
|
|
50
|
-
const url =
|
|
51
|
-
"https://storage.googleapis.com/my-bucket/object.tgz" +
|
|
52
|
-
"?GoogleAccessId=service-account%40project.iam.gserviceaccount.com" +
|
|
53
|
-
"&Expires=1700000000" +
|
|
54
|
-
"&Signature=abc123";
|
|
55
|
-
|
|
56
|
-
const result = validateGcsSignedUrl(url);
|
|
57
|
-
expect(result.ok).toBe(true);
|
|
58
|
-
if (result.ok) {
|
|
59
|
-
expect(result.host).toBe("storage.googleapis.com");
|
|
60
|
-
expect(result.path).toBe("/my-bucket/object.tgz");
|
|
61
|
-
}
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
test("rejects http:// scheme", () => {
|
|
65
|
-
const url =
|
|
66
|
-
"http://storage.googleapis.com/my-bucket/object.tgz" +
|
|
67
|
-
"?X-Goog-Signature=deadbeef";
|
|
68
|
-
|
|
69
|
-
const result = validateGcsSignedUrl(url);
|
|
70
|
-
expect(result).toEqual({ ok: false, reason: "scheme" });
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
test("accepts http:// for a host on a non-default allowlist", () => {
|
|
74
|
-
// A non-default allowlist (e.g. local/minikube where the bundle is
|
|
75
|
-
// served over http from host.docker.internal) relaxes the scheme check
|
|
76
|
-
// to also accept http and skips the explicit-port check.
|
|
77
|
-
const url =
|
|
78
|
-
"http://host.docker.internal:8000/bucket/object.tgz" +
|
|
79
|
-
"?X-Goog-Signature=deadbeef";
|
|
80
|
-
|
|
81
|
-
const result = validateGcsSignedUrl(url, {
|
|
82
|
-
allowedHosts: ["host.docker.internal"],
|
|
83
|
-
});
|
|
84
|
-
expect(result.ok).toBe(true);
|
|
85
|
-
if (result.ok) {
|
|
86
|
-
expect(result.host).toBe("host.docker.internal");
|
|
87
|
-
expect(result.path).toBe("/bucket/object.tgz");
|
|
88
|
-
}
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
test("still rejects a host absent from a non-default allowlist", () => {
|
|
92
|
-
const url =
|
|
93
|
-
"http://evil.internal/bucket/object.tgz" + "?X-Goog-Signature=deadbeef";
|
|
94
|
-
|
|
95
|
-
const result = validateGcsSignedUrl(url, {
|
|
96
|
-
allowedHosts: ["host.docker.internal"],
|
|
97
|
-
});
|
|
98
|
-
expect(result).toEqual({ ok: false, reason: "host" });
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
test("rejects a non-GCS host", () => {
|
|
102
|
-
const url =
|
|
103
|
-
"https://evil.com/my-bucket/object.tgz" + "?X-Goog-Signature=deadbeef";
|
|
104
|
-
|
|
105
|
-
const result = validateGcsSignedUrl(url);
|
|
106
|
-
expect(result).toEqual({ ok: false, reason: "host" });
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
test("rejects a GCS URL with no signature", () => {
|
|
110
|
-
const url = "https://storage.googleapis.com/bucket/key";
|
|
111
|
-
|
|
112
|
-
const result = validateGcsSignedUrl(url);
|
|
113
|
-
expect(result).toEqual({ ok: false, reason: "missing_signature" });
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
test("accepts a SigV4-signed URL for a host on a non-default allowlist", () => {
|
|
117
|
-
// The local/minikube platform serves SigV4 presigned URLs (SeaweedFS)
|
|
118
|
-
// over plain http — X-Amz-Signature satisfies the signature-shape
|
|
119
|
-
// requirement for explicitly allowlisted hosts.
|
|
120
|
-
const url =
|
|
121
|
-
"http://host.docker.internal:8000/bucket/object.tgz" +
|
|
122
|
-
"?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Signature=deadbeef";
|
|
123
|
-
|
|
124
|
-
const result = validateGcsSignedUrl(url, {
|
|
125
|
-
allowedHosts: ["host.docker.internal"],
|
|
126
|
-
});
|
|
127
|
-
expect(result.ok).toBe(true);
|
|
128
|
-
if (result.ok) {
|
|
129
|
-
expect(result.host).toBe("host.docker.internal");
|
|
130
|
-
expect(result.path).toBe("/bucket/object.tgz");
|
|
131
|
-
}
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
test("rejects an unsigned URL even on a non-default allowlist", () => {
|
|
135
|
-
const url = "http://host.docker.internal:8000/bucket/object.tgz";
|
|
136
|
-
|
|
137
|
-
const result = validateGcsSignedUrl(url, {
|
|
138
|
-
allowedHosts: ["host.docker.internal"],
|
|
139
|
-
});
|
|
140
|
-
expect(result).toEqual({ ok: false, reason: "missing_signature" });
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
test("rejects X-Amz-Signature on the default GCS allowlist", () => {
|
|
144
|
-
// SigV4 is only meaningful for allowlisted S3-compatible hosts; a GCS
|
|
145
|
-
// URL signed with the wrong scheme's param is not a GCS presigned URL.
|
|
146
|
-
const url =
|
|
147
|
-
"https://storage.googleapis.com/bucket/key?X-Amz-Signature=deadbeef";
|
|
148
|
-
|
|
149
|
-
const result = validateGcsSignedUrl(url);
|
|
150
|
-
expect(result).toEqual({ ok: false, reason: "missing_signature" });
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
test("still rejects traversal in a SigV4-signed allowlisted URL", () => {
|
|
154
|
-
const url =
|
|
155
|
-
"http://host.docker.internal:8000/bucket/../secret" +
|
|
156
|
-
"?X-Amz-Signature=deadbeef";
|
|
157
|
-
|
|
158
|
-
const result = validateGcsSignedUrl(url, {
|
|
159
|
-
allowedHosts: ["host.docker.internal"],
|
|
160
|
-
});
|
|
161
|
-
expect(result).toEqual({ ok: false, reason: "path_traversal" });
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
test("rejects a GCS URL with a non-default port (1234)", () => {
|
|
165
|
-
const url =
|
|
166
|
-
"https://storage.googleapis.com:1234/bucket/key" +
|
|
167
|
-
"?X-Goog-Signature=deadbeef";
|
|
168
|
-
|
|
169
|
-
const result = validateGcsSignedUrl(url);
|
|
170
|
-
expect(result).toEqual({ ok: false, reason: "port" });
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
test("rejects a GCS URL with HTTP default port (:80)", () => {
|
|
174
|
-
const url =
|
|
175
|
-
"https://storage.googleapis.com:80/bucket/key" +
|
|
176
|
-
"?X-Goog-Signature=deadbeef";
|
|
177
|
-
|
|
178
|
-
const result = validateGcsSignedUrl(url);
|
|
179
|
-
expect(result).toEqual({ ok: false, reason: "port" });
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
test("accepts a GCS URL with explicit :443 (normalized to empty port)", () => {
|
|
183
|
-
// WHATWG URL normalizes `:443` to an empty port string for HTTPS,
|
|
184
|
-
// so the port check (`parsed.port !== ""`) does not reject it.
|
|
185
|
-
// A correctly-issued signed URL with an explicit default port is
|
|
186
|
-
// therefore unaffected by the port guard.
|
|
187
|
-
const url =
|
|
188
|
-
"https://storage.googleapis.com:443/my-bucket/object.tgz" +
|
|
189
|
-
"?X-Goog-Signature=deadbeef";
|
|
190
|
-
|
|
191
|
-
// Sanity-check the WHATWG normalization assumption.
|
|
192
|
-
expect(new URL(url).port).toBe("");
|
|
193
|
-
|
|
194
|
-
const result = validateGcsSignedUrl(url);
|
|
195
|
-
expect(result.ok).toBe(true);
|
|
196
|
-
if (result.ok) {
|
|
197
|
-
expect(result.host).toBe("storage.googleapis.com");
|
|
198
|
-
expect(result.path).toBe("/my-bucket/object.tgz");
|
|
199
|
-
}
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
test("rejects path traversal", () => {
|
|
203
|
-
const url =
|
|
204
|
-
"https://storage.googleapis.com/bucket/../foo" +
|
|
205
|
-
"?X-Goog-Signature=deadbeef";
|
|
206
|
-
|
|
207
|
-
const result = validateGcsSignedUrl(url);
|
|
208
|
-
expect(result).toEqual({ ok: false, reason: "path_traversal" });
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
test("rejects path traversal with encoded slash (%2e%2e%2fother)", () => {
|
|
212
|
-
const url =
|
|
213
|
-
"https://storage.googleapis.com/bucket/%2e%2e%2fother" +
|
|
214
|
-
"?X-Goog-Signature=deadbeef";
|
|
215
|
-
|
|
216
|
-
const result = validateGcsSignedUrl(url);
|
|
217
|
-
expect(result).toEqual({ ok: false, reason: "path_traversal" });
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
test("rejects path traversal with backslash separators", () => {
|
|
221
|
-
// Note: the WHATWG URL parser normalizes `\` to `/` for special
|
|
222
|
-
// schemes like `https:`, so `new URL()` would collapse this to
|
|
223
|
-
// `/secret`. The raw-path guard must still reject it.
|
|
224
|
-
const url =
|
|
225
|
-
"https://storage.googleapis.com/bucket\\..\\secret" +
|
|
226
|
-
"?X-Goog-Signature=deadbeef";
|
|
227
|
-
|
|
228
|
-
const result = validateGcsSignedUrl(url);
|
|
229
|
-
expect(result).toEqual({ ok: false, reason: "path_traversal" });
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
test("rejects path traversal with uppercase percent encoding", () => {
|
|
233
|
-
const url =
|
|
234
|
-
"https://storage.googleapis.com/bucket/%2E%2E/other" +
|
|
235
|
-
"?X-Goog-Signature=deadbeef";
|
|
236
|
-
|
|
237
|
-
const result = validateGcsSignedUrl(url);
|
|
238
|
-
expect(result).toEqual({ ok: false, reason: "path_traversal" });
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
test("rejects encoded dot-dot at the start of the path", () => {
|
|
242
|
-
const url =
|
|
243
|
-
"https://storage.googleapis.com/%2e%2e/bucket/key" +
|
|
244
|
-
"?X-Goog-Signature=deadbeef";
|
|
245
|
-
|
|
246
|
-
const result = validateGcsSignedUrl(url);
|
|
247
|
-
expect(result).toEqual({ ok: false, reason: "path_traversal" });
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
test("rejects a malformed URL", () => {
|
|
251
|
-
const result = validateGcsSignedUrl("not a url at all");
|
|
252
|
-
expect(result).toEqual({ ok: false, reason: "invalid_url" });
|
|
253
|
-
});
|
|
254
|
-
});
|
|
@@ -1,346 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for the in-memory migration job registry.
|
|
3
|
-
*
|
|
4
|
-
* Covered:
|
|
5
|
-
* - Happy path: start -> poll pending -> poll running -> poll complete with result.
|
|
6
|
-
* - Failure path: runner throws -> job ends in `failed` with mapped error.
|
|
7
|
-
* - `kFetchBodyError` path: tagged thrown object maps to `fetch_failed` with
|
|
8
|
-
* optional `upstreamStatus` preserved.
|
|
9
|
-
* - Concurrent limit: second export while one is pending/running rejects with
|
|
10
|
-
* `JobAlreadyInProgressError`; a job of the other type is allowed alongside.
|
|
11
|
-
* - TTL sweep: completed job older than the TTL is evicted; job that is not
|
|
12
|
-
* yet past TTL is retained.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import { describe, expect, test } from "bun:test";
|
|
16
|
-
|
|
17
|
-
import {
|
|
18
|
-
JobAlreadyInProgressError,
|
|
19
|
-
type MigrationJob,
|
|
20
|
-
MigrationJobRegistry,
|
|
21
|
-
} from "../job-registry.js";
|
|
22
|
-
|
|
23
|
-
const kFetchBodyError = Symbol.for("cadet.migrationImport.fetchBodyError");
|
|
24
|
-
|
|
25
|
-
/** Spin the microtask queue N times so `queueMicrotask`-scheduled work runs. */
|
|
26
|
-
async function flushMicrotasks(n = 4): Promise<void> {
|
|
27
|
-
for (let i = 0; i < n; i++) {
|
|
28
|
-
await Promise.resolve();
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
describe("MigrationJobRegistry", () => {
|
|
33
|
-
test("happy path: pending -> running -> complete with result", async () => {
|
|
34
|
-
const registry = new MigrationJobRegistry();
|
|
35
|
-
|
|
36
|
-
let release: (value: string) => void = () => {};
|
|
37
|
-
const gate = new Promise<string>((resolve) => {
|
|
38
|
-
release = resolve;
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
const job = registry.startJob("export", async (): Promise<string> => {
|
|
42
|
-
return await gate;
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
// Returned synchronously in pending state.
|
|
46
|
-
expect(job.status).toBe("pending");
|
|
47
|
-
expect(job.id).toMatch(
|
|
48
|
-
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/,
|
|
49
|
-
);
|
|
50
|
-
expect(job.type).toBe("export");
|
|
51
|
-
expect(job.createdAt).toBeGreaterThan(0);
|
|
52
|
-
expect(job.startedAt).toBeUndefined();
|
|
53
|
-
expect(job.completedAt).toBeUndefined();
|
|
54
|
-
|
|
55
|
-
// Let the microtask run -> status flips to running with `startedAt` set.
|
|
56
|
-
await flushMicrotasks();
|
|
57
|
-
const running = registry.getJob(job.id);
|
|
58
|
-
expect(running).not.toBeNull();
|
|
59
|
-
expect(running!.status).toBe("running");
|
|
60
|
-
expect(running!.startedAt).toBeGreaterThanOrEqual(running!.createdAt);
|
|
61
|
-
|
|
62
|
-
// Resolve the runner and let it settle.
|
|
63
|
-
release("the-result");
|
|
64
|
-
await flushMicrotasks();
|
|
65
|
-
|
|
66
|
-
const done = registry.getJob(job.id);
|
|
67
|
-
expect(done).not.toBeNull();
|
|
68
|
-
expect(done!.status).toBe("complete");
|
|
69
|
-
expect(done!.result).toBe("the-result");
|
|
70
|
-
expect(done!.completedAt).toBeGreaterThanOrEqual(done!.startedAt ?? 0);
|
|
71
|
-
expect(done!.error).toBeUndefined();
|
|
72
|
-
|
|
73
|
-
// `listJobs` sees it too.
|
|
74
|
-
expect(registry.listJobs().map((j) => j.id)).toContain(job.id);
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
test("failure path: runner throws -> failed with mapped error", async () => {
|
|
78
|
-
const registry = new MigrationJobRegistry();
|
|
79
|
-
const job = registry.startJob("export", async () => {
|
|
80
|
-
throw new Error("boom");
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
await flushMicrotasks();
|
|
84
|
-
|
|
85
|
-
const done = registry.getJob(job.id);
|
|
86
|
-
expect(done).not.toBeNull();
|
|
87
|
-
expect(done!.status).toBe("failed");
|
|
88
|
-
expect(done!.error).toEqual({ code: "unknown", message: "boom" });
|
|
89
|
-
expect(done!.result).toBeUndefined();
|
|
90
|
-
expect(done!.completedAt).toBeGreaterThanOrEqual(done!.startedAt ?? 0);
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
test("runner-provided `code` overrides the default `unknown`", async () => {
|
|
94
|
-
const registry = new MigrationJobRegistry();
|
|
95
|
-
const job = registry.startJob("import", async () => {
|
|
96
|
-
const err = new Error("invalid manifest") as Error & { code: string };
|
|
97
|
-
err.code = "invalid_manifest";
|
|
98
|
-
throw err;
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
await flushMicrotasks();
|
|
102
|
-
|
|
103
|
-
const done = registry.getJob(job.id);
|
|
104
|
-
expect(done!.status).toBe("failed");
|
|
105
|
-
expect(done!.error).toEqual({
|
|
106
|
-
code: "invalid_manifest",
|
|
107
|
-
message: "invalid manifest",
|
|
108
|
-
});
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
test("kFetchBodyError path: tagged error maps to `fetch_failed`", async () => {
|
|
112
|
-
const registry = new MigrationJobRegistry();
|
|
113
|
-
const job = registry.startJob("import", async () => {
|
|
114
|
-
const err = new Error("upstream hung up") as Error & {
|
|
115
|
-
upstreamStatus?: number;
|
|
116
|
-
};
|
|
117
|
-
(err as unknown as Record<symbol, boolean>)[kFetchBodyError] = true;
|
|
118
|
-
err.upstreamStatus = 502;
|
|
119
|
-
throw err;
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
await flushMicrotasks();
|
|
123
|
-
|
|
124
|
-
const done = registry.getJob(job.id);
|
|
125
|
-
expect(done).not.toBeNull();
|
|
126
|
-
expect(done!.status).toBe("failed");
|
|
127
|
-
expect(done!.error).toEqual({
|
|
128
|
-
code: "fetch_failed",
|
|
129
|
-
message: "upstream hung up",
|
|
130
|
-
upstreamStatus: 502,
|
|
131
|
-
});
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
test("kFetchBodyError without upstreamStatus omits the field", async () => {
|
|
135
|
-
const registry = new MigrationJobRegistry();
|
|
136
|
-
const job = registry.startJob("import", async () => {
|
|
137
|
-
const err = new Error("aborted") as Error;
|
|
138
|
-
(err as unknown as Record<symbol, boolean>)[kFetchBodyError] = true;
|
|
139
|
-
throw err;
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
await flushMicrotasks();
|
|
143
|
-
|
|
144
|
-
const done = registry.getJob(job.id)!;
|
|
145
|
-
expect(done.status).toBe("failed");
|
|
146
|
-
expect(done.error).toEqual({
|
|
147
|
-
code: "fetch_failed",
|
|
148
|
-
message: "aborted",
|
|
149
|
-
});
|
|
150
|
-
expect(done.error?.upstreamStatus).toBeUndefined();
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
test("concurrent limit: second same-type job rejects; other type is allowed", async () => {
|
|
154
|
-
const registry = new MigrationJobRegistry();
|
|
155
|
-
|
|
156
|
-
let release: () => void = () => {};
|
|
157
|
-
const gate = new Promise<void>((resolve) => {
|
|
158
|
-
release = resolve;
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
// First export is pending (microtask hasn't run yet) — second export
|
|
162
|
-
// immediately rejects on the still-pending first job.
|
|
163
|
-
const first = registry.startJob("export", async () => {
|
|
164
|
-
await gate;
|
|
165
|
-
});
|
|
166
|
-
expect(first.status).toBe("pending");
|
|
167
|
-
|
|
168
|
-
expect(() => registry.startJob("export", async () => {})).toThrow(
|
|
169
|
-
JobAlreadyInProgressError,
|
|
170
|
-
);
|
|
171
|
-
|
|
172
|
-
// Advance to running — a same-type attempt still rejects.
|
|
173
|
-
await flushMicrotasks();
|
|
174
|
-
expect(registry.getJob(first.id)!.status).toBe("running");
|
|
175
|
-
|
|
176
|
-
let caught: unknown = null;
|
|
177
|
-
try {
|
|
178
|
-
registry.startJob("export", async () => {});
|
|
179
|
-
} catch (err) {
|
|
180
|
-
caught = err;
|
|
181
|
-
}
|
|
182
|
-
expect(caught).toBeInstanceOf(JobAlreadyInProgressError);
|
|
183
|
-
expect((caught as JobAlreadyInProgressError).existingJobId).toBe(first.id);
|
|
184
|
-
|
|
185
|
-
// A job of the *other* type is allowed in parallel.
|
|
186
|
-
const importJob = registry.startJob("import", async () => "ok");
|
|
187
|
-
expect(importJob.type).toBe("import");
|
|
188
|
-
expect(importJob.id).not.toBe(first.id);
|
|
189
|
-
|
|
190
|
-
// Settle both before leaving the test so no promises leak.
|
|
191
|
-
release();
|
|
192
|
-
await flushMicrotasks();
|
|
193
|
-
expect(registry.getJob(first.id)!.status).toBe("complete");
|
|
194
|
-
expect(registry.getJob(importJob.id)!.status).toBe("complete");
|
|
195
|
-
|
|
196
|
-
// And now a new export is allowed since nothing is in flight.
|
|
197
|
-
const third = registry.startJob("export", async () => "again");
|
|
198
|
-
expect(third.status).toBe("pending");
|
|
199
|
-
await flushMicrotasks();
|
|
200
|
-
expect(registry.getJob(third.id)!.status).toBe("complete");
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
test("returned job snapshots are decoupled from internal registry state", async () => {
|
|
204
|
-
const registry = new MigrationJobRegistry();
|
|
205
|
-
|
|
206
|
-
let release: () => void = () => {};
|
|
207
|
-
const gate = new Promise<void>((resolve) => {
|
|
208
|
-
release = resolve;
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
const first = registry.startJob("export", async () => {
|
|
212
|
-
await gate;
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
// The synchronous return of startJob must be a snapshot, not the
|
|
216
|
-
// internal record. Mutating it (e.g. to simulate a caller stomping on
|
|
217
|
-
// `status`) must not unblock the single-in-flight invariant while the
|
|
218
|
-
// runner is still active.
|
|
219
|
-
first.status = "complete";
|
|
220
|
-
first.completedAt = Date.now();
|
|
221
|
-
|
|
222
|
-
await flushMicrotasks();
|
|
223
|
-
// Internal state is still running — the snapshot mutation did not leak.
|
|
224
|
-
expect(registry.getJob(first.id)!.status).toBe("running");
|
|
225
|
-
|
|
226
|
-
// Attempting a second same-type job must still reject.
|
|
227
|
-
expect(() => registry.startJob("export", async () => {})).toThrow(
|
|
228
|
-
JobAlreadyInProgressError,
|
|
229
|
-
);
|
|
230
|
-
|
|
231
|
-
// getJob snapshots are also decoupled: mutating the return value of
|
|
232
|
-
// getJob does not leak into listJobs or subsequent getJob calls.
|
|
233
|
-
const polled = registry.getJob(first.id)!;
|
|
234
|
-
polled.status = "failed";
|
|
235
|
-
expect(registry.getJob(first.id)!.status).toBe("running");
|
|
236
|
-
expect(registry.listJobs().find((j) => j.id === first.id)!.status).toBe(
|
|
237
|
-
"running",
|
|
238
|
-
);
|
|
239
|
-
|
|
240
|
-
// Settle.
|
|
241
|
-
release();
|
|
242
|
-
await flushMicrotasks();
|
|
243
|
-
expect(registry.getJob(first.id)!.status).toBe("complete");
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
test("TTL sweep: completed jobs older than TTL are evicted", async () => {
|
|
247
|
-
const registry = new MigrationJobRegistry();
|
|
248
|
-
registry.completedJobTtlMs = 1_000; // 1s for test purposes
|
|
249
|
-
|
|
250
|
-
const job = registry.startJob("export", async () => "done");
|
|
251
|
-
await flushMicrotasks();
|
|
252
|
-
const completed = registry.getJob(job.id)!;
|
|
253
|
-
expect(completed.status).toBe("complete");
|
|
254
|
-
|
|
255
|
-
// Before TTL: sweep is a no-op.
|
|
256
|
-
const originalNow = Date.now;
|
|
257
|
-
try {
|
|
258
|
-
Date.now = () => (completed.completedAt ?? 0) + 500;
|
|
259
|
-
registry.sweep();
|
|
260
|
-
expect(registry.getJob(job.id)).not.toBeNull();
|
|
261
|
-
expect(registry.listJobs().map((j) => j.id)).toContain(job.id);
|
|
262
|
-
|
|
263
|
-
// Past TTL: sweep evicts the completed job.
|
|
264
|
-
Date.now = () => (completed.completedAt ?? 0) + 2_000;
|
|
265
|
-
registry.sweep();
|
|
266
|
-
expect(registry.getJob(job.id)).toBeNull();
|
|
267
|
-
expect(registry.listJobs().map((j) => j.id)).not.toContain(job.id);
|
|
268
|
-
} finally {
|
|
269
|
-
Date.now = originalNow;
|
|
270
|
-
}
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
test("TTL sweep: failed jobs are also evicted past TTL", async () => {
|
|
274
|
-
const registry = new MigrationJobRegistry();
|
|
275
|
-
registry.completedJobTtlMs = 500;
|
|
276
|
-
|
|
277
|
-
const job = registry.startJob("import", async () => {
|
|
278
|
-
throw new Error("nope");
|
|
279
|
-
});
|
|
280
|
-
await flushMicrotasks();
|
|
281
|
-
const failed = registry.getJob(job.id)!;
|
|
282
|
-
expect(failed.status).toBe("failed");
|
|
283
|
-
|
|
284
|
-
const originalNow = Date.now;
|
|
285
|
-
try {
|
|
286
|
-
Date.now = () => (failed.completedAt ?? 0) + 10_000;
|
|
287
|
-
registry.sweep();
|
|
288
|
-
expect(registry.getJob(job.id)).toBeNull();
|
|
289
|
-
} finally {
|
|
290
|
-
Date.now = originalNow;
|
|
291
|
-
}
|
|
292
|
-
});
|
|
293
|
-
|
|
294
|
-
test("TTL sweep: running jobs are never evicted", async () => {
|
|
295
|
-
const registry = new MigrationJobRegistry();
|
|
296
|
-
registry.completedJobTtlMs = 1;
|
|
297
|
-
|
|
298
|
-
let release: () => void = () => {};
|
|
299
|
-
const gate = new Promise<void>((resolve) => {
|
|
300
|
-
release = resolve;
|
|
301
|
-
});
|
|
302
|
-
const job = registry.startJob("export", async () => {
|
|
303
|
-
await gate;
|
|
304
|
-
});
|
|
305
|
-
await flushMicrotasks();
|
|
306
|
-
expect(registry.getJob(job.id)!.status).toBe("running");
|
|
307
|
-
|
|
308
|
-
const originalNow = Date.now;
|
|
309
|
-
try {
|
|
310
|
-
Date.now = () => Number.MAX_SAFE_INTEGER;
|
|
311
|
-
registry.sweep();
|
|
312
|
-
expect(registry.getJob(job.id)).not.toBeNull();
|
|
313
|
-
} finally {
|
|
314
|
-
Date.now = originalNow;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
// Cleanup: let the runner resolve.
|
|
318
|
-
release();
|
|
319
|
-
await flushMicrotasks();
|
|
320
|
-
});
|
|
321
|
-
|
|
322
|
-
test("getJob returns null for unknown ids", () => {
|
|
323
|
-
const registry = new MigrationJobRegistry();
|
|
324
|
-
expect(registry.getJob("nope")).toBeNull();
|
|
325
|
-
});
|
|
326
|
-
|
|
327
|
-
test("JobAlreadyInProgressError carries the existing job id", () => {
|
|
328
|
-
const err = new JobAlreadyInProgressError("export", "abc-123");
|
|
329
|
-
expect(err).toBeInstanceOf(Error);
|
|
330
|
-
expect(err.name).toBe("JobAlreadyInProgressError");
|
|
331
|
-
expect(err.existingJobId).toBe("abc-123");
|
|
332
|
-
expect(err.message).toContain("export");
|
|
333
|
-
expect(err.message).toContain("abc-123");
|
|
334
|
-
});
|
|
335
|
-
|
|
336
|
-
test("MigrationJob interface shape is preserved on success", async () => {
|
|
337
|
-
const registry = new MigrationJobRegistry();
|
|
338
|
-
const job = registry.startJob("export", async () => ({ bytes: 42 }));
|
|
339
|
-
await flushMicrotasks();
|
|
340
|
-
const done: MigrationJob = registry.getJob(job.id)!;
|
|
341
|
-
expect(done.id).toBe(job.id);
|
|
342
|
-
expect(done.type).toBe("export");
|
|
343
|
-
expect(done.status).toBe("complete");
|
|
344
|
-
expect(done.result).toEqual({ bytes: 42 });
|
|
345
|
-
});
|
|
346
|
-
});
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for `getOriginMode` — the vbundle manifest `origin.mode` derivation.
|
|
3
|
-
*
|
|
4
|
-
* Covers:
|
|
5
|
-
* - `IS_CLOUD=true` → "managed" (wins over IS_CONTAINERIZED).
|
|
6
|
-
* - `IS_CONTAINERIZED=true` (no IS_CLOUD) → "self-hosted-remote".
|
|
7
|
-
* - Neither → "self-hosted-local".
|
|
8
|
-
* - Regression: a logged-in local assistant holding cloud-proxy
|
|
9
|
-
* credentials (platform URL + assistant_api_key) must NOT be classified
|
|
10
|
-
* "managed" — every logged-in local has those credentials, and a
|
|
11
|
-
* "managed" stamp trips the importer's `secrets_redacted must be true
|
|
12
|
-
* when origin.mode is 'managed'` refine, breaking local→platform
|
|
13
|
-
* teleport. The helper keys on the IS_CLOUD deployment env only —
|
|
14
|
-
* nothing here consults the credential store, so the regression case is
|
|
15
|
-
* simply "no env flags set".
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
import { afterEach, describe, expect, test } from "bun:test";
|
|
19
|
-
|
|
20
|
-
import { getOriginMode } from "../origin-mode.js";
|
|
21
|
-
|
|
22
|
-
const ORIGINAL_IS_CLOUD = process.env.IS_CLOUD;
|
|
23
|
-
const ORIGINAL_IS_CONTAINERIZED = process.env.IS_CONTAINERIZED;
|
|
24
|
-
|
|
25
|
-
function restore(name: string, value: string | undefined): void {
|
|
26
|
-
if (value === undefined) {
|
|
27
|
-
delete process.env[name];
|
|
28
|
-
} else {
|
|
29
|
-
process.env[name] = value;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
afterEach(() => {
|
|
34
|
-
restore("IS_CLOUD", ORIGINAL_IS_CLOUD);
|
|
35
|
-
restore("IS_CONTAINERIZED", ORIGINAL_IS_CONTAINERIZED);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
describe("getOriginMode", () => {
|
|
39
|
-
test("IS_CLOUD=true → managed", async () => {
|
|
40
|
-
process.env.IS_CLOUD = "true";
|
|
41
|
-
delete process.env.IS_CONTAINERIZED;
|
|
42
|
-
expect(await getOriginMode()).toBe("managed");
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
test("IS_CLOUD wins over IS_CONTAINERIZED", async () => {
|
|
46
|
-
process.env.IS_CLOUD = "1";
|
|
47
|
-
process.env.IS_CONTAINERIZED = "true";
|
|
48
|
-
expect(await getOriginMode()).toBe("managed");
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
test("IS_CONTAINERIZED=true without IS_CLOUD → self-hosted-remote", async () => {
|
|
52
|
-
delete process.env.IS_CLOUD;
|
|
53
|
-
process.env.IS_CONTAINERIZED = "true";
|
|
54
|
-
expect(await getOriginMode()).toBe("self-hosted-remote");
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
test("neither flag → self-hosted-local (incl. logged-in locals with cloud-proxy creds)", async () => {
|
|
58
|
-
delete process.env.IS_CLOUD;
|
|
59
|
-
delete process.env.IS_CONTAINERIZED;
|
|
60
|
-
expect(await getOriginMode()).toBe("self-hosted-local");
|
|
61
|
-
});
|
|
62
|
-
});
|