""" Download System Stress Testing. This module tests the download queue and management system under heavy load and stress conditions. """ import asyncio from unittest.mock import AsyncMock, MagicMock import pytest from src.server.models.download import DownloadPriority, EpisodeIdentifier from src.server.services.anime_service import AnimeService from src.server.services.download_service import DownloadService @pytest.mark.performance class TestDownloadQueueStress: """Stress testing for download queue.""" @pytest.fixture def mock_anime_service(self): """Create mock AnimeService.""" service = MagicMock(spec=AnimeService) service.download = AsyncMock(return_value=True) return service @pytest.fixture def download_service(self, mock_anime_service, tmp_path): """Create download service with mock.""" persistence_path = str(tmp_path / "test_queue.json") service = DownloadService( anime_service=mock_anime_service, max_concurrent_downloads=10, max_retries=3, persistence_path=persistence_path, ) return service @pytest.mark.asyncio async def test_concurrent_download_additions( self, download_service ): """Test adding many downloads concurrently.""" num_downloads = 100 # Add downloads concurrently tasks = [ download_service.add_to_queue( serie_id=f"series-{i}", serie_name=f"Test Series {i}", episodes=[EpisodeIdentifier(season=1, episode=1)], priority=DownloadPriority.NORMAL, ) for i in range(num_downloads) ] results = await asyncio.gather(*tasks, return_exceptions=True) # Count successful additions successful = sum( 1 for r in results if not isinstance(r, Exception) ) # Should handle at least 90% successfully success_rate = (successful / num_downloads) * 100 assert ( success_rate >= 90.0 ), f"Queue addition success rate too low: {success_rate}%" @pytest.mark.asyncio async def test_queue_capacity(self, download_service): """Test queue behavior at capacity.""" # Fill queue beyond reasonable capacity num_downloads = 1000 for i in range(num_downloads): try: await download_service.add_to_queue( serie_id=f"series-{i}", serie_name=f"Test Series {i}", episodes=[EpisodeIdentifier(season=1, episode=1)], priority=DownloadPriority.NORMAL, ) except Exception: # Queue might have limits pass # Queue should still be functional status = await download_service.get_queue_status() assert status is not None, "Queue became non-functional" @pytest.mark.asyncio async def test_rapid_queue_operations(self, download_service): """Test rapid add/remove operations.""" num_operations = 200 operations = [] for i in range(num_operations): if i % 2 == 0: # Add operation operations.append( download_service.add_to_queue( serie_id=f"series-{i}", serie_name=f"Test Series {i}", episodes=[EpisodeIdentifier(season=1, episode=1)], priority=DownloadPriority.NORMAL, ) ) else: # Remove operation - get item IDs from pending queue item_ids = list( download_service._pending_items_by_id.keys() ) if item_ids: operations.append( download_service.remove_from_queue([item_ids[0]]) ) results = await asyncio.gather( *operations, return_exceptions=True ) # Most operations should succeed successful = sum( 1 for r in results if not isinstance(r, Exception) ) success_rate = (successful / len(results)) * 100 if results else 0 assert success_rate >= 80.0, "Operation success rate too low" @pytest.mark.asyncio async def test_concurrent_queue_reads(self, download_service): """Test concurrent queue status reads.""" # Add some items to queue for i in range(10): await download_service.add_to_queue( serie_id=f"series-{i}", serie_name=f"Test Series {i}", episodes=[EpisodeIdentifier(season=1, episode=1)], priority=DownloadPriority.NORMAL, ) # Perform many concurrent reads num_reads = 100 tasks = [ download_service.get_queue_status() for _ in range(num_reads) ] results = await asyncio.gather(*tasks, return_exceptions=True) # All reads should succeed successful = sum( 1 for r in results if not isinstance(r, Exception) ) assert ( successful == num_reads ), "Some queue reads failed" @pytest.mark.performance class TestDownloadMemoryUsage: """Test memory usage under load.""" @pytest.fixture def mock_anime_service(self): """Create mock AnimeService.""" service = MagicMock(spec=AnimeService) service.download = AsyncMock(return_value=True) return service @pytest.fixture def download_service(self, mock_anime_service, tmp_path): """Create download service with mock.""" persistence_path = str(tmp_path / "test_queue.json") service = DownloadService( anime_service=mock_anime_service, max_concurrent_downloads=10, max_retries=3, persistence_path=persistence_path, ) return service @pytest.mark.asyncio async def test_queue_memory_leak(self, download_service): """Test for memory leaks in queue operations.""" # This is a placeholder for memory profiling # In real implementation, would use memory_profiler # or similar tools # Perform many operations for i in range(1000): await download_service.add_to_queue( serie_id=f"series-{i}", serie_name=f"Test Series {i}", episodes=[EpisodeIdentifier(season=1, episode=1)], priority=DownloadPriority.NORMAL, ) if i % 100 == 0: # Clear some items periodically item_ids = list(download_service._pending_items_by_id.keys()) if item_ids: await download_service.remove_from_queue([item_ids[0]]) # Service should still be functional status = await download_service.get_queue_status() assert status is not None @pytest.mark.performance class TestDownloadConcurrency: """Test concurrent download handling.""" @pytest.fixture def mock_anime_service(self): """Create mock AnimeService with slow downloads.""" service = MagicMock(spec=AnimeService) async def slow_download(*args, **kwargs): # Simulate slow download await asyncio.sleep(0.1) return True service.download = slow_download return service @pytest.fixture def download_service(self, mock_anime_service, tmp_path): """Create download service with mock.""" persistence_path = str(tmp_path / "test_queue.json") service = DownloadService( anime_service=mock_anime_service, max_concurrent_downloads=10, max_retries=3, persistence_path=persistence_path, ) return service @pytest.mark.asyncio async def test_concurrent_download_execution( self, download_service ): """Test executing multiple downloads concurrently.""" # Start multiple downloads num_downloads = 20 tasks = [ download_service.add_to_queue( serie_id=f"series-{i}", serie_name=f"Test Series {i}", episodes=[EpisodeIdentifier(season=1, episode=1)], priority=DownloadPriority.NORMAL, ) for i in range(num_downloads) ] await asyncio.gather(*tasks) # All downloads should be queued status = await download_service.get_queue_status() total = ( len(status.pending_queue) + len(status.active_downloads) + len(status.completed_downloads) ) assert total <= num_downloads @pytest.mark.asyncio async def test_download_priority_under_load( self, download_service ): """Test that priority is respected under load.""" # Add downloads with different priorities await download_service.add_to_queue( serie_id="series-1", serie_name="Test Series 1", episodes=[EpisodeIdentifier(season=1, episode=1)], priority=DownloadPriority.LOW, ) await download_service.add_to_queue( serie_id="series-2", serie_name="Test Series 2", episodes=[EpisodeIdentifier(season=1, episode=1)], priority=DownloadPriority.HIGH, ) await download_service.add_to_queue( serie_id="series-3", serie_name="Test Series 3", episodes=[EpisodeIdentifier(season=1, episode=1)], priority=DownloadPriority.NORMAL, ) # High priority should be processed first status = await download_service.get_queue_status() assert status is not None @pytest.mark.performance class TestDownloadErrorHandling: """Test error handling under stress.""" @pytest.fixture def mock_failing_anime_service(self): """Create mock AnimeService that fails downloads.""" service = MagicMock(spec=AnimeService) service.download = AsyncMock( side_effect=Exception("Download failed") ) return service @pytest.fixture def download_service_failing( self, mock_failing_anime_service, tmp_path ): """Create download service with failing mock.""" persistence_path = str(tmp_path / "test_queue.json") service = DownloadService( anime_service=mock_failing_anime_service, max_concurrent_downloads=10, max_retries=3, persistence_path=persistence_path, ) return service @pytest.fixture def mock_anime_service(self): """Create mock AnimeService.""" service = MagicMock(spec=AnimeService) service.download = AsyncMock(return_value=True) return service @pytest.fixture def download_service(self, mock_anime_service, tmp_path): """Create download service with mock.""" persistence_path = str(tmp_path / "test_queue.json") service = DownloadService( anime_service=mock_anime_service, max_concurrent_downloads=10, max_retries=3, persistence_path=persistence_path, ) return service @pytest.mark.asyncio async def test_multiple_failed_downloads( self, download_service_failing ): """Test handling of many failed downloads.""" # Add multiple downloads for i in range(50): await download_service_failing.add_to_queue( serie_id=f"series-{i}", serie_name=f"Test Series {i}", episodes=[EpisodeIdentifier(season=1, episode=1)], priority=DownloadPriority.NORMAL, ) # Service should remain stable despite failures status = await download_service_failing.get_queue_status() assert status is not None @pytest.mark.asyncio async def test_recovery_from_errors(self, download_service): """Test system recovery after errors.""" # Cause some errors try: await download_service.remove_from_queue(["nonexistent-id"]) except Exception: pass # System should still work await download_service.add_to_queue( serie_id="series-1", serie_name="Test Series 1", episodes=[EpisodeIdentifier(season=1, episode=1)], priority=DownloadPriority.NORMAL, ) status = await download_service.get_queue_status() assert status is not None