import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { NextRequest } from 'next/server'; import { GET } from '@/app/api/v1/control-center/conversations/route'; import { prisma } from '@/lib/db'; import * as auth from '@/lib/auth'; // Get the mocked prisma instance const mockPrisma = vi.mocked(prisma); // Mock the auth module vi.mock('@/lib/auth', () => ({ getSession: vi.fn() })); // Mock the conversationService through the control-center module vi.mock('@/lib/control-center', () => ({ conversationService: { listByUser: vi.fn() } })); // Import the mocked conversation service import { conversationService } from '@/lib/control-center'; const mockConversationService = vi.mocked(conversationService); describe('Conversations API', () => { beforeEach(() => { vi.clearAllMocks(); }); afterEach(() => { vi.resetAllMocks(); }); describe('GET /api/v1/control-center/conversations', () => { it('should return 401 when not authenticated', async () => { vi.mocked(auth.getSession).mockResolvedValue(null); const request = new NextRequest('http://localhost:3000/api/v1/control-center/conversations'); const response = await GET(request); expect(response.status).toBe(401); const json = await response.json(); expect(json.error).toBe('Unauthorized'); }); it('should return conversations for authenticated user', async () => { const mockUser = { id: 'user-123', email: 'test@example.com', role: 'user' as const, createdAt: new Date() }; const mockConversations = [ { id: 'conv-1', userId: 'user-123', title: 'First conversation', createdAt: new Date('2024-01-01'), updatedAt: new Date('2024-01-01'), _count: { messages: 5 } }, { id: 'conv-2', userId: 'user-123', title: 'Second conversation', createdAt: new Date('2024-01-02'), updatedAt: new Date('2024-01-02'), _count: { messages: 3 } } ]; vi.mocked(auth.getSession).mockResolvedValue({ user: mockUser }); mockConversationService.listByUser.mockResolvedValue(mockConversations); mockPrisma.controlCenterConversation.count.mockResolvedValue(2); const request = new NextRequest('http://localhost:3000/api/v1/control-center/conversations'); const response = await GET(request); expect(response.status).toBe(200); const json = await response.json(); expect(json.conversations).toHaveLength(2); expect(json.total).toBe(2); expect(json.limit).toBe(20); expect(json.offset).toBe(0); expect(mockConversationService.listByUser).toHaveBeenCalledWith('user-123', 20, 0); }); it('should handle pagination parameters correctly', async () => { const mockUser = { id: 'user-456', email: 'test@example.com', role: 'user' as const, createdAt: new Date() }; vi.mocked(auth.getSession).mockResolvedValue({ user: mockUser }); mockConversationService.listByUser.mockResolvedValue([]); mockPrisma.controlCenterConversation.count.mockResolvedValue(50); const request = new NextRequest( 'http://localhost:3000/api/v1/control-center/conversations?limit=10&offset=20' ); const response = await GET(request); expect(response.status).toBe(200); const json = await response.json(); expect(json.limit).toBe(10); expect(json.offset).toBe(20); expect(mockConversationService.listByUser).toHaveBeenCalledWith('user-456', 10, 20); }); it('should use default pagination when parameters are invalid', async () => { const mockUser = { id: 'user-789', email: 'test@example.com', role: 'user' as const, createdAt: new Date() }; vi.mocked(auth.getSession).mockResolvedValue({ user: mockUser }); mockConversationService.listByUser.mockResolvedValue([]); mockPrisma.controlCenterConversation.count.mockResolvedValue(0); // Invalid parameters should parse as NaN, then parseInt will return NaN const request = new NextRequest( 'http://localhost:3000/api/v1/control-center/conversations?limit=invalid&offset=bad' ); const response = await GET(request); expect(response.status).toBe(200); const json = await response.json(); // parseInt('invalid') returns NaN, which is falsy, but the code uses || not ?? // So NaN will use the default value expect(mockConversationService.listByUser).toHaveBeenCalledWith( 'user-789', expect.any(Number), expect.any(Number) ); }); it('should handle empty conversation list', async () => { const mockUser = { id: 'new-user', email: 'new@example.com', role: 'user' as const, createdAt: new Date() }; vi.mocked(auth.getSession).mockResolvedValue({ user: mockUser }); mockConversationService.listByUser.mockResolvedValue([]); mockPrisma.controlCenterConversation.count.mockResolvedValue(0); const request = new NextRequest('http://localhost:3000/api/v1/control-center/conversations'); const response = await GET(request); expect(response.status).toBe(200); const json = await response.json(); expect(json.conversations).toEqual([]); expect(json.total).toBe(0); }); it('should return 500 on database error', async () => { const mockUser = { id: 'user-error', email: 'error@example.com', role: 'user' as const, createdAt: new Date() }; vi.mocked(auth.getSession).mockResolvedValue({ user: mockUser }); mockConversationService.listByUser.mockRejectedValue(new Error('Database connection failed')); // Suppress console.error for this test const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); const request = new NextRequest('http://localhost:3000/api/v1/control-center/conversations'); const response = await GET(request); expect(response.status).toBe(500); const json = await response.json(); expect(json.error).toBe('Failed to fetch conversations'); consoleSpy.mockRestore(); }); it('should count conversations correctly for the authenticated user only', async () => { const mockUser = { id: 'specific-user', email: 'specific@example.com', role: 'user' as const, createdAt: new Date() }; vi.mocked(auth.getSession).mockResolvedValue({ user: mockUser }); mockConversationService.listByUser.mockResolvedValue([]); mockPrisma.controlCenterConversation.count.mockResolvedValue(25); const request = new NextRequest('http://localhost:3000/api/v1/control-center/conversations'); const response = await GET(request); expect(response.status).toBe(200); const json = await response.json(); expect(json.total).toBe(25); expect(mockPrisma.controlCenterConversation.count).toHaveBeenCalledWith({ where: { userId: 'specific-user' } }); }); it('should return conversations with message count', async () => { const mockUser = { id: 'user-with-convos', email: 'convos@example.com', role: 'user' as const, createdAt: new Date() }; const mockConversations = [ { id: 'conv-with-messages', userId: 'user-with-convos', title: 'Conversation with messages', createdAt: new Date(), updatedAt: new Date(), _count: { messages: 15 } } ]; vi.mocked(auth.getSession).mockResolvedValue({ user: mockUser }); mockConversationService.listByUser.mockResolvedValue(mockConversations); mockPrisma.controlCenterConversation.count.mockResolvedValue(1); const request = new NextRequest('http://localhost:3000/api/v1/control-center/conversations'); const response = await GET(request); expect(response.status).toBe(200); const json = await response.json(); expect(json.conversations[0]._count.messages).toBe(15); }); }); describe('Pagination edge cases', () => { it('should handle large offset beyond total count', async () => { const mockUser = { id: 'user-large-offset', email: 'large@example.com', role: 'user' as const, createdAt: new Date() }; vi.mocked(auth.getSession).mockResolvedValue({ user: mockUser }); mockConversationService.listByUser.mockResolvedValue([]); mockPrisma.controlCenterConversation.count.mockResolvedValue(5); const request = new NextRequest( 'http://localhost:3000/api/v1/control-center/conversations?offset=100' ); const response = await GET(request); expect(response.status).toBe(200); const json = await response.json(); expect(json.conversations).toEqual([]); expect(json.total).toBe(5); expect(json.offset).toBe(100); }); it('should handle zero limit', async () => { const mockUser = { id: 'user-zero-limit', email: 'zero@example.com', role: 'user' as const, createdAt: new Date() }; vi.mocked(auth.getSession).mockResolvedValue({ user: mockUser }); mockConversationService.listByUser.mockResolvedValue([]); mockPrisma.controlCenterConversation.count.mockResolvedValue(10); const request = new NextRequest( 'http://localhost:3000/api/v1/control-center/conversations?limit=0' ); const response = await GET(request); expect(response.status).toBe(200); // Note: limit=0 with parseInt returns 0, which is falsy but a valid number // The API should handle this, returning 0 items }); it('should handle negative pagination values gracefully', async () => { const mockUser = { id: 'user-negative', email: 'negative@example.com', role: 'user' as const, createdAt: new Date() }; vi.mocked(auth.getSession).mockResolvedValue({ user: mockUser }); mockConversationService.listByUser.mockResolvedValue([]); mockPrisma.controlCenterConversation.count.mockResolvedValue(10); const request = new NextRequest( 'http://localhost:3000/api/v1/control-center/conversations?limit=-5&offset=-10' ); const response = await GET(request); // The API should still return a response (behavior depends on Prisma handling) expect(response.status).toBe(200); }); }); });