""" Unit Tests for BaseService Tests for base service functionality with mocks. """ import pytest from unittest.mock import MagicMock, patch import sys import os sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from app.services.base import BaseService, ServiceConfig from app.services.mocks import MockCacheService class MockRepo: """Mock repository for testing.""" def __init__(self): self.data = {} class MockService(BaseService): """Concrete implementation for testing.""" cache_patterns = ["test*", "mock*"] def __init__(self, config=None, **kwargs): super().__init__(config=config, **kwargs) self.last_operation = None def get_data(self, key: str): """Sample method using base service features.""" self.last_operation = f"get_{key}" return {"key": key, "value": "test"} def update_data(self, key: str, value: str): """Sample method with cache invalidation.""" self.last_operation = f"update_{key}" self.invalidate_cache_for("test", key) return {"key": key, "value": value} def require_auth_test(self, token: str): """Test auth requirement.""" return self.require_auth(token) class TestServiceConfig: """Tests for ServiceConfig.""" def test_default_config(self): """Test default configuration.""" config = ServiceConfig() assert config.player_repo is None assert config.team_repo is None assert config.cache is None def test_config_with_repos(self): """Test configuration with repositories.""" player_repo = MockRepo() team_repo = MockRepo() cache = MockCacheService() config = ServiceConfig( player_repo=player_repo, team_repo=team_repo, cache=cache ) assert config.player_repo is player_repo assert config.team_repo is team_repo assert config.cache is cache class TestBaseServiceInit: """Tests for BaseService initialization.""" def test_init_with_config(self): """Test initialization with config object.""" config = ServiceConfig(cache=MockCacheService()) service = MockService(config=config) assert service._cache is not None def test_init_with_kwargs(self): """Test initialization with keyword arguments.""" cache = MockCacheService() service = MockService(cache=cache) assert service._cache is cache def test_config_overrides_kwargs(self): """Test that config takes precedence over kwargs.""" cache1 = MockCacheService() cache2 = MockCacheService() config = ServiceConfig(cache=cache1) service = MockService(config=config, cache=cache2) # Config should take precedence assert service._cache is cache1 class TestBaseServiceCacheInvalidation: """Tests for cache invalidation methods.""" def test_invalidate_cache_for_entity(self): """Test invalidating cache for a specific entity.""" cache = MockCacheService() cache.set("test:123:data", '{"test": "value"}', 300) config = ServiceConfig(cache=cache) service = MockService(config=config) # Should not throw service.invalidate_cache_for("test", entity_id=123) def test_invalidate_related_cache(self): """Test invalidating multiple cache patterns.""" cache = MockCacheService() # Set some cache entries cache.set("test1:data", '{"1": "data"}', 300) cache.set("mock2:data", '{"2": "data"}', 300) cache.set("other:data", '{"3": "data"}', 300) config = ServiceConfig(cache=cache) service = MockService(config=config) # Invalidate patterns service.invalidate_related_cache(["test*", "mock*"]) # test* and mock* should be cleared assert not cache.exists("test1:data") assert not cache.exists("mock2:data") # other should remain assert cache.exists("other:data") class TestBaseServiceErrorHandling: """Tests for error handling methods.""" def test_handle_error_no_rethrow(self): """Test error handling without rethrowing.""" service = MockService() result = service.handle_error("Test operation", ValueError("test error"), rethrow=False) assert "error" in result assert "Test operation" in result["error"] def test_handle_error_with_rethrow(self): """Test error handling that rethrows.""" service = MockService() with pytest.raises(Exception) as exc_info: service.handle_error("Test operation", ValueError("test error"), rethrow=True) assert "Test operation" in str(exc_info.value) class TestBaseServiceAuth: """Tests for authentication methods.""" @pytest.mark.skip(reason="Requires FastAPI dependencies not available in test environment") def test_require_auth_valid_token(self): """Test valid token authentication.""" service = MockService() with patch('app.services.base.valid_token', return_value=True): result = service.require_auth_test("valid_token") assert result is True @pytest.mark.skip(reason="Requires FastAPI dependencies not available in test environment") def test_require_auth_invalid_token(self): """Test invalid token authentication.""" service = MockService() with patch('app.services.base.valid_token', return_value=False): with pytest.raises(Exception) as exc_info: service.require_auth_test("invalid_token") assert exc_info.value.status_code == 401 class TestBaseServiceQueryParams: """Tests for query parameter parsing.""" def test_parse_query_params_remove_none(self): """Test removing None values.""" service = MockService() result = service.parse_query_params({ "name": "test", "age": None, "active": True, "empty": [] }) assert "name" in result assert "age" not in result assert "active" in result assert "empty" not in result # Empty list removed def test_parse_query_params_keep_none(self): """Test keeping None values when specified.""" service = MockService() result = service.parse_query_params({ "name": "test", "age": None }, remove_none=False) assert "name" in result assert "age" in result assert result["age"] is None class TestBaseServiceCsvFormatting: """Tests for CSV formatting.""" def test_format_csv_response(self): """Test CSV formatting.""" service = MockService() headers = ["Name", "Age", "City"] rows = [ ["John", "30", "NYC"], ["Jane", "25", "LA"] ] csv = service.format_csv_response(headers, rows) assert "Name" in csv assert "John" in csv assert "Jane" in csv # Run tests if executed directly if __name__ == "__main__": pytest.main([__file__, "-v"])