ci_mock = $this->createMock(stdClass::class); $this->model_mock = $this->createMock(stdClass::class); // Mock get_instance function if (!function_exists('get_instance')) { function get_instance() { return $GLOBALS['CI_INSTANCE']; } } $GLOBALS['CI_INSTANCE'] = $this->ci_mock; // Load and create ErrorHandler instance require_once 'modules/desk_moloni/libraries/ErrorHandler.php'; $this->error_handler = new ErrorHandler(); } #[Test] #[Group('unit')] public function testErrorHandlerInitialization(): void { $this->assertInstanceOf(ErrorHandler::class, $this->error_handler); // Test constants are defined $this->assertEquals('low', ErrorHandler::SEVERITY_LOW); $this->assertEquals('medium', ErrorHandler::SEVERITY_MEDIUM); $this->assertEquals('high', ErrorHandler::SEVERITY_HIGH); $this->assertEquals('critical', ErrorHandler::SEVERITY_CRITICAL); } #[Test] #[Group('unit')] #[DataProvider('errorSeverityProvider')] public function testLogError(string $severity, string $category, string $code, string $message): void { $context = [ 'entity_id' => 123, 'operation' => 'sync_customer', 'additional_data' => ['key' => 'value'] ]; $result = $this->error_handler->log_error($severity, $category, $code, $message, $context); $this->assertTrue($result); } public static function errorSeverityProvider(): array { return [ 'Low API Error' => [ ErrorHandler::SEVERITY_LOW, ErrorHandler::CATEGORY_API, ErrorHandler::ERROR_API_TIMEOUT, 'API request timed out' ], 'Medium Sync Error' => [ ErrorHandler::SEVERITY_MEDIUM, ErrorHandler::CATEGORY_SYNC, ErrorHandler::ERROR_SYNC_VALIDATION, 'Data validation failed' ], 'High Auth Error' => [ ErrorHandler::SEVERITY_HIGH, ErrorHandler::CATEGORY_AUTHENTICATION, ErrorHandler::ERROR_API_AUTHENTICATION, 'Authentication failed' ], 'Critical System Error' => [ ErrorHandler::SEVERITY_CRITICAL, ErrorHandler::CATEGORY_SYSTEM, 'SYSTEM_FAILURE', 'Critical system failure' ] ]; } #[Test] #[Group('unit')] public function testHandleException(): void { $exception = new Exception('Test exception message', 500); $context = ['operation' => 'test_operation']; $result = $this->error_handler->handle_exception($exception, $context); $this->assertIsArray($result); $this->assertArrayHasKey('error_id', $result); $this->assertArrayHasKey('severity', $result); $this->assertArrayHasKey('category', $result); $this->assertArrayHasKey('message', $result); } #[Test] #[Group('unit')] public function testApiErrorHandling(): void { $api_response = [ 'status_code' => 401, 'response' => ['error' => 'Unauthorized'], 'request_data' => ['endpoint' => 'customers/create'] ]; $result = $this->error_handler->handle_api_error($api_response); $this->assertIsArray($result); $this->assertEquals(ErrorHandler::CATEGORY_API, $result['category']); $this->assertEquals(ErrorHandler::ERROR_API_AUTHENTICATION, $result['code']); $this->assertEquals(ErrorHandler::SEVERITY_HIGH, $result['severity']); } #[Test] #[Group('unit')] #[DataProvider('apiStatusCodeProvider')] public function testApiStatusCodeMapping(int $status_code, string $expected_error_code, string $expected_severity): void { $api_response = [ 'status_code' => $status_code, 'response' => ['error' => 'API Error'], 'request_data' => ['endpoint' => 'test/endpoint'] ]; $result = $this->error_handler->handle_api_error($api_response); $this->assertEquals($expected_error_code, $result['code']); $this->assertEquals($expected_severity, $result['severity']); } public static function apiStatusCodeProvider(): array { return [ 'Timeout 408' => [408, ErrorHandler::ERROR_API_TIMEOUT, ErrorHandler::SEVERITY_MEDIUM], 'Unauthorized 401' => [401, ErrorHandler::ERROR_API_AUTHENTICATION, ErrorHandler::SEVERITY_HIGH], 'Rate Limited 429' => [429, ErrorHandler::ERROR_API_RATE_LIMIT, ErrorHandler::SEVERITY_MEDIUM], 'Server Error 500' => [500, ErrorHandler::ERROR_API_CONNECTION, ErrorHandler::SEVERITY_HIGH], 'Bad Gateway 502' => [502, ErrorHandler::ERROR_API_CONNECTION, ErrorHandler::SEVERITY_HIGH] ]; } #[Test] #[Group('unit')] public function testSyncConflictHandling(): void { $conflict_data = [ 'entity_type' => 'customer', 'perfex_id' => 123, 'moloni_id' => '456', 'conflicted_fields' => ['name', 'email'], 'perfex_data' => ['name' => 'Perfex Name'], 'moloni_data' => ['name' => 'Moloni Name'] ]; $result = $this->error_handler->handle_sync_conflict($conflict_data); $this->assertIsArray($result); $this->assertEquals(ErrorHandler::CATEGORY_SYNC, $result['category']); $this->assertEquals(ErrorHandler::ERROR_SYNC_CONFLICT, $result['code']); $this->assertEquals(ErrorHandler::SEVERITY_MEDIUM, $result['severity']); } #[Test] #[Group('unit')] public function testValidationErrorHandling(): void { $validation_errors = [ 'company' => 'Company name is required', 'email' => 'Invalid email format', 'vat' => 'VAT number format is invalid' ]; $context = [ 'entity_type' => 'customer', 'entity_id' => 789 ]; $result = $this->error_handler->handle_validation_errors($validation_errors, $context); $this->assertIsArray($result); $this->assertEquals(ErrorHandler::CATEGORY_VALIDATION, $result['category']); $this->assertEquals(ErrorHandler::ERROR_SYNC_VALIDATION, $result['code']); $this->assertEquals(ErrorHandler::SEVERITY_LOW, $result['severity']); $this->assertArrayHasKey('validation_errors', $result['context']); } #[Test] #[Group('unit')] public function testErrorSeverityEscalation(): void { // Simulate multiple errors of the same type to test escalation $error_data = [ 'category' => ErrorHandler::CATEGORY_API, 'code' => ErrorHandler::ERROR_API_CONNECTION, 'message' => 'Connection failed' ]; // First occurrence - should be medium severity $result1 = $this->error_handler->log_error( ErrorHandler::SEVERITY_MEDIUM, $error_data['category'], $error_data['code'], $error_data['message'] ); // Multiple occurrences should escalate severity (if implemented) $result2 = $this->error_handler->check_error_escalation($error_data['code']); if (is_array($result2)) { $this->assertArrayHasKey('escalated', $result2); $this->assertArrayHasKey('new_severity', $result2); } else { // Mark test as incomplete if escalation not implemented $this->markTestIncomplete('Error escalation not implemented'); } } #[Test] #[Group('unit')] public function testErrorNotification(): void { $critical_error = [ 'severity' => ErrorHandler::SEVERITY_CRITICAL, 'category' => ErrorHandler::CATEGORY_SYSTEM, 'code' => 'SYSTEM_FAILURE', 'message' => 'Critical system failure detected' ]; // Should trigger notifications for critical errors $result = $this->error_handler->send_error_notification($critical_error); if (method_exists($this->error_handler, 'send_error_notification')) { $this->assertTrue($result); } else { $this->markTestSkipped('Error notification not implemented'); } } #[Test] #[Group('unit')] public function testErrorFiltering(): void { // Test error filtering by category $filters = [ 'category' => ErrorHandler::CATEGORY_API, 'severity' => ErrorHandler::SEVERITY_HIGH, 'date_from' => date('Y-m-d', strtotime('-7 days')), 'date_to' => date('Y-m-d') ]; $result = $this->error_handler->get_filtered_errors($filters); if (method_exists($this->error_handler, 'get_filtered_errors')) { $this->assertIsArray($result); $this->assertArrayHasKey('errors', $result); $this->assertArrayHasKey('total_count', $result); } else { $this->markTestSkipped('Error filtering not implemented'); } } #[Test] #[Group('unit')] public function testErrorStatistics(): void { $result = $this->error_handler->get_error_statistics(); if (method_exists($this->error_handler, 'get_error_statistics')) { $this->assertIsArray($result); $this->assertArrayHasKey('total_errors', $result); $this->assertArrayHasKey('by_severity', $result); $this->assertArrayHasKey('by_category', $result); $this->assertArrayHasKey('recent_errors', $result); } else { $this->markTestSkipped('Error statistics not implemented'); } } #[Test] #[Group('unit')] public function testErrorContext(): void { $context = [ 'user_id' => 1, 'session_id' => 'sess_123456', 'ip_address' => '192.168.1.1', 'user_agent' => 'Mozilla/5.0...', 'request_url' => '/admin/desk_moloni/sync', 'request_method' => 'POST', 'request_data' => ['action' => 'sync_customer'], 'memory_usage' => memory_get_usage(true), 'execution_time' => 0.5 ]; $result = $this->error_handler->log_error( ErrorHandler::SEVERITY_LOW, ErrorHandler::CATEGORY_SYNC, 'TEST_ERROR', 'Test error with context', $context ); $this->assertTrue($result); } #[Test] #[Group('unit')] public function testErrorSanitization(): void { // Test that sensitive data is sanitized from error logs $context = [ 'password' => 'secret123', 'client_secret' => 'very_secret', 'api_key' => 'api_key_value', 'access_token' => 'token_value', 'normal_field' => 'normal_value' ]; $sanitized_context = $this->error_handler->sanitize_context($context); if (method_exists($this->error_handler, 'sanitize_context')) { $this->assertEquals('***', $sanitized_context['password']); $this->assertEquals('***', $sanitized_context['client_secret']); $this->assertEquals('***', $sanitized_context['api_key']); $this->assertEquals('***', $sanitized_context['access_token']); $this->assertEquals('normal_value', $sanitized_context['normal_field']); } else { // If method doesn't exist, test that sensitive fields are handled internally $result = $this->error_handler->log_error( ErrorHandler::SEVERITY_LOW, ErrorHandler::CATEGORY_SYSTEM, 'TEST_SANITIZATION', 'Test error with sensitive data', $context ); $this->assertTrue($result); } } #[Test] #[Group('unit')] public function testErrorRecovery(): void { $error_data = [ 'code' => ErrorHandler::ERROR_API_CONNECTION, 'category' => ErrorHandler::CATEGORY_API, 'context' => [ 'endpoint' => 'customers/create', 'entity_id' => 123 ] ]; $recovery_result = $this->error_handler->attempt_error_recovery($error_data); if (method_exists($this->error_handler, 'attempt_error_recovery')) { $this->assertIsArray($recovery_result); $this->assertArrayHasKey('success', $recovery_result); $this->assertArrayHasKey('recovery_action', $recovery_result); } else { $this->markTestSkipped('Error recovery not implemented'); } } #[Test] #[Group('unit')] public function testErrorRetry(): void { $error_data = [ 'code' => ErrorHandler::ERROR_API_TIMEOUT, 'category' => ErrorHandler::CATEGORY_API, 'retry_count' => 1, 'max_retries' => 3 ]; $should_retry = $this->error_handler->should_retry_after_error($error_data); if (method_exists($this->error_handler, 'should_retry_after_error')) { $this->assertTrue($should_retry); } else { $this->markTestSkipped('Error retry logic not implemented'); } // Test max retries exceeded $error_data['retry_count'] = 4; $should_not_retry = $this->error_handler->should_retry_after_error($error_data); if (method_exists($this->error_handler, 'should_retry_after_error')) { $this->assertFalse($should_not_retry); } } protected function tearDown(): void { $this->error_handler = null; $this->ci_mock = null; $this->model_mock = null; parent::tearDown(); } }