218 lines
6.7 KiB
TypeScript
218 lines
6.7 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';
|
|
|
|
// 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: new Types.ObjectId(),
|
|
email: 'test@example.com',
|
|
displayName: 'Test User',
|
|
} as IUser;
|
|
|
|
mockDrone = {
|
|
_id: new Types.ObjectId(),
|
|
hostname: 'test-host',
|
|
workspaceDir: '/test/workspace',
|
|
status: 'available',
|
|
} as IDroneRegistration;
|
|
|
|
mockChatSession = {
|
|
_id: new Types.ObjectId(),
|
|
name: 'Test Session',
|
|
mode: 'build',
|
|
provider: new Types.ObjectId(),
|
|
selectedModel: 'llama3.1',
|
|
} as IChatSession;
|
|
|
|
mockProject = {
|
|
_id: new Types.ObjectId(),
|
|
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: new Types.ObjectId(),
|
|
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: new Types.ObjectId(),
|
|
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.toHexString());
|
|
}),
|
|
},
|
|
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.toHexString());
|
|
});
|
|
|
|
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, '');
|
|
});
|
|
});
|
|
});
|