@mycadet/gateway 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.json +1 -1
- package/package.json +1 -1
- package/src/__tests__/actor-token-revocation.test.ts +41 -34
- package/src/__tests__/contact-store-mirror-op-missing.test.ts +33 -70
- package/src/__tests__/contacts-mirror-op-reporter.test.ts +33 -89
- package/src/__tests__/credential-requests.test.ts +4 -4
- package/src/__tests__/devices-mine.test.ts +187 -0
- package/src/__tests__/edge-auth.test.ts +1 -20
- package/src/__tests__/edge-guardian-auth.test.ts +1 -17
- package/src/__tests__/feature-flag-resolver.test.ts +2 -30
- package/src/__tests__/feature-flag-watcher-callback.test.ts +0 -22
- package/src/__tests__/feature-flags-route.test.ts +2 -191
- package/src/__tests__/guardian-bootstrap-lookups.test.ts +18 -25
- package/src/__tests__/guardian-integrity-reporter.test.ts +51 -82
- package/src/__tests__/guardian-integrity.test.ts +16 -20
- package/src/__tests__/handle-inbound-trust-verdict.test.ts +1 -14
- package/src/__tests__/ipc-feature-flag-routes.test.ts +0 -56
- package/src/__tests__/lum-2783-recovery-e2e.test.ts +0 -12
- package/src/__tests__/post-assistant-ready-deferred.test.ts +0 -18
- package/src/__tests__/refresh-device-binding.test.ts +0 -12
- package/src/__tests__/remote-web-pairing-token.test.ts +4 -14
- package/src/__tests__/runtime-client.test.ts +7 -7
- package/src/__tests__/speech-relay-websocket.test.ts +4 -4
- package/src/__tests__/trust-verdict-resolver-guardian-integrity.test.ts +30 -36
- package/src/__tests__/twilio-webhooks.test.ts +2 -2
- package/src/auth/actor-token-revocation.ts +34 -9
- package/src/cli/__tests__/pairing.test.ts +95 -0
- package/src/cli/index.ts +1 -1
- package/src/cli/pairing.ts +81 -6
- package/src/contacts-mirror-op-reporter.ts +19 -36
- package/src/db/connection.ts +0 -10
- package/src/feature-flag-resolver.ts +2 -7
- package/src/feature-flag-watcher.ts +1 -14
- package/src/guardian-integrity-reporter.ts +19 -47
- package/src/http/middleware/auth.ts +33 -27
- package/src/http/routes/auth-token.ts +1 -1
- package/src/http/routes/devices-mine.ts +103 -0
- package/src/http/routes/feature-flags.ts +1 -8
- package/src/http/routes/guardian-pin.ts +2 -11
- package/src/http/routes/ipc-runtime-proxy.ts +1 -1
- package/src/http/routes/live-voice-websocket.ts +1 -1
- package/src/http/routes/runtime-audio-stream.ts +1 -1
- package/src/http/routes/runtime-proxy.ts +2 -2
- package/src/http/routes/twilio-voice-webhook.test.ts +13 -13
- package/src/index.ts +11 -198
- package/src/ipc/feature-flag-handlers.ts +2 -9
- package/src/remote-web/pairing-challenge-store.ts +15 -0
- package/src/risk/command-registry/commands/assistant.ts +0 -24
- package/src/risk/command-registry.test.ts +2 -4
- package/src/schema.ts +44 -372
- package/src/__tests__/auth-fallback-count-tracker.test.ts +0 -92
- package/src/__tests__/auth-fallback-reporter.test.ts +0 -101
- package/src/__tests__/backup-worker-cloud-mode.test.ts +0 -44
- package/src/__tests__/backup-worker-export-timeout.test.ts +0 -164
- package/src/__tests__/migration-teleport-gcs-proxy.test.ts +0 -216
- package/src/__tests__/remote-feature-flag-sync.test.ts +0 -961
- package/src/auth-fallback-count-tracker.ts +0 -113
- package/src/auth-fallback-reporter.ts +0 -127
- package/src/backup/backup-key.ts +0 -138
- package/src/backup/backup-routes.ts +0 -161
- package/src/backup/backup-worker.ts +0 -425
- package/src/backup/list-snapshots.ts +0 -97
- package/src/backup/local-writer.ts +0 -87
- package/src/backup/offsite-writer.ts +0 -182
- package/src/backup/paths.ts +0 -113
- package/src/backup/stream-crypt.ts +0 -258
- package/src/device-id.ts +0 -18
- package/src/feature-flag-remote-store.ts +0 -166
- package/src/http/routes/migration-proxy.ts +0 -636
- package/src/internal-telemetry-client.ts +0 -39
- package/src/ipc/debug-export-handlers.test.ts +0 -102
- package/src/ipc/debug-export-handlers.ts +0 -81
- package/src/remote-feature-flag-sync.ts +0 -440
- package/src/watchdog-reporter.ts +0 -110
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { afterEach, expect, mock, test } from "bun:test";
|
|
2
|
-
|
|
3
|
-
let exportRequested = false;
|
|
4
|
-
const originalIsPlatform = process.env.IS_CLOUD;
|
|
5
|
-
|
|
6
|
-
mock.module("../config-file-utils.js", () => ({
|
|
7
|
-
readConfigFileOrEmpty: () => ({
|
|
8
|
-
backup: {
|
|
9
|
-
enabled: true,
|
|
10
|
-
intervalHours: 1,
|
|
11
|
-
retention: 3,
|
|
12
|
-
offsite: { enabled: false },
|
|
13
|
-
},
|
|
14
|
-
}),
|
|
15
|
-
}));
|
|
16
|
-
|
|
17
|
-
mock.module("../fetch.js", () => ({
|
|
18
|
-
fetchImpl: async () => {
|
|
19
|
-
exportRequested = true;
|
|
20
|
-
throw new Error("Platform backup export must not be requested");
|
|
21
|
-
},
|
|
22
|
-
}));
|
|
23
|
-
|
|
24
|
-
afterEach(() => {
|
|
25
|
-
if (originalIsPlatform === undefined) {
|
|
26
|
-
delete process.env.IS_CLOUD;
|
|
27
|
-
} else {
|
|
28
|
-
process.env.IS_CLOUD = originalIsPlatform;
|
|
29
|
-
}
|
|
30
|
-
mock.restore();
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
test("platform mode disables the backup worker", async () => {
|
|
34
|
-
process.env.IS_CLOUD = "true";
|
|
35
|
-
const { startBackupWorker } = await import("../backup/backup-worker.js");
|
|
36
|
-
|
|
37
|
-
const worker = startBackupWorker({
|
|
38
|
-
assistantRuntimeBaseUrl: "http://127.0.0.1:7821",
|
|
39
|
-
});
|
|
40
|
-
await worker.runOnce();
|
|
41
|
-
worker.stop();
|
|
42
|
-
|
|
43
|
-
expect(exportRequested).toBeFalse();
|
|
44
|
-
});
|
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Regression tests for the export deadline in the backup worker.
|
|
3
|
-
*
|
|
4
|
-
* `fetch` resolves as soon as the response headers arrive; the bundle streams
|
|
5
|
-
* in afterwards. The worker used to clear its abort timer in a `finally` on
|
|
6
|
-
* that call, which left the download — the part that actually takes an hour on
|
|
7
|
-
* a large workspace — with no deadline at all. A body that stalled after
|
|
8
|
-
* headers hung `performBackup` forever, and with it the `snapshotInProgress`
|
|
9
|
-
* mutex, so every later tick logged "snapshot already in progress" until the
|
|
10
|
-
* gateway was restarted.
|
|
11
|
-
*
|
|
12
|
-
* These drive a response whose headers arrive normally and whose body never
|
|
13
|
-
* produces a chunk, and assert the run gives up and lets go of the mutex.
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import { mkdtemp, rm } from "node:fs/promises";
|
|
17
|
-
import { tmpdir } from "node:os";
|
|
18
|
-
import { join } from "node:path";
|
|
19
|
-
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
20
|
-
|
|
21
|
-
let localDir: string;
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* The failure this file guards against is a hang, which a bare `await` would
|
|
25
|
-
* inherit — a regression would stall the runner instead of reporting. Race
|
|
26
|
-
* every run against a deadline so it fails fast and says why.
|
|
27
|
-
*/
|
|
28
|
-
function withDeadline<T>(work: Promise<T>, label: string): Promise<T> {
|
|
29
|
-
return Promise.race([
|
|
30
|
-
work,
|
|
31
|
-
new Promise<never>((_, reject) =>
|
|
32
|
-
setTimeout(
|
|
33
|
-
() => reject(new Error(`${label} did not settle within 10s`)),
|
|
34
|
-
10_000,
|
|
35
|
-
),
|
|
36
|
-
),
|
|
37
|
-
]);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Records the init the worker passed to fetch, so a test can assert on the
|
|
42
|
-
* deadline options rather than waiting one out.
|
|
43
|
-
*/
|
|
44
|
-
let lastInit: (RequestInit & { timeout?: boolean | number }) | undefined;
|
|
45
|
-
|
|
46
|
-
/** A response that sends headers immediately and then never sends a chunk. */
|
|
47
|
-
function stalledBodyResponse(): Response {
|
|
48
|
-
const body = new ReadableStream<Uint8Array>({
|
|
49
|
-
start(controller) {
|
|
50
|
-
// One chunk so the transfer visibly begins, then silence forever.
|
|
51
|
-
controller.enqueue(new Uint8Array([0x1f, 0x8b]));
|
|
52
|
-
},
|
|
53
|
-
});
|
|
54
|
-
return new Response(body, { status: 200 });
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
beforeEach(async () => {
|
|
58
|
-
localDir = await mkdtemp(join(tmpdir(), "backup-worker-timeout-"));
|
|
59
|
-
|
|
60
|
-
mock.module("../config-file-utils.js", () => ({
|
|
61
|
-
readConfigFileOrEmpty: () => ({
|
|
62
|
-
backup: {
|
|
63
|
-
enabled: true,
|
|
64
|
-
intervalHours: 6,
|
|
65
|
-
retention: 3,
|
|
66
|
-
// Offsite off keeps the run local: no encryption key is needed, so
|
|
67
|
-
// nothing touches the gateway security dir.
|
|
68
|
-
offsite: { enabled: false },
|
|
69
|
-
localDirectory: localDir,
|
|
70
|
-
},
|
|
71
|
-
}),
|
|
72
|
-
}));
|
|
73
|
-
|
|
74
|
-
mock.module("../auth/token-exchange.js", () => ({
|
|
75
|
-
mintServiceToken: () => "test-service-token",
|
|
76
|
-
}));
|
|
77
|
-
|
|
78
|
-
mock.module("../fetch.js", () => ({
|
|
79
|
-
fetchImpl: async (
|
|
80
|
-
_input: string | URL | Request,
|
|
81
|
-
init?: RequestInit & { timeout?: boolean | number },
|
|
82
|
-
) => {
|
|
83
|
-
lastInit = init;
|
|
84
|
-
init?.signal?.throwIfAborted();
|
|
85
|
-
return stalledBodyResponse();
|
|
86
|
-
},
|
|
87
|
-
}));
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
afterEach(async () => {
|
|
91
|
-
mock.restore();
|
|
92
|
-
await rm(localDir, { recursive: true, force: true });
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
describe("backup export deadline", () => {
|
|
96
|
-
test("a body that stalls after headers aborts instead of hanging", async () => {
|
|
97
|
-
const { createSnapshotNow } = await import("../backup/backup-worker.js");
|
|
98
|
-
|
|
99
|
-
const startedAt = Date.now();
|
|
100
|
-
const run = withDeadline(
|
|
101
|
-
createSnapshotNow({
|
|
102
|
-
assistantRuntimeBaseUrl: "http://127.0.0.1:7821",
|
|
103
|
-
exportTimeoutMs: 300,
|
|
104
|
-
}),
|
|
105
|
-
"stalled export",
|
|
106
|
-
);
|
|
107
|
-
|
|
108
|
-
await expect(run).rejects.toThrow(/aborted|abort/i);
|
|
109
|
-
// The deadline fired rather than the call returning on its own; the guard
|
|
110
|
-
// is that this resolved at all, well inside the real 60-minute budget.
|
|
111
|
-
expect(Date.now() - startedAt).toBeLessThan(15_000);
|
|
112
|
-
}, 30_000);
|
|
113
|
-
|
|
114
|
-
test("the concurrency mutex is released after a stalled transfer", async () => {
|
|
115
|
-
const { createSnapshotNow } = await import("../backup/backup-worker.js");
|
|
116
|
-
|
|
117
|
-
const deps = {
|
|
118
|
-
assistantRuntimeBaseUrl: "http://127.0.0.1:7821",
|
|
119
|
-
exportTimeoutMs: 300,
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
await expect(
|
|
123
|
-
withDeadline(createSnapshotNow(deps), "first stalled export"),
|
|
124
|
-
).rejects.toThrow();
|
|
125
|
-
|
|
126
|
-
// A wedged mutex surfaces here: the second run is refused outright rather
|
|
127
|
-
// than getting as far as its own stalled transfer.
|
|
128
|
-
const secondFailure = await withDeadline(
|
|
129
|
-
createSnapshotNow(deps),
|
|
130
|
-
"second stalled export",
|
|
131
|
-
).then(
|
|
132
|
-
() => null,
|
|
133
|
-
(err: unknown) => (err instanceof Error ? err.message : String(err)),
|
|
134
|
-
);
|
|
135
|
-
|
|
136
|
-
expect(secondFailure).not.toBeNull();
|
|
137
|
-
expect(secondFailure).not.toContain("already in progress");
|
|
138
|
-
}, 30_000);
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
describe("backup export request options", () => {
|
|
142
|
-
test("opts out of the runtime's default request timeout", async () => {
|
|
143
|
-
// The daemon builds the entire archive before it can write a response
|
|
144
|
-
// header, so on a large workspace no header arrives for many minutes. The
|
|
145
|
-
// runtime's 300-second default would kill that request — and it is NOT
|
|
146
|
-
// extended by the abort signal below, so the explicit budget alone does
|
|
147
|
-
// not save it. This is the option that makes the budget real.
|
|
148
|
-
const { createSnapshotNow } = await import("../backup/backup-worker.js");
|
|
149
|
-
|
|
150
|
-
await expect(
|
|
151
|
-
withDeadline(
|
|
152
|
-
createSnapshotNow({
|
|
153
|
-
assistantRuntimeBaseUrl: "http://127.0.0.1:7821",
|
|
154
|
-
exportTimeoutMs: 300,
|
|
155
|
-
}),
|
|
156
|
-
"stalled export",
|
|
157
|
-
),
|
|
158
|
-
).rejects.toThrow();
|
|
159
|
-
|
|
160
|
-
expect(lastInit?.timeout).toBe(false);
|
|
161
|
-
// The caller's own deadline is still what governs.
|
|
162
|
-
expect(lastInit?.signal).toBeInstanceOf(AbortSignal);
|
|
163
|
-
}, 30_000);
|
|
164
|
-
});
|
|
@@ -1,216 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect, mock, afterEach } from "bun:test";
|
|
2
|
-
import type { GatewayConfig } from "../config.js";
|
|
3
|
-
import { initSigningKey } from "../auth/token-service.js";
|
|
4
|
-
|
|
5
|
-
const TEST_SIGNING_KEY = Buffer.from("test-signing-key-at-least-32-bytes-long");
|
|
6
|
-
initSigningKey(TEST_SIGNING_KEY);
|
|
7
|
-
|
|
8
|
-
type FetchFn = (
|
|
9
|
-
input: string | URL | Request,
|
|
10
|
-
init?: RequestInit,
|
|
11
|
-
) => Promise<Response>;
|
|
12
|
-
let fetchMock: ReturnType<typeof mock<FetchFn>> = mock(
|
|
13
|
-
async () => new Response(),
|
|
14
|
-
);
|
|
15
|
-
|
|
16
|
-
mock.module("../fetch.js", () => ({
|
|
17
|
-
fetchImpl: (...args: Parameters<FetchFn>) => fetchMock(...args),
|
|
18
|
-
}));
|
|
19
|
-
|
|
20
|
-
const {
|
|
21
|
-
createMigrationExportToGcsProxyHandler,
|
|
22
|
-
createMigrationImportFromGcsProxyHandler,
|
|
23
|
-
createMigrationJobStatusProxyHandler,
|
|
24
|
-
} = await import("../http/routes/migration-proxy.js");
|
|
25
|
-
|
|
26
|
-
function makeConfig(overrides: Partial<GatewayConfig> = {}): GatewayConfig {
|
|
27
|
-
const merged: GatewayConfig = {
|
|
28
|
-
assistantRuntimeBaseUrl: "http://localhost:7821",
|
|
29
|
-
routingEntries: [],
|
|
30
|
-
port: 7830,
|
|
31
|
-
runtimeProxyRequireAuth: true,
|
|
32
|
-
runtimeTimeoutMs: 30000,
|
|
33
|
-
runtimeMaxRetries: 2,
|
|
34
|
-
runtimeInitialBackoffMs: 500,
|
|
35
|
-
maxWebhookPayloadBytes: 1048576,
|
|
36
|
-
logFile: { dir: undefined, retentionDays: 30 },
|
|
37
|
-
maxAttachmentBytes: {
|
|
38
|
-
telegram: 50 * 1024 * 1024,
|
|
39
|
-
slack: 100 * 1024 * 1024,
|
|
40
|
-
whatsapp: 16 * 1024 * 1024,
|
|
41
|
-
default: 50 * 1024 * 1024,
|
|
42
|
-
},
|
|
43
|
-
maxAttachmentConcurrency: 3,
|
|
44
|
-
gatewayInternalBaseUrl: "http://127.0.0.1:7830",
|
|
45
|
-
trustProxy: false,
|
|
46
|
-
...overrides,
|
|
47
|
-
};
|
|
48
|
-
return merged;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
afterEach(() => {
|
|
52
|
-
fetchMock = mock(async () => new Response());
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
describe("teleport-gcs migration proxies", () => {
|
|
56
|
-
test("export-to-gcs: forwards POST body to daemon and returns 202", async () => {
|
|
57
|
-
let capturedUrl: string | undefined;
|
|
58
|
-
let capturedMethod: string | undefined;
|
|
59
|
-
let capturedBody: ArrayBuffer | undefined;
|
|
60
|
-
let capturedHeaders: Headers | undefined;
|
|
61
|
-
fetchMock = mock(
|
|
62
|
-
async (input: string | URL | Request, init?: RequestInit) => {
|
|
63
|
-
capturedUrl = String(input);
|
|
64
|
-
capturedMethod = init?.method;
|
|
65
|
-
capturedBody = init?.body as ArrayBuffer;
|
|
66
|
-
capturedHeaders = init?.headers as unknown as Headers;
|
|
67
|
-
return new Response(
|
|
68
|
-
JSON.stringify({ job_id: "job-export-1", status: "pending" }),
|
|
69
|
-
{
|
|
70
|
-
status: 202,
|
|
71
|
-
headers: { "content-type": "application/json" },
|
|
72
|
-
},
|
|
73
|
-
);
|
|
74
|
-
},
|
|
75
|
-
);
|
|
76
|
-
|
|
77
|
-
const handler = createMigrationExportToGcsProxyHandler(makeConfig());
|
|
78
|
-
const bodyText = JSON.stringify({
|
|
79
|
-
upload_url: "https://storage.example/u",
|
|
80
|
-
description: "x",
|
|
81
|
-
});
|
|
82
|
-
const res = await handler(
|
|
83
|
-
new Request("http://localhost:7830/v1/migrations/export-to-gcs", {
|
|
84
|
-
method: "POST",
|
|
85
|
-
headers: {
|
|
86
|
-
authorization: "Bearer caller-token",
|
|
87
|
-
"content-type": "application/json",
|
|
88
|
-
},
|
|
89
|
-
body: bodyText,
|
|
90
|
-
}),
|
|
91
|
-
);
|
|
92
|
-
|
|
93
|
-
expect(res.status).toBe(202);
|
|
94
|
-
expect(capturedUrl).toBe(
|
|
95
|
-
"http://localhost:7821/v1/migrations/export-to-gcs",
|
|
96
|
-
);
|
|
97
|
-
expect(capturedMethod).toBe("POST");
|
|
98
|
-
expect(capturedHeaders?.get("authorization")).toMatch(/^Bearer ey/);
|
|
99
|
-
expect(capturedHeaders?.has("host")).toBe(false);
|
|
100
|
-
expect(Buffer.from(capturedBody!).toString("utf8")).toBe(bodyText);
|
|
101
|
-
expect(await res.json()).toEqual({
|
|
102
|
-
job_id: "job-export-1",
|
|
103
|
-
status: "pending",
|
|
104
|
-
});
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
test("import-from-gcs: forwards POST body to daemon and returns 202", async () => {
|
|
108
|
-
let capturedUrl: string | undefined;
|
|
109
|
-
let capturedMethod: string | undefined;
|
|
110
|
-
let capturedHeaders: Headers | undefined;
|
|
111
|
-
fetchMock = mock(
|
|
112
|
-
async (input: string | URL | Request, init?: RequestInit) => {
|
|
113
|
-
capturedUrl = String(input);
|
|
114
|
-
capturedMethod = init?.method;
|
|
115
|
-
capturedHeaders = init?.headers as unknown as Headers;
|
|
116
|
-
return new Response(
|
|
117
|
-
JSON.stringify({ job_id: "job-import-1", status: "pending" }),
|
|
118
|
-
{
|
|
119
|
-
status: 202,
|
|
120
|
-
headers: { "content-type": "application/json" },
|
|
121
|
-
},
|
|
122
|
-
);
|
|
123
|
-
},
|
|
124
|
-
);
|
|
125
|
-
|
|
126
|
-
const handler = createMigrationImportFromGcsProxyHandler(makeConfig());
|
|
127
|
-
const res = await handler(
|
|
128
|
-
new Request("http://localhost:7830/v1/migrations/import-from-gcs", {
|
|
129
|
-
method: "POST",
|
|
130
|
-
headers: {
|
|
131
|
-
authorization: "Bearer caller-token",
|
|
132
|
-
"content-type": "application/json",
|
|
133
|
-
},
|
|
134
|
-
body: JSON.stringify({ bundle_url: "https://storage.example/b" }),
|
|
135
|
-
}),
|
|
136
|
-
);
|
|
137
|
-
|
|
138
|
-
expect(res.status).toBe(202);
|
|
139
|
-
expect(capturedUrl).toBe(
|
|
140
|
-
"http://localhost:7821/v1/migrations/import-from-gcs",
|
|
141
|
-
);
|
|
142
|
-
expect(capturedMethod).toBe("POST");
|
|
143
|
-
expect(capturedHeaders?.get("authorization")).toMatch(/^Bearer ey/);
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
test("jobs/:job_id: forwards GET to daemon with encoded job id", async () => {
|
|
147
|
-
let capturedUrl: string | undefined;
|
|
148
|
-
let capturedMethod: string | undefined;
|
|
149
|
-
fetchMock = mock(
|
|
150
|
-
async (input: string | URL | Request, init?: RequestInit) => {
|
|
151
|
-
capturedUrl = String(input);
|
|
152
|
-
capturedMethod = init?.method;
|
|
153
|
-
return new Response(
|
|
154
|
-
JSON.stringify({
|
|
155
|
-
job_id: "abc/def",
|
|
156
|
-
type: "export",
|
|
157
|
-
status: "processing",
|
|
158
|
-
}),
|
|
159
|
-
{
|
|
160
|
-
status: 200,
|
|
161
|
-
headers: { "content-type": "application/json" },
|
|
162
|
-
},
|
|
163
|
-
);
|
|
164
|
-
},
|
|
165
|
-
);
|
|
166
|
-
|
|
167
|
-
const handler = createMigrationJobStatusProxyHandler(makeConfig());
|
|
168
|
-
const res = await handler(
|
|
169
|
-
new Request("http://localhost:7830/v1/migrations/jobs/abc%2Fdef"),
|
|
170
|
-
"abc/def",
|
|
171
|
-
);
|
|
172
|
-
|
|
173
|
-
expect(res.status).toBe(200);
|
|
174
|
-
expect(capturedMethod).toBe("GET");
|
|
175
|
-
expect(capturedUrl).toBe(
|
|
176
|
-
"http://localhost:7821/v1/migrations/jobs/abc%2Fdef",
|
|
177
|
-
);
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
test("export-to-gcs: returns 502 on upstream connection failure", async () => {
|
|
181
|
-
fetchMock = mock(async () => {
|
|
182
|
-
throw new Error("connection refused");
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
const handler = createMigrationExportToGcsProxyHandler(makeConfig());
|
|
186
|
-
const res = await handler(
|
|
187
|
-
new Request("http://localhost:7830/v1/migrations/export-to-gcs", {
|
|
188
|
-
method: "POST",
|
|
189
|
-
headers: { "content-type": "application/json" },
|
|
190
|
-
body: "{}",
|
|
191
|
-
}),
|
|
192
|
-
);
|
|
193
|
-
|
|
194
|
-
expect(res.status).toBe(502);
|
|
195
|
-
expect(await res.json()).toEqual({ error: "Bad Gateway" });
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
test("jobs/:job_id: forwards upstream non-2xx (e.g. 404 job_not_found)", async () => {
|
|
199
|
-
fetchMock = mock(
|
|
200
|
-
async () =>
|
|
201
|
-
new Response(JSON.stringify({ error: { code: "job_not_found" } }), {
|
|
202
|
-
status: 404,
|
|
203
|
-
headers: { "content-type": "application/json" },
|
|
204
|
-
}),
|
|
205
|
-
);
|
|
206
|
-
|
|
207
|
-
const handler = createMigrationJobStatusProxyHandler(makeConfig());
|
|
208
|
-
const res = await handler(
|
|
209
|
-
new Request("http://localhost:7830/v1/migrations/jobs/unknown"),
|
|
210
|
-
"unknown",
|
|
211
|
-
);
|
|
212
|
-
|
|
213
|
-
expect(res.status).toBe(404);
|
|
214
|
-
expect(await res.json()).toEqual({ error: { code: "job_not_found" } });
|
|
215
|
-
});
|
|
216
|
-
});
|