First Release of Claw3D (#11)
Co-authored-by: iamlukethedev <iamlukethedev@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,267 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import {
|
||||
createGatewayAgent,
|
||||
deleteGatewayAgent,
|
||||
renameGatewayAgent,
|
||||
resolveHeartbeatSettings,
|
||||
removeGatewayHeartbeatOverride,
|
||||
updateGatewayHeartbeat,
|
||||
} from "@/lib/gateway/agentConfig";
|
||||
import { GatewayResponseError, type GatewayClient } from "@/lib/gateway/GatewayClient";
|
||||
|
||||
describe("gateway agent helpers", () => {
|
||||
it("creates a new agent via agents.create and derives workspace from the config path", async () => {
|
||||
const client = {
|
||||
call: vi.fn(async (method: string, params?: unknown) => {
|
||||
if (method === "config.get") {
|
||||
return {
|
||||
exists: true,
|
||||
hash: "hash-create-1",
|
||||
path: "/Users/test/.openclaw/openclaw.json",
|
||||
config: { agents: { list: [{ id: "agent-1", name: "Agent One" }] } },
|
||||
};
|
||||
}
|
||||
if (method === "agents.create") {
|
||||
expect(params).toEqual({
|
||||
name: "New Agent",
|
||||
workspace: "/Users/test/.openclaw/workspace-new-agent",
|
||||
});
|
||||
return { ok: true, agentId: "new-agent", name: "New Agent", workspace: "ignored" };
|
||||
}
|
||||
throw new Error("unexpected method");
|
||||
}),
|
||||
} as unknown as GatewayClient;
|
||||
|
||||
const entry = await createGatewayAgent({ client, name: "New Agent" });
|
||||
expect(entry.id).toBe("new-agent");
|
||||
expect(entry.name).toBe("New Agent");
|
||||
});
|
||||
|
||||
it("slugifies workspace names from agent names", async () => {
|
||||
const client = {
|
||||
call: vi.fn(async (method: string, params?: unknown) => {
|
||||
if (method === "config.get") {
|
||||
return {
|
||||
exists: true,
|
||||
hash: "hash-create-slug-1",
|
||||
path: "/Users/test/.openclaw/openclaw.json",
|
||||
config: { agents: { list: [] } },
|
||||
};
|
||||
}
|
||||
if (method === "agents.create") {
|
||||
expect(params).toEqual({
|
||||
name: "My Project",
|
||||
workspace: "/Users/test/.openclaw/workspace-my-project",
|
||||
});
|
||||
return { ok: true, agentId: "my-project", name: "My Project", workspace: "ignored" };
|
||||
}
|
||||
throw new Error("unexpected method");
|
||||
}),
|
||||
} as unknown as GatewayClient;
|
||||
|
||||
const entry = await createGatewayAgent({ client, name: "My Project" });
|
||||
expect(entry.id).toBe("my-project");
|
||||
expect(entry.name).toBe("My Project");
|
||||
});
|
||||
|
||||
it("returns no-op on deleting a missing agent", async () => {
|
||||
const client = {
|
||||
call: vi.fn(async (method: string) => {
|
||||
if (method === "agents.delete") {
|
||||
throw new GatewayResponseError({
|
||||
code: "INVALID_REQUEST",
|
||||
message: 'agent "agent-2" not found',
|
||||
});
|
||||
}
|
||||
throw new Error("unexpected method");
|
||||
}),
|
||||
} as unknown as GatewayClient;
|
||||
|
||||
const result = await deleteGatewayAgent({
|
||||
client,
|
||||
agentId: "agent-2",
|
||||
});
|
||||
|
||||
expect(result).toEqual({ removed: false, removedBindings: 0 });
|
||||
expect(client.call).toHaveBeenCalledTimes(1);
|
||||
expect((client.call as ReturnType<typeof vi.fn>).mock.calls[0][0]).toBe("agents.delete");
|
||||
});
|
||||
|
||||
it("fails fast on empty create name", async () => {
|
||||
const client = {
|
||||
call: vi.fn(),
|
||||
} as unknown as GatewayClient;
|
||||
|
||||
await expect(createGatewayAgent({ client, name: " " })).rejects.toThrow(
|
||||
"Agent name is required."
|
||||
);
|
||||
expect(client.call).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("fails when create name produces an empty id slug", async () => {
|
||||
const client = {
|
||||
call: vi.fn(async (method: string) => {
|
||||
if (method === "config.get") {
|
||||
return {
|
||||
exists: true,
|
||||
hash: "hash-create-empty-slug-1",
|
||||
path: "/Users/test/.openclaw/openclaw.json",
|
||||
config: {
|
||||
agents: { list: [] },
|
||||
},
|
||||
};
|
||||
}
|
||||
throw new Error("unexpected method");
|
||||
}),
|
||||
} as unknown as GatewayClient;
|
||||
|
||||
await expect(createGatewayAgent({ client, name: "!!!" })).rejects.toThrow(
|
||||
"Name produced an empty folder name."
|
||||
);
|
||||
expect(client.call).toHaveBeenCalledTimes(1);
|
||||
expect((client.call as ReturnType<typeof vi.fn>).mock.calls[0]?.[0]).toBe("config.get");
|
||||
});
|
||||
|
||||
it("returns current settings when no heartbeat override exists to remove", async () => {
|
||||
const client = {
|
||||
call: vi.fn(async (method: string) => {
|
||||
if (method === "config.get") {
|
||||
return {
|
||||
exists: true,
|
||||
hash: "hash-remove-1",
|
||||
path: "/Users/test/.openclaw/openclaw.json",
|
||||
config: {
|
||||
agents: {
|
||||
defaults: {
|
||||
heartbeat: {
|
||||
every: "10m",
|
||||
target: "last",
|
||||
includeReasoning: false,
|
||||
ackMaxChars: 300,
|
||||
},
|
||||
},
|
||||
list: [{ id: "agent-1", name: "Agent One" }],
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
throw new Error("unexpected method");
|
||||
}),
|
||||
} as unknown as GatewayClient;
|
||||
|
||||
const result = await removeGatewayHeartbeatOverride({
|
||||
client,
|
||||
agentId: "agent-1",
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
heartbeat: {
|
||||
every: "10m",
|
||||
target: "last",
|
||||
includeReasoning: false,
|
||||
ackMaxChars: 300,
|
||||
activeHours: null,
|
||||
},
|
||||
hasOverride: false,
|
||||
});
|
||||
expect(client.call).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("renames an agent via agents.update", async () => {
|
||||
const client = {
|
||||
call: vi.fn(async (method: string, params?: unknown) => {
|
||||
if (method === "agents.update") {
|
||||
expect(params).toEqual({ agentId: "agent-1", name: "New Name" });
|
||||
return { ok: true, agentId: "agent-1" };
|
||||
}
|
||||
throw new Error("unexpected method");
|
||||
}),
|
||||
} as unknown as GatewayClient;
|
||||
|
||||
await renameGatewayAgent({ client, agentId: "agent-1", name: "New Name" });
|
||||
});
|
||||
|
||||
it("resolves heartbeat defaults and overrides", () => {
|
||||
const config = {
|
||||
agents: {
|
||||
defaults: {
|
||||
heartbeat: {
|
||||
every: "2h",
|
||||
target: "last",
|
||||
includeReasoning: false,
|
||||
ackMaxChars: 200,
|
||||
},
|
||||
},
|
||||
list: [
|
||||
{
|
||||
id: "agent-1",
|
||||
heartbeat: { every: "30m", target: "none", includeReasoning: true },
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
const result = resolveHeartbeatSettings(config, "agent-1");
|
||||
expect(result.heartbeat.every).toBe("30m");
|
||||
expect(result.heartbeat.target).toBe("none");
|
||||
expect(result.heartbeat.includeReasoning).toBe(true);
|
||||
expect(result.hasOverride).toBe(true);
|
||||
});
|
||||
|
||||
it("updates heartbeat overrides via config.patch", async () => {
|
||||
const client = {
|
||||
call: vi.fn(async (method: string, params?: unknown) => {
|
||||
if (method === "config.get") {
|
||||
return {
|
||||
exists: true,
|
||||
hash: "hash-2",
|
||||
path: "/Users/test/.openclaw/openclaw.json",
|
||||
config: {
|
||||
agents: {
|
||||
defaults: {
|
||||
heartbeat: {
|
||||
every: "1h",
|
||||
target: "last",
|
||||
includeReasoning: false,
|
||||
ackMaxChars: 300,
|
||||
},
|
||||
},
|
||||
list: [{ id: "agent-1" }],
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
if (method === "config.patch") {
|
||||
const raw = (params as { raw?: string }).raw ?? "";
|
||||
const parsed = JSON.parse(raw) as {
|
||||
agents?: { list?: Array<{ id?: string; heartbeat?: unknown }> };
|
||||
};
|
||||
const entry = parsed.agents?.list?.find((item) => item.id === "agent-1");
|
||||
expect(entry && typeof entry === "object").toBe(true);
|
||||
return { ok: true };
|
||||
}
|
||||
throw new Error("unexpected method");
|
||||
}),
|
||||
} as unknown as GatewayClient;
|
||||
|
||||
const result = await updateGatewayHeartbeat({
|
||||
client,
|
||||
agentId: "agent-1",
|
||||
payload: {
|
||||
override: true,
|
||||
heartbeat: {
|
||||
every: "15m",
|
||||
target: "none",
|
||||
includeReasoning: true,
|
||||
ackMaxChars: 120,
|
||||
activeHours: { start: "08:00", end: "18:00" },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.heartbeat.every).toBe("15m");
|
||||
expect(result.heartbeat.target).toBe("none");
|
||||
expect(result.heartbeat.includeReasoning).toBe(true);
|
||||
expect(result.hasOverride).toBe(true);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user