"""Unit tests for DTO validation utilities""" import pytest from unittest.mock import Mock from src.application.dtos.dto_validation import ( ValidationError, validate_dto, validation_required, validate_field, validate_required, validate_type, validate_range, validate_choices ) class TestValidationError: """Test cases for ValidationError""" def test_validation_error_basic(self): """Test basic ValidationError creation""" error = ValidationError("Test error message") assert str(error) == "Validation error: Test error message" assert error.message == "Test error message" assert error.field is None assert error.value is None def test_validation_error_with_field(self): """Test ValidationError with field information""" error = ValidationError("Invalid value", field="test_field", value="invalid_value") assert str(error) == "Validation error for field 'test_field': Invalid value" assert error.message == "Invalid value" assert error.field == "test_field" assert error.value == "invalid_value" def test_validation_error_without_field_but_with_value(self): """Test ValidationError without field but with value""" error = ValidationError("Invalid value", value="test_value") assert str(error) == "Validation error: Invalid value" assert error.message == "Invalid value" assert error.field is None assert error.value == "test_value" class TestValidateDto: """Test cases for validate_dto function""" def test_validate_dto_success_with_validate_method(self): """Test successful DTO validation with _validate method""" mock_dto = Mock() mock_dto._validate = Mock() result = validate_dto(mock_dto) assert result is True mock_dto._validate.assert_called_once() def test_validate_dto_success_without_validate_method(self): """Test successful DTO validation without _validate method""" mock_dto = Mock() del mock_dto._validate # Remove the _validate method result = validate_dto(mock_dto) assert result is True def test_validate_dto_failure_value_error(self): """Test DTO validation failure with ValueError""" mock_dto = Mock() mock_dto._validate.side_effect = ValueError("Validation failed") with pytest.raises(ValidationError, match="Validation failed"): validate_dto(mock_dto) def test_validate_dto_failure_unexpected_error(self): """Test DTO validation failure with unexpected error""" mock_dto = Mock() mock_dto._validate.side_effect = RuntimeError("Unexpected error") with pytest.raises(ValidationError, match="Validation failed: Unexpected error"): validate_dto(mock_dto) class TestValidationRequired: """Test cases for validation_required decorator""" def test_validation_required_success(self): """Test validation_required decorator with successful validation""" mock_instance = Mock() mock_instance._validate = Mock() @validation_required def test_method(self): return "success" result = test_method(mock_instance) assert result == "success" mock_instance._validate.assert_called_once() def test_validation_required_validation_error(self): """Test validation_required decorator with validation error""" mock_instance = Mock() mock_instance._validate.side_effect = ValueError("Validation failed") @validation_required def test_method(self): return "success" with pytest.raises(ValidationError, match="Validation failed"): test_method(mock_instance) def test_validation_required_method_error(self): """Test validation_required decorator with method execution error""" mock_instance = Mock() mock_instance._validate = Mock() @validation_required def test_method(self): raise RuntimeError("Method error") with pytest.raises(ValidationError, match="Error in test_method: Method error"): test_method(mock_instance) def test_validation_required_preserves_function_metadata(self): """Test that validation_required preserves function metadata""" @validation_required def test_method(self): """Test method docstring""" return "success" assert test_method.__name__ == "test_method" assert test_method.__doc__ == "Test method docstring" class TestValidateField: """Test cases for validate_field function""" def test_validate_field_success(self): """Test successful field validation""" def is_positive(value): return value > 0 result = validate_field(5, "test_field", is_positive) assert result == 5 def test_validate_field_failure_with_custom_message(self): """Test field validation failure with custom error message""" def is_positive(value): return value > 0 with pytest.raises(ValidationError, match="Custom error message"): validate_field(-1, "test_field", is_positive, "Custom error message") def test_validate_field_failure_with_default_message(self): """Test field validation failure with default error message""" def is_positive(value): return value > 0 with pytest.raises(ValidationError, match="Invalid value for field 'test_field'"): validate_field(-1, "test_field", is_positive) def test_validate_field_validator_exception(self): """Test field validation with validator raising exception""" def failing_validator(value): raise RuntimeError("Validator error") with pytest.raises(ValidationError, match="Validation error for field 'test_field': Validator error"): validate_field(5, "test_field", failing_validator) class TestValidateRequired: """Test cases for validate_required function""" def test_validate_required_success_with_value(self): """Test successful required validation with valid value""" result = validate_required("test_value", "test_field") assert result == "test_value" def test_validate_required_success_with_zero(self): """Test successful required validation with zero (falsy but valid)""" result = validate_required(0, "test_field") assert result == 0 def test_validate_required_success_with_false(self): """Test successful required validation with False (falsy but valid)""" result = validate_required(False, "test_field") assert result is False def test_validate_required_failure_with_none(self): """Test required validation failure with None""" with pytest.raises(ValidationError, match="Field 'test_field' is required"): validate_required(None, "test_field") def test_validate_required_failure_with_empty_string(self): """Test required validation failure with empty string""" with pytest.raises(ValidationError, match="Field 'test_field' cannot be empty"): validate_required("", "test_field") def test_validate_required_failure_with_empty_list(self): """Test required validation failure with empty list""" with pytest.raises(ValidationError, match="Field 'test_field' cannot be empty"): validate_required([], "test_field") def test_validate_required_failure_with_empty_dict(self): """Test required validation failure with empty dict""" with pytest.raises(ValidationError, match="Field 'test_field' cannot be empty"): validate_required({}, "test_field") class TestValidateType: """Test cases for validate_type function""" def test_validate_type_success_single_type(self): """Test successful type validation with single type""" result = validate_type("test", "test_field", str) assert result == "test" def test_validate_type_success_multiple_types(self): """Test successful type validation with multiple types""" result1 = validate_type("test", "test_field", (str, int)) assert result1 == "test" result2 = validate_type(123, "test_field", (str, int)) assert result2 == 123 def test_validate_type_failure_single_type(self): """Test type validation failure with single type""" with pytest.raises(ValidationError, match="Field 'test_field' must be of type str, got int"): validate_type(123, "test_field", str) def test_validate_type_failure_multiple_types(self): """Test type validation failure with multiple types""" with pytest.raises(ValidationError, match="Field 'test_field' must be of type str or int, got float"): validate_type(1.5, "test_field", (str, int)) class TestValidateRange: """Test cases for validate_range function""" def test_validate_range_success_within_range(self): """Test successful range validation within range""" result = validate_range(5, "test_field", min_value=1, max_value=10) assert result == 5 def test_validate_range_success_at_boundaries(self): """Test successful range validation at boundaries""" result1 = validate_range(1, "test_field", min_value=1, max_value=10) assert result1 == 1 result2 = validate_range(10, "test_field", min_value=1, max_value=10) assert result2 == 10 def test_validate_range_success_only_min(self): """Test successful range validation with only minimum""" result = validate_range(5, "test_field", min_value=1) assert result == 5 def test_validate_range_success_only_max(self): """Test successful range validation with only maximum""" result = validate_range(5, "test_field", max_value=10) assert result == 5 def test_validate_range_success_no_limits(self): """Test successful range validation with no limits""" result = validate_range(5, "test_field") assert result == 5 def test_validate_range_failure_below_minimum(self): """Test range validation failure below minimum""" with pytest.raises(ValidationError, match="Field 'test_field' must be >= 1, got 0"): validate_range(0, "test_field", min_value=1, max_value=10) def test_validate_range_failure_above_maximum(self): """Test range validation failure above maximum""" with pytest.raises(ValidationError, match="Field 'test_field' must be <= 10, got 11"): validate_range(11, "test_field", min_value=1, max_value=10) def test_validate_range_with_float_values(self): """Test range validation with float values""" result = validate_range(1.5, "test_field", min_value=1.0, max_value=2.0) assert result == 1.5 class TestValidateChoices: """Test cases for validate_choices function""" def test_validate_choices_success(self): """Test successful choices validation""" choices = ["option1", "option2", "option3"] result = validate_choices("option2", "test_field", choices) assert result == "option2" def test_validate_choices_failure(self): """Test choices validation failure""" choices = ["option1", "option2", "option3"] with pytest.raises(ValidationError, match="Field 'test_field' must be one of \\['option1', 'option2', 'option3'\\], got 'invalid'"): validate_choices("invalid", "test_field", choices) def test_validate_choices_with_different_types(self): """Test choices validation with different value types""" choices = [1, 2, 3, "four"] result1 = validate_choices(2, "test_field", choices) assert result1 == 2 result2 = validate_choices("four", "test_field", choices) assert result2 == "four" def test_validate_choices_empty_choices(self): """Test choices validation with empty choices list""" with pytest.raises(ValidationError, match="Field 'test_field' must be one of \\[\\], got 'value'"): validate_choices("value", "test_field", [])