import { describe, it, expect, vi, beforeEach } from 'vitest'; import { ConversationService } from '@/lib/control-center/conversation-service'; import { prisma } from '@/lib/db'; // Get the mocked prisma instance const mockPrisma = vi.mocked(prisma); describe('ConversationService', () => { let conversationService: ConversationService; beforeEach(() => { conversationService = new ConversationService(); vi.clearAllMocks(); }); describe('create', () => { it('should create a conversation with userId and title', async () => { const mockConversation = { id: 'conv-123', userId: 'user-456', title: 'Test Conversation', createdAt: new Date(), updatedAt: new Date() }; mockPrisma.controlCenterConversation.create.mockResolvedValue(mockConversation); const result = await conversationService.create('user-456', 'Test Conversation'); expect(mockPrisma.controlCenterConversation.create).toHaveBeenCalledWith({ data: { userId: 'user-456', title: 'Test Conversation' } }); expect(result).toEqual(mockConversation); }); it('should create a conversation without title', async () => { const mockConversation = { id: 'conv-123', userId: 'user-456', title: null, createdAt: new Date(), updatedAt: new Date() }; mockPrisma.controlCenterConversation.create.mockResolvedValue(mockConversation); const result = await conversationService.create('user-456'); expect(mockPrisma.controlCenterConversation.create).toHaveBeenCalledWith({ data: { userId: 'user-456', title: undefined } }); expect(result).toEqual(mockConversation); }); }); describe('getById', () => { it('should return conversation with messages ordered by createdAt', async () => { const mockConversation = { id: 'conv-123', userId: 'user-456', title: 'Test Conversation', createdAt: new Date(), updatedAt: new Date(), messages: [ { id: 'msg-1', conversationId: 'conv-123', role: 'user', content: 'Hello', createdAt: new Date('2024-01-01') }, { id: 'msg-2', conversationId: 'conv-123', role: 'assistant', content: 'Hi there!', createdAt: new Date('2024-01-02') } ] }; mockPrisma.controlCenterConversation.findUnique.mockResolvedValue(mockConversation); const result = await conversationService.getById('conv-123'); expect(mockPrisma.controlCenterConversation.findUnique).toHaveBeenCalledWith({ where: { id: 'conv-123' }, include: { messages: { orderBy: { createdAt: 'asc' } } } }); expect(result).toEqual(mockConversation); expect(result?.messages).toHaveLength(2); }); it('should return null for non-existent conversation', async () => { mockPrisma.controlCenterConversation.findUnique.mockResolvedValue(null); const result = await conversationService.getById('non-existent'); expect(result).toBeNull(); }); }); describe('listByUser', () => { it('should return paginated conversations for a user', async () => { const mockConversations = [ { id: 'conv-1', userId: 'user-456', title: 'Conversation 1', createdAt: new Date(), updatedAt: new Date(), _count: { messages: 5 } }, { id: 'conv-2', userId: 'user-456', title: 'Conversation 2', createdAt: new Date(), updatedAt: new Date(), _count: { messages: 3 } } ]; mockPrisma.controlCenterConversation.findMany.mockResolvedValue(mockConversations); const result = await conversationService.listByUser('user-456', 20, 0); expect(mockPrisma.controlCenterConversation.findMany).toHaveBeenCalledWith({ where: { userId: 'user-456' }, orderBy: { updatedAt: 'desc' }, take: 20, skip: 0, include: { _count: { select: { messages: true } } } }); expect(result).toEqual(mockConversations); expect(result).toHaveLength(2); }); it('should use default pagination values', async () => { mockPrisma.controlCenterConversation.findMany.mockResolvedValue([]); await conversationService.listByUser('user-456'); expect(mockPrisma.controlCenterConversation.findMany).toHaveBeenCalledWith({ where: { userId: 'user-456' }, orderBy: { updatedAt: 'desc' }, take: 20, skip: 0, include: { _count: { select: { messages: true } } } }); }); it('should apply custom pagination parameters', async () => { mockPrisma.controlCenterConversation.findMany.mockResolvedValue([]); await conversationService.listByUser('user-456', 10, 5); expect(mockPrisma.controlCenterConversation.findMany).toHaveBeenCalledWith({ where: { userId: 'user-456' }, orderBy: { updatedAt: 'desc' }, take: 10, skip: 5, include: { _count: { select: { messages: true } } } }); }); }); describe('addMessage', () => { it('should add a message and update conversation timestamp', async () => { const mockMessage = { id: 'msg-123', conversationId: 'conv-456', role: 'user', content: 'Hello!', toolCalls: null, toolResults: null, model: null, inputTokens: null, outputTokens: null, createdAt: new Date() }; mockPrisma.controlCenterConversation.update.mockResolvedValue({} as any); mockPrisma.controlCenterMessage.create.mockResolvedValue(mockMessage); const messageData = { role: 'user' as const, content: 'Hello!' }; const result = await conversationService.addMessage('conv-456', messageData); expect(mockPrisma.controlCenterConversation.update).toHaveBeenCalledWith({ where: { id: 'conv-456' }, data: { updatedAt: expect.any(Date) } }); expect(mockPrisma.controlCenterMessage.create).toHaveBeenCalledWith({ data: { conversationId: 'conv-456', ...messageData } }); expect(result).toEqual(mockMessage); }); it('should add assistant message with tool calls and usage data', async () => { const toolCalls = [{ id: 'tc-1', name: 'get_contacts', input: {} }]; const toolResults = [{ toolCallId: 'tc-1', success: true, result: [] }]; const mockMessage = { id: 'msg-123', conversationId: 'conv-456', role: 'assistant', content: 'Here are your contacts', toolCalls, toolResults, model: 'claude-sonnet-4-20250514', inputTokens: 100, outputTokens: 50, createdAt: new Date() }; mockPrisma.controlCenterConversation.update.mockResolvedValue({} as any); mockPrisma.controlCenterMessage.create.mockResolvedValue(mockMessage); const messageData = { role: 'assistant' as const, content: 'Here are your contacts', toolCalls, toolResults, model: 'claude-sonnet-4-20250514', inputTokens: 100, outputTokens: 50 }; const result = await conversationService.addMessage('conv-456', messageData); expect(mockPrisma.controlCenterMessage.create).toHaveBeenCalledWith({ data: { conversationId: 'conv-456', ...messageData } }); expect(result.model).toBe('claude-sonnet-4-20250514'); expect(result.inputTokens).toBe(100); expect(result.outputTokens).toBe(50); }); }); describe('updateTitle', () => { it('should update conversation title', async () => { const mockConversation = { id: 'conv-123', userId: 'user-456', title: 'New Title', createdAt: new Date(), updatedAt: new Date() }; mockPrisma.controlCenterConversation.update.mockResolvedValue(mockConversation); const result = await conversationService.updateTitle('conv-123', 'New Title'); expect(mockPrisma.controlCenterConversation.update).toHaveBeenCalledWith({ where: { id: 'conv-123' }, data: { title: 'New Title' } }); expect(result.title).toBe('New Title'); }); }); describe('delete', () => { it('should delete a conversation', async () => { const mockConversation = { id: 'conv-123', userId: 'user-456', title: 'Deleted', createdAt: new Date(), updatedAt: new Date() }; mockPrisma.controlCenterConversation.delete.mockResolvedValue(mockConversation); const result = await conversationService.delete('conv-123'); expect(mockPrisma.controlCenterConversation.delete).toHaveBeenCalledWith({ where: { id: 'conv-123' } }); expect(result).toEqual(mockConversation); }); it('should throw error for non-existent conversation', async () => { mockPrisma.controlCenterConversation.delete.mockRejectedValue( new Error('Record not found') ); await expect(conversationService.delete('non-existent')).rejects.toThrow( 'Record not found' ); }); }); });