/** * Tests for HostTransferProxy targetClientId behaviour (Phase 1). * * Covers: * - requestToHost() explicit valid targetClientId → validates, broadcasts with targetClientId * - requestToHost() auto-resolve when exactly one host_file-capable client → auto-resolves * - requestToHost() unknown targetClientId → early error, no broadcast * - requestToHost() incapable client → early error, no broadcast * - requestToSandbox() explicit valid targetClientId → same 4 cases * - Abort path sends host_transfer_cancel with targetClientId * - cancel(requestId) reads targetClientId from pending interaction * - dispose() reads targetClientId from pending interaction * - getTargetClientIdForTransfer() returns correct value after requestToHost() * - Timeout message includes client ID when resolvedTargetClientId is set * - Regression: no-targetClientId requestToHost / requestToSandbox (smoke tests) */ import { afterEach, describe, expect, jest, mock, test } from "bun:test"; const sentMessages: unknown[] = []; const sentMessageOptions: unknown[] = []; const resolvedInteractionIds: string[] = []; let mockHasClient = false; type MockClient = { clientId: string; capabilities: string[]; actorPrincipalId?: string; }; let mockCapableClients: Array = []; let mockClientRegistry: Map = new Map(); // Pre-existing Phase 1 routing tests use a single user identity. The same-user // check added in the host_transfer fix is exercised separately below. const TEST_PRINCIPAL = "test-user"; mock.module("../runtime/assistant-event-hub.js", () => ({ broadcastMessage: ( msg: unknown, _conversationId?: string, options?: unknown, ) => { sentMessages.push(msg); sentMessageOptions.push(options); }, assistantEventHub: { getMostRecentClientByCapability: (cap: string) => cap === "host_file" && mockHasClient ? { id: "mock-client" } : null, listClientsByCapability: (_cap: string) => mockCapableClients, getClientById: (clientId: string) => mockClientRegistry.get(clientId), getActorPrincipalIdForClient: (clientId: string) => mockClientRegistry.get(clientId)?.actorPrincipalId, }, })); const pendingInteractionMap = new Map>(); mock.module("../runtime/pending-interactions.js", () => ({ register: (requestId: string, interaction: Record) => { pendingInteractionMap.set(requestId, interaction); }, resolve: (requestId: string) => { const interaction = pendingInteractionMap.get(requestId); pendingInteractionMap.delete(requestId); resolvedInteractionIds.push(requestId); return interaction; }, get: (requestId: string) => pendingInteractionMap.get(requestId), getByKind: (_kind: string) => Array.from(pendingInteractionMap.entries()) .filter(([, v]) => v.kind === _kind) .map(([requestId, v]) => ({ requestId, ...v })), getByConversation: () => [], removeByConversation: () => {}, clear: () => pendingInteractionMap.clear(), })); const { HostTransferProxy } = await import("../daemon/host-transfer-proxy.js"); /** * Poll until sentMessages reaches the expected length. * Needed because requestToHost() does async readFile before broadcasting. */ async function waitForMessages( msgs: unknown[], expectedLength: number, timeoutMs = 2000, ): Promise { const start = Date.now(); while (msgs.length < expectedLength) { if (Date.now() - start > timeoutMs) { throw new Error( `Timed out waiting for ${expectedLength} message(s), got ${msgs.length}`, ); } await new Promise((r) => setTimeout(r, 5)); } } describe("HostTransferProxy — targetClientId", () => { let proxy: InstanceType; function setup() { sentMessages.length = 0; sentMessageOptions.length = 0; resolvedInteractionIds.length = 0; pendingInteractionMap.clear(); mockHasClient = false; mockCapableClients = []; mockClientRegistry = new Map(); HostTransferProxy.reset(); proxy = new (HostTransferProxy as any)(); } function setupSingleClient(clientId = "client-1") { const entry: MockClient = { clientId, capabilities: ["host_file"], actorPrincipalId: TEST_PRINCIPAL, }; mockCapableClients = [entry]; mockClientRegistry.set(clientId, entry); } afterEach(() => { proxy?.dispose(); HostTransferProxy.reset(); }); // ── requestToHost() — explicit valid targetClientId ─────────────────── describe("requestToHost() — explicit valid targetClientId", () => { test("broadcasts with targetClientId in message body and options", async () => { setup(); setupSingleClient("client-mac"); // Create a fake file for requestToHost to read const fileContent = "hello"; const srcPath = `/tmp/htp-targeted-${Date.now()}.txt`; await globalThis.Bun.write(srcPath, fileContent); const resultPromise = proxy.requestToHost( { sourcePath: srcPath, destPath: "/host/dest.txt", overwrite: false, conversationId: "conv-1", targetClientId: "client-mac", }, undefined, TEST_PRINCIPAL, ); await waitForMessages(sentMessages, 1); const sent = sentMessages[0] as Record; expect(sent.type).toBe("host_transfer_request"); expect(sent.targetClientId).toBe("client-mac"); const opts = sentMessageOptions[0] as Record | undefined; expect(opts?.targetClientId).toBe("client-mac"); const requestId = sent.requestId as string; proxy.resolveTransferResult(requestId, { isError: false, bytesWritten: 5, }); const result = await resultPromise; expect(result.isError).toBe(false); }); }); // ── requestToHost() — auto-resolve single capable client ───────────── describe("requestToHost() — auto-resolve when exactly one capable client", () => { test("auto-resolves targetClientId to the single capable client", async () => { setup(); setupSingleClient("client-solo"); const srcPath = `/tmp/htp-targeted-solo-${Date.now()}.txt`; await globalThis.Bun.write(srcPath, "content"); const resultPromise = proxy.requestToHost( { sourcePath: srcPath, destPath: "/host/dest.txt", overwrite: false, conversationId: "conv-2", }, undefined, TEST_PRINCIPAL, ); await waitForMessages(sentMessages, 1); const sent = sentMessages[0] as Record; expect(sent.targetClientId).toBe("client-solo"); const opts = sentMessageOptions[0] as Record | undefined; expect(opts?.targetClientId).toBe("client-solo"); const requestId = sent.requestId as string; proxy.resolveTransferResult(requestId, { isError: false }); const result = await resultPromise; expect(result.isError).toBe(false); }); }); // ── requestToHost() — unknown targetClientId ───────────────────────── describe("requestToHost() — unknown targetClientId", () => { test("returns error immediately without broadcasting", async () => { setup(); // No clients registered const result = await proxy.requestToHost({ sourcePath: "/tmp/file.txt", destPath: "/host/dest.txt", overwrite: false, conversationId: "conv-3", targetClientId: "client-ghost", }); expect(result.isError).toBe(true); expect(result.content).toContain("client-ghost"); expect(result.content).toContain( "assistant clients list --capability host_file", ); expect(sentMessages).toHaveLength(0); }); }); // ── requestToHost() — incapable client ─────────────────────────────── describe("requestToHost() — client lacks host_file capability", () => { test("returns error immediately without broadcasting", async () => { setup(); mockClientRegistry.set("client-no-file", { clientId: "client-no-file", capabilities: ["host_bash"], }); const result = await proxy.requestToHost({ sourcePath: "/tmp/file.txt", destPath: "/host/dest.txt", overwrite: false, conversationId: "conv-4", targetClientId: "client-no-file", }); expect(result.isError).toBe(true); expect(result.content).toContain("client-no-file"); expect(result.content).toContain("does not support host_file"); expect(sentMessages).toHaveLength(0); }); }); // ── requestToSandbox() — explicit valid targetClientId ──────────────── describe("requestToSandbox() — explicit valid targetClientId", () => { test("broadcasts with targetClientId in message body and options", async () => { setup(); setupSingleClient("client-mac"); const resultPromise = proxy.requestToSandbox( { sourcePath: "/host/source.txt", destPath: "/sandbox/dest.txt", conversationId: "conv-5", targetClientId: "client-mac", }, undefined, TEST_PRINCIPAL, ); expect(sentMessages).toHaveLength(1); const sent = sentMessages[0] as Record; expect(sent.type).toBe("host_transfer_request"); expect(sent.targetClientId).toBe("client-mac"); const opts = sentMessageOptions[0] as Record | undefined; expect(opts?.targetClientId).toBe("client-mac"); // Cancel to resolve the promise proxy.cancel(sent.requestId as string); await resultPromise; }); }); // ── requestToSandbox() — auto-resolve single capable client ────────── describe("requestToSandbox() — auto-resolve when exactly one capable client", () => { test("auto-resolves targetClientId", async () => { setup(); setupSingleClient("client-solo"); const resultPromise = proxy.requestToSandbox( { sourcePath: "/host/source.txt", destPath: "/sandbox/dest.txt", conversationId: "conv-6", }, undefined, TEST_PRINCIPAL, ); expect(sentMessages).toHaveLength(1); const sent = sentMessages[0] as Record; expect(sent.targetClientId).toBe("client-solo"); proxy.cancel(sent.requestId as string); await resultPromise; }); }); // ── requestToSandbox() — unknown targetClientId ────────────────────── describe("requestToSandbox() — unknown targetClientId", () => { test("returns error immediately without broadcasting", async () => { setup(); const result = await proxy.requestToSandbox({ sourcePath: "/host/source.txt", destPath: "/sandbox/dest.txt", conversationId: "conv-7", targetClientId: "client-ghost", }); expect(result.isError).toBe(true); expect(result.content).toContain("client-ghost"); expect(sentMessages).toHaveLength(0); }); }); // ── requestToSandbox() — incapable client ──────────────────────────── describe("requestToSandbox() — client lacks host_file capability", () => { test("returns error immediately without broadcasting", async () => { setup(); mockClientRegistry.set("client-no-file", { clientId: "client-no-file", capabilities: ["host_bash"], }); const result = await proxy.requestToSandbox({ sourcePath: "/host/source.txt", destPath: "/sandbox/dest.txt", conversationId: "conv-8", targetClientId: "client-no-file", }); expect(result.isError).toBe(true); expect(result.content).toContain("does not support host_file"); expect(sentMessages).toHaveLength(0); }); }); // ── Abort path includes targetClientId in cancel (requestToHost) ────── describe("abort path — requestToHost sends cancel with targetClientId", () => { test("cancel broadcast includes targetClientId when request was targeted", async () => { setup(); setupSingleClient("client-abc"); const srcPath = `/tmp/htp-targeted-abort-${Date.now()}.txt`; await globalThis.Bun.write(srcPath, "content"); const controller = new AbortController(); const resultPromise = proxy.requestToHost( { sourcePath: srcPath, destPath: "/host/dest.txt", overwrite: false, conversationId: "conv-9", targetClientId: "client-abc", }, controller.signal, TEST_PRINCIPAL, ); await waitForMessages(sentMessages, 1); const sent = sentMessages[0] as Record; expect(sent.targetClientId).toBe("client-abc"); controller.abort(); const result = await resultPromise; expect(result.isError).toBe(true); // Second message is the cancel expect(sentMessages).toHaveLength(2); const cancelMsg = sentMessages[1] as Record; expect(cancelMsg.type).toBe("host_transfer_cancel"); expect(cancelMsg.targetClientId).toBe("client-abc"); const cancelOpts = sentMessageOptions[1] as | Record | undefined; expect(cancelOpts?.targetClientId).toBe("client-abc"); }); }); // ── cancel(requestId) reads targetClientId from pending interaction ─── describe("cancel() reads targetClientId from pending interaction", () => { test("cancel broadcast includes targetClientId", async () => { setup(); setupSingleClient("client-xyz"); const resultPromise = proxy.requestToSandbox( { sourcePath: "/host/source.txt", destPath: "/sandbox/dest.txt", conversationId: "conv-10", targetClientId: "client-xyz", }, undefined, TEST_PRINCIPAL, ); const sent = sentMessages[0] as Record; const requestId = sent.requestId as string; expect(sent.targetClientId).toBe("client-xyz"); proxy.cancel(requestId); const result = await resultPromise; expect(result.isError).toBe(true); expect(result.content).toBe("Transfer cancelled"); // Cancel message expect(sentMessages).toHaveLength(2); const cancelMsg = sentMessages[1] as Record; expect(cancelMsg.type).toBe("host_transfer_cancel"); expect(cancelMsg.targetClientId).toBe("client-xyz"); const cancelOpts = sentMessageOptions[1] as | Record | undefined; expect(cancelOpts?.targetClientId).toBe("client-xyz"); }); }); // ── dispose() reads targetClientId from pending interaction ─────────── describe("dispose() reads targetClientId from pending interaction", () => { test("dispose cancel broadcast includes targetClientId for targeted request", () => { setup(); setupSingleClient("client-dispose"); const p = proxy.requestToSandbox( { sourcePath: "/host/source.txt", destPath: "/sandbox/dest.txt", conversationId: "conv-11", targetClientId: "client-dispose", }, undefined, TEST_PRINCIPAL, ); p.catch(() => {}); // expected rejection on dispose const sent = sentMessages[0] as Record; expect(sent.targetClientId).toBe("client-dispose"); const requestId = sent.requestId as string; proxy.dispose(); const cancelMessages = sentMessages .slice(1) .filter( (m) => (m as Record).type === "host_transfer_cancel", ) as Array>; expect(cancelMessages).toHaveLength(1); expect(cancelMessages[0].requestId).toBe(requestId); expect(cancelMessages[0].targetClientId).toBe("client-dispose"); }); }); // ── getTargetClientIdForTransfer() ──────────────────────────────────── describe("getTargetClientIdForTransfer()", () => { test("returns targetClientId after requestToHost()", async () => { setup(); setupSingleClient("client-peek"); const srcPath = `/tmp/htp-targeted-peek-${Date.now()}.txt`; await globalThis.Bun.write(srcPath, "content"); const resultPromise = proxy.requestToHost( { sourcePath: srcPath, destPath: "/host/dest.txt", overwrite: false, conversationId: "conv-12", targetClientId: "client-peek", }, undefined, TEST_PRINCIPAL, ); await waitForMessages(sentMessages, 1); const sent = sentMessages[0] as Record; const transferId = sent.transferId as string; expect(proxy.getTargetClientIdForTransfer(transferId)).toBe( "client-peek", ); // Clean up const requestId = sent.requestId as string; proxy.resolveTransferResult(requestId, { isError: false }); await resultPromise; }); test("returns null for untargeted transfer", async () => { setup(); // No clients — no auto-resolve const srcPath = `/tmp/htp-targeted-null-${Date.now()}.txt`; await globalThis.Bun.write(srcPath, "hello"); const resultPromise = proxy.requestToHost({ sourcePath: srcPath, destPath: "/host/dest.txt", overwrite: false, conversationId: "conv-13", }); await waitForMessages(sentMessages, 1); const sent = sentMessages[0] as Record; const transferId = sent.transferId as string; expect(proxy.getTargetClientIdForTransfer(transferId)).toBeNull(); const requestId = sent.requestId as string; proxy.resolveTransferResult(requestId, { isError: false }); await resultPromise; }); test("returns null for unknown transferId", () => { setup(); expect(proxy.getTargetClientIdForTransfer("nonexistent-id")).toBeNull(); }); }); // ── Timeout message includes clientId ───────────────────────────────── describe("timeout message includes clientId when targeted", () => { test("timeout resolve message mentions resolvedTargetClientId for requestToSandbox", async () => { setup(); setupSingleClient("client-timeout"); jest.useFakeTimers(); try { const resultPromise = proxy.requestToSandbox( { sourcePath: "/host/source.txt", destPath: "/sandbox/dest.txt", conversationId: "conv-14", targetClientId: "client-timeout", }, undefined, TEST_PRINCIPAL, ); const sent = sentMessages[0] as Record; expect(sent.targetClientId).toBe("client-timeout"); // Advance past the 120s default timeout jest.advanceTimersByTime(121 * 1000); const result = await resultPromise; expect(result.isError).toBe(true); expect(result.content).toContain("client-timeout"); } finally { jest.useRealTimers(); } }); }); // ── Regression: no-targetClientId path is unbroken ─────────────────── describe("regression — untargeted requestToHost completes normally", () => { test("no-targetClientId requestToHost resolves successfully", async () => { setup(); // Multiple clients so no auto-resolve mockCapableClients = [ { clientId: "client-a", capabilities: ["host_file"] }, { clientId: "client-b", capabilities: ["host_file"] }, ]; const srcPath = `/tmp/htp-regression-tohost-${Date.now()}.txt`; await globalThis.Bun.write(srcPath, "regression content"); const resultPromise = proxy.requestToHost({ sourcePath: srcPath, destPath: "/host/dest.txt", overwrite: false, conversationId: "conv-reg-1", }); await waitForMessages(sentMessages, 1); const sent = sentMessages[0] as Record; expect(sent.type).toBe("host_transfer_request"); expect(sent.targetClientId).toBeUndefined(); const opts = sentMessageOptions[0] as Record | undefined; expect(opts?.targetClientId).toBeUndefined(); const requestId = sent.requestId as string; proxy.resolveTransferResult(requestId, { isError: false, bytesWritten: 18, }); const result = await resultPromise; expect(result.isError).toBe(false); expect(result.content).toContain("successfully"); }); }); describe("regression — untargeted requestToSandbox completes normally", () => { test("no-targetClientId requestToSandbox resolves via cancel", async () => { setup(); // Multiple clients so no auto-resolve mockCapableClients = [ { clientId: "client-a", capabilities: ["host_file"] }, { clientId: "client-b", capabilities: ["host_file"] }, ]; const resultPromise = proxy.requestToSandbox({ sourcePath: "/host/source.txt", destPath: "/sandbox/dest.txt", conversationId: "conv-reg-2", }); expect(sentMessages).toHaveLength(1); const sent = sentMessages[0] as Record; expect(sent.type).toBe("host_transfer_request"); expect(sent.targetClientId).toBeUndefined(); proxy.cancel(sent.requestId as string); const result = await resultPromise; expect(result.isError).toBe(true); expect(result.content).toBe("Transfer cancelled"); }); }); // ── Same-user binding (sourceActorPrincipalId) ─────────────────────── describe("same-user binding (sourceActorPrincipalId)", () => { test("requestToHost: targeted request from same user reaches pendingInteractions", async () => { setup(); mockClientRegistry.set("client-A", { clientId: "client-A", capabilities: ["host_file"], actorPrincipalId: "user-A", }); mockCapableClients = [mockClientRegistry.get("client-A")!]; const srcPath = `/tmp/htp-same-user-ok-${Date.now()}.txt`; await globalThis.Bun.write(srcPath, "ok"); const resultPromise = proxy.requestToHost( { sourcePath: srcPath, destPath: "/host/dest.txt", overwrite: false, conversationId: "conv-same-1", targetClientId: "client-A", }, undefined, "user-A", ); await waitForMessages(sentMessages, 1); const sent = sentMessages[0] as Record; expect(sent.type).toBe("host_transfer_request"); expect(sent.targetClientId).toBe("client-A"); const requestId = sent.requestId as string; proxy.resolveTransferResult(requestId, { isError: false }); await resultPromise; }); test("requestToHost: targeted request from a different user is rejected", async () => { setup(); mockClientRegistry.set("client-A", { clientId: "client-A", capabilities: ["host_file"], actorPrincipalId: "user-A", }); mockCapableClients = [mockClientRegistry.get("client-A")!]; const srcPath = `/tmp/htp-cross-user-${Date.now()}.txt`; await globalThis.Bun.write(srcPath, "data"); const result = await proxy.requestToHost( { sourcePath: srcPath, destPath: "/host/dest.txt", overwrite: false, conversationId: "conv-same-2", targetClientId: "client-A", }, undefined, "user-B", ); expect(result.isError).toBe(true); expect(result.content).toContain("signed in as a different user"); expect(sentMessages).toHaveLength(0); }); test("requestToHost: targeted request without source principal is rejected", async () => { setup(); mockClientRegistry.set("client-A", { clientId: "client-A", capabilities: ["host_file"], actorPrincipalId: "user-A", }); mockCapableClients = [mockClientRegistry.get("client-A")!]; const srcPath = `/tmp/htp-no-principal-${Date.now()}.txt`; await globalThis.Bun.write(srcPath, "data"); const result = await proxy.requestToHost( { sourcePath: srcPath, destPath: "/host/dest.txt", overwrite: false, conversationId: "conv-same-3", targetClientId: "client-A", }, undefined, undefined, ); expect(result.isError).toBe(true); expect(result.content).toContain("no authenticated actor"); expect(sentMessages).toHaveLength(0); }); test("requestToHost: targeted request to a client with no actor principal is rejected", async () => { setup(); mockClientRegistry.set("client-A", { clientId: "client-A", capabilities: ["host_file"], // actorPrincipalId omitted (legacy/service-token client). }); mockCapableClients = [mockClientRegistry.get("client-A")!]; const srcPath = `/tmp/htp-target-no-principal-${Date.now()}.txt`; await globalThis.Bun.write(srcPath, "data"); const result = await proxy.requestToHost( { sourcePath: srcPath, destPath: "/host/dest.txt", overwrite: false, conversationId: "conv-same-4", targetClientId: "client-A", }, undefined, "user-A", ); expect(result.isError).toBe(true); expect(result.content).toContain( "registered without an authenticated user", ); expect(sentMessages).toHaveLength(0); }); test("requestToHost: auto-resolve picks the same-user client when there's exactly one", async () => { setup(); const a: MockClient = { clientId: "client-A", capabilities: ["host_file"], actorPrincipalId: "user-A", }; const b: MockClient = { clientId: "client-B", capabilities: ["host_file"], actorPrincipalId: "user-B", }; mockCapableClients = [a, b]; mockClientRegistry.set("client-A", a); mockClientRegistry.set("client-B", b); const srcPath = `/tmp/htp-auto-same-user-${Date.now()}.txt`; await globalThis.Bun.write(srcPath, "data"); const resultPromise = proxy.requestToHost( { sourcePath: srcPath, destPath: "/host/dest.txt", overwrite: false, conversationId: "conv-same-5", }, undefined, "user-A", ); await waitForMessages(sentMessages, 1); const sent = sentMessages[0] as Record; expect(sent.targetClientId).toBe("client-A"); const requestId = sent.requestId as string; proxy.resolveTransferResult(requestId, { isError: false }); await resultPromise; }); test("requestToHost: auto-resolve falls through when no client matches the source user", async () => { setup(); mockClientRegistry.set("client-A", { clientId: "client-A", capabilities: ["host_file"], actorPrincipalId: "user-A", }); mockCapableClients = [mockClientRegistry.get("client-A")!]; const srcPath = `/tmp/htp-auto-no-match-${Date.now()}.txt`; await globalThis.Bun.write(srcPath, "data"); const resultPromise = proxy.requestToHost( { sourcePath: srcPath, destPath: "/host/dest.txt", overwrite: false, conversationId: "conv-same-6", }, undefined, "user-C", ); await waitForMessages(sentMessages, 1); const sent = sentMessages[0] as Record; expect(sent.targetClientId).toBeUndefined(); const requestId = sent.requestId as string; proxy.resolveTransferResult(requestId, { isError: false }); await resultPromise; }); test("requestToSandbox: targeted request from a different user is rejected", async () => { setup(); mockClientRegistry.set("client-A", { clientId: "client-A", capabilities: ["host_file"], actorPrincipalId: "user-A", }); mockCapableClients = [mockClientRegistry.get("client-A")!]; const result = await proxy.requestToSandbox( { sourcePath: "/host/source.txt", destPath: "/sandbox/dest.txt", conversationId: "conv-same-7", targetClientId: "client-A", }, undefined, "user-B", ); expect(result.isError).toBe(true); expect(result.content).toContain("signed in as a different user"); expect(sentMessages).toHaveLength(0); }); test("requestToSandbox: targeted request from same user proceeds", async () => { setup(); mockClientRegistry.set("client-A", { clientId: "client-A", capabilities: ["host_file"], actorPrincipalId: "user-A", }); mockCapableClients = [mockClientRegistry.get("client-A")!]; const resultPromise = proxy.requestToSandbox( { sourcePath: "/host/source.txt", destPath: "/sandbox/dest.txt", conversationId: "conv-same-8", targetClientId: "client-A", }, undefined, "user-A", ); expect(sentMessages).toHaveLength(1); const sent = sentMessages[0] as Record; expect(sent.targetClientId).toBe("client-A"); proxy.cancel(sent.requestId as string); await resultPromise; }); test("requestToSandbox: auto-resolve picks the same-user client when there's exactly one", async () => { setup(); const a: MockClient = { clientId: "client-A", capabilities: ["host_file"], actorPrincipalId: "user-A", }; const b: MockClient = { clientId: "client-B", capabilities: ["host_file"], actorPrincipalId: "user-B", }; mockCapableClients = [a, b]; mockClientRegistry.set("client-A", a); mockClientRegistry.set("client-B", b); const resultPromise = proxy.requestToSandbox( { sourcePath: "/host/source.txt", destPath: "/sandbox/dest.txt", conversationId: "conv-same-9", }, undefined, "user-A", ); expect(sentMessages).toHaveLength(1); const sent = sentMessages[0] as Record; expect(sent.targetClientId).toBe("client-A"); proxy.cancel(sent.requestId as string); await resultPromise; }); }); });