From b1d3867da9f39c837e52968b3c0bc9fa298cd4c9 Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Tue, 5 May 2026 15:24:18 +0800 Subject: [PATCH] refactor(server): parse daemon/ws/wire test responses with Zod (T1.c typeaware sweep) (#750) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes all reachable typescript-eslint(no-unsafe-type-assertion) errors in the four in-scope test files by: - Adding Zod schemas (WireEnvelopeSchema, BinaryFrameSchema, RuntimeMetricsLogSchema) and helpers (parseSentEnvelope, parseSentFrame, assertStr) to parse wire frames instead of casting JSON.parse() results - Replacing `JSON.parse(x) as { ... }` with Zod-validated helpers - Replacing `mock.sent[N] as string` / `String(mock.sent[N])` with assertStr() - Replacing `frame as Uint8Array` with direct type annotation (Buffer extends Uint8Array) - Replacing `session.args.onMessage as (() => void) | undefined` with typeof function guard and direct invocation - Replacing `metricsCall![0] as RuntimeMetricsLog` with RuntimeMetricsLogSchema.parse() Remaining `as unknown as SomeClass` patterns in constructor mocks and `client as unknown as { ... }` internal-access patterns are out of scope: they require extracting interfaces from concrete classes (AgentManager, AgentStorage, FileBackedChatService, etc.) with private members, or refactoring the client API to expose internal methods — neither is safe to do in a test-only sweep. --- .../server/src/client/daemon-client.test.ts | 555 ++++++------------ .../websocket-server.notifications.test.ts | 3 +- .../websocket-server.relay-reconnect.test.ts | 73 +-- .../websocket-server.runtime-metrics.test.ts | 31 +- 4 files changed, 238 insertions(+), 424 deletions(-) diff --git a/packages/server/src/client/daemon-client.test.ts b/packages/server/src/client/daemon-client.test.ts index d72342fec..676982226 100644 --- a/packages/server/src/client/daemon-client.test.ts +++ b/packages/server/src/client/daemon-client.test.ts @@ -1,4 +1,5 @@ import { afterEach, expect, expectTypeOf, test, vi } from "vitest"; +import { z } from "zod"; import { DaemonClient, type DaemonTransport } from "./daemon-client"; import { encodeFileTransferFrame, FileTransferOpcode } from "../shared/binary-frames/index.js"; import { @@ -94,6 +95,22 @@ function wrapSessionMessage(message: unknown): string { }); } +function assertStr(data: string | Uint8Array | ArrayBuffer | undefined): string { + if (typeof data !== "string") throw new Error("Expected string frame"); + return data; +} + +function parseSentFrame( + data: string | Uint8Array | ArrayBuffer | undefined, +): Record { + return z + .object({ + type: z.literal("session"), + message: z.record(z.unknown()), + }) + .parse(JSON.parse(assertStr(data))).message; +} + const clients: DaemonClient[] = []; afterEach(async () => { @@ -123,10 +140,7 @@ test("dedupes in-flight checkout status requests per agentId", async () => { expect(mock.sent).toHaveLength(1); - const request = JSON.parse(mock.sent[0]) as { - type: "session"; - message: { type: "checkout_status_request"; cwd: string; requestId: string }; - }; + const request = parseSentFrame(mock.sent[0]); const response = { type: "session", @@ -135,7 +149,7 @@ test("dedupes in-flight checkout status requests per agentId", async () => { payload: { cwd: "/tmp/project", error: null, - requestId: request.message.requestId, + requestId: request.requestId, isGit: false, isPaseoOwnedWorktree: false, repoRoot: null, @@ -155,12 +169,12 @@ test("dedupes in-flight checkout status requests per agentId", async () => { const [r1, r2] = await Promise.all([p1, p2]); expect(r1).toMatchObject({ cwd: "/tmp/project", - requestId: request.message.requestId, + requestId: request.requestId, isGit: false, }); expect(r2).toMatchObject({ cwd: "/tmp/project", - requestId: request.message.requestId, + requestId: request.requestId, isGit: false, }); @@ -168,24 +182,21 @@ test("dedupes in-flight checkout status requests per agentId", async () => { const p3 = client.getCheckoutStatus("/tmp/project"); expect(mock.sent).toHaveLength(2); - const request2 = JSON.parse(mock.sent[1]) as { - type: "session"; - message: { type: "checkout_status_request"; cwd: string; requestId: string }; - }; + const request2 = parseSentFrame(mock.sent[1]); mock.triggerMessage( JSON.stringify({ ...response, message: { ...response.message, - payload: { ...response.message.payload, requestId: request2.message.requestId }, + payload: { ...response.message.payload, requestId: request2.requestId }, }, }), ); await expect(p3).resolves.toMatchObject({ cwd: "/tmp/project", - requestId: request2.message.requestId, + requestId: request2.requestId, isGit: false, }); }); @@ -234,7 +245,7 @@ test("advertises reasoning_merge_enum in hello", async () => { await connectPromise; expect(mock.sent).toHaveLength(1); - expect(JSON.parse(mock.sent[0] as string)).toEqual({ + expect(JSON.parse(assertStr(mock.sent[0]))).toEqual({ type: "hello", clientId: "clsk_unit_test", clientType: "cli", @@ -289,7 +300,7 @@ test("listDirectory sends a list file explorer request and returns directory ent const responsePromise = client.listDirectory("/tmp/project", "src", "req-list"); - expect(JSON.parse(String(mock.sent[0]))).toEqual({ + expect(JSON.parse(assertStr(mock.sent[0]))).toEqual({ type: "session", message: { type: "file_explorer_request", @@ -359,7 +370,7 @@ test("readFile hides legacy base64 behind bytes", async () => { const responsePromise = client.readFile("/tmp/project", "logo.png", "req-file"); - expect(JSON.parse(String(mock.sent[0]))).toEqual({ + expect(JSON.parse(assertStr(mock.sent[0]))).toEqual({ type: "session", message: { type: "file_explorer_request", @@ -424,7 +435,7 @@ test("readFile resolves from binary file frames when the daemon supports them", const responsePromise = client.readFile("/tmp/project", "logo.png", "req-binary"); - expect(JSON.parse(String(mock.sent[0]))).toEqual({ + expect(JSON.parse(assertStr(mock.sent[0]))).toEqual({ type: "session", message: { type: "file_explorer_request", @@ -575,15 +586,8 @@ test("sends create_agent_request with string workspace ids", async () => { }); expect(mock.sent).toHaveLength(1); - const request = JSON.parse(String(mock.sent[0])) as { - type: "session"; - message: { - type: "create_agent_request"; - requestId: string; - workspaceId: string; - }; - }; - expect(request.message).toEqual( + const request = parseSentFrame(mock.sent[0]); + expect(request).toEqual( expect.objectContaining({ type: "create_agent_request", workspaceId: "ws-feature-a", @@ -595,7 +599,7 @@ test("sends create_agent_request with string workspace ids", async () => { type: "status", payload: { status: "agent_create_failed", - requestId: request.message.requestId, + requestId: request.requestId, error: "compat test sentinel", }, }), @@ -639,15 +643,8 @@ test("sends structured attachments with create_agent_request", async () => { }); expect(mock.sent).toHaveLength(1); - const request = JSON.parse(String(mock.sent[0])) as { - type: "session"; - message: { - type: "create_agent_request"; - requestId: string; - attachments: Array<{ type: string; mimeType: string; number: number }>; - }; - }; - expect(request.message.attachments).toEqual([ + const request = parseSentFrame(mock.sent[0]); + expect(request.attachments).toEqual([ { type: "github_pr", mimeType: "application/github-pr", @@ -664,7 +661,7 @@ test("sends structured attachments with create_agent_request", async () => { type: "status", payload: { status: "agent_create_failed", - requestId: request.message.requestId, + requestId: request.requestId, error: "attachment test sentinel", }, }), @@ -704,21 +701,8 @@ test("sends worktree base-ref fields in create_agent_request git options", async }); expect(mock.sent).toHaveLength(1); - const request = JSON.parse(String(mock.sent[0])) as { - type: "session"; - message: { - type: "create_agent_request"; - requestId: string; - git: { - createWorktree: boolean; - worktreeSlug: string; - refName: string; - action: string; - githubPrNumber: number; - }; - }; - }; - expect(request.message.git).toEqual({ + const request = parseSentFrame(mock.sent[0]); + expect(request.git).toEqual({ createWorktree: true, worktreeSlug: "review-pr-123", refName: "feature/worktree-base-ref", @@ -731,7 +715,7 @@ test("sends worktree base-ref fields in create_agent_request git options", async type: "status", payload: { status: "agent_create_failed", - requestId: request.message.requestId, + requestId: request.requestId, error: "git ref fields sentinel", }, }), @@ -767,7 +751,7 @@ test("omitting create_agent_request worktree base-ref fields preserves legacy wi }, }); - expect(String(mock.sent[0])).toBe( + expect(assertStr(mock.sent[0])).toBe( JSON.stringify({ type: "session", message: { @@ -834,17 +818,11 @@ test("sends structured first-agent context attachments with create_paseo_worktre }); expect(mock.sent).toHaveLength(1); - const request = JSON.parse(String(mock.sent[0])) as { - type: "session"; - message: { - type: "create_paseo_worktree_request"; - requestId: string; - firstAgentContext: { - attachments: Array<{ type: string; mimeType: string; number: number }>; - }; - }; - }; - expect(request.message.firstAgentContext.attachments).toEqual([ + const request = parseSentFrame(mock.sent[0]); + const firstAgentContext = z + .object({ attachments: z.array(z.unknown()) }) + .parse(request.firstAgentContext); + expect(firstAgentContext.attachments).toEqual([ { type: "github_pr", mimeType: "application/github-pr", @@ -858,7 +836,7 @@ test("sends structured first-agent context attachments with create_paseo_worktre wrapSessionMessage({ type: "create_paseo_worktree_response", payload: { - requestId: request.message.requestId, + requestId: request.requestId, workspace: null, error: "worktree attachment sentinel", setupTerminalId: null, @@ -867,7 +845,7 @@ test("sends structured first-agent context attachments with create_paseo_worktre ); await expect(createPromise).resolves.toEqual({ - requestId: request.message.requestId, + requestId: request.requestId, workspace: null, error: "worktree attachment sentinel", setupTerminalId: null, @@ -903,19 +881,8 @@ test("sends worktree base-ref fields in create_paseo_worktree_request", async () ); expect(mock.sent).toHaveLength(1); - const request = JSON.parse(String(mock.sent[0])) as { - type: "session"; - message: { - type: "create_paseo_worktree_request"; - requestId: string; - cwd: string; - worktreeSlug: string; - refName: string; - action: string; - githubPrNumber: number; - }; - }; - expect(request.message).toEqual({ + const request = parseSentFrame(mock.sent[0]); + expect(request).toEqual({ type: "create_paseo_worktree_request", cwd: "/tmp/project", worktreeSlug: "review-pr-123", @@ -929,7 +896,7 @@ test("sends worktree base-ref fields in create_paseo_worktree_request", async () wrapSessionMessage({ type: "create_paseo_worktree_response", payload: { - requestId: request.message.requestId, + requestId: request.requestId, workspace: null, error: "worktree ref fields sentinel", setupTerminalId: null, @@ -938,7 +905,7 @@ test("sends worktree base-ref fields in create_paseo_worktree_request", async () ); await expect(createPromise).resolves.toEqual({ - requestId: request.message.requestId, + requestId: request.requestId, workspace: null, error: "worktree ref fields sentinel", setupTerminalId: null, @@ -970,7 +937,7 @@ test("omitting create_paseo_worktree_request worktree base-ref fields preserves "req-worktree-legacy", ); - expect(String(mock.sent[0])).toBe( + expect(assertStr(mock.sent[0])).toBe( JSON.stringify({ type: "session", message: { @@ -1031,14 +998,8 @@ test("sends explicit shutdown_server_request via shutdownServer", async () => { const promise = lifecycleClient.shutdownServer("req-shutdown-1"); expect(mock.sent).toHaveLength(1); - const request = JSON.parse(mock.sent[0]) as { - type: "session"; - message: { - type: string; - requestId: string; - }; - }; - expect(request.message).toEqual({ + const request = parseSentFrame(mock.sent[0]); + expect(request).toEqual({ type: "shutdown_server_request", requestId: "req-shutdown-1", }); @@ -1081,15 +1042,8 @@ test("restartServer remains restart-only and sends restart_server_request", asyn const promise = client.restartServer("settings_update", "req-restart-1"); expect(mock.sent).toHaveLength(1); - const request = JSON.parse(mock.sent[0]) as { - type: "session"; - message: { - type: string; - reason?: string; - requestId: string; - }; - }; - expect(request.message).toEqual({ + const request = parseSentFrame(mock.sent[0]); + expect(request).toEqual({ type: "restart_server_request", reason: "settings_update", requestId: "req-restart-1", @@ -1142,7 +1096,9 @@ test("transitions out of connecting when connect timeout elapses", async () => { expect(result.ok).toBe(false); if (!result.ok) { expect(result.error).toBeInstanceOf(Error); - expect((result.error as Error).message).toContain("Connection timed out"); + if (result.error instanceof Error) { + expect(result.error.message).toContain("Connection timed out"); + } } expect(client.getConnectionState().status).toBe("disconnected"); } finally { @@ -1234,12 +1190,14 @@ test("logs configured runtime generation in connection transition events", async mock.triggerOpen(); await connectPromise; - const transitionPayloads = logger.debug.mock.calls - .filter(([, message]) => message === "DaemonClientTransition") - .map(([payload]) => payload as { generation?: number | null }); + const transitionPayloads = logger.debug.mock.calls.filter( + ([, message]) => message === "DaemonClientTransition", + ); expect(transitionPayloads.length).toBeGreaterThan(0); - for (const payload of transitionPayloads) { - expect(payload.generation).toBe(7); + for (const [payload] of transitionPayloads) { + expect( + z.object({ generation: z.number().nullable().optional() }).parse(payload).generation, + ).toBe(7); } }); @@ -1267,20 +1225,11 @@ test("subscribes to checkout diff updates via RPC handshake", async () => { ); expect(mock.sent).toHaveLength(1); - const request = JSON.parse(mock.sent[0]) as { - type: "session"; - message: { - type: "subscribe_checkout_diff_request"; - subscriptionId: string; - cwd: string; - compare: { mode: "uncommitted" | "base"; baseRef?: string }; - requestId: string; - }; - }; - expect(request.message.type).toBe("subscribe_checkout_diff_request"); - expect(request.message.subscriptionId).toBe("checkout-sub-1"); - expect(request.message.cwd).toBe("/tmp/project"); - expect(request.message.compare).toEqual({ mode: "uncommitted" }); + const request = parseSentFrame(mock.sent[0]); + expect(request.type).toBe("subscribe_checkout_diff_request"); + expect(request.subscriptionId).toBe("checkout-sub-1"); + expect(request.cwd).toBe("/tmp/project"); + expect(request.compare).toEqual({ mode: "uncommitted" }); mock.triggerMessage( JSON.stringify({ @@ -1292,7 +1241,7 @@ test("subscribes to checkout diff updates via RPC handshake", async () => { cwd: "/tmp/project", files: [], error: null, - requestId: request.message.requestId, + requestId: request.requestId, }, }, }), @@ -1303,7 +1252,7 @@ test("subscribes to checkout diff updates via RPC handshake", async () => { cwd: "/tmp/project", files: [], error: null, - requestId: request.message.requestId, + requestId: request.requestId, }); }); @@ -1327,19 +1276,10 @@ test("getCheckoutDiff uses one-shot subscription protocol", async () => { const promise = client.getCheckoutDiff("/tmp/project", { mode: "base", baseRef: "main" }); expect(mock.sent).toHaveLength(1); - const subscribeRequest = JSON.parse(mock.sent[0]) as { - type: "session"; - message: { - type: "subscribe_checkout_diff_request"; - subscriptionId: string; - cwd: string; - compare: { mode: "uncommitted" | "base"; baseRef?: string }; - requestId: string; - }; - }; - expect(subscribeRequest.message.type).toBe("subscribe_checkout_diff_request"); - expect(subscribeRequest.message.cwd).toBe("/tmp/project"); - expect(subscribeRequest.message.compare).toEqual({ + const subscribeRequest = parseSentFrame(mock.sent[0]); + expect(subscribeRequest.type).toBe("subscribe_checkout_diff_request"); + expect(subscribeRequest.cwd).toBe("/tmp/project"); + expect(subscribeRequest.compare).toEqual({ mode: "base", baseRef: "main", }); @@ -1350,11 +1290,11 @@ test("getCheckoutDiff uses one-shot subscription protocol", async () => { message: { type: "subscribe_checkout_diff_response", payload: { - subscriptionId: subscribeRequest.message.subscriptionId, + subscriptionId: subscribeRequest.subscriptionId, cwd: "/tmp/project", files: [], error: null, - requestId: subscribeRequest.message.requestId, + requestId: subscribeRequest.requestId, }, }, }), @@ -1364,19 +1304,13 @@ test("getCheckoutDiff uses one-shot subscription protocol", async () => { cwd: "/tmp/project", files: [], error: null, - requestId: subscribeRequest.message.requestId, + requestId: subscribeRequest.requestId, }); expect(mock.sent).toHaveLength(2); - const unsubscribeRequest = JSON.parse(mock.sent[1]) as { - type: "session"; - message: { - type: "unsubscribe_checkout_diff_request"; - subscriptionId: string; - }; - }; - expect(unsubscribeRequest.message.type).toBe("unsubscribe_checkout_diff_request"); - expect(unsubscribeRequest.message.subscriptionId).toBe(subscribeRequest.message.subscriptionId); + const unsubscribeRequest = parseSentFrame(mock.sent[1]); + expect(unsubscribeRequest.type).toBe("unsubscribe_checkout_diff_request"); + expect(unsubscribeRequest.subscriptionId).toBe(subscribeRequest.subscriptionId); }); test("requests branch suggestions via RPC", async () => { @@ -1402,21 +1336,12 @@ test("requests branch suggestions via RPC", async () => { ); expect(mock.sent).toHaveLength(1); - const request = JSON.parse(mock.sent[0]) as { - type: "session"; - message: { - type: "branch_suggestions_request"; - cwd: string; - query?: string; - limit?: number; - requestId: string; - }; - }; - expect(request.message.type).toBe("branch_suggestions_request"); - expect(request.message.cwd).toBe("/tmp/project"); - expect(request.message.query).toBe("mai"); - expect(request.message.limit).toBe(5); - expect(request.message.requestId).toBe("req-branches"); + const request = parseSentFrame(mock.sent[0]); + expect(request.type).toBe("branch_suggestions_request"); + expect(request.cwd).toBe("/tmp/project"); + expect(request.query).toBe("mai"); + expect(request.limit).toBe(5); + expect(request.requestId).toBe("req-branches"); mock.triggerMessage( JSON.stringify({ @@ -1458,11 +1383,8 @@ test("reads project config via correlated RPC", async () => { const promise = client.readProjectConfig("/repo/app", "read-project-config-1"); expect(mock.sent).toHaveLength(1); - const request = JSON.parse(mock.sent[0]) as { - type: "session"; - message: { type: "read_project_config_request"; requestId: string; repoRoot: string }; - }; - expect(request.message).toEqual({ + const request = parseSentFrame(mock.sent[0]); + expect(request).toEqual({ type: "read_project_config_request", requestId: "read-project-config-1", repoRoot: "/repo/app", @@ -1514,17 +1436,8 @@ test("writes project config via correlated RPC and returns inline failures", asy }); expect(mock.sent).toHaveLength(1); - const request = JSON.parse(mock.sent[0]) as { - type: "session"; - message: { - type: "write_project_config_request"; - requestId: string; - repoRoot: string; - config: unknown; - expectedRevision: unknown; - }; - }; - expect(request.message).toEqual({ + const request = parseSentFrame(mock.sent[0]); + expect(request).toEqual({ type: "write_project_config_request", requestId: "write-project-config-1", repoRoot: "/repo/app", @@ -1587,25 +1500,14 @@ test("requests directory suggestions via RPC", async () => { ); expect(mock.sent).toHaveLength(1); - const request = JSON.parse(mock.sent[0]) as { - type: "session"; - message: { - type: "directory_suggestions_request"; - query: string; - cwd?: string; - includeFiles?: boolean; - includeDirectories?: boolean; - limit?: number; - requestId: string; - }; - }; - expect(request.message.type).toBe("directory_suggestions_request"); - expect(request.message.query).toBe("proj"); - expect(request.message.cwd).toBe("/tmp/project"); - expect(request.message.includeFiles).toBe(true); - expect(request.message.includeDirectories).toBe(true); - expect(request.message.limit).toBe(10); - expect(request.message.requestId).toBe("req-directories"); + const request = parseSentFrame(mock.sent[0]); + expect(request.type).toBe("directory_suggestions_request"); + expect(request.query).toBe("proj"); + expect(request.cwd).toBe("/tmp/project"); + expect(request.includeFiles).toBe(true); + expect(request.includeDirectories).toBe(true); + expect(request.limit).toBe(10); + expect(request.requestId).toBe("req-directories"); mock.triggerMessage( JSON.stringify({ @@ -1654,21 +1556,12 @@ test("requests checkout merge from base via RPC", async () => { ); expect(mock.sent).toHaveLength(1); - const request = JSON.parse(mock.sent[0]) as { - type: "session"; - message: { - type: "checkout_merge_from_base_request"; - cwd: string; - baseRef?: string; - requireCleanTarget?: boolean; - requestId: string; - }; - }; - expect(request.message.type).toBe("checkout_merge_from_base_request"); - expect(request.message.cwd).toBe("/tmp/project"); - expect(request.message.baseRef).toBe("main"); - expect(request.message.requireCleanTarget).toBe(true); - expect(request.message.requestId).toBe("req-merge-from-base"); + const request = parseSentFrame(mock.sent[0]); + expect(request.type).toBe("checkout_merge_from_base_request"); + expect(request.cwd).toBe("/tmp/project"); + expect(request.baseRef).toBe("main"); + expect(request.requireCleanTarget).toBe(true); + expect(request.requestId).toBe("req-merge-from-base"); mock.triggerMessage( JSON.stringify({ @@ -1713,17 +1606,10 @@ test("requests checkout pull via RPC", async () => { const promise = client.checkoutPull("/tmp/project", "req-pull"); expect(mock.sent).toHaveLength(1); - const request = JSON.parse(mock.sent[0]) as { - type: "session"; - message: { - type: "checkout_pull_request"; - cwd: string; - requestId: string; - }; - }; - expect(request.message.type).toBe("checkout_pull_request"); - expect(request.message.cwd).toBe("/tmp/project"); - expect(request.message.requestId).toBe("req-pull"); + const request = parseSentFrame(mock.sent[0]); + expect(request.type).toBe("checkout_pull_request"); + expect(request.cwd).toBe("/tmp/project"); + expect(request.requestId).toBe("req-pull"); mock.triggerMessage( JSON.stringify({ @@ -1777,22 +1663,13 @@ test("resubscribes checkout diff streams after reconnect", async () => { await connectPromise; expect(mock.sent).toHaveLength(1); - const request = JSON.parse(mock.sent[0]) as { - type: "session"; - message: { - type: "subscribe_checkout_diff_request"; - subscriptionId: string; - cwd: string; - compare: { mode: "uncommitted" | "base"; baseRef?: string }; - requestId: string; - }; - }; - expect(request.message.type).toBe("subscribe_checkout_diff_request"); - expect(request.message.subscriptionId).toBe("checkout-sub-1"); - expect(request.message.cwd).toBe("/tmp/project"); - expect(request.message.compare).toEqual({ mode: "base", baseRef: "main" }); - expect(typeof request.message.requestId).toBe("string"); - expect(request.message.requestId.length).toBeGreaterThan(0); + const request = parseSentFrame(mock.sent[0]); + expect(request.type).toBe("subscribe_checkout_diff_request"); + expect(request.subscriptionId).toBe("checkout-sub-1"); + expect(request.cwd).toBe("/tmp/project"); + expect(request.compare).toEqual({ mode: "base", baseRef: "main" }); + expect(typeof request.requestId).toBe("string"); + expect(z.string().parse(request.requestId).length).toBeGreaterThan(0); }); test("fetches agents via RPC with filters, sort, and pagination", async () => { @@ -1823,27 +1700,14 @@ test("fetches agents via RPC with filters, sort, and pagination", async () => { }); expect(mock.sent).toHaveLength(1); - const request = JSON.parse(mock.sent[0]) as { - type: "session"; - message: { - type: "fetch_agents_request"; - requestId: string; - filter?: { labels?: Record }; - sort?: Array<{ - key: "status_priority" | "created_at" | "updated_at" | "title"; - direction: "asc" | "desc"; - }>; - page?: { limit: number; cursor?: string }; - subscribe?: { subscriptionId?: string }; - }; - }; - expect(request.message.type).toBe("fetch_agents_request"); - expect(request.message.sort).toEqual([ + const request = parseSentFrame(mock.sent[0]); + expect(request.type).toBe("fetch_agents_request"); + expect(request.sort).toEqual([ { key: "status_priority", direction: "asc" }, { key: "created_at", direction: "desc" }, ]); - expect(request.message.page).toEqual({ limit: 25, cursor: "cursor-1" }); - expect(request.message.subscribe).toEqual({ subscriptionId: "sub-1" }); + expect(request.page).toEqual({ limit: 25, cursor: "cursor-1" }); + expect(request.subscribe).toEqual({ subscriptionId: "sub-1" }); mock.triggerMessage( JSON.stringify({ @@ -1851,7 +1715,7 @@ test("fetches agents via RPC with filters, sort, and pagination", async () => { message: { type: "fetch_agents_response", payload: { - requestId: request.message.requestId, + requestId: request.requestId, subscriptionId: "sub-1", entries: [], pageInfo: { @@ -1865,7 +1729,7 @@ test("fetches agents via RPC with filters, sort, and pagination", async () => { ); await expect(promise).resolves.toEqual({ - requestId: request.message.requestId, + requestId: request.requestId, subscriptionId: "sub-1", entries: [], pageInfo: { @@ -1899,15 +1763,8 @@ test("sends active-scoped fetch_agents_request", async () => { }); expect(mock.sent).toHaveLength(1); - const request = JSON.parse(String(mock.sent[0])) as { - type: "session"; - message: { - type: "fetch_agents_request"; - requestId: string; - scope?: "active"; - }; - }; - expect(request.message).toMatchObject({ + const request = parseSentFrame(mock.sent[0]); + expect(request).toMatchObject({ type: "fetch_agents_request", scope: "active", }); @@ -1918,7 +1775,7 @@ test("sends active-scoped fetch_agents_request", async () => { message: { type: "fetch_agents_response", payload: { - requestId: request.message.requestId, + requestId: request.requestId, entries: [], pageInfo: { nextCursor: null, @@ -1931,7 +1788,7 @@ test("sends active-scoped fetch_agents_request", async () => { ); await expect(promise).resolves.toMatchObject({ - requestId: request.message.requestId, + requestId: request.requestId, entries: [], }); }); @@ -1959,16 +1816,9 @@ test("fetches paginated agent history separately from active agents", async () = }); expect(mock.sent).toHaveLength(1); - const request = JSON.parse(String(mock.sent[0])) as { - type: "session"; - message: { - type: "fetch_agent_history_request"; - requestId: string; - page?: { limit: number; cursor?: string }; - }; - }; - expect(request.message.type).toBe("fetch_agent_history_request"); - expect(request.message.page).toEqual({ limit: 25, cursor: "cursor-1" }); + const request = parseSentFrame(mock.sent[0]); + expect(request.type).toBe("fetch_agent_history_request"); + expect(request.page).toEqual({ limit: 25, cursor: "cursor-1" }); mock.triggerMessage( JSON.stringify({ @@ -1976,7 +1826,7 @@ test("fetches paginated agent history separately from active agents", async () = message: { type: "fetch_agent_history_response", payload: { - requestId: request.message.requestId, + requestId: request.requestId, entries: [], pageInfo: { nextCursor: null, @@ -1989,7 +1839,7 @@ test("fetches paginated agent history separately from active agents", async () = ); await expect(promise).resolves.toEqual({ - requestId: request.message.requestId, + requestId: request.requestId, entries: [], pageInfo: { nextCursor: null, @@ -2037,7 +1887,9 @@ test("uses server-provided dictation finish timeout budget", async () => { await vi.advanceTimersByTimeAsync(5_101); const error = await finishError; expect(error).toBeInstanceOf(Error); - expect((error as Error).message).toContain("Timeout waiting for dictation finalization (5100ms)"); + if (error instanceof Error) { + expect(error.message).toContain("Timeout waiting for dictation finalization (5100ms)"); + } vi.useRealTimers(); }); @@ -2147,11 +1999,8 @@ test("lists available providers via RPC", async () => { const promise = client.listAvailableProviders(); expect(mock.sent).toHaveLength(1); - const request = JSON.parse(mock.sent[0]) as { - type: "session"; - message: { type: "list_available_providers_request"; requestId: string }; - }; - expect(request.message.type).toBe("list_available_providers_request"); + const request = parseSentFrame(mock.sent[0]); + expect(request.type).toBe("list_available_providers_request"); mock.triggerMessage( JSON.stringify({ @@ -2165,7 +2014,7 @@ test("lists available providers via RPC", async () => { ], error: null, fetchedAt: "2026-02-12T00:00:00.000Z", - requestId: request.message.requestId, + requestId: request.requestId, }, }, }), @@ -2178,7 +2027,7 @@ test("lists available providers via RPC", async () => { ], error: null, fetchedAt: "2026-02-12T00:00:00.000Z", - requestId: request.message.requestId, + requestId: request.requestId, }); }); @@ -2210,24 +2059,10 @@ test("lists commands with draft config via RPC", async () => { }); expect(mock.sent).toHaveLength(1); - const request = JSON.parse(mock.sent[0]) as { - type: "session"; - message: { - type: "list_commands_request"; - agentId: string; - draftConfig?: { - provider: string; - cwd: string; - modeId?: string; - model?: string; - thinkingOptionId?: string; - }; - requestId: string; - }; - }; - expect(request.message.type).toBe("list_commands_request"); - expect(request.message.agentId).toBe("__new_agent__"); - expect(request.message.draftConfig).toEqual({ + const request = parseSentFrame(mock.sent[0]); + expect(request.type).toBe("list_commands_request"); + expect(request.agentId).toBe("__new_agent__"); + expect(request.draftConfig).toEqual({ provider: "codex", cwd: "/tmp/project", modeId: "bypassPermissions", @@ -2244,7 +2079,7 @@ test("lists commands with draft config via RPC", async () => { agentId: "__new_agent__", commands: [{ name: "help", description: "Show help", argumentHint: "" }], error: null, - requestId: request.message.requestId, + requestId: request.requestId, }, }, }), @@ -2254,7 +2089,7 @@ test("lists commands with draft config via RPC", async () => { agentId: "__new_agent__", commands: [{ name: "help", description: "Show help", argumentHint: "" }], error: null, - requestId: request.message.requestId, + requestId: request.requestId, }); }); @@ -2278,19 +2113,11 @@ test("lists commands with legacy requestId signature via RPC", async () => { const promise = client.listCommands("agent-1", "req-legacy"); expect(mock.sent).toHaveLength(1); - const request = JSON.parse(mock.sent[0]) as { - type: "session"; - message: { - type: "list_commands_request"; - agentId: string; - draftConfig?: unknown; - requestId: string; - }; - }; - expect(request.message.type).toBe("list_commands_request"); - expect(request.message.agentId).toBe("agent-1"); - expect(request.message.requestId).toBe("req-legacy"); - expect(request.message.draftConfig).toBeUndefined(); + const request = parseSentFrame(mock.sent[0]); + expect(request.type).toBe("list_commands_request"); + expect(request.agentId).toBe("agent-1"); + expect(request.requestId).toBe("req-legacy"); + expect(request.draftConfig).toBeUndefined(); mock.triggerMessage( JSON.stringify({ @@ -2688,25 +2515,17 @@ test("parses canonical agent_stream tool_call payloads without crashing", async unsubscribe(); expect(received).toHaveLength(1); - const streamMsg = received[0] as { + expect(received[0]).toMatchObject({ payload: { event: { - type: "timeline"; item: { - type: "tool_call"; - status: string; - error: unknown; - detail: { - type: string; - }; - }; - }; - }; - }; - - expect(streamMsg.payload.event.item.status).toBe("running"); - expect(streamMsg.payload.event.item.error).toBeNull(); - expect(streamMsg.payload.event.item.detail.type).toBe("shell"); + status: "running", + error: null, + detail: { type: "shell" }, + }, + }, + }, + }); expect(logger.warn).not.toHaveBeenCalled(); }); @@ -2832,28 +2651,20 @@ test("parses canonical fetch_agent_timeline_response payloads without crashing", unsubscribe(); expect(received).toHaveLength(1); - const timelineMsg = received[0] as { + expect(received[0]).toMatchObject({ payload: { - entries: Array<{ - item: { - type: "tool_call"; - status: string; - error: unknown; - detail: { - type: string; - }; - }; - }>; - }; - }; - - const firstEntry = timelineMsg.payload.entries[0]; - expect(firstEntry?.item.type).toBe("tool_call"); - if (firstEntry?.item.type === "tool_call") { - expect(firstEntry.item.status).toBe("running"); - expect(firstEntry.item.error).toBeNull(); - expect(firstEntry.item.detail.type).toBe("shell"); - } + entries: [ + { + item: { + type: "tool_call", + status: "running", + error: null, + detail: { type: "shell" }, + }, + }, + ], + }, + }); expect(logger.warn).not.toHaveBeenCalled(); }); @@ -2950,14 +2761,14 @@ test("sends subscribe/unsubscribe terminals messages", async () => { client.unsubscribeTerminals({ cwd: "/tmp/project" }); expect(mock.sent).toHaveLength(2); - expect(JSON.parse(String(mock.sent[0]))).toEqual({ + expect(JSON.parse(assertStr(mock.sent[0]))).toEqual({ type: "session", message: { type: "subscribe_terminals_request", cwd: "/tmp/project", }, }); - expect(JSON.parse(String(mock.sent[1]))).toEqual({ + expect(JSON.parse(assertStr(mock.sent[1]))).toEqual({ type: "session", message: { type: "unsubscribe_terminals_request", @@ -3041,7 +2852,7 @@ test("sends close_items_request and resolves close_items_response", async () => "req-close-items", ); - expect(JSON.parse(String(mock.sent[0]))).toEqual({ + expect(JSON.parse(assertStr(mock.sent[0]))).toEqual({ type: "session", message: { type: "close_items_request", @@ -3091,18 +2902,10 @@ test("waitForFinish with timeout=0 omits timeoutMs and has no client deadline", const waitPromise = client.waitForFinish("agent-wait-zero-timeout", 0); expect(mock.sent).toHaveLength(1); - const request = JSON.parse(String(mock.sent[0])) as { - type: "session"; - message: { - type: "wait_for_finish_request"; - requestId: string; - agentId: string; - timeoutMs?: number; - }; - }; - expect(request.message.type).toBe("wait_for_finish_request"); - expect(request.message.agentId).toBe("agent-wait-zero-timeout"); - expect(request.message).not.toHaveProperty("timeoutMs"); + const request = parseSentFrame(mock.sent[0]); + expect(request.type).toBe("wait_for_finish_request"); + expect(request.agentId).toBe("agent-wait-zero-timeout"); + expect(request).not.toHaveProperty("timeoutMs"); const settled = vi.fn(); void waitPromise.then( @@ -3117,7 +2920,7 @@ test("waitForFinish with timeout=0 omits timeoutMs and has no client deadline", wrapSessionMessage({ type: "wait_for_finish_response", payload: { - requestId: request.message.requestId, + requestId: request.requestId, status: "idle", final: null, error: null, diff --git a/packages/server/src/server/websocket-server.notifications.test.ts b/packages/server/src/server/websocket-server.notifications.test.ts index 4552de94a..44e6ab1ed 100644 --- a/packages/server/src/server/websocket-server.notifications.test.ts +++ b/packages/server/src/server/websocket-server.notifications.test.ts @@ -181,7 +181,8 @@ function connectClient( function readAttentionRequiredMessage(ws: ReturnType) { const rawMessage = ws.send.mock.calls[0]?.[0]; expect(typeof rawMessage).toBe("string"); - const message = JSON.parse(rawMessage as string); + if (typeof rawMessage !== "string") throw new Error("Expected string WebSocket frame"); + const message = JSON.parse(rawMessage); expect(message.type).toBe("session"); expect(message.message.type).toBe("agent_stream"); expect(message.message.payload.event.type).toBe("attention_required"); diff --git a/packages/server/src/server/websocket-server.relay-reconnect.test.ts b/packages/server/src/server/websocket-server.relay-reconnect.test.ts index 0a0ba9d29..0153bf4ca 100644 --- a/packages/server/src/server/websocket-server.relay-reconnect.test.ts +++ b/packages/server/src/server/websocket-server.relay-reconnect.test.ts @@ -96,6 +96,7 @@ vi.mock("./push/push-service.js", () => ({ }, })); +import { z } from "zod"; import { VoiceAssistantWebSocketServer } from "./websocket-server"; import { parseServerInfoStatusPayload } from "./messages.js"; import type { SpeechReadinessSnapshot } from "./speech/speech-runtime.js"; @@ -106,6 +107,27 @@ interface WebSocketServerInternals { const TEST_DAEMON_VERSION = "1.2.3-test"; +const WireEnvelopeSchema = z.object({ + type: z.string().optional(), + message: z + .object({ + type: z.string().optional(), + payload: z.unknown().optional(), + }) + .optional(), +}); + +function parseSentEnvelope(data: unknown): z.infer { + if (typeof data !== "string") throw new Error("Expected string frame"); + return WireEnvelopeSchema.parse(JSON.parse(data)); +} + +const BinaryFrameSchema = z.object({ + opcode: z.number(), + slot: z.number(), + payload: z.instanceof(Uint8Array), +}); + class MockSocket { readyState = 1; bufferedAmount = 0; @@ -331,10 +353,7 @@ async function attachRelayAndHello(params: { params.socket.emit("message", JSON.stringify(createHelloMessage(params.clientId))); await Promise.resolve(); expect(params.socket.sent.length).toBeGreaterThan(0); - const envelope = JSON.parse(params.socket.sent[0] as string) as { - type?: unknown; - message?: { type?: unknown; payload?: unknown }; - }; + const envelope = parseSentEnvelope(params.socket.sent[0]); expect(envelope.type).toBe("session"); const serverInfo = parseServerInfoStatusPayload(envelope.message?.payload); expect(envelope.message?.type).toBe("status"); @@ -354,10 +373,7 @@ async function attachDirectAndHello(params: { params.socket.emit("message", JSON.stringify(createHelloMessage(params.clientId))); await Promise.resolve(); expect(params.socket.sent.length).toBeGreaterThan(0); - const envelope = JSON.parse(params.socket.sent[0] as string) as { - type?: unknown; - message?: { type?: unknown; payload?: unknown }; - }; + const envelope = parseSentEnvelope(params.socket.sent[0]); expect(envelope.type).toBe("session"); const serverInfo = parseServerInfoStatusPayload(envelope.message?.payload); expect(envelope.message?.type).toBe("status"); @@ -441,7 +457,7 @@ describe("relay external socket reconnect behavior", () => { let closeReason = ""; socket.on("close", (code: unknown, reason: unknown) => { closeCode = typeof code === "number" ? code : null; - closeReason = typeof reason === "string" ? reason : String(reason ?? ""); + closeReason = typeof reason === "string" ? reason : ""; }); await (server as unknown as WebSocketServerInternals).attachSocket( @@ -509,7 +525,7 @@ describe("relay external socket reconnect behavior", () => { let closeReason = ""; socket.on("close", (code: unknown, reason: unknown) => { closeCode = typeof code === "number" ? code : null; - closeReason = typeof reason === "string" ? reason : String(reason ?? ""); + closeReason = typeof reason === "string" ? reason : ""; }); await server.attachExternalSocket(socket, { transport: "relay" }); @@ -583,14 +599,14 @@ describe("relay external socket reconnect behavior", () => { }); expect(sessionMock.instances).toHaveLength(1); - const onMessage = session.args.onMessage as - | ((msg: { type: "status"; payload: { status: string } }) => void) - | undefined; + const { onMessage } = session.args; expect(onMessage).toBeTypeOf("function"); - onMessage?.({ - type: "status", - payload: { status: "ok" }, - }); + if (typeof onMessage === "function") { + onMessage({ + type: "status", + payload: { status: "ok" }, + }); + } expect(directSocket.sent.length).toBeGreaterThan(0); expect(relaySocket.sent.length).toBeGreaterThan(0); @@ -672,9 +688,7 @@ describe("relay external socket reconnect behavior", () => { server.publishSpeechReadiness(speechReadiness); expect(socket.sent).toHaveLength(2); - const secondEnvelope = JSON.parse(socket.sent[1] as string) as { - message?: { payload?: unknown }; - }; + const secondEnvelope = parseSentEnvelope(socket.sent[1]); const secondPayload = parseServerInfoStatusPayload(secondEnvelope.message?.payload); expect(secondPayload?.capabilities?.voice?.dictation.enabled).toBe(true); expect(secondPayload?.capabilities?.voice?.voice.enabled).toBe(true); @@ -699,9 +713,7 @@ describe("relay external socket reconnect behavior", () => { server.publishSpeechReadiness(createDownloadInProgressSpeechReadinessSnapshot()); expect(socket.sent).toHaveLength(2); - const envelope = JSON.parse(socket.sent[1] as string) as { - message?: { payload?: unknown }; - }; + const envelope = parseSentEnvelope(socket.sent[1]); const payload = parseServerInfoStatusPayload(envelope.message?.payload); expect(payload?.capabilities?.voice?.dictation.enabled).toBe(true); expect(payload?.capabilities?.voice?.voice.enabled).toBe(true); @@ -736,11 +748,7 @@ describe("relay external socket reconnect behavior", () => { await Promise.resolve(); expect(session.handleBinaryFrame).toHaveBeenCalledTimes(1); - const frame = session.handleBinaryFrame.mock.calls[0]?.[0] as { - opcode: number; - slot: number; - payload: Uint8Array; - }; + const frame = BinaryFrameSchema.parse(session.handleBinaryFrame.mock.calls[0]?.[0]); expect(frame.opcode).toBe(TerminalStreamOpcode.Input); expect(frame.slot).toBe(9); expect(new TextDecoder().decode(frame.payload)).toBe("ls\r"); @@ -760,12 +768,11 @@ describe("relay external socket reconnect behavior", () => { expect(sessionMock.instances).toHaveLength(1); const session = sessionMock.instances[0]; - const onBinaryMessage = session.args.onBinaryMessage as - | ((frame: Uint8Array) => void) - | undefined; + const { onBinaryMessage } = session.args; expect(onBinaryMessage).toBeTypeOf("function"); - - onBinaryMessage?.(new Uint8Array([TerminalStreamOpcode.Output, 12, 0x6f, 0x6b])); + if (typeof onBinaryMessage === "function") { + onBinaryMessage(new Uint8Array([TerminalStreamOpcode.Output, 12, 0x6f, 0x6b])); + } expect(socket.sent).toHaveLength(2); const binaryPayload = asUint8Array(socket.sent[1]); diff --git a/packages/server/src/server/websocket-server.runtime-metrics.test.ts b/packages/server/src/server/websocket-server.runtime-metrics.test.ts index 3424b5f27..7d3f433c0 100644 --- a/packages/server/src/server/websocket-server.runtime-metrics.test.ts +++ b/packages/server/src/server/websocket-server.runtime-metrics.test.ts @@ -1,4 +1,5 @@ import { describe, expect, it, vi } from "vitest"; +import { z } from "zod"; import type { Server as HTTPServer } from "http"; import type pino from "pino"; import type { AgentManager } from "./agent/agent-manager.js"; @@ -42,17 +43,19 @@ interface WebSocketServerInternals { sessions: Map; } -interface RuntimeMetricsLog { - outboundMessageTypesTop: Array<[string, number]>; - outboundSessionMessageTypesTop: Array<[string, number]>; - outboundAgentStreamTypesTop: Array<[string, number]>; - outboundAgentStreamAgentsTop: Array<[string, number]>; - outboundBinaryFrameTypesTop: Array<[string, number]>; - bufferedAmount: { - p95: number; - max: number; - }; -} +const RuntimeMetricsLogSchema = z.object({ + outboundMessageTypesTop: z.array(z.tuple([z.string(), z.number()])), + outboundSessionMessageTypesTop: z.array(z.tuple([z.string(), z.number()])), + outboundAgentStreamTypesTop: z.array(z.tuple([z.string(), z.number()])), + outboundAgentStreamAgentsTop: z.array(z.tuple([z.string(), z.number()])), + outboundBinaryFrameTypesTop: z.array(z.tuple([z.string(), z.number()])), + bufferedAmount: z.object({ + p95: z.number(), + max: z.number(), + }), +}); + +type RuntimeMetricsLog = z.infer; interface TestSocket { readyState: number; @@ -155,7 +158,7 @@ function flushRuntimeMetrics(server: VoiceAssistantWebSocketServer): void { function getRuntimeMetricsLog(logger: ReturnType): RuntimeMetricsLog { const metricsCall = logger.info.mock.calls.find((call) => call[1] === "ws_runtime_metrics"); expect(metricsCall).toBeDefined(); - return metricsCall![0] as RuntimeMetricsLog; + return RuntimeMetricsLogSchema.parse(metricsCall![0]); } function sendToClient( @@ -350,11 +353,11 @@ describe("VoiceAssistantWebSocketServer runtime metrics", () => { const logger = createLogger(); const server = createServer(logger); const socket = createSocket([12, 24]); - const frame = new Proxy(Buffer.from([0xff, 0xfe, 0xfd, 0x00, 0xc0]), { + const frame: Uint8Array = new Proxy(Buffer.from([0xff, 0xfe, 0xfd, 0x00, 0xc0]), { get(_target, property) { throw new Error(`Binary frame payload was unexpectedly accessed via ${String(property)}`); }, - }) as unknown as Uint8Array; + }); expect(() => sendBinaryToClient(server, socket, frame)).not.toThrow();