""" Tests for unified error handling system Tests the exception hierarchy, error handler middleware, and error response format. """ import pytest from datetime import datetime from src.utils.errors import ( AppException, BusinessException, SystemException, ErrorCode, InvalidParameterException, ResourceNotFoundException, ProjectNotFoundException, TaskNotFoundException, TaskTimeoutException, TaskQueueFullException, ModelNotFoundException, GenerationFailedException, StorageException, RateLimitExceededException, UnauthorizedException, ForbiddenException, ConflictException ) class TestExceptionHierarchy: """Test exception class hierarchy and initialization""" def test_app_exception_base(self): """Test AppException base class""" exc = AppException( code=ErrorCode.UNKNOWN_ERROR, message="Test error", details={"key": "value"}, status_code=500 ) assert exc.code == ErrorCode.UNKNOWN_ERROR assert exc.message == "Test error" assert exc.details == {"key": "value"} assert exc.status_code == 500 # Test to_dict conversion exc_dict = exc.to_dict() assert exc_dict["code"] == "1000" assert exc_dict["message"] == "Test error" assert exc_dict["details"] == {"key": "value"} def test_business_exception(self): """Test BusinessException defaults to 400 status""" exc = BusinessException( code=ErrorCode.INVALID_PARAMETER, message="Invalid input" ) assert exc.status_code == 400 assert isinstance(exc, AppException) def test_system_exception(self): """Test SystemException defaults to 500 status""" exc = SystemException( code=ErrorCode.UNKNOWN_ERROR, message="System failure" ) assert exc.status_code == 500 assert isinstance(exc, AppException) class TestBusinessExceptions: """Test specific business exception classes""" def test_invalid_parameter_exception(self): """Test InvalidParameterException""" exc = InvalidParameterException(field="email", reason="Invalid format") assert exc.code == ErrorCode.INVALID_PARAMETER assert "email" in exc.message assert exc.details["field"] == "email" assert exc.details["reason"] == "Invalid format" assert exc.status_code == 400 def test_resource_not_found_exception(self): """Test ResourceNotFoundException""" exc = ResourceNotFoundException(resource_type="User", resource_id="123") assert exc.code == ErrorCode.NOT_FOUND assert "User" in exc.message assert exc.details["resource_type"] == "User" assert exc.details["resource_id"] == "123" def test_project_not_found_exception(self): """Test ProjectNotFoundException""" exc = ProjectNotFoundException(project_id="proj_123") assert exc.code == ErrorCode.PROJECT_NOT_FOUND assert exc.details["project_id"] == "proj_123" assert isinstance(exc, BusinessException) def test_task_not_found_exception(self): """Test TaskNotFoundException""" exc = TaskNotFoundException(task_id="task_123") assert exc.code == ErrorCode.TASK_NOT_FOUND assert exc.details["task_id"] == "task_123" def test_model_not_found_exception(self): """Test ModelNotFoundException""" exc = ModelNotFoundException(model_id="flux-pro") assert exc.code == ErrorCode.MODEL_NOT_FOUND assert exc.details["model_id"] == "flux-pro" def test_rate_limit_exceeded_exception(self): """Test RateLimitExceededException""" exc = RateLimitExceededException(limit=100, window=60) assert exc.code == ErrorCode.RATE_LIMIT_EXCEEDED assert exc.status_code == 429 assert exc.details["limit"] == 100 assert exc.details["window_seconds"] == 60 def test_unauthorized_exception(self): """Test UnauthorizedException""" exc = UnauthorizedException(reason="Invalid token") assert exc.code == ErrorCode.UNAUTHORIZED assert exc.status_code == 401 assert exc.details["reason"] == "Invalid token" def test_forbidden_exception(self): """Test ForbiddenException""" exc = ForbiddenException(reason="Insufficient permissions") assert exc.code == ErrorCode.FORBIDDEN assert exc.status_code == 403 def test_conflict_exception(self): """Test ConflictException""" exc = ConflictException(resource_type="Project", reason="Name already exists") assert exc.code == ErrorCode.CONFLICT assert exc.status_code == 409 class TestSystemExceptions: """Test specific system exception classes""" def test_task_timeout_exception(self): """Test TaskTimeoutException""" exc = TaskTimeoutException(task_id="task_123", timeout=300) assert exc.code == ErrorCode.TASK_TIMEOUT assert exc.status_code == 500 assert exc.details["task_id"] == "task_123" assert exc.details["timeout_seconds"] == 300 assert isinstance(exc, SystemException) def test_task_queue_full_exception(self): """Test TaskQueueFullException""" exc = TaskQueueFullException(queue_size=1000) assert exc.code == ErrorCode.TASK_QUEUE_FULL assert exc.status_code == 500 assert exc.details["queue_size"] == 1000 def test_generation_failed_exception(self): """Test GenerationFailedException""" exc = GenerationFailedException(reason="API error", provider="dashscope") assert exc.code == ErrorCode.GENERATION_FAILED assert exc.status_code == 500 assert exc.details["reason"] == "API error" assert exc.details["provider"] == "dashscope" def test_storage_exception(self): """Test StorageException""" exc = StorageException(operation="upload", reason="Disk full") assert exc.code == ErrorCode.STORAGE_ERROR assert exc.status_code == 500 assert exc.details["operation"] == "upload" assert exc.details["reason"] == "Disk full" class TestErrorCodes: """Test error code enumeration""" def test_error_code_values(self): """Test error code values follow the format""" # Success assert ErrorCode.SUCCESS.value == "0000" # General errors (1xxx) assert ErrorCode.UNKNOWN_ERROR.value == "1000" assert ErrorCode.INVALID_PARAMETER.value == "1001" assert ErrorCode.NOT_FOUND.value == "1004" # Business errors (2xxx) assert ErrorCode.PROJECT_NOT_FOUND.value == "2001" assert ErrorCode.ASSET_NOT_FOUND.value == "2011" # Task errors (3xxx) assert ErrorCode.TASK_NOT_FOUND.value == "3002" assert ErrorCode.TASK_TIMEOUT.value == "3003" # AI service errors (4xxx) assert ErrorCode.MODEL_NOT_FOUND.value == "4001" assert ErrorCode.GENERATION_FAILED.value == "4003" # Storage errors (5xxx) assert ErrorCode.STORAGE_ERROR.value == "5001" assert ErrorCode.FILE_NOT_FOUND.value == "5002" def test_error_code_categories(self): """Test error codes are properly categorized""" # All general errors start with 1 assert ErrorCode.UNKNOWN_ERROR.value.startswith("1") assert ErrorCode.INVALID_PARAMETER.value.startswith("1") # All business errors start with 2 assert ErrorCode.PROJECT_NOT_FOUND.value.startswith("2") # All task errors start with 3 assert ErrorCode.TASK_TIMEOUT.value.startswith("3") # All AI service errors start with 4 assert ErrorCode.MODEL_NOT_FOUND.value.startswith("4") # All storage errors start with 5 assert ErrorCode.STORAGE_ERROR.value.startswith("5") class TestExceptionToDictConversion: """Test exception to dictionary conversion for API responses""" def test_simple_exception_to_dict(self): """Test basic exception to dict conversion""" exc = ProjectNotFoundException(project_id="proj_123") exc_dict = exc.to_dict() assert "code" in exc_dict assert "message" in exc_dict assert "details" in exc_dict assert exc_dict["code"] == "2001" assert exc_dict["details"]["project_id"] == "proj_123" def test_exception_with_complex_details(self): """Test exception with nested details""" exc = AppException( code=ErrorCode.INVALID_PARAMETER, message="Validation failed", details={ "errors": [ {"field": "email", "message": "Invalid format"}, {"field": "age", "message": "Must be positive"} ] } ) exc_dict = exc.to_dict() assert len(exc_dict["details"]["errors"]) == 2 assert exc_dict["details"]["errors"][0]["field"] == "email" if __name__ == "__main__": pytest.main([__file__, "-v"])