"""Unit tests for media scan startup logic.""" from unittest.mock import AsyncMock, Mock, patch import pytest from src.server.database.system_settings_service import SystemSettingsService class TestMediaScanStartup: """Test media scan startup behavior.""" @pytest.mark.asyncio async def test_media_scan_runs_on_first_startup(self): """Test that media scan runs when flag is False.""" # Mock SystemSettingsService to return False for media scan completed with patch.object( SystemSettingsService, 'is_initial_media_scan_completed', new_callable=AsyncMock ) as mock_is_completed: mock_is_completed.return_value = False with patch.object( SystemSettingsService, 'mark_initial_media_scan_completed', new_callable=AsyncMock ) as mock_mark_completed: # Mock the check incomplete series function mock_check_incomplete = AsyncMock() # Simulate the startup logic is_media_scan_done = await SystemSettingsService.is_initial_media_scan_completed(None) assert is_media_scan_done is False # If not done, the startup should call check_incomplete_series if not is_media_scan_done: await mock_check_incomplete() await SystemSettingsService.mark_initial_media_scan_completed(None) # Verify check_incomplete was called mock_check_incomplete.assert_called_once() # Verify mark_completed was called mock_mark_completed.assert_called_once() @pytest.mark.asyncio async def test_media_scan_skipped_on_subsequent_startup(self): """Test that media scan is skipped when flag is True.""" # Mock SystemSettingsService to return True for media scan completed with patch.object( SystemSettingsService, 'is_initial_media_scan_completed', new_callable=AsyncMock ) as mock_is_completed: mock_is_completed.return_value = True with patch.object( SystemSettingsService, 'mark_initial_media_scan_completed', new_callable=AsyncMock ) as mock_mark_completed: # Mock the check incomplete series function mock_check_incomplete = AsyncMock() # Simulate the startup logic is_media_scan_done = await SystemSettingsService.is_initial_media_scan_completed(None) assert is_media_scan_done is True # If done, the startup should NOT call check_incomplete_series if not is_media_scan_done: await mock_check_incomplete() await SystemSettingsService.mark_initial_media_scan_completed(None) # Verify check_incomplete was NOT called mock_check_incomplete.assert_not_called() # Verify mark_completed was NOT called mock_mark_completed.assert_not_called() @pytest.mark.asyncio async def test_media_scan_handles_check_error_gracefully(self): """Test that startup handles errors when checking media scan status.""" # Mock SystemSettingsService to raise an exception with patch.object( SystemSettingsService, 'is_initial_media_scan_completed', new_callable=AsyncMock ) as mock_is_completed: mock_is_completed.side_effect = Exception("Database error") # Simulate the startup logic with error handling is_media_scan_done = False try: is_media_scan_done = await SystemSettingsService.is_initial_media_scan_completed(None) except Exception: # In case of error, assume not done is_media_scan_done = False # Should default to False (not done) on error assert is_media_scan_done is False # Verify the exception was raised mock_is_completed.assert_called_once() @pytest.mark.asyncio async def test_media_scan_handles_mark_error_gracefully(self): """Test that startup handles errors when marking media scan complete.""" with patch.object( SystemSettingsService, 'is_initial_media_scan_completed', new_callable=AsyncMock ) as mock_is_completed: mock_is_completed.return_value = False with patch.object( SystemSettingsService, 'mark_initial_media_scan_completed', new_callable=AsyncMock ) as mock_mark_completed: # Make mark_completed raise an exception mock_mark_completed.side_effect = Exception("Database write error") mock_check_incomplete = AsyncMock() # Simulate the startup logic with error handling is_media_scan_done = await SystemSettingsService.is_initial_media_scan_completed(None) if not is_media_scan_done: await mock_check_incomplete() # Try to mark as completed, but handle error try: await SystemSettingsService.mark_initial_media_scan_completed(None) except Exception: # Error should be caught and logged, but not crash startup pass # Verify check_incomplete was still called mock_check_incomplete.assert_called_once() # Verify mark_completed was attempted mock_mark_completed.assert_called_once() @pytest.mark.asyncio async def test_check_incomplete_series_integration(self): """Test the _check_incomplete_series_on_startup function behavior.""" from src.server.database.models import AnimeSeries # Mock database session mock_db = AsyncMock() # Create mock series with incomplete data mock_series_incomplete = Mock(spec=AnimeSeries) mock_series_incomplete.key = "incomplete-series" mock_series_incomplete.name = "Incomplete Series" mock_series_incomplete.folder = "Incomplete Series" mock_series_incomplete.year = 2020 mock_series_incomplete.loading_status = "pending" mock_series_incomplete.episodes_loaded = False mock_series_incomplete.has_nfo = False mock_series_incomplete.logo_loaded = False mock_series_incomplete.images_loaded = False # Create mock series with complete data mock_series_complete = Mock(spec=AnimeSeries) mock_series_complete.key = "complete-series" mock_series_complete.loading_status = "completed" mock_series_complete.episodes_loaded = True mock_series_complete.has_nfo = True mock_series_complete.logo_loaded = True mock_series_complete.images_loaded = True # Mock AnimeSeriesService.get_all to return both series with patch('src.server.database.service.AnimeSeriesService.get_all') as mock_get_all: mock_get_all.return_value = [mock_series_incomplete, mock_series_complete] # Mock background loader mock_background_loader = Mock() mock_background_loader.add_series_loading_task = AsyncMock() # Import and call the function from src.server.fastapi_app import _check_incomplete_series_on_startup # Mock get_db_session (it's imported inside the function) with patch('src.server.database.connection.get_db_session') as mock_get_db: mock_get_db.return_value.__aenter__.return_value = mock_db await _check_incomplete_series_on_startup(mock_background_loader) # Verify only incomplete series was queued mock_background_loader.add_series_loading_task.assert_called_once_with( key="incomplete-series", folder="Incomplete Series", name="Incomplete Series", year=2020 ) @pytest.mark.asyncio async def test_check_incomplete_series_all_complete(self): """Test behavior when all series have complete data.""" from src.server.database.models import AnimeSeries mock_db = AsyncMock() # Create mock series with complete data mock_series = Mock(spec=AnimeSeries) mock_series.key = "complete-series" mock_series.loading_status = "completed" mock_series.episodes_loaded = True mock_series.has_nfo = True mock_series.logo_loaded = True mock_series.images_loaded = True with patch('src.server.database.service.AnimeSeriesService.get_all') as mock_get_all: mock_get_all.return_value = [mock_series] mock_background_loader = Mock() mock_background_loader.add_series_loading_task = AsyncMock() from src.server.fastapi_app import _check_incomplete_series_on_startup with patch('src.server.database.connection.get_db_session') as mock_get_db: mock_get_db.return_value.__aenter__.return_value = mock_db await _check_incomplete_series_on_startup(mock_background_loader) # Verify no series were queued mock_background_loader.add_series_loading_task.assert_not_called() @pytest.mark.asyncio async def test_check_incomplete_series_missing_episodes(self): """Test that series with missing episodes are queued.""" from src.server.database.models import AnimeSeries mock_db = AsyncMock() # Series marked complete but missing episodes mock_series = Mock(spec=AnimeSeries) mock_series.key = "series-no-episodes" mock_series.name = "Series Without Episodes" mock_series.folder = "Series Without Episodes" mock_series.year = 2021 mock_series.loading_status = "completed" mock_series.episodes_loaded = False # Missing mock_series.has_nfo = True mock_series.logo_loaded = True mock_series.images_loaded = True with patch('src.server.database.service.AnimeSeriesService.get_all') as mock_get_all: mock_get_all.return_value = [mock_series] mock_background_loader = Mock() mock_background_loader.add_series_loading_task = AsyncMock() from src.server.fastapi_app import _check_incomplete_series_on_startup with patch('src.server.database.connection.get_db_session') as mock_get_db: mock_get_db.return_value.__aenter__.return_value = mock_db await _check_incomplete_series_on_startup(mock_background_loader) # Verify series was queued for loading mock_background_loader.add_series_loading_task.assert_called_once() @pytest.mark.asyncio async def test_check_incomplete_series_error_handling(self): """Test error handling in check_incomplete_series_on_startup.""" mock_background_loader = Mock() # Mock database error with patch('src.server.database.connection.get_db_session') as mock_get_db: mock_get_db.side_effect = Exception("Database connection error") from src.server.fastapi_app import _check_incomplete_series_on_startup # Should not raise exception try: await _check_incomplete_series_on_startup(mock_background_loader) except Exception as e: pytest.fail(f"Function should not raise exception, but raised: {e}")