gadget/gadget-code/tests/drone-session.test.ts
Rob Colbert 11bdd5e3b0 make reasoning effort configurable; remove sign up concept
- Implemented reasoning effort setting in SESSION panel of Chat Sessio
View
- Removed all ability to "sign up" for an account
2026-05-08 11:40:30 -04:00

323 lines
9.6 KiB
TypeScript

import { describe, it, expect, beforeEach, vi } from "vitest";
import { Types } from "mongoose";
import { DroneSession } from "../src/lib/drone-session";
import {
IDroneRegistration,
IUser,
ChatTurnStatus,
GadgetId,
} from "@gadget/api";
import SocketService from "../src/services/socket";
import { ChatTurn } from "../src/models/chat-turn";
import { nanoid } from "nanoid";
// Mock dependencies
vi.mock("../src/services/socket");
vi.mock("../src/models/chat-turn");
describe("DroneSession", () => {
let mockSocket: any;
let mockRegistration: IDroneRegistration;
let mockUser: IUser;
let droneSession: DroneSession;
let mockChatSessionId: GadgetId;
let mockTurnId: GadgetId;
beforeEach(() => {
vi.clearAllMocks();
mockSocket = {
id: "test-drone-socket",
on: vi.fn(),
emit: vi.fn(),
};
mockUser = {
_id: nanoid(),
email: "test@example.com",
displayName: "Test User",
} as IUser;
mockRegistration = {
_id: nanoid(),
user: mockUser,
hostname: "test-host",
workspaceDir: "/test/workspace",
status: "available",
} as IDroneRegistration;
mockChatSessionId = nanoid();
mockTurnId = nanoid();
droneSession = new DroneSession(mockSocket, mockRegistration);
});
describe("register", () => {
it("should register event handlers for drone events", () => {
droneSession.register();
expect(mockSocket.on).toHaveBeenCalledWith(
"thinking",
expect.any(Function),
);
expect(mockSocket.on).toHaveBeenCalledWith(
"response",
expect.any(Function),
);
expect(mockSocket.on).toHaveBeenCalledWith(
"toolCall",
expect.any(Function),
);
expect(mockSocket.on).toHaveBeenCalledWith(
"workOrderComplete",
expect.any(Function),
);
expect(mockSocket.on).toHaveBeenCalledWith(
"requestCrashRecovery",
expect.any(Function),
);
expect(mockSocket.on).toHaveBeenCalledWith(
"requestTermination",
expect.any(Function),
);
});
});
describe("setChatSessionId", () => {
it("should set the chat session ID", () => {
droneSession.setChatSessionId(mockChatSessionId);
expect(() =>
droneSession.setChatSessionId(mockChatSessionId),
).not.toThrow();
});
});
describe("setCurrentTurnId", () => {
it("should set the current turn ID", () => {
droneSession.setCurrentTurnId(mockTurnId);
expect(() => droneSession.setCurrentTurnId(mockTurnId)).not.toThrow();
});
});
describe("onThinking", () => {
it("should route thinking event to code session", async () => {
const mockCodeSession = {
socket: { emit: vi.fn() },
onThinking: vi.fn(),
};
vi.mocked(SocketService.getCodeSessionByChatSessionId).mockReturnValue(
mockCodeSession as any,
);
droneSession.setChatSessionId(mockChatSessionId);
await droneSession.onThinking("thinking content");
expect(SocketService.getCodeSessionByChatSessionId).toHaveBeenCalledWith(
mockChatSessionId,
);
expect(mockCodeSession.onThinking).toHaveBeenCalledWith(
"thinking content",
);
});
it("should log warning if no chat session is active", async () => {
await droneSession.onThinking("thinking content");
// No exception thrown, warning logged internally
expect(mockSocket.emit).not.toHaveBeenCalled();
});
});
describe("onResponse", () => {
it("should route response event to code session", async () => {
const mockCodeSession = {
socket: { emit: vi.fn() },
onResponse: vi.fn(),
};
vi.mocked(SocketService.getCodeSessionByChatSessionId).mockReturnValue(
mockCodeSession as any,
);
droneSession.setChatSessionId(mockChatSessionId);
await droneSession.onResponse("response content");
expect(SocketService.getCodeSessionByChatSessionId).toHaveBeenCalledWith(
mockChatSessionId,
);
expect(mockCodeSession.onResponse).toHaveBeenCalledWith(
"response content",
);
});
it("should log warning if no chat session is active", async () => {
await droneSession.onResponse("response content");
// No exception thrown, warning logged internally
expect(mockSocket.emit).not.toHaveBeenCalled();
});
});
describe("onToolCall", () => {
it("should route toolCall event to code session and update ChatTurn", async () => {
const mockCodeSession = {
socket: { emit: vi.fn() },
onToolCall: vi.fn(),
};
const mockTurn = {
blocks: [],
toolCalls: [],
stats: { toolCallCount: 0 },
save: vi.fn().mockResolvedValue(undefined),
};
vi.mocked(SocketService.getCodeSessionByChatSessionId).mockReturnValue(
mockCodeSession as any,
);
vi.mocked(ChatTurn.findById).mockResolvedValue(mockTurn as any);
droneSession.setChatSessionId(mockChatSessionId);
droneSession.setCurrentTurnId(mockTurnId);
await droneSession.onToolCall(
"call-123",
"readFile",
'{"path":"test.ts"}',
"file contents",
);
expect(SocketService.getCodeSessionByChatSessionId).toHaveBeenCalledWith(
mockChatSessionId,
);
expect(mockCodeSession.onToolCall).toHaveBeenCalledWith(
"call-123",
"readFile",
'{"path":"test.ts"}',
"file contents",
);
expect(ChatTurn.findById).toHaveBeenCalledWith(mockTurnId);
expect(mockTurn.toolCalls).toHaveLength(1);
expect(mockTurn.toolCalls[0]).toEqual({
callId: "call-123",
name: "readFile",
parameters: '{"path":"test.ts"}',
response: "file contents",
});
expect(mockTurn.stats.toolCallCount).toBe(1);
expect(mockTurn.save).toHaveBeenCalled();
});
it("should log warning if no chat session is active", async () => {
await droneSession.onToolCall("call-1", "test", "{}", "result");
// No exception thrown, warning logged internally
expect(mockSocket.emit).not.toHaveBeenCalled();
});
});
describe("onWorkOrderComplete", () => {
it("should update ChatTurn status and emit to code session on success", async () => {
const mockCodeSession = {
socket: { emit: vi.fn() },
onWorkOrderComplete: vi.fn(),
};
const mockTurn = {
status: ChatTurnStatus.Processing,
save: vi.fn().mockResolvedValue(undefined),
};
vi.mocked(SocketService.getCodeSessionByChatSessionId).mockReturnValue(
mockCodeSession as any,
);
vi.mocked(ChatTurn.findById).mockResolvedValue(mockTurn as any);
droneSession.setChatSessionId(mockChatSessionId);
await droneSession.onWorkOrderComplete(mockTurnId, true);
expect(ChatTurn.findById).toHaveBeenCalledWith(mockTurnId);
expect(mockTurn.status).toBe(ChatTurnStatus.Finished);
expect(mockTurn.save).toHaveBeenCalled();
expect(mockCodeSession.onWorkOrderComplete).toHaveBeenCalledWith(
mockTurnId,
true,
undefined,
);
expect(droneSession.currentTurnId).toBeUndefined();
});
it("should update ChatTurn to Error status on failure", async () => {
const mockCodeSession = {
socket: { emit: vi.fn() },
onWorkOrderComplete: vi.fn(),
};
const mockTurn = {
status: ChatTurnStatus.Processing,
errorMessage: "",
save: vi.fn().mockResolvedValue(undefined),
};
vi.mocked(SocketService.getCodeSessionByChatSessionId).mockReturnValue(
mockCodeSession as any,
);
vi.mocked(ChatTurn.findById).mockResolvedValue(mockTurn as any);
droneSession.setChatSessionId(mockChatSessionId);
await droneSession.onWorkOrderComplete(
mockTurnId,
false,
"Agent crashed",
);
expect(mockTurn.status).toBe(ChatTurnStatus.Error);
expect(mockTurn.errorMessage).toBe("Agent crashed");
expect(mockTurn.save).toHaveBeenCalled();
});
it("should log warning if no chat session is active", async () => {
await droneSession.onWorkOrderComplete(mockTurnId, true);
// No exception thrown, warning logged internally
expect(mockSocket.emit).not.toHaveBeenCalled();
});
});
describe("onRequestTermination", () => {
it("should forward requestTermination to drone socket with logging", async () => {
const mockCallback = vi.fn();
const mockDroneCallback = vi.fn();
mockSocket.emit.mockImplementation((event: string, ...args: any[]) => {
if (event === "requestTermination" && args.length > 0) {
const cb = args[args.length - 1];
if (typeof cb === "function") {
cb(true);
}
}
});
await droneSession.onRequestTermination(mockCallback);
expect(mockSocket.emit).toHaveBeenCalledWith(
"requestTermination",
expect.any(Function),
);
expect(mockCallback).toHaveBeenCalledWith(true);
});
it("should pass through failure response from drone", async () => {
const mockCallback = vi.fn();
mockSocket.emit.mockImplementation((event: string, ...args: any[]) => {
if (event === "requestTermination" && args.length > 0) {
const cb = args[args.length - 1];
if (typeof cb === "function") {
cb(false);
}
}
});
await droneSession.onRequestTermination(mockCallback);
expect(mockSocket.emit).toHaveBeenCalledWith(
"requestTermination",
expect.any(Function),
);
expect(mockCallback).toHaveBeenCalledWith(false);
});
});
});