legco_ai_assistant/backend/app/test/test_phase3_config.py

178 lines
5.8 KiB
Python

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