"""Phase 3.1 tests: Configuration and infrastructure setup for YouTube proxy. Covers: - Config fields: youtube_proxy_enabled, yt_dlp_timeout, yt_dlp_cache_ttl defaults and env loading - Model schemas: YouTubeExtractRequest, YouTubeStreamResponse, StreamFormat - Service stubs: YouTubeService, HLSProxyService instantiation - Router registration: youtube.router mounted, endpoint responds 200 with mock """ from unittest.mock import MagicMock, patch import pytest from fastapi import FastAPI from fastapi.testclient import TestClient class TestYouTubeProxyConfig: """Config fields for YouTube proxy exist with correct defaults.""" @pytest.fixture(autouse=True) def clear_cache(self): from app.core.config import get_settings get_settings.cache_clear() yield get_settings.cache_clear() def test_defaults(self): from app.core.config import get_settings s = get_settings() assert s.youtube_proxy_enabled is True assert s.yt_dlp_timeout == 30 assert s.yt_dlp_cache_ttl == 300 def test_env_override(self, monkeypatch): monkeypatch.setenv("YOUTUBE_PROXY_ENABLED", "false") monkeypatch.setenv("YT_DLP_TIMEOUT", "60") monkeypatch.setenv("YT_DLP_CACHE_TTL", "600") from app.core.config import get_settings s = get_settings() assert s.youtube_proxy_enabled is False assert s.yt_dlp_timeout == 60 assert s.yt_dlp_cache_ttl == 600 def test_bool_parsing(self, monkeypatch): """Bool fields accept 'true'/'false', '1'/'0' (pydantic-settings).""" monkeypatch.setenv("YOUTUBE_PROXY_ENABLED", "0") from app.core.config import get_settings s = get_settings() assert s.youtube_proxy_enabled is False class TestYouTubeModels: """Pydantic models for YouTube stream extraction.""" def test_extract_request(self): from app.models.youtube import YouTubeExtractRequest req = YouTubeExtractRequest(url="https://www.youtube.com/watch?v=abc123") assert req.url == "https://www.youtube.com/watch?v=abc123" def test_stream_response_defaults(self): from app.models.youtube import YouTubeStreamResponse resp = YouTubeStreamResponse(video_id="abc123", title="Test Video") assert resp.video_id == "abc123" assert resp.title == "Test Video" assert resp.is_live is False assert resp.is_upcoming is False assert resp.video_proxy_url is None assert resp.audio_proxy_url is None assert resp.formats == [] assert resp.error is None def test_stream_format(self): from app.models.youtube import StreamFormat fmt = StreamFormat( format_id="140", url="https://example.com/audio.m3u8", is_audio_only=True, codec="mp4a.40.2", ) assert fmt.format_id == "140" assert fmt.is_audio_only is True assert fmt.is_video_only is False assert fmt.resolution is None class TestYouTubeServices: """Service stubs can be imported and instantiated.""" def test_youtube_service_instantiate(self): from app.services.youtube_service import YouTubeService svc = YouTubeService(timeout=30, cache_ttl=300) assert svc.timeout == 30 assert svc.cache_ttl == 300 def test_youtube_service_extract_is_async(self): from app.services.youtube_service import YouTubeService svc = YouTubeService(timeout=30, cache_ttl=300) import inspect assert inspect.iscoroutinefunction(svc.extract_streams) def test_hls_proxy_instantiate(self): from app.services.hls_proxy import HLSProxyService svc = HLSProxyService() assert svc is not None class TestYouTubeRouter: """YouTube router is mounted and stub endpoint responds correctly.""" @pytest.fixture def youtube_client(self): from app.routers.youtube import router from app.core.config import get_settings get_settings.cache_clear() app = FastAPI() app.include_router(router, prefix="/api/v1") return TestClient(app) def test_extract_responds_with_mocked_ytdlp(self, youtube_client): from app.routers.youtube import _get_youtube_service _get_youtube_service.cache_clear() vod_info = { "id": "test123", "title": "Test", "thumbnail": "https://example.com/thumb.jpg", "live_status": "not_live", "formats": [ { "format_id": "135", "height": 480, "vcodec": "avc1", "acodec": "none", "ext": "mp4", "protocol": "https", "url": "https://example.com/video.mp4", "tbr": 1200, }, { "format_id": "140", "vcodec": "none", "acodec": "mp4a", "ext": "m4a", "protocol": "https", "url": "https://example.com/audio.m4a", "abr": 128, }, ], } mock_ydl = MagicMock() mock_instance = MagicMock() mock_instance.extract_info.return_value = vod_info mock_ydl.__enter__.return_value = mock_instance with patch("app.services.youtube_service.yt_dlp.YoutubeDL", return_value=mock_ydl): resp = youtube_client.post( "/api/v1/youtube/extract", json={"url": "https://www.youtube.com/watch?v=test123"}, ) assert resp.status_code == 200 data = resp.json() assert data["video_id"] == "test123" assert data["video_proxy_url"] is not None assert data["audio_proxy_url"] is not None def test_router_tag(self): from app.routers.youtube import router assert any(tag == "youtube" for tag in router.tags)