- New TUI using Textual framework with responsive interface - Search, results, download, and queue screens for YouTube video management - Download queue system with sequential background downloads - Status bar with queue count and download progress indicators - Comprehensive test suite (97 tests) with 100% pass rate - Modern Python packaging with pyproject.toml and uv support - Added requirements-tui.txt for TUI-specific dependencies - Updated setup.py and install.sh for TUI integration - Enhanced README.md with TUI usage documentation
236 lines
6.2 KiB
Python
236 lines
6.2 KiB
Python
"""
|
|
Test fixtures and configuration for YouTube TUI tests
|
|
"""
|
|
|
|
import asyncio
|
|
from unittest.mock import AsyncMock, MagicMock, patch
|
|
|
|
import pytest
|
|
|
|
# Test fixtures
|
|
|
|
|
|
@pytest.fixture
|
|
def sample_video_data():
|
|
"""Sample video data for testing"""
|
|
return {
|
|
"video_id": "dQw4w9WgXcQ",
|
|
"title": "Rick Astley - Never Gonna Give You Up",
|
|
"channel": "RickAstleyVEVO",
|
|
"channel_id": "UCuZqHn2U8f4o7bVlY8v8w",
|
|
"duration": "3:33",
|
|
"view_count": "1000000",
|
|
"upload_date": "20091025",
|
|
"description": "The official video for Rick Astley's hit song.",
|
|
"thumbnail_url": "https://i.ytimg.com/vi/dQw4w9WgXcQ/hqdefault.jpg",
|
|
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
|
|
"is_short": False,
|
|
}
|
|
|
|
|
|
@pytest.fixture
|
|
def sample_video_object(sample_video_data):
|
|
"""Sample Video object for testing"""
|
|
from youtube_tui.models.video import Video
|
|
|
|
return Video(**sample_video_data)
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_yt_dlp_result():
|
|
"""Mock yt-dlp search result"""
|
|
return {
|
|
"id": "abc123",
|
|
"title": "Sample Video",
|
|
"author": "Sample Channel",
|
|
"channel": "Sample Channel",
|
|
"channel_id": "channel123",
|
|
"length": "10:30",
|
|
"view_count": 150000,
|
|
"upload_date": "20240115",
|
|
"description": "A sample video description",
|
|
"thumbnail": "https://i.ytimg.com/vi/abc123/hqdefault.jpg",
|
|
"url": "https://www.youtube.com/watch?v=abc123",
|
|
}
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_youtube_service():
|
|
"""Mock YouTubeService for testing"""
|
|
from youtube_cli.main import YouTubeCLI
|
|
from youtube_tui.services.youtube import YouTubeService
|
|
|
|
# Create a real service with mocked cli
|
|
with patch.object(YouTubeCLI, "__init__", return_value=None):
|
|
service = YouTubeService.__new__(YouTubeService)
|
|
service.cli = MagicMock()
|
|
service.console = MagicMock()
|
|
|
|
# Mock async methods
|
|
service.search_videos = AsyncMock()
|
|
service.download_video = AsyncMock()
|
|
service.download_playlist = AsyncMock()
|
|
service.get_categories = AsyncMock()
|
|
service.is_video_downloaded = AsyncMock()
|
|
service.add_to_archive = AsyncMock()
|
|
service.get_archive = AsyncMock()
|
|
service.get_downloaded_video_ids = AsyncMock()
|
|
service.remove_from_archive = AsyncMock()
|
|
service._create_video_from_result = MagicMock()
|
|
|
|
return service
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_video():
|
|
"""Mock Video object"""
|
|
from youtube_tui.models.video import Video
|
|
|
|
video = Video(
|
|
video_id="test123",
|
|
title="Test Video",
|
|
channel="Test Channel",
|
|
channel_id="channel123",
|
|
duration="5:00",
|
|
view_count="1000",
|
|
upload_date="20240101",
|
|
description="Test description",
|
|
thumbnail_url="https://example.com/thumb.jpg",
|
|
url="https://www.youtube.com/watch?v=test123",
|
|
is_short=False,
|
|
)
|
|
return video
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_search_results():
|
|
"""Mock search results"""
|
|
from youtube_tui.models.video import Video
|
|
|
|
videos = [
|
|
Video(
|
|
video_id=f"video{i}",
|
|
title=f"Video {i}",
|
|
channel=f"Channel {i}",
|
|
channel_id=f"channel{i}",
|
|
duration=f"{i}:00",
|
|
view_count=str(i * 1000),
|
|
upload_date="20240101",
|
|
description=f"Description {i}",
|
|
is_short=(i % 3 == 0),
|
|
url=f"https://www.youtube.com/watch?v=video{i}",
|
|
)
|
|
for i in range(1, 16)
|
|
]
|
|
return videos
|
|
|
|
|
|
@pytest.fixture
|
|
def app_config(tmp_path):
|
|
"""Temporary app configuration"""
|
|
config = {
|
|
"download_dir": str(tmp_path / "downloads"),
|
|
"default_locations": [
|
|
str(tmp_path / "downloads"),
|
|
str(tmp_path / "movies"),
|
|
],
|
|
"max_videos_per_page": 15,
|
|
"yt_dlp_args": {
|
|
"format": "bestvideo[height=1080]+bestaudio",
|
|
},
|
|
"network_share_path": str(tmp_path / "network"),
|
|
"default_network_subfolder": "General",
|
|
}
|
|
return config
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_app():
|
|
"""Mock Textual App for testing screens"""
|
|
from textual.app import App
|
|
|
|
mock_app = MagicMock(spec=App)
|
|
mock_app.screen_stack = [None, None, None] # Simulate screen stack
|
|
mock_app.notify = MagicMock()
|
|
mock_app.exit = MagicMock()
|
|
|
|
# Mock push_screen and pop_screen
|
|
mock_app.push_screen = MagicMock()
|
|
mock_app.push_results_screen = MagicMock()
|
|
mock_app.pop_screen = MagicMock()
|
|
mock_app.action_open_search = MagicMock()
|
|
mock_app.run_background = MagicMock()
|
|
|
|
return mock_app
|
|
|
|
|
|
# Async test utilities
|
|
|
|
|
|
@pytest.fixture
|
|
def event_loop():
|
|
"""Create an event loop for async tests"""
|
|
loop = asyncio.new_event_loop()
|
|
yield loop
|
|
loop.close()
|
|
|
|
|
|
@pytest.fixture
|
|
async def async_mock_search_results():
|
|
"""Async mock search results"""
|
|
from youtube_tui.models.video import Video
|
|
|
|
videos = [
|
|
Video(
|
|
video_id=f"video{i}",
|
|
title=f"Video {i}",
|
|
channel=f"Channel {i}",
|
|
channel_id=f"channel{i}",
|
|
duration=f"{i}:00",
|
|
view_count=str(i * 1000),
|
|
upload_date="20240101",
|
|
description=f"Description {i}",
|
|
is_short=(i % 3 == 0),
|
|
url=f"https://www.youtube.com/watch?v=video{i}",
|
|
)
|
|
for i in range(1, 16)
|
|
]
|
|
return videos
|
|
|
|
|
|
# Mock patches
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_yt_dlp():
|
|
"""Patch yt-dlp for testing"""
|
|
with patch("youtube_tui.services.youtube.subprocess") as mock_subprocess:
|
|
mock_result = MagicMock()
|
|
mock_result.returncode = 0
|
|
mock_result.stdout = "2024.01.01"
|
|
mock_subprocess.run.return_value = mock_result
|
|
yield mock_subprocess
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_archive():
|
|
"""Mock archive data"""
|
|
return {
|
|
"video123": {
|
|
"url": "https://www.youtube.com/watch?v=video123",
|
|
"id": "video123",
|
|
"title": "Downloaded Video",
|
|
}
|
|
}
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_archive_file(tmp_path, mock_archive):
|
|
"""Create a mock archive file"""
|
|
archive_path = tmp_path / "archive.json"
|
|
import json
|
|
|
|
with open(archive_path, "w") as f:
|
|
json.dump(mock_archive, f)
|
|
return archive_path
|