huskies: merge 950

This commit is contained in:
dave
2026-05-13 08:41:57 +00:00
parent 7491eec257
commit 4a8ed4348b
38 changed files with 354 additions and 4329 deletions
+37 -106
View File
@@ -3,27 +3,14 @@ import type { AgentConfigInfo, AgentEvent, AgentInfo } from "./agents";
import { agentsApi, subscribeAgentStream } from "./agents";
import { installRpcMock } from "./__test_utils__/mockRpcWebSocket";
const mockFetch = vi.fn();
beforeEach(() => {
vi.stubGlobal("fetch", mockFetch);
vi.stubGlobal("fetch", vi.fn());
});
afterEach(() => {
vi.restoreAllMocks();
});
function okResponse(body: unknown) {
return new Response(JSON.stringify(body), {
status: 200,
headers: { "Content-Type": "application/json" },
});
}
function errorResponse(status: number, text: string) {
return new Response(text, { status });
}
const sampleAgent: AgentInfo = {
story_id: "42_story_test",
agent_name: "coder",
@@ -48,89 +35,51 @@ const sampleConfig: AgentConfigInfo = {
describe("agentsApi", () => {
describe("startAgent", () => {
it("sends POST to /agents/start with story_id", async () => {
mockFetch.mockResolvedValueOnce(okResponse(sampleAgent));
it("dispatches agents.start RPC with story_id and returns AgentInfo", async () => {
const rpc = installRpcMock();
rpc.respond("agents.start", sampleAgent);
const result = await agentsApi.startAgent("42_story_test");
expect(mockFetch).toHaveBeenCalledWith(
"/api/agents/start",
expect.objectContaining({
method: "POST",
body: JSON.stringify({
story_id: "42_story_test",
agent_name: undefined,
}),
}),
);
expect(rpc.calls).toEqual([
{
method: "agents.start",
params: { story_id: "42_story_test", agent_name: undefined },
},
]);
expect(result).toEqual(sampleAgent);
});
it("sends POST with optional agent_name", async () => {
mockFetch.mockResolvedValueOnce(okResponse(sampleAgent));
it("sends optional agent_name in params", async () => {
const rpc = installRpcMock();
rpc.respond("agents.start", sampleAgent);
await agentsApi.startAgent("42_story_test", "coder");
expect(mockFetch).toHaveBeenCalledWith(
"/api/agents/start",
expect.objectContaining({
body: JSON.stringify({
story_id: "42_story_test",
agent_name: "coder",
}),
}),
);
});
it("uses custom baseUrl when provided", async () => {
mockFetch.mockResolvedValueOnce(okResponse(sampleAgent));
await agentsApi.startAgent(
"42_story_test",
undefined,
"http://localhost:3002/api",
);
expect(mockFetch).toHaveBeenCalledWith(
"http://localhost:3002/api/agents/start",
expect.objectContaining({ method: "POST" }),
);
expect(rpc.calls).toEqual([
{
method: "agents.start",
params: { story_id: "42_story_test", agent_name: "coder" },
},
]);
});
});
describe("stopAgent", () => {
it("sends POST to /agents/stop with story_id and agent_name", async () => {
mockFetch.mockResolvedValueOnce(okResponse(true));
it("dispatches agents.stop RPC with story_id and agent_name", async () => {
const rpc = installRpcMock();
rpc.respond("agents.stop", true);
const result = await agentsApi.stopAgent("42_story_test", "coder");
expect(mockFetch).toHaveBeenCalledWith(
"/api/agents/stop",
expect.objectContaining({
method: "POST",
body: JSON.stringify({
story_id: "42_story_test",
agent_name: "coder",
}),
}),
);
expect(rpc.calls).toEqual([
{
method: "agents.stop",
params: { story_id: "42_story_test", agent_name: "coder" },
},
]);
expect(result).toBe(true);
});
it("uses custom baseUrl when provided", async () => {
mockFetch.mockResolvedValueOnce(okResponse(false));
await agentsApi.stopAgent(
"42_story_test",
"coder",
"http://localhost:3002/api",
);
expect(mockFetch).toHaveBeenCalledWith(
"http://localhost:3002/api/agents/stop",
expect.objectContaining({ method: "POST" }),
);
});
});
describe("getAgentConfig", () => {
@@ -157,46 +106,28 @@ describe("agentsApi", () => {
});
describe("reloadConfig", () => {
it("sends POST to /agents/config/reload", async () => {
mockFetch.mockResolvedValueOnce(okResponse([sampleConfig]));
it("dispatches agent_config.list RPC and returns the config list", async () => {
const rpc = installRpcMock();
rpc.respond("agent_config.list", [sampleConfig]);
const result = await agentsApi.reloadConfig();
expect(mockFetch).toHaveBeenCalledWith(
"/api/agents/config/reload",
expect.objectContaining({ method: "POST" }),
);
expect(rpc.calls).toEqual([
{ method: "agent_config.list", params: {} },
]);
expect(result).toEqual([sampleConfig]);
});
it("uses custom baseUrl when provided", async () => {
mockFetch.mockResolvedValueOnce(okResponse([]));
await agentsApi.reloadConfig("http://localhost:3002/api");
expect(mockFetch).toHaveBeenCalledWith(
"http://localhost:3002/api/agents/config/reload",
expect.objectContaining({ method: "POST" }),
);
});
});
describe("error handling", () => {
it("throws on non-ok HTTP response from startAgent", async () => {
mockFetch.mockResolvedValueOnce(errorResponse(404, "story not found"));
it("surfaces RPC errors from startAgent", async () => {
const rpc = installRpcMock();
rpc.respondError("agents.start", "story not found", "NOT_FOUND");
await expect(agentsApi.startAgent("missing_story")).rejects.toThrow(
"story not found",
);
});
it("throws with status code from startAgent when body is empty", async () => {
mockFetch.mockResolvedValueOnce(errorResponse(500, ""));
await expect(agentsApi.startAgent("missing_story")).rejects.toThrow(
"Request failed (500)",
);
});
});
});