Raj718's picture
feat: Complete Task 8 - Error Handling & System Resilience with 36 tests passing
a0de322
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<typeof sessionManager>;
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<script>alert("xss")</script>',
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('<sanitized>')
},
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');
});
});
});