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 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; }; 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 }, }), }; 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 ); }); 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"); }); }); });