""" Unit tests for dependency injection system. This module tests the FastAPI dependency injection utilities including SeriesApp dependency, database session dependency, and authentication dependencies. """ from unittest.mock import MagicMock, Mock, patch import pytest from fastapi import HTTPException, status from fastapi.security import HTTPAuthorizationCredentials from src.server.utils.dependencies import ( CommonQueryParams, common_parameters, get_current_user, get_database_session, get_series_app, log_request_dependency, optional_auth, rate_limit_dependency, require_auth, reset_series_app, ) class TestSeriesAppDependency: """Test cases for SeriesApp dependency injection.""" def setup_method(self): """Setup for each test method.""" # Reset the global SeriesApp instance before each test reset_series_app() @patch('src.server.utils.dependencies.settings') @patch('src.server.utils.dependencies.SeriesApp') def test_get_series_app_success(self, mock_series_app_class, mock_settings): """Test successful SeriesApp dependency injection.""" # Arrange mock_settings.anime_directory = "/path/to/anime" mock_series_app_instance = Mock() mock_series_app_class.return_value = mock_series_app_instance # Act result = get_series_app() # Assert assert result == mock_series_app_instance mock_series_app_class.assert_called_once_with("/path/to/anime") @patch('src.server.utils.dependencies.settings') def test_get_series_app_no_directory_configured(self, mock_settings): """Test SeriesApp dependency when directory is not configured.""" # Arrange mock_settings.anime_directory = "" # Act & Assert with pytest.raises(HTTPException) as exc_info: get_series_app() assert (exc_info.value.status_code == status.HTTP_503_SERVICE_UNAVAILABLE) assert "Anime directory not configured" in str(exc_info.value.detail) @patch('src.server.utils.dependencies.settings') @patch('src.server.utils.dependencies.SeriesApp') def test_get_series_app_initialization_error(self, mock_series_app_class, mock_settings): """Test SeriesApp dependency when initialization fails.""" # Arrange mock_settings.anime_directory = "/path/to/anime" mock_series_app_class.side_effect = Exception("Initialization failed") # Act & Assert with pytest.raises(HTTPException) as exc_info: get_series_app() assert (exc_info.value.status_code == status.HTTP_500_INTERNAL_SERVER_ERROR) assert "Failed to initialize SeriesApp" in str(exc_info.value.detail) @patch('src.server.utils.dependencies.settings') @patch('src.server.utils.dependencies.SeriesApp') def test_get_series_app_singleton_behavior(self, mock_series_app_class, mock_settings): """Test SeriesApp dependency returns same instance on calls.""" # Arrange mock_settings.anime_directory = "/path/to/anime" mock_series_app_instance = Mock() mock_series_app_class.return_value = mock_series_app_instance # Act result1 = get_series_app() result2 = get_series_app() # Assert assert result1 == result2 assert result1 == mock_series_app_instance # SeriesApp should only be instantiated once mock_series_app_class.assert_called_once_with("/path/to/anime") def test_reset_series_app(self): """Test resetting the global SeriesApp instance.""" # Act reset_series_app() # Assert - this should complete without error class TestDatabaseDependency: """Test cases for database session dependency injection.""" def test_get_database_session_not_implemented(self): """Test that database session dependency is async generator.""" import inspect # Test that function exists and is an async generator function assert inspect.isfunction(get_database_session) assert inspect.isasyncgenfunction(get_database_session) class TestAuthenticationDependencies: """Test cases for authentication dependency injection.""" def test_get_current_user_not_implemented(self): """Test that current user dependency rejects invalid tokens.""" # Arrange credentials = HTTPAuthorizationCredentials( scheme="Bearer", credentials="invalid-token" ) # Act & Assert with pytest.raises(HTTPException) as exc_info: get_current_user(credentials) # Should raise 401 for invalid token assert (exc_info.value.status_code == status.HTTP_401_UNAUTHORIZED) def test_require_auth_with_user(self): """Test require_auth dependency with authenticated user.""" # Arrange mock_user = {"user_id": 123, "username": "testuser"} # Act result = require_auth(mock_user) # Assert assert result == mock_user def test_optional_auth_without_credentials(self): """Test optional authentication without credentials.""" # Act result = optional_auth(None) # Assert assert result is None @patch('src.server.utils.dependencies.get_current_user') def test_optional_auth_with_valid_credentials(self, mock_get_current_user): """Test optional authentication with valid credentials.""" # Arrange credentials = HTTPAuthorizationCredentials( scheme="Bearer", credentials="valid-token" ) mock_user = {"user_id": 123, "username": "testuser"} mock_get_current_user.return_value = mock_user # Act result = optional_auth(credentials) # Assert assert result == mock_user mock_get_current_user.assert_called_once_with(credentials) @patch('src.server.utils.dependencies.get_current_user') def test_optional_auth_with_invalid_credentials(self, mock_get_current_user): """Test optional authentication with invalid credentials.""" # Arrange credentials = HTTPAuthorizationCredentials( scheme="Bearer", credentials="invalid-token" ) mock_get_current_user.side_effect = HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token" ) # Act result = optional_auth(credentials) # Assert assert result is None mock_get_current_user.assert_called_once_with(credentials) class TestCommonQueryParams: """Test cases for common query parameters.""" def test_common_query_params_initialization(self): """Test CommonQueryParams initialization.""" # Act params = CommonQueryParams(skip=10, limit=50) # Assert assert params.skip == 10 assert params.limit == 50 def test_common_query_params_defaults(self): """Test CommonQueryParams with default values.""" # Act params = CommonQueryParams() # Assert assert params.skip == 0 assert params.limit == 100 def test_common_parameters_dependency(self): """Test common parameters dependency function.""" # Act params = common_parameters(skip=20, limit=30) # Assert assert isinstance(params, CommonQueryParams) assert params.skip == 20 assert params.limit == 30 def test_common_parameters_dependency_defaults(self): """Test common parameters dependency with defaults.""" # Act params = common_parameters() # Assert assert isinstance(params, CommonQueryParams) assert params.skip == 0 assert params.limit == 100 class TestUtilityDependencies: """Test cases for utility dependencies.""" @pytest.mark.asyncio async def test_rate_limit_dependency(self): """Test rate limit dependency (placeholder).""" from unittest.mock import Mock # Create a mock request mock_request = Mock() mock_request.client = Mock() mock_request.client.host = "127.0.0.1" # Act - should complete without error await rate_limit_dependency(mock_request) # Assert - no exception should be raised @pytest.mark.asyncio async def test_log_request_dependency(self): """Test log request dependency (placeholder).""" from unittest.mock import Mock # Create a mock request mock_request = Mock() mock_request.method = "GET" mock_request.url = Mock() mock_request.url.path = "/test" mock_request.client = Mock() mock_request.client.host = "127.0.0.1" mock_request.query_params = {} # Act - should complete without error await log_request_dependency(mock_request) # Assert - no exception should be raised class TestIntegrationScenarios: """Integration test scenarios for dependency injection.""" def test_series_app_lifecycle(self): """Test the complete SeriesApp dependency lifecycle.""" # Use separate mock instances for each call with patch('src.server.utils.dependencies.settings') as mock_settings: with patch('src.server.utils.dependencies.SeriesApp') as mock_series_app_class: # Arrange mock_settings.anime_directory = "/path/to/anime" # Create separate mock instances for each instantiation mock_instance1 = MagicMock() mock_instance2 = MagicMock() mock_series_app_class.side_effect = [mock_instance1, mock_instance2] # Act - Get SeriesApp instance app1 = get_series_app() app2 = get_series_app() # Should return same instance # Reset and get again reset_series_app() app3 = get_series_app() # Assert assert app1 == app2 # Same instance due to singleton behavior assert app1 != app3 # Different instance after reset # Called twice due to reset assert mock_series_app_class.call_count == 2