""" Unit tests for base video downloader functionality """ import pytest import asyncio from unittest.mock import Mock, AsyncMock, patch from pathlib import Path from backend.models.video_download import ( DownloadMethod, DownloadPreferences, VideoDownloadResult, DownloadStatus, DownloaderException ) from backend.services.video_downloaders.base_downloader import ( BaseVideoDownloader, DownloaderFactory, DownloadTimeout ) class TestBaseVideoDownloader: """Test base video downloader functionality""" class MockDownloader(BaseVideoDownloader): """Mock implementation of BaseVideoDownloader""" def __init__(self, method=DownloadMethod.PYTUBEFIX, config=None): super().__init__(method, config) self.should_succeed = True self.test_connection_result = True async def download_video(self, url, preferences): if self.should_succeed: video_id = await self.extract_video_id(url) return VideoDownloadResult( video_id=video_id, video_url=url, status=DownloadStatus.COMPLETED, method=self.method ) else: raise DownloaderException("Mock download failed") async def test_connection(self): return self.test_connection_result @pytest.fixture def mock_downloader(self): return self.MockDownloader() def test_downloader_initialization(self, mock_downloader): """Test downloader initialization""" assert mock_downloader.method == DownloadMethod.PYTUBEFIX assert mock_downloader.config == {} assert hasattr(mock_downloader, 'logger') @pytest.mark.asyncio async def test_extract_video_id_success(self, mock_downloader): """Test successful video ID extraction""" test_urls = [ "https://youtube.com/watch?v=dQw4w9WgXcQ", "https://www.youtube.com/watch?v=dQw4w9WgXcQ", "https://youtu.be/dQw4w9WgXcQ", "https://youtube.com/embed/dQw4w9WgXcQ", "https://youtube.com/v/dQw4w9WgXcQ" ] for url in test_urls: video_id = await mock_downloader.extract_video_id(url) assert video_id == "dQw4w9WgXcQ" @pytest.mark.asyncio async def test_extract_video_id_failure(self, mock_downloader): """Test video ID extraction failure""" invalid_urls = [ "https://example.com/video", "not a url", "https://youtube.com/playlist?list=PLxxx", "" ] for url in invalid_urls: with pytest.raises(DownloaderException): await mock_downloader.extract_video_id(url) @pytest.mark.asyncio async def test_successful_download(self, mock_downloader): """Test successful video download""" url = "https://youtube.com/watch?v=dQw4w9WgXcQ" preferences = DownloadPreferences() result = await mock_downloader.download_video(url, preferences) assert result.status == DownloadStatus.COMPLETED assert result.video_id == "dQw4w9WgXcQ" assert result.video_url == url assert result.method == DownloadMethod.PYTUBEFIX @pytest.mark.asyncio async def test_failed_download(self, mock_downloader): """Test failed video download""" mock_downloader.should_succeed = False url = "https://youtube.com/watch?v=dQw4w9WgXcQ" preferences = DownloadPreferences() with pytest.raises(DownloaderException): await mock_downloader.download_video(url, preferences) @pytest.mark.asyncio async def test_connection_test_success(self, mock_downloader): """Test successful connection test""" result = await mock_downloader.test_connection() assert result is True @pytest.mark.asyncio async def test_connection_test_failure(self, mock_downloader): """Test failed connection test""" mock_downloader.test_connection_result = False result = await mock_downloader.test_connection() assert result is False def test_create_result(self, mock_downloader): """Test result creation""" video_id = "test123" video_url = "https://youtube.com/watch?v=test123" error_msg = "Test error" result = mock_downloader.create_result( video_id, video_url, DownloadStatus.FAILED, error_msg ) assert result.video_id == video_id assert result.video_url == video_url assert result.status == DownloadStatus.FAILED assert result.method == DownloadMethod.PYTUBEFIX assert result.error_message == error_msg def test_default_capabilities(self, mock_downloader): """Test default capability methods""" assert mock_downloader.supports_audio_only() is False assert mock_downloader.supports_quality_selection() is False assert mock_downloader.get_supported_formats() == ["mp4"] @pytest.mark.asyncio async def test_cleanup_temp_files(self, mock_downloader, tmp_path): """Test temporary file cleanup""" # Create test directory and files temp_dir = tmp_path / "test_temp" temp_dir.mkdir() (temp_dir / "test_file.txt").write_text("test") assert temp_dir.exists() assert len(list(temp_dir.iterdir())) == 1 # Clean up await mock_downloader.cleanup_temp_files(temp_dir) assert not temp_dir.exists() class TestDownloaderFactory: """Test downloader factory functionality""" class TestDownloader(BaseVideoDownloader): async def download_video(self, url, preferences): pass async def test_connection(self): return True def test_register_and_create(self): """Test downloader registration and creation""" # Register test downloader test_method = DownloadMethod.PYTUBEFIX DownloaderFactory.register(test_method, self.TestDownloader) # Check if registered available = DownloaderFactory.get_available_methods() assert test_method in available # Create instance downloader = DownloaderFactory.create(test_method, {'test': 'config'}) assert isinstance(downloader, self.TestDownloader) assert downloader.method == test_method assert downloader.config == {'test': 'config'} def test_create_unsupported_method(self): """Test creation of unsupported method""" with pytest.raises(ValueError, match="Unsupported download method"): DownloaderFactory.create(DownloadMethod.FAILED) class TestDownloadTimeout: """Test download timeout functionality""" @pytest.mark.asyncio async def test_timeout_success(self): """Test successful operation within timeout""" async def quick_task(): await asyncio.sleep(0.1) return "success" timeout = DownloadTimeout(1) # 1 second timeout result = await timeout.run(quick_task()) assert result == "success" @pytest.mark.asyncio async def test_timeout_exceeded(self): """Test operation that exceeds timeout""" async def slow_task(): await asyncio.sleep(2) return "success" timeout = DownloadTimeout(0.5) # 0.5 second timeout with pytest.raises(DownloaderException, match="Operation timed out"): await timeout.run(slow_task()) @pytest.mark.asyncio async def test_timeout_context_manager(self): """Test timeout as context manager""" async def quick_task(): await asyncio.sleep(0.1) return "success" async with DownloadTimeout(1) as timeout: result = await timeout.run(quick_task()) assert result == "success" @pytest.mark.asyncio async def test_timeout_task_cleanup(self): """Test that tasks are properly cleaned up on timeout""" task_cancelled = False async def slow_task(): nonlocal task_cancelled try: await asyncio.sleep(2) except asyncio.CancelledError: task_cancelled = True raise timeout = DownloadTimeout(0.1) with pytest.raises(DownloaderException): await timeout.run(slow_task()) # Give some time for cleanup await asyncio.sleep(0.1) assert task_cancelled @pytest.mark.integration class TestBaseDownloaderIntegration: """Integration tests for base downloader""" @pytest.mark.asyncio async def test_video_id_extraction_edge_cases(self): """Test video ID extraction with edge cases""" downloader = TestBaseVideoDownloader.MockDownloader() # Test with query parameters url_with_params = "https://youtube.com/watch?v=dQw4w9WgXcQ&t=10s&list=PLxxx" video_id = await downloader.extract_video_id(url_with_params) assert video_id == "dQw4w9WgXcQ" # Test mobile URL mobile_url = "https://m.youtube.com/watch?v=dQw4w9WgXcQ" video_id = await downloader.extract_video_id(mobile_url) assert video_id == "dQw4w9WgXcQ" # Test shortened URL with parameters short_url = "https://youtu.be/dQw4w9WgXcQ?t=10" video_id = await downloader.extract_video_id(short_url) assert video_id == "dQw4w9WgXcQ"