178 lines
5.8 KiB
Python
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)
|