281 lines
9.6 KiB
Python
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" |