Files
claw3d/tests/unit/gatewayAgentOverrides.test.ts
T
Luke The Dev 4fa4f13558 First Release of Claw3D (#11)
Co-authored-by: iamlukethedev <iamlukethedev@users.noreply.github.com>
2026-03-19 23:14:04 -05:00

374 lines
11 KiB
TypeScript

import { describe, expect, it, vi } from "vitest";
import { GatewayResponseError } from "@/lib/gateway/GatewayClient";
import type { GatewayClient } from "@/lib/gateway/GatewayClient";
import { updateGatewayAgentOverrides } from "@/lib/gateway/agentConfig";
describe("updateGatewayAgentOverrides", () => {
it("writes additive alsoAllow entries for per-agent tools", async () => {
const client = {
call: vi.fn(async (method: string, params?: unknown) => {
if (method === "config.get") {
return {
exists: true,
hash: "cfg-additive-1",
config: {
agents: {
list: [{ id: "agent-1", tools: { profile: "coding" } }],
},
},
};
}
if (method === "config.set") {
const raw = (params as { raw?: string }).raw ?? "";
const parsed = JSON.parse(raw) as {
agents?: { list?: Array<{ id?: string; tools?: { profile?: string; alsoAllow?: string[]; deny?: string[] } }> };
};
const entry = parsed.agents?.list?.find((item) => item.id === "agent-1");
expect(entry?.tools).toEqual({
profile: "coding",
alsoAllow: ["group:web", "group:runtime"],
deny: ["group:fs"],
});
return { ok: true };
}
throw new Error(`unexpected method ${method}`);
}),
} as unknown as GatewayClient;
await updateGatewayAgentOverrides({
client,
agentId: "agent-1",
overrides: {
tools: {
profile: "coding",
alsoAllow: ["group:web", "group:web", " group:runtime "],
deny: ["group:fs", "group:fs"],
},
},
});
});
it("drops legacy allow when writing additive alsoAllow", async () => {
const client = {
call: vi.fn(async (method: string, params?: unknown) => {
if (method === "config.get") {
return {
exists: true,
hash: "cfg-additive-2",
config: {
agents: {
list: [{ id: "agent-1", tools: { profile: "coding", allow: ["group:web"] } }],
},
},
};
}
if (method === "config.set") {
const raw = (params as { raw?: string }).raw ?? "";
const parsed = JSON.parse(raw) as {
agents?: {
list?: Array<{
id?: string;
tools?: {
profile?: string;
allow?: string[];
alsoAllow?: string[];
deny?: string[];
};
}>;
};
};
const entry = parsed.agents?.list?.find((item) => item.id === "agent-1");
expect(entry?.tools).toEqual({
profile: "coding",
alsoAllow: ["group:runtime"],
deny: ["group:fs"],
});
return { ok: true };
}
throw new Error(`unexpected method ${method}`);
}),
} as unknown as GatewayClient;
await updateGatewayAgentOverrides({
client,
agentId: "agent-1",
overrides: {
tools: {
alsoAllow: ["group:runtime"],
deny: ["group:fs"],
},
},
});
});
it("preserves redacted non-agent fields when writing full config", async () => {
const client = {
call: vi.fn(async (method: string, params?: unknown) => {
if (method === "config.get") {
return {
exists: true,
hash: "cfg-redacted-1",
config: {
models: {
providers: {
xai: {
models: [{ id: "grok", maxTokens: "__OPENCLAW_REDACTED__" }],
},
},
},
agents: {
list: [{ id: "agent-1" }],
},
},
};
}
if (method === "config.set") {
const raw = (params as { raw?: string }).raw ?? "";
const parsed = JSON.parse(raw) as Record<string, unknown>;
expect(parsed.models).toEqual({
providers: {
xai: {
models: [{ id: "grok", maxTokens: "__OPENCLAW_REDACTED__" }],
},
},
});
expect(parsed.agents).toEqual({
list: [
{
id: "agent-1",
tools: {
profile: "coding",
alsoAllow: ["group:runtime"],
},
},
],
});
return { ok: true };
}
throw new Error(`unexpected method ${method}`);
}),
} as unknown as GatewayClient;
await updateGatewayAgentOverrides({
client,
agentId: "agent-1",
overrides: {
tools: {
profile: "coding",
alsoAllow: ["group:runtime"],
},
},
});
});
it("preserves concurrent config changes when config.set retries after stale hash", async () => {
let configGetCount = 0;
let configSetCount = 0;
const callOrder: string[] = [];
const client = {
call: vi.fn(async (method: string, params?: unknown) => {
callOrder.push(method);
if (method === "config.get") {
configGetCount += 1;
if (configGetCount === 1) {
return {
exists: true,
hash: "cfg-retry-1",
config: {
gateway: {
reload: {
mode: "hybrid",
},
},
agents: {
list: [{ id: "agent-1" }],
},
},
};
}
return {
exists: true,
hash: "cfg-retry-2",
config: {
gateway: {
reload: {
mode: "off",
},
},
agents: {
list: [{ id: "agent-1" }],
},
},
};
}
if (method === "config.set") {
configSetCount += 1;
const payload = params as { raw?: string; baseHash?: string };
const parsed = JSON.parse(payload.raw ?? "") as {
gateway?: { reload?: { mode?: string } };
agents?: {
list?: Array<{
id?: string;
tools?: {
profile?: string;
alsoAllow?: string[];
};
}>;
};
};
if (configSetCount === 1) {
expect(payload.baseHash).toBe("cfg-retry-1");
expect(parsed.gateway?.reload?.mode).toBe("hybrid");
throw new GatewayResponseError({
code: "INVALID_REQUEST",
message: "config changed since last load; re-run config.get and retry",
});
}
expect(payload.baseHash).toBe("cfg-retry-2");
expect(parsed.gateway?.reload?.mode).toBe("off");
expect(parsed.agents?.list?.find((entry) => entry.id === "agent-1")?.tools).toEqual({
profile: "coding",
alsoAllow: ["group:web"],
});
return { ok: true };
}
throw new Error(`unexpected method ${method}`);
}),
} as unknown as GatewayClient;
await updateGatewayAgentOverrides({
client,
agentId: "agent-1",
overrides: {
tools: {
profile: "coding",
alsoAllow: ["group:web"],
},
},
});
expect(configGetCount).toBe(2);
expect(configSetCount).toBe(2);
expect(callOrder).toEqual(["config.get", "config.set", "config.get", "config.set"]);
});
it("omits baseHash when config does not exist yet", async () => {
let configSetCount = 0;
const client = {
call: vi.fn(async (method: string, params?: unknown) => {
if (method === "config.get") {
return {
exists: false,
config: {
agents: {
list: [],
},
},
};
}
if (method === "config.set") {
configSetCount += 1;
const payload = params as { raw?: string; baseHash?: string };
const parsed = JSON.parse(payload.raw ?? "") as {
agents?: {
list?: Array<{
id?: string;
tools?: {
alsoAllow?: string[];
deny?: string[];
};
}>;
};
};
expect(payload.baseHash).toBeUndefined();
expect(parsed.agents?.list?.find((entry) => entry.id === "agent-1")?.tools).toEqual({
alsoAllow: ["group:runtime"],
deny: ["group:fs"],
});
return { ok: true };
}
throw new Error(`unexpected method ${method}`);
}),
} as unknown as GatewayClient;
await updateGatewayAgentOverrides({
client,
agentId: "agent-1",
overrides: {
tools: {
alsoAllow: ["group:runtime"],
deny: ["group:fs"],
},
},
});
expect(configSetCount).toBe(1);
});
it("fails after a single stale-hash retry attempt", async () => {
let configGetCount = 0;
let configSetCount = 0;
const client = {
call: vi.fn(async (method: string) => {
if (method === "config.get") {
configGetCount += 1;
return {
exists: true,
hash: `cfg-stale-${configGetCount}`,
config: {
agents: {
list: [{ id: "agent-1" }],
},
},
};
}
if (method === "config.set") {
configSetCount += 1;
throw new GatewayResponseError({
code: "INVALID_REQUEST",
message: "config changed since last load; re-run config.get and retry",
});
}
throw new Error(`unexpected method ${method}`);
}),
} as unknown as GatewayClient;
await expect(
updateGatewayAgentOverrides({
client,
agentId: "agent-1",
overrides: {
tools: {
alsoAllow: ["group:runtime"],
},
},
})
).rejects.toBeInstanceOf(GatewayResponseError);
expect(configGetCount).toBe(2);
expect(configSetCount).toBe(2);
});
it("fails fast when both allow and alsoAllow are provided", async () => {
const client = {
call: vi.fn(),
} as unknown as GatewayClient;
await expect(
updateGatewayAgentOverrides({
client,
agentId: "agent-1",
overrides: {
tools: {
allow: ["group:runtime"],
alsoAllow: ["group:web"],
},
},
})
).rejects.toThrow("Agent tools overrides cannot set both allow and alsoAllow.");
expect(client.call).not.toHaveBeenCalled();
});
});