/** * Test script for comprehensive error handling and logging system * Tests all components of the error handling implementation */ const axios = require('axios'); const fs = require('fs'); const path = require('path'); const BASE_URL = 'http://localhost:3000'; const API_BASE = `${BASE_URL}/api`; class ErrorHandlingTester { constructor() { this.testResults = []; this.logDir = path.join(__dirname, 'logs'); } /** * Run all error handling tests */ async runAllTests() { console.log('๐Ÿงช Starting Error Handling and Logging Tests...\n'); try { // Test 1: Database Error Handling await this.testDatabaseErrors(); // Test 2: Authentication Error Handling await this.testAuthenticationErrors(); // Test 3: Validation Error Handling await this.testValidationErrors(); // Test 4: Rate Limiting Error Handling await this.testRateLimitingErrors(); // Test 5: Logging System await this.testLoggingSystem(); // Test 6: API Error Responses await this.testAPIErrorResponses(); // Test 7: Security Event Logging await this.testSecurityEventLogging(); // Generate test report this.generateTestReport(); } catch (error) { console.error('โŒ Test suite failed:', error.message); } } /** * Test database error handling */ async testDatabaseErrors() { console.log('๐Ÿ“Š Testing Database Error Handling...'); const tests = [ { name: 'Duplicate email registration', test: async () => { // First registration await this.makeRequest('POST', '/auth/register', { email: 'test@example.com', password: 'TestPassword123!' }); // Duplicate registration const response = await this.makeRequest('POST', '/auth/register', { email: 'test@example.com', password: 'AnotherPassword123!' }, false); return response.status === 409 && response.data.code === 'EMAIL_EXISTS'; } }, { name: 'Invalid user ID in bookmark creation', test: async () => { const response = await this.makeRequest('POST', '/bookmarks', { title: 'Test Bookmark', url: 'https://example.com' }, false, 'invalid-user-token'); return response.status === 401 || response.status === 400; } } ]; await this.runTestGroup('Database Errors', tests); } /** * Test authentication error handling */ async testAuthenticationErrors() { console.log('๐Ÿ” Testing Authentication Error Handling...'); const tests = [ { name: 'Invalid credentials login', test: async () => { const response = await this.makeRequest('POST', '/auth/login', { email: 'nonexistent@example.com', password: 'wrongpassword' }, false); return response.status === 401 && response.data.code === 'INVALID_CREDENTIALS'; } }, { name: 'Expired token access', test: async () => { const response = await this.makeRequest('GET', '/user/profile', {}, false, 'expired.token.here'); return response.status === 401 && response.data.code === 'TOKEN_EXPIRED'; } }, { name: 'Invalid token format', test: async () => { const response = await this.makeRequest('GET', '/user/profile', {}, false, 'invalid-token'); return response.status === 401 && response.data.code === 'INVALID_TOKEN'; } }, { name: 'Missing authentication token', test: async () => { const response = await this.makeRequest('GET', '/user/profile', {}, false); return response.status === 401; } } ]; await this.runTestGroup('Authentication Errors', tests); } /** * Test validation error handling */ async testValidationErrors() { console.log('โœ… Testing Validation Error Handling...'); const tests = [ { name: 'Invalid email format registration', test: async () => { const response = await this.makeRequest('POST', '/auth/register', { email: 'invalid-email', password: 'TestPassword123!' }, false); return response.status === 400 && response.data.code === 'VALIDATION_ERROR'; } }, { name: 'Weak password registration', test: async () => { const response = await this.makeRequest('POST', '/auth/register', { email: 'test2@example.com', password: '123' }, false); return response.status === 400 && response.data.code === 'VALIDATION_ERROR'; } }, { name: 'Missing required fields in bookmark creation', test: async () => { const validToken = await this.getValidToken(); const response = await this.makeRequest('POST', '/bookmarks', { title: '' // Missing title and URL }, false, validToken); return response.status === 400 && response.data.code === 'MISSING_REQUIRED_FIELDS'; } }, { name: 'Invalid URL format in bookmark', test: async () => { const validToken = await this.getValidToken(); const response = await this.makeRequest('POST', '/bookmarks', { title: 'Test Bookmark', url: 'not-a-valid-url' }, false, validToken); return response.status === 400 && response.data.code === 'VALIDATION_ERROR'; } } ]; await this.runTestGroup('Validation Errors', tests); } /** * Test rate limiting error handling */ async testRateLimitingErrors() { console.log('๐Ÿšฆ Testing Rate Limiting Error Handling...'); const tests = [ { name: 'Authentication rate limiting', test: async () => { // Make multiple rapid login attempts const promises = []; for (let i = 0; i < 10; i++) { promises.push( this.makeRequest('POST', '/auth/login', { email: 'test@example.com', password: 'wrongpassword' }, false) ); } const responses = await Promise.all(promises); const rateLimitedResponse = responses.find(r => r.status === 429); return rateLimitedResponse && rateLimitedResponse.data.code === 'RATE_LIMIT_EXCEEDED'; } } ]; await this.runTestGroup('Rate Limiting Errors', tests); } /** * Test logging system */ async testLoggingSystem() { console.log('๐Ÿ“ Testing Logging System...'); const tests = [ { name: 'Log files creation', test: async () => { // Check if log files are created const logFiles = ['app', 'auth', 'database', 'api', 'security']; const today = new Date().toISOString().split('T')[0]; let allFilesExist = true; for (const logType of logFiles) { const logFile = path.join(this.logDir, `${logType}-${today}.log`); if (!fs.existsSync(logFile)) { console.log(`โš ๏ธ Log file not found: ${logFile}`); allFilesExist = false; } } return allFilesExist; } }, { name: 'Authentication failure logging', test: async () => { // Generate an authentication failure await this.makeRequest('POST', '/auth/login', { email: 'test@example.com', password: 'wrongpassword' }, false); // Check if it was logged const today = new Date().toISOString().split('T')[0]; const authLogFile = path.join(this.logDir, `auth-${today}.log`); if (fs.existsSync(authLogFile)) { const logContent = fs.readFileSync(authLogFile, 'utf8'); return logContent.includes('Authentication failure'); } return false; } }, { name: 'API request logging', test: async () => { // Make an API request await this.makeRequest('GET', '/health', {}, false); // Check if it was logged const today = new Date().toISOString().split('T')[0]; const apiLogFile = path.join(this.logDir, `api-${today}.log`); if (fs.existsSync(apiLogFile)) { const logContent = fs.readFileSync(apiLogFile, 'utf8'); return logContent.includes('API request: GET /health'); } return false; } } ]; await this.runTestGroup('Logging System', tests); } /** * Test API error responses */ async testAPIErrorResponses() { console.log('๐ŸŒ Testing API Error Responses...'); const tests = [ { name: 'Consistent error response format', test: async () => { const response = await this.makeRequest('POST', '/auth/login', { email: 'invalid@example.com', password: 'wrongpassword' }, false); const hasRequiredFields = response.data.error && response.data.code && response.data.timestamp; return response.status === 401 && hasRequiredFields; } }, { name: '404 error handling', test: async () => { const response = await this.makeRequest('GET', '/nonexistent-endpoint', {}, false); return response.status === 404 && response.data.code === 'ROUTE_NOT_FOUND'; } }, { name: 'Error response security (no stack traces in production)', test: async () => { const response = await this.makeRequest('POST', '/auth/register', { email: 'invalid-email', password: 'weak' }, false); // In production, stack traces should not be exposed const hasStackTrace = response.data.stack !== undefined; const isProduction = process.env.NODE_ENV === 'production'; return !isProduction || !hasStackTrace; } } ]; await this.runTestGroup('API Error Responses', tests); } /** * Test security event logging */ async testSecurityEventLogging() { console.log('๐Ÿ”’ Testing Security Event Logging...'); const tests = [ { name: 'Rate limit security logging', test: async () => { // Trigger rate limiting const promises = []; for (let i = 0; i < 15; i++) { promises.push( this.makeRequest('POST', '/auth/login', { email: 'test@example.com', password: 'wrongpassword' }, false) ); } await Promise.all(promises); // Check security log const today = new Date().toISOString().split('T')[0]; const securityLogFile = path.join(this.logDir, `security-${today}.log`); if (fs.existsSync(securityLogFile)) { const logContent = fs.readFileSync(securityLogFile, 'utf8'); return logContent.includes('Security event'); } return false; } } ]; await this.runTestGroup('Security Event Logging', tests); } /** * Run a group of tests */ async runTestGroup(groupName, tests) { console.log(`\n--- ${groupName} ---`); for (const test of tests) { try { const result = await test.test(); const status = result ? 'โœ… PASS' : 'โŒ FAIL'; console.log(`${status}: ${test.name}`); this.testResults.push({ group: groupName, name: test.name, passed: result }); } catch (error) { console.log(`โŒ ERROR: ${test.name} - ${error.message}`); this.testResults.push({ group: groupName, name: test.name, passed: false, error: error.message }); } } } /** * Make HTTP request with error handling */ async makeRequest(method, endpoint, data = {}, expectSuccess = true, token = null) { const config = { method, url: `${API_BASE}${endpoint}`, data, validateStatus: () => true, // Don't throw on HTTP errors timeout: 10000 }; if (token) { config.headers = { 'Authorization': `Bearer ${token}` }; } try { const response = await axios(config); return response; } catch (error) { if (expectSuccess) { throw error; } return { status: error.response?.status || 500, data: error.response?.data || { error: error.message } }; } } /** * Get a valid authentication token for testing */ async getValidToken() { try { // Register a test user await this.makeRequest('POST', '/auth/register', { email: 'testuser@example.com', password: 'TestPassword123!' }); // Login to get token const response = await this.makeRequest('POST', '/auth/login', { email: 'testuser@example.com', password: 'TestPassword123!' }); // Extract token from cookie or response return 'valid-token-placeholder'; // This would need to be implemented based on your auth system } catch (error) { console.warn('Could not get valid token for testing:', error.message); return null; } } /** * Generate test report */ generateTestReport() { console.log('\n๐Ÿ“Š Test Report'); console.log('================'); const totalTests = this.testResults.length; const passedTests = this.testResults.filter(t => t.passed).length; const failedTests = totalTests - passedTests; console.log(`Total Tests: ${totalTests}`); console.log(`Passed: ${passedTests} โœ…`); console.log(`Failed: ${failedTests} โŒ`); console.log(`Success Rate: ${((passedTests / totalTests) * 100).toFixed(1)}%`); if (failedTests > 0) { console.log('\nโŒ Failed Tests:'); this.testResults .filter(t => !t.passed) .forEach(t => { console.log(` - ${t.group}: ${t.name}`); if (t.error) { console.log(` Error: ${t.error}`); } }); } // Save report to file const reportPath = path.join(__dirname, 'error-handling-test-report.json'); fs.writeFileSync(reportPath, JSON.stringify({ timestamp: new Date().toISOString(), summary: { total: totalTests, passed: passedTests, failed: failedTests, successRate: (passedTests / totalTests) * 100 }, results: this.testResults }, null, 2)); console.log(`\n๐Ÿ“„ Detailed report saved to: ${reportPath}`); } } // Run tests if this script is executed directly if (require.main === module) { const tester = new ErrorHandlingTester(); tester.runAllTests().catch(console.error); } module.exports = ErrorHandlingTester;