cre-sync/__tests__/lib/control-center/conversation-service.test.ts
BusyBee3333 4e6467ffb0 Add CRESync CRM application with Setup page
- Build complete Next.js CRM for commercial real estate
- Add authentication with JWT sessions and role-based access
- Add GoHighLevel API integration for contacts, conversations, opportunities
- Add AI-powered Control Center with tool calling
- Add Setup page with onboarding checklist (/setup)
- Add sidebar navigation with Setup menu item
- Fix type errors in onboarding API, GHL services, and control center tools
- Add Prisma schema with SQLite for local development
- Add UI components with clay morphism design system

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-14 17:30:55 -05:00

297 lines
9.1 KiB
TypeScript

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'
);
});
});
});