test: tighten small tool assertions

This commit is contained in:
Peter Steinberger
2026-05-09 16:16:43 +01:00
parent 3f0c0ce4f4
commit 466dba3df2
3 changed files with 82 additions and 74 deletions

View File

@@ -3,6 +3,18 @@ import type { OpenClawConfig } from "../../config/types.openclaw.js";
const loadConfigMock = vi.fn<() => OpenClawConfig>();
type AgentListDetails = {
requester?: string;
allowAny?: boolean;
agents?: Array<{
id?: string;
name?: string;
configured?: boolean;
model?: string;
agentRuntime?: { id?: string; source?: string };
}>;
};
vi.mock("../../config/config.js", async () => {
const actual =
await vi.importActual<typeof import("../../config/config.js")>("../../config/config.js");
@@ -46,19 +58,16 @@ describe("agents_list tool", () => {
"call",
{},
);
const details = result.details as AgentListDetails;
expect(result.details).toMatchObject({
requester: "main",
agents: [
{
id: "codex",
name: "Codex",
configured: true,
model: "openai/gpt-5.5",
agentRuntime: { id: "codex", source: "model" },
},
],
});
expect(details.requester).toBe("main");
expect(details.agents).toHaveLength(1);
expect(details.agents?.[0]?.id).toBe("codex");
expect(details.agents?.[0]?.name).toBe("Codex");
expect(details.agents?.[0]?.configured).toBe(true);
expect(details.agents?.[0]?.model).toBe("openai/gpt-5.5");
expect(details.agents?.[0]?.agentRuntime?.id).toBe("codex");
expect(details.agents?.[0]?.agentRuntime?.source).toBe("model");
});
it("returns requester as the only target when no subagent allowlist is configured", async () => {
@@ -73,17 +82,13 @@ describe("agents_list tool", () => {
"call",
{},
);
const details = result.details as AgentListDetails;
expect(result.details).toMatchObject({
requester: "main",
allowAny: false,
agents: [
{
id: "main",
configured: true,
},
],
});
expect(details.requester).toBe("main");
expect(details.allowAny).toBe(false);
expect(details.agents).toHaveLength(1);
expect(details.agents?.[0]?.id).toBe("main");
expect(details.agents?.[0]?.configured).toBe(true);
});
it("ignores legacy env-forced plugin runtime selections", async () => {
@@ -102,15 +107,12 @@ describe("agents_list tool", () => {
"call",
{},
);
const details = result.details as AgentListDetails;
expect(result.details).toMatchObject({
agents: [
{
id: "main",
agentRuntime: { id: "codex", source: "implicit" },
},
],
});
expect(details.agents).toHaveLength(1);
expect(details.agents?.[0]?.id).toBe("main");
expect(details.agents?.[0]?.agentRuntime?.id).toBe("codex");
expect(details.agents?.[0]?.agentRuntime?.source).toBe("implicit");
});
it("ignores legacy per-agent runtime overrides", async () => {
@@ -132,14 +134,11 @@ describe("agents_list tool", () => {
"call",
{},
);
const details = result.details as AgentListDetails;
expect(result.details).toMatchObject({
agents: [
{
id: "strict",
agentRuntime: { id: "codex", source: "implicit" },
},
],
});
expect(details.agents).toHaveLength(1);
expect(details.agents?.[0]?.id).toBe("strict");
expect(details.agents?.[0]?.agentRuntime?.id).toBe("codex");
expect(details.agents?.[0]?.agentRuntime?.source).toBe("implicit");
});
});

View File

@@ -11,6 +11,16 @@ function readSchemaProperty(schema: unknown, key: string): Record<string, unknow
return property as Record<string, unknown>;
}
type HeartbeatResponseDetails = {
status?: string;
outcome?: string;
notify?: boolean;
summary?: string;
notificationText?: string;
priority?: string;
nextCheck?: string;
};
describe("createHeartbeatResponseTool", () => {
it("uses flat enum schemas for provider portability", () => {
const tool = createHeartbeatResponseTool();
@@ -18,14 +28,10 @@ describe("createHeartbeatResponseTool", () => {
const outcome = readSchemaProperty(tool.parameters, "outcome");
const priority = readSchemaProperty(tool.parameters, "priority");
expect(outcome).toMatchObject({
type: "string",
enum: ["no_change", "progress", "done", "blocked", "needs_attention"],
});
expect(priority).toMatchObject({
type: "string",
enum: ["low", "normal", "high"],
});
expect(outcome.type).toBe("string");
expect(outcome.enum).toEqual(["no_change", "progress", "done", "blocked", "needs_attention"]);
expect(priority.type).toBe("string");
expect(priority.enum).toEqual(["low", "normal", "high"]);
expect(outcome).not.toHaveProperty("anyOf");
expect(priority).not.toHaveProperty("anyOf");
});
@@ -40,12 +46,11 @@ describe("createHeartbeatResponseTool", () => {
});
expect(tool.name).toBe(HEARTBEAT_RESPONSE_TOOL_NAME);
expect(result.details).toMatchObject({
status: "recorded",
outcome: "no_change",
notify: false,
summary: "Nothing needs attention.",
});
const details = result.details as HeartbeatResponseDetails;
expect(details.status).toBe("recorded");
expect(details.outcome).toBe("no_change");
expect(details.notify).toBe(false);
expect(details.summary).toBe("Nothing needs attention.");
});
it("accepts notification text and optional scheduling metadata", async () => {
@@ -60,15 +65,14 @@ describe("createHeartbeatResponseTool", () => {
nextCheck: "2026-05-01T17:00:00Z",
});
expect(result.details).toMatchObject({
status: "recorded",
outcome: "needs_attention",
notify: true,
summary: "Build is blocked.",
notificationText: "Build is blocked on missing credentials.",
priority: "high",
nextCheck: "2026-05-01T17:00:00Z",
});
const details = result.details as HeartbeatResponseDetails;
expect(details.status).toBe("recorded");
expect(details.outcome).toBe("needs_attention");
expect(details.notify).toBe(true);
expect(details.summary).toBe("Build is blocked.");
expect(details.notificationText).toBe("Build is blocked on missing credentials.");
expect(details.priority).toBe("high");
expect(details.nextCheck).toBe("2026-05-01T17:00:00Z");
});
it("rejects missing notify because quiet vs visible delivery must be explicit", async () => {

View File

@@ -1,15 +1,20 @@
import { describe, expect, it, vi } from "vitest";
import { createSessionsYieldTool } from "./sessions-yield-tool.js";
type SessionsYieldDetails = {
status?: string;
message?: string;
error?: string;
};
describe("sessions_yield tool", () => {
it("returns error when no sessionId is provided", async () => {
const onYield = vi.fn();
const tool = createSessionsYieldTool({ onYield });
const result = await tool.execute("call-1", {});
expect(result.details).toMatchObject({
status: "error",
error: "No session context",
});
const details = result.details as SessionsYieldDetails;
expect(details.status).toBe("error");
expect(details.error).toBe("No session context");
expect(onYield).not.toHaveBeenCalled();
});
@@ -17,7 +22,9 @@ describe("sessions_yield tool", () => {
const onYield = vi.fn();
const tool = createSessionsYieldTool({ sessionId: "test-session", onYield });
const result = await tool.execute("call-1", {});
expect(result.details).toMatchObject({ status: "yielded", message: "Turn yielded." });
const details = result.details as SessionsYieldDetails;
expect(details.status).toBe("yielded");
expect(details.message).toBe("Turn yielded.");
expect(onYield).toHaveBeenCalledOnce();
expect(onYield).toHaveBeenCalledWith("Turn yielded.");
});
@@ -26,10 +33,9 @@ describe("sessions_yield tool", () => {
const onYield = vi.fn();
const tool = createSessionsYieldTool({ sessionId: "test-session", onYield });
const result = await tool.execute("call-1", { message: "Waiting for fact-checker" });
expect(result.details).toMatchObject({
status: "yielded",
message: "Waiting for fact-checker",
});
const details = result.details as SessionsYieldDetails;
expect(details.status).toBe("yielded");
expect(details.message).toBe("Waiting for fact-checker");
expect(onYield).toHaveBeenCalledOnce();
expect(onYield).toHaveBeenCalledWith("Waiting for fact-checker");
});
@@ -37,9 +43,8 @@ describe("sessions_yield tool", () => {
it("returns error without onYield callback", async () => {
const tool = createSessionsYieldTool({ sessionId: "test-session" });
const result = await tool.execute("call-1", {});
expect(result.details).toMatchObject({
status: "error",
error: "Yield not supported in this context",
});
const details = result.details as SessionsYieldDetails;
expect(details.status).toBe("error");
expect(details.error).toBe("Yield not supported in this context");
});
});