Files
GoClaw/server/tools.test.ts
Manus 86a1ee9062 Checkpoint: Full Development Complete: All 4 Phases
## Phase 1 (Fixed): Agent Management UI
- Исправлена авторизация: agents переведены на publicProcedure
- AgentDetailModal: 5 вкладок (General, LLM Params, Tools, History, Stats)
- Полное редактирование: model, provider, temperature, topP, maxTokens, frequencyPenalty, presencePenalty, systemPrompt
- Управление allowedTools и allowedDomains через теги
- AgentCreateModal: создание агентов с выбором модели из Ollama API
- Кнопка Metrics на каждой карточке агента

## Phase 2+3: Tool Binding System
- server/tools.ts: реестр из 10 инструментов (http_get, http_post, shell_exec, file_read, file_write, docker_list, docker_exec, docker_logs, browser_navigate, browser_screenshot)
- Безопасное выполнение: проверка allowedTools агента, accessControl из БД
- tools.execute tRPC endpoint
- Tools.tsx: страница управления инструментами с тест-выполнением
- Добавлен пункт "Инструменты" в sidebar навигацию

## Phase 4: Metrics & History
- AgentMetrics.tsx: детальная страница метрик по агенту
- Request Timeline: bar chart по часам (success/error)
- Conversation Log: история диалогов с пагинацией
- Raw Metrics Table: все метрики с токенами и временем
- Time range selector: 6h/24h/48h/7d
- Маршрут /agents/:id/metrics

## Tests: 24/24 passed
- server/auth.logout.test.ts (1)
- server/agents.test.ts (7)
- server/tools.test.ts (13)
- server/ollama.test.ts (3)
2026-03-20 16:52:27 -04:00

129 lines
4.4 KiB
TypeScript

import { describe, it, expect, vi, beforeEach } from "vitest";
import { getAllTools, getToolById, executeTool, TOOL_REGISTRY } from "./tools";
// Mock agents module
vi.mock("./agents", () => ({
getAgentById: vi.fn(),
getAgentAccessControl: vi.fn(),
}));
import { getAgentById, getAgentAccessControl } from "./agents";
const mockAgent = {
id: 1,
name: "Test Agent",
model: "gpt-4",
provider: "OpenAI",
allowedTools: ["http_get", "http_post", "file_read"],
temperature: "0.7",
maxTokens: 2048,
};
describe("Tool Registry", () => {
it("should return all tools", () => {
const tools = getAllTools();
expect(tools.length).toBeGreaterThan(0);
expect(tools.every((t) => t.id && t.name && t.category)).toBe(true);
});
it("should have required fields for each tool", () => {
const tools = getAllTools();
for (const tool of tools) {
expect(tool.id).toBeTruthy();
expect(tool.name).toBeTruthy();
expect(tool.description).toBeTruthy();
expect(tool.category).toBeTruthy();
expect(typeof tool.dangerous).toBe("boolean");
expect(typeof tool.parameters).toBe("object");
}
});
it("should find tool by ID", () => {
const tool = getToolById("http_get");
expect(tool).toBeDefined();
expect(tool?.name).toBe("HTTP GET");
expect(tool?.category).toBe("http");
});
it("should return undefined for unknown tool ID", () => {
const tool = getToolById("nonexistent_tool");
expect(tool).toBeUndefined();
});
it("should have http tools", () => {
const httpTools = TOOL_REGISTRY.filter((t) => t.category === "http");
expect(httpTools.length).toBeGreaterThanOrEqual(2);
});
it("should have shell tools", () => {
const shellTools = TOOL_REGISTRY.filter((t) => t.category === "shell");
expect(shellTools.length).toBeGreaterThanOrEqual(1);
});
it("should have docker tools", () => {
const dockerTools = TOOL_REGISTRY.filter((t) => t.category === "docker");
expect(dockerTools.length).toBeGreaterThanOrEqual(2);
});
it("should mark dangerous tools correctly", () => {
const shellExec = getToolById("shell_exec");
expect(shellExec?.dangerous).toBe(true);
const httpGet = getToolById("http_get");
expect(httpGet?.dangerous).toBe(false);
});
});
describe("Tool Execution", () => {
beforeEach(() => {
vi.clearAllMocks();
(getAgentById as any).mockResolvedValue(mockAgent);
(getAgentAccessControl as any).mockResolvedValue([]);
});
it("should return error for non-existent agent", async () => {
(getAgentById as any).mockResolvedValue(null);
const result = await executeTool(999, "http_get", { url: "https://example.com" });
expect(result.success).toBe(false);
expect(result.error).toContain("Agent not found");
});
it("should return error for unknown tool", async () => {
const result = await executeTool(1, "nonexistent_tool", {});
expect(result.success).toBe(false);
expect(result.error).toContain("Unknown tool");
});
it("should block tool not in agent's allowed list", async () => {
const agentWithLimitedTools = { ...mockAgent, allowedTools: ["http_get"] };
(getAgentById as any).mockResolvedValue(agentWithLimitedTools);
const result = await executeTool(1, "shell_exec", { command: "echo hello" });
expect(result.success).toBe(false);
expect(result.error).toContain("not in agent's allowed tools list");
});
it("should block tool explicitly denied in access control", async () => {
(getAgentAccessControl as any).mockResolvedValue([
{ tool: "http_get", isAllowed: false },
]);
const agentWithAllTools = { ...mockAgent, allowedTools: [] };
(getAgentById as any).mockResolvedValue(agentWithAllTools);
const result = await executeTool(1, "http_get", { url: "https://example.com" });
expect(result.success).toBe(false);
expect(result.error).toContain("blocked");
});
it("should include executionTimeMs in result", async () => {
// Agent with no allowed tools (empty = all allowed)
const agentAllTools = { ...mockAgent, allowedTools: [] };
(getAgentById as any).mockResolvedValue(agentAllTools);
// This will fail (no actual HTTP), but should still return executionTimeMs
const result = await executeTool(1, "http_get", { url: "https://httpbin.org/get" });
expect(typeof result.executionTimeMs).toBe("number");
expect(result.executionTimeMs).toBeGreaterThanOrEqual(0);
});
});