refactor(server): parse daemon/ws/wire test responses with Zod (T1.c typeaware sweep) (#750)
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.
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
import { afterEach, expect, expectTypeOf, test, vi } from "vitest";
|
import { afterEach, expect, expectTypeOf, test, vi } from "vitest";
|
||||||
|
import { z } from "zod";
|
||||||
import { DaemonClient, type DaemonTransport } from "./daemon-client";
|
import { DaemonClient, type DaemonTransport } from "./daemon-client";
|
||||||
import { encodeFileTransferFrame, FileTransferOpcode } from "../shared/binary-frames/index.js";
|
import { encodeFileTransferFrame, FileTransferOpcode } from "../shared/binary-frames/index.js";
|
||||||
import {
|
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<string, unknown> {
|
||||||
|
return z
|
||||||
|
.object({
|
||||||
|
type: z.literal("session"),
|
||||||
|
message: z.record(z.unknown()),
|
||||||
|
})
|
||||||
|
.parse(JSON.parse(assertStr(data))).message;
|
||||||
|
}
|
||||||
|
|
||||||
const clients: DaemonClient[] = [];
|
const clients: DaemonClient[] = [];
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
@@ -123,10 +140,7 @@ test("dedupes in-flight checkout status requests per agentId", async () => {
|
|||||||
|
|
||||||
expect(mock.sent).toHaveLength(1);
|
expect(mock.sent).toHaveLength(1);
|
||||||
|
|
||||||
const request = JSON.parse(mock.sent[0]) as {
|
const request = parseSentFrame(mock.sent[0]);
|
||||||
type: "session";
|
|
||||||
message: { type: "checkout_status_request"; cwd: string; requestId: string };
|
|
||||||
};
|
|
||||||
|
|
||||||
const response = {
|
const response = {
|
||||||
type: "session",
|
type: "session",
|
||||||
@@ -135,7 +149,7 @@ test("dedupes in-flight checkout status requests per agentId", async () => {
|
|||||||
payload: {
|
payload: {
|
||||||
cwd: "/tmp/project",
|
cwd: "/tmp/project",
|
||||||
error: null,
|
error: null,
|
||||||
requestId: request.message.requestId,
|
requestId: request.requestId,
|
||||||
isGit: false,
|
isGit: false,
|
||||||
isPaseoOwnedWorktree: false,
|
isPaseoOwnedWorktree: false,
|
||||||
repoRoot: null,
|
repoRoot: null,
|
||||||
@@ -155,12 +169,12 @@ test("dedupes in-flight checkout status requests per agentId", async () => {
|
|||||||
const [r1, r2] = await Promise.all([p1, p2]);
|
const [r1, r2] = await Promise.all([p1, p2]);
|
||||||
expect(r1).toMatchObject({
|
expect(r1).toMatchObject({
|
||||||
cwd: "/tmp/project",
|
cwd: "/tmp/project",
|
||||||
requestId: request.message.requestId,
|
requestId: request.requestId,
|
||||||
isGit: false,
|
isGit: false,
|
||||||
});
|
});
|
||||||
expect(r2).toMatchObject({
|
expect(r2).toMatchObject({
|
||||||
cwd: "/tmp/project",
|
cwd: "/tmp/project",
|
||||||
requestId: request.message.requestId,
|
requestId: request.requestId,
|
||||||
isGit: false,
|
isGit: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -168,24 +182,21 @@ test("dedupes in-flight checkout status requests per agentId", async () => {
|
|||||||
const p3 = client.getCheckoutStatus("/tmp/project");
|
const p3 = client.getCheckoutStatus("/tmp/project");
|
||||||
expect(mock.sent).toHaveLength(2);
|
expect(mock.sent).toHaveLength(2);
|
||||||
|
|
||||||
const request2 = JSON.parse(mock.sent[1]) as {
|
const request2 = parseSentFrame(mock.sent[1]);
|
||||||
type: "session";
|
|
||||||
message: { type: "checkout_status_request"; cwd: string; requestId: string };
|
|
||||||
};
|
|
||||||
|
|
||||||
mock.triggerMessage(
|
mock.triggerMessage(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
...response,
|
...response,
|
||||||
message: {
|
message: {
|
||||||
...response.message,
|
...response.message,
|
||||||
payload: { ...response.message.payload, requestId: request2.message.requestId },
|
payload: { ...response.message.payload, requestId: request2.requestId },
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
await expect(p3).resolves.toMatchObject({
|
await expect(p3).resolves.toMatchObject({
|
||||||
cwd: "/tmp/project",
|
cwd: "/tmp/project",
|
||||||
requestId: request2.message.requestId,
|
requestId: request2.requestId,
|
||||||
isGit: false,
|
isGit: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -234,7 +245,7 @@ test("advertises reasoning_merge_enum in hello", async () => {
|
|||||||
await connectPromise;
|
await connectPromise;
|
||||||
|
|
||||||
expect(mock.sent).toHaveLength(1);
|
expect(mock.sent).toHaveLength(1);
|
||||||
expect(JSON.parse(mock.sent[0] as string)).toEqual({
|
expect(JSON.parse(assertStr(mock.sent[0]))).toEqual({
|
||||||
type: "hello",
|
type: "hello",
|
||||||
clientId: "clsk_unit_test",
|
clientId: "clsk_unit_test",
|
||||||
clientType: "cli",
|
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");
|
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",
|
type: "session",
|
||||||
message: {
|
message: {
|
||||||
type: "file_explorer_request",
|
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");
|
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",
|
type: "session",
|
||||||
message: {
|
message: {
|
||||||
type: "file_explorer_request",
|
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");
|
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",
|
type: "session",
|
||||||
message: {
|
message: {
|
||||||
type: "file_explorer_request",
|
type: "file_explorer_request",
|
||||||
@@ -575,15 +586,8 @@ test("sends create_agent_request with string workspace ids", async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(mock.sent).toHaveLength(1);
|
expect(mock.sent).toHaveLength(1);
|
||||||
const request = JSON.parse(String(mock.sent[0])) as {
|
const request = parseSentFrame(mock.sent[0]);
|
||||||
type: "session";
|
expect(request).toEqual(
|
||||||
message: {
|
|
||||||
type: "create_agent_request";
|
|
||||||
requestId: string;
|
|
||||||
workspaceId: string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
expect(request.message).toEqual(
|
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
type: "create_agent_request",
|
type: "create_agent_request",
|
||||||
workspaceId: "ws-feature-a",
|
workspaceId: "ws-feature-a",
|
||||||
@@ -595,7 +599,7 @@ test("sends create_agent_request with string workspace ids", async () => {
|
|||||||
type: "status",
|
type: "status",
|
||||||
payload: {
|
payload: {
|
||||||
status: "agent_create_failed",
|
status: "agent_create_failed",
|
||||||
requestId: request.message.requestId,
|
requestId: request.requestId,
|
||||||
error: "compat test sentinel",
|
error: "compat test sentinel",
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
@@ -639,15 +643,8 @@ test("sends structured attachments with create_agent_request", async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(mock.sent).toHaveLength(1);
|
expect(mock.sent).toHaveLength(1);
|
||||||
const request = JSON.parse(String(mock.sent[0])) as {
|
const request = parseSentFrame(mock.sent[0]);
|
||||||
type: "session";
|
expect(request.attachments).toEqual([
|
||||||
message: {
|
|
||||||
type: "create_agent_request";
|
|
||||||
requestId: string;
|
|
||||||
attachments: Array<{ type: string; mimeType: string; number: number }>;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
expect(request.message.attachments).toEqual([
|
|
||||||
{
|
{
|
||||||
type: "github_pr",
|
type: "github_pr",
|
||||||
mimeType: "application/github-pr",
|
mimeType: "application/github-pr",
|
||||||
@@ -664,7 +661,7 @@ test("sends structured attachments with create_agent_request", async () => {
|
|||||||
type: "status",
|
type: "status",
|
||||||
payload: {
|
payload: {
|
||||||
status: "agent_create_failed",
|
status: "agent_create_failed",
|
||||||
requestId: request.message.requestId,
|
requestId: request.requestId,
|
||||||
error: "attachment test sentinel",
|
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);
|
expect(mock.sent).toHaveLength(1);
|
||||||
const request = JSON.parse(String(mock.sent[0])) as {
|
const request = parseSentFrame(mock.sent[0]);
|
||||||
type: "session";
|
expect(request.git).toEqual({
|
||||||
message: {
|
|
||||||
type: "create_agent_request";
|
|
||||||
requestId: string;
|
|
||||||
git: {
|
|
||||||
createWorktree: boolean;
|
|
||||||
worktreeSlug: string;
|
|
||||||
refName: string;
|
|
||||||
action: string;
|
|
||||||
githubPrNumber: number;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
expect(request.message.git).toEqual({
|
|
||||||
createWorktree: true,
|
createWorktree: true,
|
||||||
worktreeSlug: "review-pr-123",
|
worktreeSlug: "review-pr-123",
|
||||||
refName: "feature/worktree-base-ref",
|
refName: "feature/worktree-base-ref",
|
||||||
@@ -731,7 +715,7 @@ test("sends worktree base-ref fields in create_agent_request git options", async
|
|||||||
type: "status",
|
type: "status",
|
||||||
payload: {
|
payload: {
|
||||||
status: "agent_create_failed",
|
status: "agent_create_failed",
|
||||||
requestId: request.message.requestId,
|
requestId: request.requestId,
|
||||||
error: "git ref fields sentinel",
|
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({
|
JSON.stringify({
|
||||||
type: "session",
|
type: "session",
|
||||||
message: {
|
message: {
|
||||||
@@ -834,17 +818,11 @@ test("sends structured first-agent context attachments with create_paseo_worktre
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(mock.sent).toHaveLength(1);
|
expect(mock.sent).toHaveLength(1);
|
||||||
const request = JSON.parse(String(mock.sent[0])) as {
|
const request = parseSentFrame(mock.sent[0]);
|
||||||
type: "session";
|
const firstAgentContext = z
|
||||||
message: {
|
.object({ attachments: z.array(z.unknown()) })
|
||||||
type: "create_paseo_worktree_request";
|
.parse(request.firstAgentContext);
|
||||||
requestId: string;
|
expect(firstAgentContext.attachments).toEqual([
|
||||||
firstAgentContext: {
|
|
||||||
attachments: Array<{ type: string; mimeType: string; number: number }>;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
expect(request.message.firstAgentContext.attachments).toEqual([
|
|
||||||
{
|
{
|
||||||
type: "github_pr",
|
type: "github_pr",
|
||||||
mimeType: "application/github-pr",
|
mimeType: "application/github-pr",
|
||||||
@@ -858,7 +836,7 @@ test("sends structured first-agent context attachments with create_paseo_worktre
|
|||||||
wrapSessionMessage({
|
wrapSessionMessage({
|
||||||
type: "create_paseo_worktree_response",
|
type: "create_paseo_worktree_response",
|
||||||
payload: {
|
payload: {
|
||||||
requestId: request.message.requestId,
|
requestId: request.requestId,
|
||||||
workspace: null,
|
workspace: null,
|
||||||
error: "worktree attachment sentinel",
|
error: "worktree attachment sentinel",
|
||||||
setupTerminalId: null,
|
setupTerminalId: null,
|
||||||
@@ -867,7 +845,7 @@ test("sends structured first-agent context attachments with create_paseo_worktre
|
|||||||
);
|
);
|
||||||
|
|
||||||
await expect(createPromise).resolves.toEqual({
|
await expect(createPromise).resolves.toEqual({
|
||||||
requestId: request.message.requestId,
|
requestId: request.requestId,
|
||||||
workspace: null,
|
workspace: null,
|
||||||
error: "worktree attachment sentinel",
|
error: "worktree attachment sentinel",
|
||||||
setupTerminalId: null,
|
setupTerminalId: null,
|
||||||
@@ -903,19 +881,8 @@ test("sends worktree base-ref fields in create_paseo_worktree_request", async ()
|
|||||||
);
|
);
|
||||||
|
|
||||||
expect(mock.sent).toHaveLength(1);
|
expect(mock.sent).toHaveLength(1);
|
||||||
const request = JSON.parse(String(mock.sent[0])) as {
|
const request = parseSentFrame(mock.sent[0]);
|
||||||
type: "session";
|
expect(request).toEqual({
|
||||||
message: {
|
|
||||||
type: "create_paseo_worktree_request";
|
|
||||||
requestId: string;
|
|
||||||
cwd: string;
|
|
||||||
worktreeSlug: string;
|
|
||||||
refName: string;
|
|
||||||
action: string;
|
|
||||||
githubPrNumber: number;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
expect(request.message).toEqual({
|
|
||||||
type: "create_paseo_worktree_request",
|
type: "create_paseo_worktree_request",
|
||||||
cwd: "/tmp/project",
|
cwd: "/tmp/project",
|
||||||
worktreeSlug: "review-pr-123",
|
worktreeSlug: "review-pr-123",
|
||||||
@@ -929,7 +896,7 @@ test("sends worktree base-ref fields in create_paseo_worktree_request", async ()
|
|||||||
wrapSessionMessage({
|
wrapSessionMessage({
|
||||||
type: "create_paseo_worktree_response",
|
type: "create_paseo_worktree_response",
|
||||||
payload: {
|
payload: {
|
||||||
requestId: request.message.requestId,
|
requestId: request.requestId,
|
||||||
workspace: null,
|
workspace: null,
|
||||||
error: "worktree ref fields sentinel",
|
error: "worktree ref fields sentinel",
|
||||||
setupTerminalId: null,
|
setupTerminalId: null,
|
||||||
@@ -938,7 +905,7 @@ test("sends worktree base-ref fields in create_paseo_worktree_request", async ()
|
|||||||
);
|
);
|
||||||
|
|
||||||
await expect(createPromise).resolves.toEqual({
|
await expect(createPromise).resolves.toEqual({
|
||||||
requestId: request.message.requestId,
|
requestId: request.requestId,
|
||||||
workspace: null,
|
workspace: null,
|
||||||
error: "worktree ref fields sentinel",
|
error: "worktree ref fields sentinel",
|
||||||
setupTerminalId: null,
|
setupTerminalId: null,
|
||||||
@@ -970,7 +937,7 @@ test("omitting create_paseo_worktree_request worktree base-ref fields preserves
|
|||||||
"req-worktree-legacy",
|
"req-worktree-legacy",
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(String(mock.sent[0])).toBe(
|
expect(assertStr(mock.sent[0])).toBe(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
type: "session",
|
type: "session",
|
||||||
message: {
|
message: {
|
||||||
@@ -1031,14 +998,8 @@ test("sends explicit shutdown_server_request via shutdownServer", async () => {
|
|||||||
const promise = lifecycleClient.shutdownServer("req-shutdown-1");
|
const promise = lifecycleClient.shutdownServer("req-shutdown-1");
|
||||||
|
|
||||||
expect(mock.sent).toHaveLength(1);
|
expect(mock.sent).toHaveLength(1);
|
||||||
const request = JSON.parse(mock.sent[0]) as {
|
const request = parseSentFrame(mock.sent[0]);
|
||||||
type: "session";
|
expect(request).toEqual({
|
||||||
message: {
|
|
||||||
type: string;
|
|
||||||
requestId: string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
expect(request.message).toEqual({
|
|
||||||
type: "shutdown_server_request",
|
type: "shutdown_server_request",
|
||||||
requestId: "req-shutdown-1",
|
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");
|
const promise = client.restartServer("settings_update", "req-restart-1");
|
||||||
|
|
||||||
expect(mock.sent).toHaveLength(1);
|
expect(mock.sent).toHaveLength(1);
|
||||||
const request = JSON.parse(mock.sent[0]) as {
|
const request = parseSentFrame(mock.sent[0]);
|
||||||
type: "session";
|
expect(request).toEqual({
|
||||||
message: {
|
|
||||||
type: string;
|
|
||||||
reason?: string;
|
|
||||||
requestId: string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
expect(request.message).toEqual({
|
|
||||||
type: "restart_server_request",
|
type: "restart_server_request",
|
||||||
reason: "settings_update",
|
reason: "settings_update",
|
||||||
requestId: "req-restart-1",
|
requestId: "req-restart-1",
|
||||||
@@ -1142,7 +1096,9 @@ test("transitions out of connecting when connect timeout elapses", async () => {
|
|||||||
expect(result.ok).toBe(false);
|
expect(result.ok).toBe(false);
|
||||||
if (!result.ok) {
|
if (!result.ok) {
|
||||||
expect(result.error).toBeInstanceOf(Error);
|
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");
|
expect(client.getConnectionState().status).toBe("disconnected");
|
||||||
} finally {
|
} finally {
|
||||||
@@ -1234,12 +1190,14 @@ test("logs configured runtime generation in connection transition events", async
|
|||||||
mock.triggerOpen();
|
mock.triggerOpen();
|
||||||
await connectPromise;
|
await connectPromise;
|
||||||
|
|
||||||
const transitionPayloads = logger.debug.mock.calls
|
const transitionPayloads = logger.debug.mock.calls.filter(
|
||||||
.filter(([, message]) => message === "DaemonClientTransition")
|
([, message]) => message === "DaemonClientTransition",
|
||||||
.map(([payload]) => payload as { generation?: number | null });
|
);
|
||||||
expect(transitionPayloads.length).toBeGreaterThan(0);
|
expect(transitionPayloads.length).toBeGreaterThan(0);
|
||||||
for (const payload of transitionPayloads) {
|
for (const [payload] of transitionPayloads) {
|
||||||
expect(payload.generation).toBe(7);
|
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);
|
expect(mock.sent).toHaveLength(1);
|
||||||
const request = JSON.parse(mock.sent[0]) as {
|
const request = parseSentFrame(mock.sent[0]);
|
||||||
type: "session";
|
expect(request.type).toBe("subscribe_checkout_diff_request");
|
||||||
message: {
|
expect(request.subscriptionId).toBe("checkout-sub-1");
|
||||||
type: "subscribe_checkout_diff_request";
|
expect(request.cwd).toBe("/tmp/project");
|
||||||
subscriptionId: string;
|
expect(request.compare).toEqual({ mode: "uncommitted" });
|
||||||
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" });
|
|
||||||
|
|
||||||
mock.triggerMessage(
|
mock.triggerMessage(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
@@ -1292,7 +1241,7 @@ test("subscribes to checkout diff updates via RPC handshake", async () => {
|
|||||||
cwd: "/tmp/project",
|
cwd: "/tmp/project",
|
||||||
files: [],
|
files: [],
|
||||||
error: null,
|
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",
|
cwd: "/tmp/project",
|
||||||
files: [],
|
files: [],
|
||||||
error: null,
|
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" });
|
const promise = client.getCheckoutDiff("/tmp/project", { mode: "base", baseRef: "main" });
|
||||||
|
|
||||||
expect(mock.sent).toHaveLength(1);
|
expect(mock.sent).toHaveLength(1);
|
||||||
const subscribeRequest = JSON.parse(mock.sent[0]) as {
|
const subscribeRequest = parseSentFrame(mock.sent[0]);
|
||||||
type: "session";
|
expect(subscribeRequest.type).toBe("subscribe_checkout_diff_request");
|
||||||
message: {
|
expect(subscribeRequest.cwd).toBe("/tmp/project");
|
||||||
type: "subscribe_checkout_diff_request";
|
expect(subscribeRequest.compare).toEqual({
|
||||||
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({
|
|
||||||
mode: "base",
|
mode: "base",
|
||||||
baseRef: "main",
|
baseRef: "main",
|
||||||
});
|
});
|
||||||
@@ -1350,11 +1290,11 @@ test("getCheckoutDiff uses one-shot subscription protocol", async () => {
|
|||||||
message: {
|
message: {
|
||||||
type: "subscribe_checkout_diff_response",
|
type: "subscribe_checkout_diff_response",
|
||||||
payload: {
|
payload: {
|
||||||
subscriptionId: subscribeRequest.message.subscriptionId,
|
subscriptionId: subscribeRequest.subscriptionId,
|
||||||
cwd: "/tmp/project",
|
cwd: "/tmp/project",
|
||||||
files: [],
|
files: [],
|
||||||
error: null,
|
error: null,
|
||||||
requestId: subscribeRequest.message.requestId,
|
requestId: subscribeRequest.requestId,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
@@ -1364,19 +1304,13 @@ test("getCheckoutDiff uses one-shot subscription protocol", async () => {
|
|||||||
cwd: "/tmp/project",
|
cwd: "/tmp/project",
|
||||||
files: [],
|
files: [],
|
||||||
error: null,
|
error: null,
|
||||||
requestId: subscribeRequest.message.requestId,
|
requestId: subscribeRequest.requestId,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(mock.sent).toHaveLength(2);
|
expect(mock.sent).toHaveLength(2);
|
||||||
const unsubscribeRequest = JSON.parse(mock.sent[1]) as {
|
const unsubscribeRequest = parseSentFrame(mock.sent[1]);
|
||||||
type: "session";
|
expect(unsubscribeRequest.type).toBe("unsubscribe_checkout_diff_request");
|
||||||
message: {
|
expect(unsubscribeRequest.subscriptionId).toBe(subscribeRequest.subscriptionId);
|
||||||
type: "unsubscribe_checkout_diff_request";
|
|
||||||
subscriptionId: string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
expect(unsubscribeRequest.message.type).toBe("unsubscribe_checkout_diff_request");
|
|
||||||
expect(unsubscribeRequest.message.subscriptionId).toBe(subscribeRequest.message.subscriptionId);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("requests branch suggestions via RPC", async () => {
|
test("requests branch suggestions via RPC", async () => {
|
||||||
@@ -1402,21 +1336,12 @@ test("requests branch suggestions via RPC", async () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
expect(mock.sent).toHaveLength(1);
|
expect(mock.sent).toHaveLength(1);
|
||||||
const request = JSON.parse(mock.sent[0]) as {
|
const request = parseSentFrame(mock.sent[0]);
|
||||||
type: "session";
|
expect(request.type).toBe("branch_suggestions_request");
|
||||||
message: {
|
expect(request.cwd).toBe("/tmp/project");
|
||||||
type: "branch_suggestions_request";
|
expect(request.query).toBe("mai");
|
||||||
cwd: string;
|
expect(request.limit).toBe(5);
|
||||||
query?: string;
|
expect(request.requestId).toBe("req-branches");
|
||||||
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");
|
|
||||||
|
|
||||||
mock.triggerMessage(
|
mock.triggerMessage(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
@@ -1458,11 +1383,8 @@ test("reads project config via correlated RPC", async () => {
|
|||||||
const promise = client.readProjectConfig("/repo/app", "read-project-config-1");
|
const promise = client.readProjectConfig("/repo/app", "read-project-config-1");
|
||||||
|
|
||||||
expect(mock.sent).toHaveLength(1);
|
expect(mock.sent).toHaveLength(1);
|
||||||
const request = JSON.parse(mock.sent[0]) as {
|
const request = parseSentFrame(mock.sent[0]);
|
||||||
type: "session";
|
expect(request).toEqual({
|
||||||
message: { type: "read_project_config_request"; requestId: string; repoRoot: string };
|
|
||||||
};
|
|
||||||
expect(request.message).toEqual({
|
|
||||||
type: "read_project_config_request",
|
type: "read_project_config_request",
|
||||||
requestId: "read-project-config-1",
|
requestId: "read-project-config-1",
|
||||||
repoRoot: "/repo/app",
|
repoRoot: "/repo/app",
|
||||||
@@ -1514,17 +1436,8 @@ test("writes project config via correlated RPC and returns inline failures", asy
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(mock.sent).toHaveLength(1);
|
expect(mock.sent).toHaveLength(1);
|
||||||
const request = JSON.parse(mock.sent[0]) as {
|
const request = parseSentFrame(mock.sent[0]);
|
||||||
type: "session";
|
expect(request).toEqual({
|
||||||
message: {
|
|
||||||
type: "write_project_config_request";
|
|
||||||
requestId: string;
|
|
||||||
repoRoot: string;
|
|
||||||
config: unknown;
|
|
||||||
expectedRevision: unknown;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
expect(request.message).toEqual({
|
|
||||||
type: "write_project_config_request",
|
type: "write_project_config_request",
|
||||||
requestId: "write-project-config-1",
|
requestId: "write-project-config-1",
|
||||||
repoRoot: "/repo/app",
|
repoRoot: "/repo/app",
|
||||||
@@ -1587,25 +1500,14 @@ test("requests directory suggestions via RPC", async () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
expect(mock.sent).toHaveLength(1);
|
expect(mock.sent).toHaveLength(1);
|
||||||
const request = JSON.parse(mock.sent[0]) as {
|
const request = parseSentFrame(mock.sent[0]);
|
||||||
type: "session";
|
expect(request.type).toBe("directory_suggestions_request");
|
||||||
message: {
|
expect(request.query).toBe("proj");
|
||||||
type: "directory_suggestions_request";
|
expect(request.cwd).toBe("/tmp/project");
|
||||||
query: string;
|
expect(request.includeFiles).toBe(true);
|
||||||
cwd?: string;
|
expect(request.includeDirectories).toBe(true);
|
||||||
includeFiles?: boolean;
|
expect(request.limit).toBe(10);
|
||||||
includeDirectories?: boolean;
|
expect(request.requestId).toBe("req-directories");
|
||||||
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");
|
|
||||||
|
|
||||||
mock.triggerMessage(
|
mock.triggerMessage(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
@@ -1654,21 +1556,12 @@ test("requests checkout merge from base via RPC", async () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
expect(mock.sent).toHaveLength(1);
|
expect(mock.sent).toHaveLength(1);
|
||||||
const request = JSON.parse(mock.sent[0]) as {
|
const request = parseSentFrame(mock.sent[0]);
|
||||||
type: "session";
|
expect(request.type).toBe("checkout_merge_from_base_request");
|
||||||
message: {
|
expect(request.cwd).toBe("/tmp/project");
|
||||||
type: "checkout_merge_from_base_request";
|
expect(request.baseRef).toBe("main");
|
||||||
cwd: string;
|
expect(request.requireCleanTarget).toBe(true);
|
||||||
baseRef?: string;
|
expect(request.requestId).toBe("req-merge-from-base");
|
||||||
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");
|
|
||||||
|
|
||||||
mock.triggerMessage(
|
mock.triggerMessage(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
@@ -1713,17 +1606,10 @@ test("requests checkout pull via RPC", async () => {
|
|||||||
const promise = client.checkoutPull("/tmp/project", "req-pull");
|
const promise = client.checkoutPull("/tmp/project", "req-pull");
|
||||||
|
|
||||||
expect(mock.sent).toHaveLength(1);
|
expect(mock.sent).toHaveLength(1);
|
||||||
const request = JSON.parse(mock.sent[0]) as {
|
const request = parseSentFrame(mock.sent[0]);
|
||||||
type: "session";
|
expect(request.type).toBe("checkout_pull_request");
|
||||||
message: {
|
expect(request.cwd).toBe("/tmp/project");
|
||||||
type: "checkout_pull_request";
|
expect(request.requestId).toBe("req-pull");
|
||||||
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");
|
|
||||||
|
|
||||||
mock.triggerMessage(
|
mock.triggerMessage(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
@@ -1777,22 +1663,13 @@ test("resubscribes checkout diff streams after reconnect", async () => {
|
|||||||
await connectPromise;
|
await connectPromise;
|
||||||
|
|
||||||
expect(mock.sent).toHaveLength(1);
|
expect(mock.sent).toHaveLength(1);
|
||||||
const request = JSON.parse(mock.sent[0]) as {
|
const request = parseSentFrame(mock.sent[0]);
|
||||||
type: "session";
|
expect(request.type).toBe("subscribe_checkout_diff_request");
|
||||||
message: {
|
expect(request.subscriptionId).toBe("checkout-sub-1");
|
||||||
type: "subscribe_checkout_diff_request";
|
expect(request.cwd).toBe("/tmp/project");
|
||||||
subscriptionId: string;
|
expect(request.compare).toEqual({ mode: "base", baseRef: "main" });
|
||||||
cwd: string;
|
expect(typeof request.requestId).toBe("string");
|
||||||
compare: { mode: "uncommitted" | "base"; baseRef?: string };
|
expect(z.string().parse(request.requestId).length).toBeGreaterThan(0);
|
||||||
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);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("fetches agents via RPC with filters, sort, and pagination", async () => {
|
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);
|
expect(mock.sent).toHaveLength(1);
|
||||||
const request = JSON.parse(mock.sent[0]) as {
|
const request = parseSentFrame(mock.sent[0]);
|
||||||
type: "session";
|
expect(request.type).toBe("fetch_agents_request");
|
||||||
message: {
|
expect(request.sort).toEqual([
|
||||||
type: "fetch_agents_request";
|
|
||||||
requestId: string;
|
|
||||||
filter?: { labels?: Record<string, string> };
|
|
||||||
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([
|
|
||||||
{ key: "status_priority", direction: "asc" },
|
{ key: "status_priority", direction: "asc" },
|
||||||
{ key: "created_at", direction: "desc" },
|
{ key: "created_at", direction: "desc" },
|
||||||
]);
|
]);
|
||||||
expect(request.message.page).toEqual({ limit: 25, cursor: "cursor-1" });
|
expect(request.page).toEqual({ limit: 25, cursor: "cursor-1" });
|
||||||
expect(request.message.subscribe).toEqual({ subscriptionId: "sub-1" });
|
expect(request.subscribe).toEqual({ subscriptionId: "sub-1" });
|
||||||
|
|
||||||
mock.triggerMessage(
|
mock.triggerMessage(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
@@ -1851,7 +1715,7 @@ test("fetches agents via RPC with filters, sort, and pagination", async () => {
|
|||||||
message: {
|
message: {
|
||||||
type: "fetch_agents_response",
|
type: "fetch_agents_response",
|
||||||
payload: {
|
payload: {
|
||||||
requestId: request.message.requestId,
|
requestId: request.requestId,
|
||||||
subscriptionId: "sub-1",
|
subscriptionId: "sub-1",
|
||||||
entries: [],
|
entries: [],
|
||||||
pageInfo: {
|
pageInfo: {
|
||||||
@@ -1865,7 +1729,7 @@ test("fetches agents via RPC with filters, sort, and pagination", async () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
await expect(promise).resolves.toEqual({
|
await expect(promise).resolves.toEqual({
|
||||||
requestId: request.message.requestId,
|
requestId: request.requestId,
|
||||||
subscriptionId: "sub-1",
|
subscriptionId: "sub-1",
|
||||||
entries: [],
|
entries: [],
|
||||||
pageInfo: {
|
pageInfo: {
|
||||||
@@ -1899,15 +1763,8 @@ test("sends active-scoped fetch_agents_request", async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(mock.sent).toHaveLength(1);
|
expect(mock.sent).toHaveLength(1);
|
||||||
const request = JSON.parse(String(mock.sent[0])) as {
|
const request = parseSentFrame(mock.sent[0]);
|
||||||
type: "session";
|
expect(request).toMatchObject({
|
||||||
message: {
|
|
||||||
type: "fetch_agents_request";
|
|
||||||
requestId: string;
|
|
||||||
scope?: "active";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
expect(request.message).toMatchObject({
|
|
||||||
type: "fetch_agents_request",
|
type: "fetch_agents_request",
|
||||||
scope: "active",
|
scope: "active",
|
||||||
});
|
});
|
||||||
@@ -1918,7 +1775,7 @@ test("sends active-scoped fetch_agents_request", async () => {
|
|||||||
message: {
|
message: {
|
||||||
type: "fetch_agents_response",
|
type: "fetch_agents_response",
|
||||||
payload: {
|
payload: {
|
||||||
requestId: request.message.requestId,
|
requestId: request.requestId,
|
||||||
entries: [],
|
entries: [],
|
||||||
pageInfo: {
|
pageInfo: {
|
||||||
nextCursor: null,
|
nextCursor: null,
|
||||||
@@ -1931,7 +1788,7 @@ test("sends active-scoped fetch_agents_request", async () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
await expect(promise).resolves.toMatchObject({
|
await expect(promise).resolves.toMatchObject({
|
||||||
requestId: request.message.requestId,
|
requestId: request.requestId,
|
||||||
entries: [],
|
entries: [],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -1959,16 +1816,9 @@ test("fetches paginated agent history separately from active agents", async () =
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(mock.sent).toHaveLength(1);
|
expect(mock.sent).toHaveLength(1);
|
||||||
const request = JSON.parse(String(mock.sent[0])) as {
|
const request = parseSentFrame(mock.sent[0]);
|
||||||
type: "session";
|
expect(request.type).toBe("fetch_agent_history_request");
|
||||||
message: {
|
expect(request.page).toEqual({ limit: 25, cursor: "cursor-1" });
|
||||||
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" });
|
|
||||||
|
|
||||||
mock.triggerMessage(
|
mock.triggerMessage(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
@@ -1976,7 +1826,7 @@ test("fetches paginated agent history separately from active agents", async () =
|
|||||||
message: {
|
message: {
|
||||||
type: "fetch_agent_history_response",
|
type: "fetch_agent_history_response",
|
||||||
payload: {
|
payload: {
|
||||||
requestId: request.message.requestId,
|
requestId: request.requestId,
|
||||||
entries: [],
|
entries: [],
|
||||||
pageInfo: {
|
pageInfo: {
|
||||||
nextCursor: null,
|
nextCursor: null,
|
||||||
@@ -1989,7 +1839,7 @@ test("fetches paginated agent history separately from active agents", async () =
|
|||||||
);
|
);
|
||||||
|
|
||||||
await expect(promise).resolves.toEqual({
|
await expect(promise).resolves.toEqual({
|
||||||
requestId: request.message.requestId,
|
requestId: request.requestId,
|
||||||
entries: [],
|
entries: [],
|
||||||
pageInfo: {
|
pageInfo: {
|
||||||
nextCursor: null,
|
nextCursor: null,
|
||||||
@@ -2037,7 +1887,9 @@ test("uses server-provided dictation finish timeout budget", async () => {
|
|||||||
await vi.advanceTimersByTimeAsync(5_101);
|
await vi.advanceTimersByTimeAsync(5_101);
|
||||||
const error = await finishError;
|
const error = await finishError;
|
||||||
expect(error).toBeInstanceOf(Error);
|
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();
|
vi.useRealTimers();
|
||||||
});
|
});
|
||||||
@@ -2147,11 +1999,8 @@ test("lists available providers via RPC", async () => {
|
|||||||
const promise = client.listAvailableProviders();
|
const promise = client.listAvailableProviders();
|
||||||
expect(mock.sent).toHaveLength(1);
|
expect(mock.sent).toHaveLength(1);
|
||||||
|
|
||||||
const request = JSON.parse(mock.sent[0]) as {
|
const request = parseSentFrame(mock.sent[0]);
|
||||||
type: "session";
|
expect(request.type).toBe("list_available_providers_request");
|
||||||
message: { type: "list_available_providers_request"; requestId: string };
|
|
||||||
};
|
|
||||||
expect(request.message.type).toBe("list_available_providers_request");
|
|
||||||
|
|
||||||
mock.triggerMessage(
|
mock.triggerMessage(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
@@ -2165,7 +2014,7 @@ test("lists available providers via RPC", async () => {
|
|||||||
],
|
],
|
||||||
error: null,
|
error: null,
|
||||||
fetchedAt: "2026-02-12T00:00:00.000Z",
|
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,
|
error: null,
|
||||||
fetchedAt: "2026-02-12T00:00:00.000Z",
|
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);
|
expect(mock.sent).toHaveLength(1);
|
||||||
|
|
||||||
const request = JSON.parse(mock.sent[0]) as {
|
const request = parseSentFrame(mock.sent[0]);
|
||||||
type: "session";
|
expect(request.type).toBe("list_commands_request");
|
||||||
message: {
|
expect(request.agentId).toBe("__new_agent__");
|
||||||
type: "list_commands_request";
|
expect(request.draftConfig).toEqual({
|
||||||
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({
|
|
||||||
provider: "codex",
|
provider: "codex",
|
||||||
cwd: "/tmp/project",
|
cwd: "/tmp/project",
|
||||||
modeId: "bypassPermissions",
|
modeId: "bypassPermissions",
|
||||||
@@ -2244,7 +2079,7 @@ test("lists commands with draft config via RPC", async () => {
|
|||||||
agentId: "__new_agent__",
|
agentId: "__new_agent__",
|
||||||
commands: [{ name: "help", description: "Show help", argumentHint: "" }],
|
commands: [{ name: "help", description: "Show help", argumentHint: "" }],
|
||||||
error: null,
|
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__",
|
agentId: "__new_agent__",
|
||||||
commands: [{ name: "help", description: "Show help", argumentHint: "" }],
|
commands: [{ name: "help", description: "Show help", argumentHint: "" }],
|
||||||
error: null,
|
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");
|
const promise = client.listCommands("agent-1", "req-legacy");
|
||||||
expect(mock.sent).toHaveLength(1);
|
expect(mock.sent).toHaveLength(1);
|
||||||
|
|
||||||
const request = JSON.parse(mock.sent[0]) as {
|
const request = parseSentFrame(mock.sent[0]);
|
||||||
type: "session";
|
expect(request.type).toBe("list_commands_request");
|
||||||
message: {
|
expect(request.agentId).toBe("agent-1");
|
||||||
type: "list_commands_request";
|
expect(request.requestId).toBe("req-legacy");
|
||||||
agentId: string;
|
expect(request.draftConfig).toBeUndefined();
|
||||||
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();
|
|
||||||
|
|
||||||
mock.triggerMessage(
|
mock.triggerMessage(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
@@ -2688,25 +2515,17 @@ test("parses canonical agent_stream tool_call payloads without crashing", async
|
|||||||
unsubscribe();
|
unsubscribe();
|
||||||
|
|
||||||
expect(received).toHaveLength(1);
|
expect(received).toHaveLength(1);
|
||||||
const streamMsg = received[0] as {
|
expect(received[0]).toMatchObject({
|
||||||
payload: {
|
payload: {
|
||||||
event: {
|
event: {
|
||||||
type: "timeline";
|
|
||||||
item: {
|
item: {
|
||||||
type: "tool_call";
|
status: "running",
|
||||||
status: string;
|
error: null,
|
||||||
error: unknown;
|
detail: { type: "shell" },
|
||||||
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");
|
|
||||||
expect(logger.warn).not.toHaveBeenCalled();
|
expect(logger.warn).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -2832,28 +2651,20 @@ test("parses canonical fetch_agent_timeline_response payloads without crashing",
|
|||||||
unsubscribe();
|
unsubscribe();
|
||||||
|
|
||||||
expect(received).toHaveLength(1);
|
expect(received).toHaveLength(1);
|
||||||
const timelineMsg = received[0] as {
|
expect(received[0]).toMatchObject({
|
||||||
payload: {
|
payload: {
|
||||||
entries: Array<{
|
entries: [
|
||||||
item: {
|
{
|
||||||
type: "tool_call";
|
item: {
|
||||||
status: string;
|
type: "tool_call",
|
||||||
error: unknown;
|
status: "running",
|
||||||
detail: {
|
error: null,
|
||||||
type: string;
|
detail: { type: "shell" },
|
||||||
};
|
},
|
||||||
};
|
},
|
||||||
}>;
|
],
|
||||||
};
|
},
|
||||||
};
|
});
|
||||||
|
|
||||||
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");
|
|
||||||
}
|
|
||||||
expect(logger.warn).not.toHaveBeenCalled();
|
expect(logger.warn).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -2950,14 +2761,14 @@ test("sends subscribe/unsubscribe terminals messages", async () => {
|
|||||||
client.unsubscribeTerminals({ cwd: "/tmp/project" });
|
client.unsubscribeTerminals({ cwd: "/tmp/project" });
|
||||||
|
|
||||||
expect(mock.sent).toHaveLength(2);
|
expect(mock.sent).toHaveLength(2);
|
||||||
expect(JSON.parse(String(mock.sent[0]))).toEqual({
|
expect(JSON.parse(assertStr(mock.sent[0]))).toEqual({
|
||||||
type: "session",
|
type: "session",
|
||||||
message: {
|
message: {
|
||||||
type: "subscribe_terminals_request",
|
type: "subscribe_terminals_request",
|
||||||
cwd: "/tmp/project",
|
cwd: "/tmp/project",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(JSON.parse(String(mock.sent[1]))).toEqual({
|
expect(JSON.parse(assertStr(mock.sent[1]))).toEqual({
|
||||||
type: "session",
|
type: "session",
|
||||||
message: {
|
message: {
|
||||||
type: "unsubscribe_terminals_request",
|
type: "unsubscribe_terminals_request",
|
||||||
@@ -3041,7 +2852,7 @@ test("sends close_items_request and resolves close_items_response", async () =>
|
|||||||
"req-close-items",
|
"req-close-items",
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(JSON.parse(String(mock.sent[0]))).toEqual({
|
expect(JSON.parse(assertStr(mock.sent[0]))).toEqual({
|
||||||
type: "session",
|
type: "session",
|
||||||
message: {
|
message: {
|
||||||
type: "close_items_request",
|
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);
|
const waitPromise = client.waitForFinish("agent-wait-zero-timeout", 0);
|
||||||
|
|
||||||
expect(mock.sent).toHaveLength(1);
|
expect(mock.sent).toHaveLength(1);
|
||||||
const request = JSON.parse(String(mock.sent[0])) as {
|
const request = parseSentFrame(mock.sent[0]);
|
||||||
type: "session";
|
expect(request.type).toBe("wait_for_finish_request");
|
||||||
message: {
|
expect(request.agentId).toBe("agent-wait-zero-timeout");
|
||||||
type: "wait_for_finish_request";
|
expect(request).not.toHaveProperty("timeoutMs");
|
||||||
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 settled = vi.fn();
|
const settled = vi.fn();
|
||||||
void waitPromise.then(
|
void waitPromise.then(
|
||||||
@@ -3117,7 +2920,7 @@ test("waitForFinish with timeout=0 omits timeoutMs and has no client deadline",
|
|||||||
wrapSessionMessage({
|
wrapSessionMessage({
|
||||||
type: "wait_for_finish_response",
|
type: "wait_for_finish_response",
|
||||||
payload: {
|
payload: {
|
||||||
requestId: request.message.requestId,
|
requestId: request.requestId,
|
||||||
status: "idle",
|
status: "idle",
|
||||||
final: null,
|
final: null,
|
||||||
error: null,
|
error: null,
|
||||||
|
|||||||
@@ -181,7 +181,8 @@ function connectClient(
|
|||||||
function readAttentionRequiredMessage(ws: ReturnType<typeof createOpenSocket>) {
|
function readAttentionRequiredMessage(ws: ReturnType<typeof createOpenSocket>) {
|
||||||
const rawMessage = ws.send.mock.calls[0]?.[0];
|
const rawMessage = ws.send.mock.calls[0]?.[0];
|
||||||
expect(typeof rawMessage).toBe("string");
|
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.type).toBe("session");
|
||||||
expect(message.message.type).toBe("agent_stream");
|
expect(message.message.type).toBe("agent_stream");
|
||||||
expect(message.message.payload.event.type).toBe("attention_required");
|
expect(message.message.payload.event.type).toBe("attention_required");
|
||||||
|
|||||||
@@ -96,6 +96,7 @@ vi.mock("./push/push-service.js", () => ({
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
import { z } from "zod";
|
||||||
import { VoiceAssistantWebSocketServer } from "./websocket-server";
|
import { VoiceAssistantWebSocketServer } from "./websocket-server";
|
||||||
import { parseServerInfoStatusPayload } from "./messages.js";
|
import { parseServerInfoStatusPayload } from "./messages.js";
|
||||||
import type { SpeechReadinessSnapshot } from "./speech/speech-runtime.js";
|
import type { SpeechReadinessSnapshot } from "./speech/speech-runtime.js";
|
||||||
@@ -106,6 +107,27 @@ interface WebSocketServerInternals {
|
|||||||
|
|
||||||
const TEST_DAEMON_VERSION = "1.2.3-test";
|
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<typeof WireEnvelopeSchema> {
|
||||||
|
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 {
|
class MockSocket {
|
||||||
readyState = 1;
|
readyState = 1;
|
||||||
bufferedAmount = 0;
|
bufferedAmount = 0;
|
||||||
@@ -331,10 +353,7 @@ async function attachRelayAndHello(params: {
|
|||||||
params.socket.emit("message", JSON.stringify(createHelloMessage(params.clientId)));
|
params.socket.emit("message", JSON.stringify(createHelloMessage(params.clientId)));
|
||||||
await Promise.resolve();
|
await Promise.resolve();
|
||||||
expect(params.socket.sent.length).toBeGreaterThan(0);
|
expect(params.socket.sent.length).toBeGreaterThan(0);
|
||||||
const envelope = JSON.parse(params.socket.sent[0] as string) as {
|
const envelope = parseSentEnvelope(params.socket.sent[0]);
|
||||||
type?: unknown;
|
|
||||||
message?: { type?: unknown; payload?: unknown };
|
|
||||||
};
|
|
||||||
expect(envelope.type).toBe("session");
|
expect(envelope.type).toBe("session");
|
||||||
const serverInfo = parseServerInfoStatusPayload(envelope.message?.payload);
|
const serverInfo = parseServerInfoStatusPayload(envelope.message?.payload);
|
||||||
expect(envelope.message?.type).toBe("status");
|
expect(envelope.message?.type).toBe("status");
|
||||||
@@ -354,10 +373,7 @@ async function attachDirectAndHello(params: {
|
|||||||
params.socket.emit("message", JSON.stringify(createHelloMessage(params.clientId)));
|
params.socket.emit("message", JSON.stringify(createHelloMessage(params.clientId)));
|
||||||
await Promise.resolve();
|
await Promise.resolve();
|
||||||
expect(params.socket.sent.length).toBeGreaterThan(0);
|
expect(params.socket.sent.length).toBeGreaterThan(0);
|
||||||
const envelope = JSON.parse(params.socket.sent[0] as string) as {
|
const envelope = parseSentEnvelope(params.socket.sent[0]);
|
||||||
type?: unknown;
|
|
||||||
message?: { type?: unknown; payload?: unknown };
|
|
||||||
};
|
|
||||||
expect(envelope.type).toBe("session");
|
expect(envelope.type).toBe("session");
|
||||||
const serverInfo = parseServerInfoStatusPayload(envelope.message?.payload);
|
const serverInfo = parseServerInfoStatusPayload(envelope.message?.payload);
|
||||||
expect(envelope.message?.type).toBe("status");
|
expect(envelope.message?.type).toBe("status");
|
||||||
@@ -441,7 +457,7 @@ describe("relay external socket reconnect behavior", () => {
|
|||||||
let closeReason = "";
|
let closeReason = "";
|
||||||
socket.on("close", (code: unknown, reason: unknown) => {
|
socket.on("close", (code: unknown, reason: unknown) => {
|
||||||
closeCode = typeof code === "number" ? code : null;
|
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(
|
await (server as unknown as WebSocketServerInternals).attachSocket(
|
||||||
@@ -509,7 +525,7 @@ describe("relay external socket reconnect behavior", () => {
|
|||||||
let closeReason = "";
|
let closeReason = "";
|
||||||
socket.on("close", (code: unknown, reason: unknown) => {
|
socket.on("close", (code: unknown, reason: unknown) => {
|
||||||
closeCode = typeof code === "number" ? code : null;
|
closeCode = typeof code === "number" ? code : null;
|
||||||
closeReason = typeof reason === "string" ? reason : String(reason ?? "");
|
closeReason = typeof reason === "string" ? reason : "";
|
||||||
});
|
});
|
||||||
|
|
||||||
await server.attachExternalSocket(socket, { transport: "relay" });
|
await server.attachExternalSocket(socket, { transport: "relay" });
|
||||||
@@ -583,14 +599,14 @@ describe("relay external socket reconnect behavior", () => {
|
|||||||
});
|
});
|
||||||
expect(sessionMock.instances).toHaveLength(1);
|
expect(sessionMock.instances).toHaveLength(1);
|
||||||
|
|
||||||
const onMessage = session.args.onMessage as
|
const { onMessage } = session.args;
|
||||||
| ((msg: { type: "status"; payload: { status: string } }) => void)
|
|
||||||
| undefined;
|
|
||||||
expect(onMessage).toBeTypeOf("function");
|
expect(onMessage).toBeTypeOf("function");
|
||||||
onMessage?.({
|
if (typeof onMessage === "function") {
|
||||||
type: "status",
|
onMessage({
|
||||||
payload: { status: "ok" },
|
type: "status",
|
||||||
});
|
payload: { status: "ok" },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
expect(directSocket.sent.length).toBeGreaterThan(0);
|
expect(directSocket.sent.length).toBeGreaterThan(0);
|
||||||
expect(relaySocket.sent.length).toBeGreaterThan(0);
|
expect(relaySocket.sent.length).toBeGreaterThan(0);
|
||||||
@@ -672,9 +688,7 @@ describe("relay external socket reconnect behavior", () => {
|
|||||||
server.publishSpeechReadiness(speechReadiness);
|
server.publishSpeechReadiness(speechReadiness);
|
||||||
expect(socket.sent).toHaveLength(2);
|
expect(socket.sent).toHaveLength(2);
|
||||||
|
|
||||||
const secondEnvelope = JSON.parse(socket.sent[1] as string) as {
|
const secondEnvelope = parseSentEnvelope(socket.sent[1]);
|
||||||
message?: { payload?: unknown };
|
|
||||||
};
|
|
||||||
const secondPayload = parseServerInfoStatusPayload(secondEnvelope.message?.payload);
|
const secondPayload = parseServerInfoStatusPayload(secondEnvelope.message?.payload);
|
||||||
expect(secondPayload?.capabilities?.voice?.dictation.enabled).toBe(true);
|
expect(secondPayload?.capabilities?.voice?.dictation.enabled).toBe(true);
|
||||||
expect(secondPayload?.capabilities?.voice?.voice.enabled).toBe(true);
|
expect(secondPayload?.capabilities?.voice?.voice.enabled).toBe(true);
|
||||||
@@ -699,9 +713,7 @@ describe("relay external socket reconnect behavior", () => {
|
|||||||
server.publishSpeechReadiness(createDownloadInProgressSpeechReadinessSnapshot());
|
server.publishSpeechReadiness(createDownloadInProgressSpeechReadinessSnapshot());
|
||||||
expect(socket.sent).toHaveLength(2);
|
expect(socket.sent).toHaveLength(2);
|
||||||
|
|
||||||
const envelope = JSON.parse(socket.sent[1] as string) as {
|
const envelope = parseSentEnvelope(socket.sent[1]);
|
||||||
message?: { payload?: unknown };
|
|
||||||
};
|
|
||||||
const payload = parseServerInfoStatusPayload(envelope.message?.payload);
|
const payload = parseServerInfoStatusPayload(envelope.message?.payload);
|
||||||
expect(payload?.capabilities?.voice?.dictation.enabled).toBe(true);
|
expect(payload?.capabilities?.voice?.dictation.enabled).toBe(true);
|
||||||
expect(payload?.capabilities?.voice?.voice.enabled).toBe(true);
|
expect(payload?.capabilities?.voice?.voice.enabled).toBe(true);
|
||||||
@@ -736,11 +748,7 @@ describe("relay external socket reconnect behavior", () => {
|
|||||||
await Promise.resolve();
|
await Promise.resolve();
|
||||||
|
|
||||||
expect(session.handleBinaryFrame).toHaveBeenCalledTimes(1);
|
expect(session.handleBinaryFrame).toHaveBeenCalledTimes(1);
|
||||||
const frame = session.handleBinaryFrame.mock.calls[0]?.[0] as {
|
const frame = BinaryFrameSchema.parse(session.handleBinaryFrame.mock.calls[0]?.[0]);
|
||||||
opcode: number;
|
|
||||||
slot: number;
|
|
||||||
payload: Uint8Array;
|
|
||||||
};
|
|
||||||
expect(frame.opcode).toBe(TerminalStreamOpcode.Input);
|
expect(frame.opcode).toBe(TerminalStreamOpcode.Input);
|
||||||
expect(frame.slot).toBe(9);
|
expect(frame.slot).toBe(9);
|
||||||
expect(new TextDecoder().decode(frame.payload)).toBe("ls\r");
|
expect(new TextDecoder().decode(frame.payload)).toBe("ls\r");
|
||||||
@@ -760,12 +768,11 @@ describe("relay external socket reconnect behavior", () => {
|
|||||||
expect(sessionMock.instances).toHaveLength(1);
|
expect(sessionMock.instances).toHaveLength(1);
|
||||||
const session = sessionMock.instances[0];
|
const session = sessionMock.instances[0];
|
||||||
|
|
||||||
const onBinaryMessage = session.args.onBinaryMessage as
|
const { onBinaryMessage } = session.args;
|
||||||
| ((frame: Uint8Array) => void)
|
|
||||||
| undefined;
|
|
||||||
expect(onBinaryMessage).toBeTypeOf("function");
|
expect(onBinaryMessage).toBeTypeOf("function");
|
||||||
|
if (typeof onBinaryMessage === "function") {
|
||||||
onBinaryMessage?.(new Uint8Array([TerminalStreamOpcode.Output, 12, 0x6f, 0x6b]));
|
onBinaryMessage(new Uint8Array([TerminalStreamOpcode.Output, 12, 0x6f, 0x6b]));
|
||||||
|
}
|
||||||
|
|
||||||
expect(socket.sent).toHaveLength(2);
|
expect(socket.sent).toHaveLength(2);
|
||||||
const binaryPayload = asUint8Array(socket.sent[1]);
|
const binaryPayload = asUint8Array(socket.sent[1]);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { describe, expect, it, vi } from "vitest";
|
import { describe, expect, it, vi } from "vitest";
|
||||||
|
import { z } from "zod";
|
||||||
import type { Server as HTTPServer } from "http";
|
import type { Server as HTTPServer } from "http";
|
||||||
import type pino from "pino";
|
import type pino from "pino";
|
||||||
import type { AgentManager } from "./agent/agent-manager.js";
|
import type { AgentManager } from "./agent/agent-manager.js";
|
||||||
@@ -42,17 +43,19 @@ interface WebSocketServerInternals {
|
|||||||
sessions: Map<unknown, unknown>;
|
sessions: Map<unknown, unknown>;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface RuntimeMetricsLog {
|
const RuntimeMetricsLogSchema = z.object({
|
||||||
outboundMessageTypesTop: Array<[string, number]>;
|
outboundMessageTypesTop: z.array(z.tuple([z.string(), z.number()])),
|
||||||
outboundSessionMessageTypesTop: Array<[string, number]>;
|
outboundSessionMessageTypesTop: z.array(z.tuple([z.string(), z.number()])),
|
||||||
outboundAgentStreamTypesTop: Array<[string, number]>;
|
outboundAgentStreamTypesTop: z.array(z.tuple([z.string(), z.number()])),
|
||||||
outboundAgentStreamAgentsTop: Array<[string, number]>;
|
outboundAgentStreamAgentsTop: z.array(z.tuple([z.string(), z.number()])),
|
||||||
outboundBinaryFrameTypesTop: Array<[string, number]>;
|
outboundBinaryFrameTypesTop: z.array(z.tuple([z.string(), z.number()])),
|
||||||
bufferedAmount: {
|
bufferedAmount: z.object({
|
||||||
p95: number;
|
p95: z.number(),
|
||||||
max: number;
|
max: z.number(),
|
||||||
};
|
}),
|
||||||
}
|
});
|
||||||
|
|
||||||
|
type RuntimeMetricsLog = z.infer<typeof RuntimeMetricsLogSchema>;
|
||||||
|
|
||||||
interface TestSocket {
|
interface TestSocket {
|
||||||
readyState: number;
|
readyState: number;
|
||||||
@@ -155,7 +158,7 @@ function flushRuntimeMetrics(server: VoiceAssistantWebSocketServer): void {
|
|||||||
function getRuntimeMetricsLog(logger: ReturnType<typeof createLogger>): RuntimeMetricsLog {
|
function getRuntimeMetricsLog(logger: ReturnType<typeof createLogger>): RuntimeMetricsLog {
|
||||||
const metricsCall = logger.info.mock.calls.find((call) => call[1] === "ws_runtime_metrics");
|
const metricsCall = logger.info.mock.calls.find((call) => call[1] === "ws_runtime_metrics");
|
||||||
expect(metricsCall).toBeDefined();
|
expect(metricsCall).toBeDefined();
|
||||||
return metricsCall![0] as RuntimeMetricsLog;
|
return RuntimeMetricsLogSchema.parse(metricsCall![0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendToClient(
|
function sendToClient(
|
||||||
@@ -350,11 +353,11 @@ describe("VoiceAssistantWebSocketServer runtime metrics", () => {
|
|||||||
const logger = createLogger();
|
const logger = createLogger();
|
||||||
const server = createServer(logger);
|
const server = createServer(logger);
|
||||||
const socket = createSocket([12, 24]);
|
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) {
|
get(_target, property) {
|
||||||
throw new Error(`Binary frame payload was unexpectedly accessed via ${String(property)}`);
|
throw new Error(`Binary frame payload was unexpectedly accessed via ${String(property)}`);
|
||||||
},
|
},
|
||||||
}) as unknown as Uint8Array;
|
});
|
||||||
|
|
||||||
expect(() => sendBinaryToClient(server, socket, frame)).not.toThrow();
|
expect(() => sendBinaryToClient(server, socket, frame)).not.toThrow();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user