- Implemented reasoning effort setting in SESSION panel of Chat Sessio View - Removed all ability to "sign up" for an account
323 lines
9.6 KiB
TypeScript
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);
|
|
});
|
|
});
|
|
});
|