gadget/gadget-code/tests/code-session.test.ts

248 lines
6.8 KiB
TypeScript

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