import { describe, it, expect, beforeEach, vi } from "vitest"; import { Types } from "mongoose"; import { CodeSession } from "../src/lib/code-session"; import { IChatSession, IProject, IUser, IDroneRegistration, ChatTurnStatus, } 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("CodeSession", () => { let mockSocket: any; let mockUser: IUser; let mockDrone: IDroneRegistration; let mockChatSession: IChatSession; let mockProject: IProject; let codeSession: CodeSession; beforeEach(() => { vi.clearAllMocks(); mockSocket = { id: "test-socket-id", on: vi.fn(), emit: vi.fn(), }; mockUser = { _id: nanoid(), email: "test@example.com", displayName: "Test User", } as IUser; mockDrone = { _id: nanoid(), hostname: "test-host", workspaceDir: "/test/workspace", status: "available", } as IDroneRegistration; mockChatSession = { _id: nanoid(), name: "Test Session", mode: "build", provider: nanoid(), selectedModel: "llama3.1", } as IChatSession; mockProject = { _id: nanoid(), slug: "test-project", name: "Test Project", } as IProject; codeSession = new CodeSession(mockSocket, mockUser); }); describe("setSelectedDrone", () => { it("should set the selected drone", () => { codeSession.setSelectedDrone(mockDrone); // Can't directly access private property, but we can verify through behavior expect(() => codeSession.setSelectedDrone(mockDrone)).not.toThrow(); }); }); describe("setChatSession", () => { it("should set the chat session and project", () => { codeSession.setChatSession(mockChatSession, mockProject); expect(() => codeSession.setChatSession(mockChatSession, mockProject), ).not.toThrow(); }); }); describe("onSubmitPrompt", () => { it("should throw error if no drone is selected", async () => { codeSession.setChatSession(mockChatSession, mockProject); await expect(codeSession.onSubmitPrompt("test prompt")).rejects.toThrow( "No drone selected", ); }); it("should throw error if no chat session is active", async () => { codeSession.setSelectedDrone(mockDrone); await expect(codeSession.onSubmitPrompt("test prompt")).rejects.toThrow( "No chat session active", ); }); it("should throw error if no project is selected", async () => { codeSession.setSelectedDrone(mockDrone); codeSession.setChatSession(mockChatSession, undefined as any); await expect(codeSession.onSubmitPrompt("test prompt")).rejects.toThrow( "No project selected", ); }); it("should create a ChatTurn and emit processWorkOrder to drone", async () => { codeSession.setSelectedDrone(mockDrone); codeSession.setChatSession(mockChatSession, mockProject); const mockTurn = { _id: nanoid(), save: vi.fn().mockResolvedValue(undefined), }; vi.mocked(ChatTurn).mockImplementation(function () { return mockTurn as any; }); const mockDroneSession = { socket: { emit: vi.fn(), }, setChatSessionId: vi.fn(), setCurrentTurnId: vi.fn(), }; vi.mocked(SocketService.getDroneSession).mockReturnValue( mockDroneSession as any, ); await codeSession.onSubmitPrompt("test prompt"); expect(ChatTurn).toHaveBeenCalledWith( expect.objectContaining({ user: mockUser._id, project: mockProject._id, session: mockChatSession._id, provider: mockChatSession.provider, llm: mockChatSession.selectedModel, status: ChatTurnStatus.Processing, prompts: { user: "test prompt", system: undefined, }, }), ); expect(mockTurn.save).toHaveBeenCalled(); expect(mockDroneSession.socket.emit).toHaveBeenCalledWith( "processWorkOrder", mockDrone, mockProject, mockChatSession, mockTurn, expect.any(Function), ); }); it("should update ChatTurn to Error status if drone rejects work order", async () => { codeSession.setSelectedDrone(mockDrone); codeSession.setChatSession(mockChatSession, mockProject); const mockTurn = { _id: nanoid(), status: ChatTurnStatus.Processing, response: "", save: vi.fn().mockResolvedValue(undefined), }; vi.mocked(ChatTurn).mockImplementation(function () { return mockTurn as any; }); const mockDroneSession = { socket: { emit: vi.fn((event, ...args) => { const callback = args[args.length - 1]; callback(false, "Drone is busy"); }), }, setChatSessionId: vi.fn(), setCurrentTurnId: vi.fn(), }; vi.mocked(SocketService.getDroneSession).mockReturnValue( mockDroneSession as any, ); await codeSession.onSubmitPrompt("test prompt"); expect(mockTurn.status).toBe(ChatTurnStatus.Error); expect(mockTurn.response).toBe("Drone is busy"); expect(mockTurn.save).toHaveBeenCalled(); }); }); describe("onRequestSessionLock", () => { it("should set selected drone, chat session, and project on success", () => { const mockDroneSession = { socket: { emit: vi.fn((event, ...args) => { const callback = args[args.length - 1]; callback(true, mockChatSession._id); }), }, setChatSessionId: vi.fn(), setCurrentTurnId: vi.fn(), }; vi.mocked(SocketService.getDroneSession).mockReturnValue( mockDroneSession as any, ); const callback = vi.fn(); codeSession.onRequestSessionLock( mockDrone, mockProject, mockChatSession, callback, ); expect(callback).toHaveBeenCalledWith(true, mockChatSession._id); }); it("should not set session data on failure", () => { const mockDroneSession = { socket: { emit: vi.fn((event, ...args) => { const callback = args[args.length - 1]; callback(false, ""); }), }, setChatSessionId: vi.fn(), setCurrentTurnId: vi.fn(), }; vi.mocked(SocketService.getDroneSession).mockReturnValue( mockDroneSession as any, ); const callback = vi.fn(); codeSession.onRequestSessionLock( mockDrone, mockProject, mockChatSession, callback, ); expect(callback).toHaveBeenCalledWith(false, ""); }); }); });