import { NextRequest } from 'next/server'; import { POST as createSession, GET as getSession, PUT as updateSession } from '../session/route'; import { POST as logActivity, GET as getActivities } from '../session/activity/route'; import { POST as trackAnalytics, GET as getAnalyticsReport } from '../analytics/route'; import { POST as trackPerformance } from '../analytics/performance/route'; import { POST as trackError } from '../analytics/error/route'; // Mock session manager jest.mock('@/lib/session-management', () => ({ sessionManager: { createSession: jest.fn(), getSession: jest.fn(), updateSessionStatus: jest.fn(), logActivity: jest.fn(), getSessionActivities: jest.fn(), trackAnalytics: jest.fn(), generateAnalyticsReport: jest.fn(), trackPerformance: jest.fn(), trackError: jest.fn(), } })); import { sessionManager } from '@/lib/session-management'; const mockSessionManager = sessionManager as jest.Mocked; describe('Session Management API', () => { beforeEach(() => { jest.clearAllMocks(); }); describe('POST /api/session', () => { it('should create a new session successfully', async () => { const mockSession = { id: 'test-session-123', createdAt: '2025-01-27T10:00:00Z', status: 'active' as const, metadata: { deviceType: 'desktop' } }; mockSessionManager.createSession.mockResolvedValue(mockSession); const request = new NextRequest('http://localhost:3000/api/session', { method: 'POST', body: JSON.stringify({ userAgent: 'Mozilla/5.0 (Test Browser)', ipAddress: '127.0.0.1', userId: null }) }); const response = await createSession(request); const data = await response.json(); expect(response.status).toBe(200); expect(data.success).toBe(true); expect(data.session.id).toBe('test-session-123'); expect(mockSessionManager.createSession).toHaveBeenCalledWith( 'Mozilla/5.0 (Test Browser)', '127.0.0.1', null ); }); it('should handle invalid user agent format', async () => { const request = new NextRequest('http://localhost:3000/api/session', { method: 'POST', body: JSON.stringify({ userAgent: 123, // Invalid type ipAddress: '127.0.0.1', userId: null }) }); const response = await createSession(request); const data = await response.json(); expect(response.status).toBe(400); expect(data.error).toBe('Invalid user agent format'); }); it('should handle session creation errors', async () => { mockSessionManager.createSession.mockRejectedValue(new Error('Database error')); const request = new NextRequest('http://localhost:3000/api/session', { method: 'POST', body: JSON.stringify({ userAgent: 'Mozilla/5.0 (Test Browser)', ipAddress: '127.0.0.1', userId: null }) }); const response = await createSession(request); const data = await response.json(); expect(response.status).toBe(500); expect(data.error).toBe('Failed to create session'); }); }); describe('GET /api/session', () => { it('should retrieve session data successfully', async () => { const mockSession = { id: 'test-session-123', createdAt: '2025-01-27T10:00:00Z', status: 'active' as const, metadata: { deviceType: 'desktop' } }; mockSessionManager.getSession.mockResolvedValue(mockSession); const request = new NextRequest('http://localhost:3000/api/session?sessionId=test-session-123'); const response = await getSession(request); const data = await response.json(); expect(response.status).toBe(200); expect(data.success).toBe(true); expect(data.session.id).toBe('test-session-123'); expect(mockSessionManager.getSession).toHaveBeenCalledWith('test-session-123'); }); it('should handle missing session ID', async () => { const request = new NextRequest('http://localhost:3000/api/session'); const response = await getSession(request); const data = await response.json(); expect(response.status).toBe(400); expect(data.error).toBe('Session ID is required'); }); it('should handle invalid session ID format', async () => { const request = new NextRequest('http://localhost:3000/api/session?sessionId=123'); const response = await getSession(request); const data = await response.json(); expect(response.status).toBe(400); expect(data.error).toBe('Invalid session ID format'); }); it('should handle session not found', async () => { mockSessionManager.getSession.mockResolvedValue(null); const request = new NextRequest('http://localhost:3000/api/session?sessionId=test-session-123'); const response = await getSession(request); const data = await response.json(); expect(response.status).toBe(404); expect(data.error).toBe('Session not found'); }); }); describe('PUT /api/session', () => { it('should update session status successfully', async () => { mockSessionManager.updateSessionStatus.mockResolvedValue(); const request = new NextRequest('http://localhost:3000/api/session?sessionId=test-session-123', { method: 'PUT', body: JSON.stringify({ status: 'expired' }) }); const response = await updateSession(request); const data = await response.json(); expect(response.status).toBe(200); expect(data.success).toBe(true); expect(mockSessionManager.updateSessionStatus).toHaveBeenCalledWith('test-session-123', 'expired'); }); it('should handle invalid status value', async () => { const request = new NextRequest('http://localhost:3000/api/session?sessionId=test-session-123', { method: 'PUT', body: JSON.stringify({ status: 'invalid' }) }); const response = await updateSession(request); const data = await response.json(); expect(response.status).toBe(400); expect(data.error).toBe('Invalid status value'); }); }); }); describe('Session Activity API', () => { beforeEach(() => { jest.clearAllMocks(); }); describe('POST /api/session/activity', () => { it('should log activity successfully', async () => { mockSessionManager.logActivity.mockResolvedValue(); const request = new NextRequest('http://localhost:3000/api/session/activity', { method: 'POST', body: JSON.stringify({ sessionId: 'test-session-123', activity: { type: 'document_upload', timestamp: '2025-01-27T10:00:00Z', metadata: { fileType: 'pdf', fileSize: 1024000 } } }) }); const response = await logActivity(request); const data = await response.json(); expect(response.status).toBe(200); expect(data.success).toBe(true); expect(mockSessionManager.logActivity).toHaveBeenCalledWith('test-session-123', { type: 'document_upload', timestamp: '2025-01-27T10:00:00Z', metadata: { fileType: 'pdf', fileSize: 1024000 } }); }); it('should handle invalid activity type', async () => { const request = new NextRequest('http://localhost:3000/api/session/activity', { method: 'POST', body: JSON.stringify({ sessionId: 'test-session-123', activity: { type: 'invalid_type', timestamp: '2025-01-27T10:00:00Z', metadata: {} } }) }); const response = await logActivity(request); const data = await response.json(); expect(response.status).toBe(400); expect(data.error).toBe('Invalid activity type'); }); it('should sanitize metadata to prevent injection', async () => { mockSessionManager.logActivity.mockResolvedValue(); const request = new NextRequest('http://localhost:3000/api/session/activity', { method: 'POST', body: JSON.stringify({ sessionId: 'test-session-123', activity: { type: 'document_upload', timestamp: '2025-01-27T10:00:00Z', metadata: { fileType: 'pdf', fileSize: 1024000, maliciousField: 'should be removed' } } }) }); const response = await logActivity(request); const data = await response.json(); expect(response.status).toBe(200); expect(data.success).toBe(true); // Verify that malicious content was sanitized expect(mockSessionManager.logActivity).toHaveBeenCalledWith('test-session-123', { type: 'document_upload', timestamp: '2025-01-27T10:00:00Z', metadata: { fileType: 'pdfscriptalert("xss")/script', // Script tags removed fileSize: 1024000 // maliciousField should be removed } }); }); }); describe('GET /api/session/activity', () => { it('should retrieve session activities successfully', async () => { const mockActivities = [ { type: 'document_upload', timestamp: '2025-01-27T10:00:00Z', metadata: { fileType: 'pdf' } }, { type: 'question_asked', timestamp: '2025-01-27T10:01:00Z', metadata: { questionLength: 50 } } ]; mockSessionManager.getSessionActivities.mockResolvedValue(mockActivities); const request = new NextRequest('http://localhost:3000/api/session/activity?sessionId=test-session-123'); const response = await getActivities(request); const data = await response.json(); expect(response.status).toBe(200); expect(data.success).toBe(true); expect(data.activities).toHaveLength(2); expect(mockSessionManager.getSessionActivities).toHaveBeenCalledWith('test-session-123'); }); }); }); describe('Analytics API', () => { beforeEach(() => { jest.clearAllMocks(); }); describe('POST /api/analytics', () => { it('should track analytics successfully', async () => { mockSessionManager.trackAnalytics.mockResolvedValue(); const request = new NextRequest('http://localhost:3000/api/analytics', { method: 'POST', body: JSON.stringify({ eventType: 'document_upload', sessionId: 'test-session-123', metrics: { fileType: 'pdf', fileSize: 1024000, processingTime: 2500 } }) }); const response = await trackAnalytics(request); const data = await response.json(); expect(response.status).toBe(200); expect(data.success).toBe(true); expect(mockSessionManager.trackAnalytics).toHaveBeenCalledWith({ eventType: 'document_upload', sessionId: 'test-session-123', timestamp: expect.any(String), metrics: { fileType: 'pdf', fileSize: 1024000, processingTime: 2500 } }); }); it('should handle invalid event type', async () => { const request = new NextRequest('http://localhost:3000/api/analytics', { method: 'POST', body: JSON.stringify({ eventType: 'invalid_event', sessionId: 'test-session-123', metrics: {} }) }); const response = await trackAnalytics(request); const data = await response.json(); expect(response.status).toBe(400); expect(data.error).toBe('Invalid event type'); }); }); describe('GET /api/analytics', () => { it('should generate analytics report successfully', async () => { const mockReport = { dailyUsage: [ { eventType: 'document_upload', count: 150 }, { eventType: 'ai_question', count: 300 } ], performanceTrends: [ { date: '2025-01-27', avgResponseTime: 1800, errorRate: 0.02 } ], errorRates: [ { type: 'redis_connection_failed', count: 5 } ], reportGenerated: '2025-01-27T10:00:00Z' }; mockSessionManager.generateAnalyticsReport.mockResolvedValue(mockReport); const request = new NextRequest('http://localhost:3000/api/analytics?startDate=2025-01-27&endDate=2025-01-28'); const response = await getAnalyticsReport(request); const data = await response.json(); expect(response.status).toBe(200); expect(data.success).toBe(true); expect(data.report).toEqual(mockReport); }); it('should handle missing date parameters', async () => { const request = new NextRequest('http://localhost:3000/api/analytics'); const response = await getAnalyticsReport(request); const data = await response.json(); expect(response.status).toBe(400); expect(data.error).toBe('Start date and end date are required'); }); it('should handle invalid date format', async () => { const request = new NextRequest('http://localhost:3000/api/analytics?startDate=invalid&endDate=2025-01-28'); const response = await getAnalyticsReport(request); const data = await response.json(); expect(response.status).toBe(400); expect(data.error).toBe('Invalid date format'); }); }); }); describe('Performance Analytics API', () => { beforeEach(() => { jest.clearAllMocks(); }); describe('POST /api/analytics/performance', () => { it('should track performance metrics successfully', async () => { mockSessionManager.trackPerformance.mockResolvedValue(); const request = new NextRequest('http://localhost:3000/api/analytics/performance', { method: 'POST', body: JSON.stringify({ sessionId: 'test-session-123', redisQueryTime: 45, geminiResponseTime: 1800, totalProcessingTime: 2500 }) }); const response = await trackPerformance(request); const data = await response.json(); expect(response.status).toBe(200); expect(data.success).toBe(true); expect(mockSessionManager.trackPerformance).toHaveBeenCalledWith({ eventType: 'performance_metric', sessionId: 'test-session-123', timestamp: expect.any(String), metrics: { redisQueryTime: 45, geminiResponseTime: 1800, totalProcessingTime: 2500, memoryUsage: expect.any(Number), userId: null } }); }); it('should return warnings for performance degradation', async () => { mockSessionManager.trackPerformance.mockResolvedValue(); const request = new NextRequest('http://localhost:3000/api/analytics/performance', { method: 'POST', body: JSON.stringify({ sessionId: 'test-session-123', redisQueryTime: 150, // Exceeds 100ms threshold geminiResponseTime: 3000, // Exceeds 2.5s threshold totalProcessingTime: 6000 // Exceeds 5s threshold }) }); const response = await trackPerformance(request); const data = await response.json(); expect(response.status).toBe(200); expect(data.success).toBe(true); expect(data.warnings).toContain('Redis query time exceeded 100ms threshold'); expect(data.warnings).toContain('Gemini response time exceeded 2.5s threshold'); expect(data.warnings).toContain('Total processing time exceeded 5s threshold'); }); }); }); describe('Error Analytics API', () => { beforeEach(() => { jest.clearAllMocks(); }); describe('POST /api/analytics/error', () => { it('should track error successfully', async () => { mockSessionManager.trackError.mockResolvedValue(); const request = new NextRequest('http://localhost:3000/api/analytics/error', { method: 'POST', body: JSON.stringify({ sessionId: 'test-session-123', error: { type: 'redis_connection_failed', message: 'Redis connection timeout', severity: 'high' }, responseTime: 5000 }) }); const response = await trackError(request); const data = await response.json(); expect(response.status).toBe(200); expect(data.success).toBe(true); expect(data.severity).toBe('high'); expect(mockSessionManager.trackError).toHaveBeenCalledWith({ eventType: 'error', sessionId: 'test-session-123', timestamp: expect.any(String), error: { type: 'redis_connection_failed', message: 'Redis connection timeout', severity: 'high' }, metrics: { responseTime: 5000, userId: null } }); }); it('should sanitize stack trace', async () => { mockSessionManager.trackError.mockResolvedValue(); const request = new NextRequest('http://localhost:3000/api/analytics/error', { method: 'POST', body: JSON.stringify({ sessionId: 'test-session-123', error: { type: 'test_error', message: 'Test error message', severity: 'medium', stack: 'Error: Test error\n at TestFunction (/path/to/file.js:10:20)\n at AnotherFunction (/path/to/another.js:15:25)' }, responseTime: 1000 }) }); const response = await trackError(request); const data = await response.json(); expect(response.status).toBe(200); expect(data.success).toBe(true); // Verify stack trace was sanitized expect(mockSessionManager.trackError).toHaveBeenCalledWith({ eventType: 'error', sessionId: 'test-session-123', timestamp: expect.any(String), error: { type: 'test_error', message: 'Test error message', severity: 'medium', stack: expect.stringContaining('') }, metrics: { responseTime: 1000, userId: null } }); }); it('should handle invalid error severity', async () => { const request = new NextRequest('http://localhost:3000/api/analytics/error', { method: 'POST', body: JSON.stringify({ sessionId: 'test-session-123', error: { type: 'test_error', message: 'Test error message', severity: 'invalid_severity' }, responseTime: 1000 }) }); const response = await trackError(request); const data = await response.json(); expect(response.status).toBe(400); expect(data.error).toBe('Valid error severity is required'); }); }); });