youtube-summarizer/backend/tests/unit/test_video_downloader_base.py

281 lines
9.6 KiB
Python

"""
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"