192 lines
5.7 KiB
TypeScript
192 lines
5.7 KiB
TypeScript
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<typeof vi.fn>;
|
|
killAllAgents: ReturnType<typeof vi.fn>;
|
|
};
|
|
let mockQueueService: {
|
|
addTask: ReturnType<typeof vi.fn>;
|
|
};
|
|
let mockSpawnerService: {
|
|
spawnAgent: ReturnType<typeof vi.fn>;
|
|
};
|
|
let mockLifecycleService: {
|
|
getAgentLifecycleState: ReturnType<typeof vi.fn>;
|
|
registerSpawnedAgent: ReturnType<typeof vi.fn>;
|
|
};
|
|
let mockEventsService: {
|
|
subscribe: ReturnType<typeof vi.fn>;
|
|
getInitialSnapshot: ReturnType<typeof vi.fn>;
|
|
createHeartbeat: ReturnType<typeof vi.fn>;
|
|
};
|
|
|
|
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");
|
|
});
|
|
});
|
|
});
|