import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; import { AgentsController } from "./agents.controller"; import { QueueService } from "../../queue/queue.service"; import { AgentSpawnerService } from "../../spawner/agent-spawner.service"; import { AgentLifecycleService } from "../../spawner/agent-lifecycle.service"; import { KillswitchService } from "../../killswitch/killswitch.service"; import { AgentEventsService } from "./agent-events.service"; import { AgentMessagesService } from "./agent-messages.service"; import { AgentControlService } from "./agent-control.service"; import { AgentTreeService } from "./agent-tree.service"; import type { KillAllResult } from "../../killswitch/killswitch.service"; describe("AgentsController - Killswitch Endpoints", () => { let controller: AgentsController; let mockKillswitchService: { killAgent: ReturnType; killAllAgents: ReturnType; }; let mockQueueService: { addTask: ReturnType; }; let mockSpawnerService: { spawnAgent: ReturnType; }; let mockLifecycleService: { getAgentLifecycleState: ReturnType; registerSpawnedAgent: ReturnType; }; let mockEventsService: { subscribe: ReturnType; getInitialSnapshot: ReturnType; createHeartbeat: ReturnType; getRecentEvents: ReturnType; }; let mockMessagesService: { getMessages: ReturnType; getReplayMessages: ReturnType; getMessagesAfter: ReturnType; }; let mockControlService: { injectMessage: ReturnType; pauseAgent: ReturnType; resumeAgent: ReturnType; }; let mockTreeService: { getTree: ReturnType; }; beforeEach(() => { mockKillswitchService = { killAgent: vi.fn(), killAllAgents: vi.fn(), }; mockQueueService = { addTask: vi.fn(), }; mockSpawnerService = { spawnAgent: vi.fn(), }; mockLifecycleService = { getAgentLifecycleState: vi.fn(), registerSpawnedAgent: vi.fn(), }; mockEventsService = { subscribe: vi.fn().mockReturnValue(() => {}), getInitialSnapshot: vi.fn().mockResolvedValue({ type: "stream.snapshot", timestamp: new Date().toISOString(), agents: 0, tasks: 0, }), createHeartbeat: vi.fn().mockReturnValue({ type: "task.processing", timestamp: new Date().toISOString(), data: { heartbeat: true }, }), getRecentEvents: vi.fn().mockReturnValue([]), }; mockMessagesService = { getMessages: vi.fn(), getReplayMessages: vi.fn().mockResolvedValue([]), getMessagesAfter: vi.fn().mockResolvedValue([]), }; mockControlService = { injectMessage: vi.fn().mockResolvedValue(undefined), pauseAgent: vi.fn().mockResolvedValue(undefined), resumeAgent: vi.fn().mockResolvedValue(undefined), }; mockTreeService = { getTree: vi.fn().mockResolvedValue([]), }; controller = new AgentsController( mockQueueService as unknown as QueueService, mockSpawnerService as unknown as AgentSpawnerService, mockLifecycleService as unknown as AgentLifecycleService, mockKillswitchService as unknown as KillswitchService, mockEventsService as unknown as AgentEventsService, mockMessagesService as unknown as AgentMessagesService, mockControlService as unknown as AgentControlService, mockTreeService as unknown as AgentTreeService ); }); afterEach(() => { vi.clearAllMocks(); }); describe("POST /agents/:agentId/kill", () => { it("should kill single agent successfully", async () => { // Arrange const agentId = "agent-123"; mockKillswitchService.killAgent.mockResolvedValue(undefined); // Act const result = await controller.killAgent(agentId); // Assert expect(mockKillswitchService.killAgent).toHaveBeenCalledWith(agentId); expect(result).toEqual({ message: `Agent ${agentId} killed successfully`, }); }); it("should throw error if agent not found", async () => { // Arrange const agentId = "agent-999"; mockKillswitchService.killAgent.mockRejectedValue(new Error("Agent agent-999 not found")); // Act & Assert await expect(controller.killAgent(agentId)).rejects.toThrow("Agent agent-999 not found"); }); it("should throw error if state transition fails", async () => { // Arrange const agentId = "agent-123"; mockKillswitchService.killAgent.mockRejectedValue(new Error("Invalid state transition")); // Act & Assert await expect(controller.killAgent(agentId)).rejects.toThrow("Invalid state transition"); }); }); describe("POST /agents/kill-all", () => { it("should kill all agents successfully", async () => { // Arrange const killAllResult: KillAllResult = { total: 3, killed: 3, failed: 0, }; mockKillswitchService.killAllAgents.mockResolvedValue(killAllResult); // Act const result = await controller.killAllAgents(); // Assert expect(mockKillswitchService.killAllAgents).toHaveBeenCalled(); expect(result).toEqual({ message: "Kill all completed: 3 killed, 0 failed", total: 3, killed: 3, failed: 0, }); }); it("should return partial results when some agents fail", async () => { // Arrange const killAllResult: KillAllResult = { total: 3, killed: 2, failed: 1, errors: ["Failed to kill agent agent-2: State transition failed"], }; mockKillswitchService.killAllAgents.mockResolvedValue(killAllResult); // Act const result = await controller.killAllAgents(); // Assert expect(mockKillswitchService.killAllAgents).toHaveBeenCalled(); expect(result).toEqual({ message: "Kill all completed: 2 killed, 1 failed", total: 3, killed: 2, failed: 1, errors: ["Failed to kill agent agent-2: State transition failed"], }); }); it("should return zero results when no agents exist", async () => { // Arrange const killAllResult: KillAllResult = { total: 0, killed: 0, failed: 0, }; mockKillswitchService.killAllAgents.mockResolvedValue(killAllResult); // Act const result = await controller.killAllAgents(); // Assert expect(mockKillswitchService.killAllAgents).toHaveBeenCalled(); expect(result).toEqual({ message: "Kill all completed: 0 killed, 0 failed", total: 0, killed: 0, failed: 0, }); }); it("should throw error if killswitch service fails", async () => { // Arrange mockKillswitchService.killAllAgents.mockRejectedValue(new Error("Internal error")); // Act & Assert await expect(controller.killAllAgents()).rejects.toThrow("Internal error"); }); }); });