refactor: split gateway server helpers and tests
This commit is contained in:
262
src/gateway/server.models-voicewake.test.ts
Normal file
262
src/gateway/server.models-voicewake.test.ts
Normal file
@@ -0,0 +1,262 @@
|
||||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { describe, expect, test } from "vitest";
|
||||
import {
|
||||
bridgeListConnected,
|
||||
bridgeSendEvent,
|
||||
bridgeStartCalls,
|
||||
connectOk,
|
||||
installGatewayTestHooks,
|
||||
onceMessage,
|
||||
piSdkMock,
|
||||
rpcReq,
|
||||
startServerWithClient,
|
||||
} from "./test-helpers.js";
|
||||
|
||||
installGatewayTestHooks();
|
||||
|
||||
describe("gateway server models + voicewake", () => {
|
||||
test(
|
||||
"voicewake.get returns defaults and voicewake.set broadcasts",
|
||||
{ timeout: 15_000 },
|
||||
async () => {
|
||||
const homeDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdis-home-"));
|
||||
const prevHome = process.env.HOME;
|
||||
process.env.HOME = homeDir;
|
||||
|
||||
const { server, ws } = await startServerWithClient();
|
||||
await connectOk(ws);
|
||||
|
||||
const initial = await rpcReq<{ triggers: string[] }>(ws, "voicewake.get");
|
||||
expect(initial.ok).toBe(true);
|
||||
expect(initial.payload?.triggers).toEqual([
|
||||
"clawd",
|
||||
"claude",
|
||||
"computer",
|
||||
]);
|
||||
|
||||
const changedP = onceMessage<{
|
||||
type: "event";
|
||||
event: string;
|
||||
payload?: unknown;
|
||||
}>(ws, (o) => o.type === "event" && o.event === "voicewake.changed");
|
||||
|
||||
const setRes = await rpcReq<{ triggers: string[] }>(ws, "voicewake.set", {
|
||||
triggers: [" hi ", "", "there"],
|
||||
});
|
||||
expect(setRes.ok).toBe(true);
|
||||
expect(setRes.payload?.triggers).toEqual(["hi", "there"]);
|
||||
|
||||
const changed = await changedP;
|
||||
expect(changed.event).toBe("voicewake.changed");
|
||||
expect(
|
||||
(changed.payload as { triggers?: unknown } | undefined)?.triggers,
|
||||
).toEqual(["hi", "there"]);
|
||||
|
||||
const after = await rpcReq<{ triggers: string[] }>(ws, "voicewake.get");
|
||||
expect(after.ok).toBe(true);
|
||||
expect(after.payload?.triggers).toEqual(["hi", "there"]);
|
||||
|
||||
const onDisk = JSON.parse(
|
||||
await fs.readFile(
|
||||
path.join(homeDir, ".clawdis", "settings", "voicewake.json"),
|
||||
"utf8",
|
||||
),
|
||||
) as { triggers?: unknown; updatedAtMs?: unknown };
|
||||
expect(onDisk.triggers).toEqual(["hi", "there"]);
|
||||
expect(typeof onDisk.updatedAtMs).toBe("number");
|
||||
|
||||
ws.close();
|
||||
await server.close();
|
||||
|
||||
if (prevHome === undefined) {
|
||||
delete process.env.HOME;
|
||||
} else {
|
||||
process.env.HOME = prevHome;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
test("pushes voicewake.changed to nodes on connect and on updates", async () => {
|
||||
const homeDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdis-home-"));
|
||||
const prevHome = process.env.HOME;
|
||||
process.env.HOME = homeDir;
|
||||
|
||||
bridgeSendEvent.mockClear();
|
||||
bridgeListConnected.mockReturnValue([{ nodeId: "n1" }]);
|
||||
|
||||
const { server, ws } = await startServerWithClient();
|
||||
await connectOk(ws);
|
||||
|
||||
const startCall = bridgeStartCalls.at(-1);
|
||||
expect(startCall).toBeTruthy();
|
||||
|
||||
await startCall?.onAuthenticated?.({ nodeId: "n1" });
|
||||
|
||||
const first = bridgeSendEvent.mock.calls.find(
|
||||
(c) => c[0]?.event === "voicewake.changed" && c[0]?.nodeId === "n1",
|
||||
)?.[0] as { payloadJSON?: string | null } | undefined;
|
||||
expect(first?.payloadJSON).toBeTruthy();
|
||||
const firstPayload = JSON.parse(String(first?.payloadJSON)) as {
|
||||
triggers?: unknown;
|
||||
};
|
||||
expect(firstPayload.triggers).toEqual(["clawd", "claude", "computer"]);
|
||||
|
||||
bridgeSendEvent.mockClear();
|
||||
|
||||
const setRes = await rpcReq<{ triggers: string[] }>(ws, "voicewake.set", {
|
||||
triggers: ["clawd", "computer"],
|
||||
});
|
||||
expect(setRes.ok).toBe(true);
|
||||
|
||||
const broadcast = bridgeSendEvent.mock.calls.find(
|
||||
(c) => c[0]?.event === "voicewake.changed" && c[0]?.nodeId === "n1",
|
||||
)?.[0] as { payloadJSON?: string | null } | undefined;
|
||||
expect(broadcast?.payloadJSON).toBeTruthy();
|
||||
const broadcastPayload = JSON.parse(String(broadcast?.payloadJSON)) as {
|
||||
triggers?: unknown;
|
||||
};
|
||||
expect(broadcastPayload.triggers).toEqual(["clawd", "computer"]);
|
||||
|
||||
ws.close();
|
||||
await server.close();
|
||||
|
||||
if (prevHome === undefined) {
|
||||
delete process.env.HOME;
|
||||
} else {
|
||||
process.env.HOME = prevHome;
|
||||
}
|
||||
});
|
||||
|
||||
test("models.list returns model catalog", async () => {
|
||||
piSdkMock.enabled = true;
|
||||
piSdkMock.models = [
|
||||
{ id: "gpt-test-z", provider: "openai", contextWindow: 0 },
|
||||
{
|
||||
id: "gpt-test-a",
|
||||
name: "A-Model",
|
||||
provider: "openai",
|
||||
contextWindow: 8000,
|
||||
},
|
||||
{
|
||||
id: "claude-test-b",
|
||||
name: "B-Model",
|
||||
provider: "anthropic",
|
||||
contextWindow: 1000,
|
||||
},
|
||||
{
|
||||
id: "claude-test-a",
|
||||
name: "A-Model",
|
||||
provider: "anthropic",
|
||||
contextWindow: 200_000,
|
||||
},
|
||||
];
|
||||
|
||||
const { server, ws } = await startServerWithClient();
|
||||
await connectOk(ws);
|
||||
|
||||
const res1 = await rpcReq<{
|
||||
models: Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
provider: string;
|
||||
contextWindow?: number;
|
||||
}>;
|
||||
}>(ws, "models.list");
|
||||
|
||||
const res2 = await rpcReq<{
|
||||
models: Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
provider: string;
|
||||
contextWindow?: number;
|
||||
}>;
|
||||
}>(ws, "models.list");
|
||||
|
||||
expect(res1.ok).toBe(true);
|
||||
expect(res2.ok).toBe(true);
|
||||
|
||||
const models = res1.payload?.models ?? [];
|
||||
expect(models).toEqual([
|
||||
{
|
||||
id: "claude-test-a",
|
||||
name: "A-Model",
|
||||
provider: "anthropic",
|
||||
contextWindow: 200_000,
|
||||
},
|
||||
{
|
||||
id: "claude-test-b",
|
||||
name: "B-Model",
|
||||
provider: "anthropic",
|
||||
contextWindow: 1000,
|
||||
},
|
||||
{
|
||||
id: "gpt-test-a",
|
||||
name: "A-Model",
|
||||
provider: "openai",
|
||||
contextWindow: 8000,
|
||||
},
|
||||
{
|
||||
id: "gpt-test-z",
|
||||
name: "gpt-test-z",
|
||||
provider: "openai",
|
||||
},
|
||||
]);
|
||||
|
||||
expect(piSdkMock.discoverCalls).toBe(1);
|
||||
|
||||
ws.close();
|
||||
await server.close();
|
||||
});
|
||||
|
||||
test("models.list rejects unknown params", async () => {
|
||||
piSdkMock.enabled = true;
|
||||
piSdkMock.models = [{ id: "gpt-test-a", name: "A", provider: "openai" }];
|
||||
|
||||
const { server, ws } = await startServerWithClient();
|
||||
await connectOk(ws);
|
||||
|
||||
const res = await rpcReq(ws, "models.list", { extra: true });
|
||||
expect(res.ok).toBe(false);
|
||||
expect(res.error?.message ?? "").toMatch(/invalid models\.list params/i);
|
||||
|
||||
ws.close();
|
||||
await server.close();
|
||||
});
|
||||
|
||||
test("bridge RPC supports models.list and validates params", async () => {
|
||||
piSdkMock.enabled = true;
|
||||
piSdkMock.models = [{ id: "gpt-test-a", name: "A", provider: "openai" }];
|
||||
|
||||
const { server, ws } = await startServerWithClient();
|
||||
await connectOk(ws);
|
||||
|
||||
const startCall = bridgeStartCalls.at(-1);
|
||||
expect(startCall).toBeTruthy();
|
||||
|
||||
const okRes = await startCall?.onRequest?.("n1", {
|
||||
id: "1",
|
||||
method: "models.list",
|
||||
paramsJSON: "{}",
|
||||
});
|
||||
expect(okRes?.ok).toBe(true);
|
||||
const okPayload = JSON.parse(String(okRes?.payloadJSON ?? "{}")) as {
|
||||
models?: unknown;
|
||||
};
|
||||
expect(Array.isArray(okPayload.models)).toBe(true);
|
||||
|
||||
const badRes = await startCall?.onRequest?.("n1", {
|
||||
id: "2",
|
||||
method: "models.list",
|
||||
paramsJSON: JSON.stringify({ extra: true }),
|
||||
});
|
||||
expect(badRes?.ok).toBe(false);
|
||||
expect(badRes && "error" in badRes ? badRes.error.code : "").toBe(
|
||||
"INVALID_REQUEST",
|
||||
);
|
||||
|
||||
ws.close();
|
||||
await server.close();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user