/** * Tests for useOrchestratorCommands hook */ import { describe, it, expect, vi, beforeEach } from "vitest"; import { renderHook, act } from "@testing-library/react"; import { useOrchestratorCommands } from "./useOrchestratorCommands"; import type { Message } from "./useChat"; // Mock fetch globally const mockFetch = vi.fn(); global.fetch = mockFetch; function makeOkResponse(data: unknown): Response { return { ok: true, status: 200, json: () => Promise.resolve(data), text: () => Promise.resolve(JSON.stringify(data)), } as unknown as Response; } /** Run executeCommand and return the result synchronously after act() */ async function runCommand( executeCommand: (content: string) => Promise, content: string ): Promise { let msg: Message | null = null; await act(async () => { msg = await executeCommand(content); }); return msg; } describe("useOrchestratorCommands", () => { beforeEach(() => { mockFetch.mockReset(); }); describe("isCommand", () => { it("returns true for messages starting with /", () => { const { result } = renderHook(() => useOrchestratorCommands()); expect(result.current.isCommand("/status")).toBe(true); expect(result.current.isCommand("/agents")).toBe(true); expect(result.current.isCommand("/help")).toBe(true); expect(result.current.isCommand(" /status")).toBe(true); }); it("returns false for regular messages", () => { const { result } = renderHook(() => useOrchestratorCommands()); expect(result.current.isCommand("hello")).toBe(false); expect(result.current.isCommand("tell me about /status")).toBe(false); expect(result.current.isCommand("")).toBe(false); }); }); describe("executeCommand", () => { describe("/help", () => { it("returns help message without network calls", async () => { const { result } = renderHook(() => useOrchestratorCommands()); const msg = await runCommand(result.current.executeCommand, "/help"); expect(mockFetch).not.toHaveBeenCalled(); expect(msg).not.toBeNull(); expect(msg?.role).toBe("assistant"); expect(msg?.content).toContain("/status"); expect(msg?.content).toContain("/agents"); expect(msg?.content).toContain("/jobs"); expect(msg?.content).toContain("/pause"); expect(msg?.content).toContain("/resume"); }); it("returns message with id and createdAt", async () => { const { result } = renderHook(() => useOrchestratorCommands()); const msg = await runCommand(result.current.executeCommand, "/help"); expect(msg?.id).toBeDefined(); expect(msg?.createdAt).toBeDefined(); }); }); describe("/status", () => { it("calls /api/orchestrator/health and returns formatted status", async () => { mockFetch.mockResolvedValueOnce( makeOkResponse({ status: "ready", version: "1.2.3", uptime: 3661 }) ); const { result } = renderHook(() => useOrchestratorCommands()); const msg = await runCommand(result.current.executeCommand, "/status"); expect(mockFetch).toHaveBeenCalledWith("/api/orchestrator/health", { method: "GET" }); expect(msg?.role).toBe("assistant"); expect(msg?.content).toContain("Ready"); expect(msg?.content).toContain("1.2.3"); expect(msg?.content).toContain("1h"); }); it("shows Not Ready when status is not ready", async () => { mockFetch.mockResolvedValueOnce(makeOkResponse({ status: "not-ready" })); const { result } = renderHook(() => useOrchestratorCommands()); const msg = await runCommand(result.current.executeCommand, "/status"); expect(msg?.content).toContain("Not Ready"); }); it("handles network error gracefully", async () => { mockFetch.mockRejectedValueOnce(new Error("Connection refused")); const { result } = renderHook(() => useOrchestratorCommands()); const msg = await runCommand(result.current.executeCommand, "/status"); expect(msg?.role).toBe("assistant"); expect(msg?.content).toContain("Error"); expect(msg?.content).toContain("Connection refused"); }); it("shows error from API response", async () => { mockFetch.mockResolvedValueOnce( makeOkResponse({ error: "ORCHESTRATOR_API_KEY is not configured" }) ); const { result } = renderHook(() => useOrchestratorCommands()); const msg = await runCommand(result.current.executeCommand, "/status"); expect(msg?.content).toContain("Not reachable"); }); }); describe("/agents", () => { it("calls /api/orchestrator/agents and returns agent table", async () => { const agents = [ { id: "agent-1", status: "active", type: "codex", startedAt: "2026-02-25T10:00:00Z" }, { id: "agent-2", agentStatus: "TERMINATED", channel: "claude", startedAt: "2026-02-25T09:00:00Z", }, ]; mockFetch.mockResolvedValueOnce(makeOkResponse(agents)); const { result } = renderHook(() => useOrchestratorCommands()); const msg = await runCommand(result.current.executeCommand, "/agents"); expect(mockFetch).toHaveBeenCalledWith("/api/orchestrator/agents", { method: "GET" }); expect(msg?.content).toContain("agent-1"); expect(msg?.content).toContain("agent-2"); expect(msg?.content).toContain("TERMINATED"); }); it("handles empty agent list", async () => { mockFetch.mockResolvedValueOnce(makeOkResponse([])); const { result } = renderHook(() => useOrchestratorCommands()); const msg = await runCommand(result.current.executeCommand, "/agents"); expect(msg?.content).toContain("No agents currently running"); }); it("handles agents in nested object", async () => { const data = { agents: [{ id: "agent-nested", status: "active" }], }; mockFetch.mockResolvedValueOnce(makeOkResponse(data)); const { result } = renderHook(() => useOrchestratorCommands()); const msg = await runCommand(result.current.executeCommand, "/agents"); expect(msg?.content).toContain("agent-nested"); }); it("handles network error gracefully", async () => { mockFetch.mockRejectedValueOnce(new Error("Timeout")); const { result } = renderHook(() => useOrchestratorCommands()); const msg = await runCommand(result.current.executeCommand, "/agents"); expect(msg?.content).toContain("Error"); expect(msg?.content).toContain("Timeout"); }); }); describe("/jobs", () => { it("calls /api/orchestrator/queue/stats", async () => { mockFetch.mockResolvedValueOnce( makeOkResponse({ pending: 3, active: 1, completed: 42, failed: 0 }) ); const { result } = renderHook(() => useOrchestratorCommands()); const msg = await runCommand(result.current.executeCommand, "/jobs"); expect(mockFetch).toHaveBeenCalledWith("/api/orchestrator/queue/stats", { method: "GET" }); expect(msg?.content).toContain("3"); expect(msg?.content).toContain("42"); expect(msg?.content).toContain("Pending"); expect(msg?.content).toContain("Completed"); }); it("/queue is an alias for /jobs", async () => { mockFetch.mockResolvedValueOnce(makeOkResponse({ pending: 0, active: 0 })); const { result } = renderHook(() => useOrchestratorCommands()); const msg = await runCommand(result.current.executeCommand, "/queue"); expect(mockFetch).toHaveBeenCalledWith("/api/orchestrator/queue/stats", { method: "GET" }); expect(msg?.role).toBe("assistant"); }); it("shows paused indicator when queue is paused", async () => { mockFetch.mockResolvedValueOnce(makeOkResponse({ pending: 0, active: 0, paused: true })); const { result } = renderHook(() => useOrchestratorCommands()); const msg = await runCommand(result.current.executeCommand, "/jobs"); expect(msg?.content).toContain("paused"); }); }); describe("/pause", () => { it("calls POST /api/orchestrator/queue/pause", async () => { mockFetch.mockResolvedValueOnce( makeOkResponse({ success: true, message: "Queue paused." }) ); const { result } = renderHook(() => useOrchestratorCommands()); const msg = await runCommand(result.current.executeCommand, "/pause"); expect(mockFetch).toHaveBeenCalledWith("/api/orchestrator/queue/pause", { method: "POST", }); expect(msg?.content).toContain("paused"); }); it("handles API error response", async () => { mockFetch.mockResolvedValueOnce(makeOkResponse({ error: "Already paused." })); const { result } = renderHook(() => useOrchestratorCommands()); const msg = await runCommand(result.current.executeCommand, "/pause"); expect(msg?.content).toContain("failed"); expect(msg?.content).toContain("Already paused"); }); it("handles network error", async () => { mockFetch.mockRejectedValueOnce(new Error("Network failure")); const { result } = renderHook(() => useOrchestratorCommands()); const msg = await runCommand(result.current.executeCommand, "/pause"); expect(msg?.content).toContain("Error"); }); }); describe("/resume", () => { it("calls POST /api/orchestrator/queue/resume", async () => { mockFetch.mockResolvedValueOnce( makeOkResponse({ success: true, message: "Queue resumed." }) ); const { result } = renderHook(() => useOrchestratorCommands()); const msg = await runCommand(result.current.executeCommand, "/resume"); expect(mockFetch).toHaveBeenCalledWith("/api/orchestrator/queue/resume", { method: "POST", }); expect(msg?.content).toContain("resumed"); }); }); describe("unknown command", () => { it("returns help hint for unknown commands", async () => { const { result } = renderHook(() => useOrchestratorCommands()); const msg = await runCommand(result.current.executeCommand, "/unknown-command"); expect(mockFetch).not.toHaveBeenCalled(); expect(msg?.content).toContain("Unknown command"); expect(msg?.content).toContain("/unknown-command"); expect(msg?.content).toContain("/help"); }); }); describe("non-command input", () => { it("returns null for regular messages", async () => { const { result } = renderHook(() => useOrchestratorCommands()); const msg = await runCommand(result.current.executeCommand, "hello world"); expect(msg).toBeNull(); expect(mockFetch).not.toHaveBeenCalled(); }); }); }); });