forked from Rockachopa/Timmy-time-dashboard
This commit is contained in:
159
tests/unit/test_content_youtube.py
Normal file
159
tests/unit/test_content_youtube.py
Normal file
@@ -0,0 +1,159 @@
|
||||
"""Unit tests for content.publishing.youtube."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from content.publishing.youtube import (
|
||||
YouTubeUploadResult,
|
||||
_daily_upload_count,
|
||||
_increment_daily_upload_count,
|
||||
_youtube_available,
|
||||
upload_episode,
|
||||
)
|
||||
|
||||
# ── _youtube_available ────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
class TestYoutubeAvailable:
|
||||
def test_returns_bool(self):
|
||||
assert isinstance(_youtube_available(), bool)
|
||||
|
||||
def test_false_when_library_missing(self):
|
||||
with patch("importlib.util.find_spec", return_value=None):
|
||||
assert _youtube_available() is False
|
||||
|
||||
|
||||
# ── daily upload counter ──────────────────────────────────────────────────────
|
||||
|
||||
|
||||
class TestDailyUploadCounter:
|
||||
def test_zero_when_no_file(self, tmp_path):
|
||||
counter_path = tmp_path / "counter.json"
|
||||
with patch(
|
||||
"content.publishing.youtube.settings",
|
||||
MagicMock(content_youtube_counter_file=str(counter_path)),
|
||||
):
|
||||
assert _daily_upload_count() == 0
|
||||
|
||||
def test_increments_correctly(self, tmp_path):
|
||||
counter_path = tmp_path / "counter.json"
|
||||
mock_settings = MagicMock(content_youtube_counter_file=str(counter_path))
|
||||
|
||||
with patch("content.publishing.youtube.settings", mock_settings):
|
||||
assert _daily_upload_count() == 0
|
||||
_increment_daily_upload_count()
|
||||
assert _daily_upload_count() == 1
|
||||
_increment_daily_upload_count()
|
||||
assert _daily_upload_count() == 2
|
||||
|
||||
def test_persists_across_calls(self, tmp_path):
|
||||
counter_path = tmp_path / "counter.json"
|
||||
mock_settings = MagicMock(content_youtube_counter_file=str(counter_path))
|
||||
|
||||
with patch("content.publishing.youtube.settings", mock_settings):
|
||||
_increment_daily_upload_count()
|
||||
_increment_daily_upload_count()
|
||||
|
||||
with patch("content.publishing.youtube.settings", mock_settings):
|
||||
assert _daily_upload_count() == 2
|
||||
|
||||
|
||||
# ── upload_episode ────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
class TestUploadEpisode:
|
||||
@pytest.mark.asyncio
|
||||
async def test_returns_failure_when_library_missing(self, tmp_path):
|
||||
video = tmp_path / "ep.mp4"
|
||||
video.write_bytes(b"fake")
|
||||
with patch("content.publishing.youtube._youtube_available", return_value=False):
|
||||
result = await upload_episode(str(video), "Title")
|
||||
assert result.success is False
|
||||
assert "google" in result.error.lower()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_returns_failure_when_video_missing(self, tmp_path):
|
||||
with patch("content.publishing.youtube._youtube_available", return_value=True):
|
||||
result = await upload_episode(str(tmp_path / "nonexistent.mp4"), "Title")
|
||||
assert result.success is False
|
||||
assert "not found" in result.error
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_returns_failure_when_quota_reached(self, tmp_path):
|
||||
video = tmp_path / "ep.mp4"
|
||||
video.write_bytes(b"fake")
|
||||
with (
|
||||
patch("content.publishing.youtube._youtube_available", return_value=True),
|
||||
patch("content.publishing.youtube._daily_upload_count", return_value=6),
|
||||
):
|
||||
result = await upload_episode(str(video), "Title")
|
||||
assert result.success is False
|
||||
assert "quota" in result.error.lower()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_successful_upload(self, tmp_path):
|
||||
video = tmp_path / "ep.mp4"
|
||||
video.write_bytes(b"fake video data")
|
||||
|
||||
fake_upload_result = YouTubeUploadResult(
|
||||
success=True,
|
||||
video_id="abc123",
|
||||
video_url="https://www.youtube.com/watch?v=abc123",
|
||||
)
|
||||
|
||||
with (
|
||||
patch("content.publishing.youtube._youtube_available", return_value=True),
|
||||
patch("content.publishing.youtube._daily_upload_count", return_value=0),
|
||||
patch(
|
||||
"asyncio.to_thread",
|
||||
return_value=fake_upload_result,
|
||||
),
|
||||
):
|
||||
result = await upload_episode(str(video), "My Episode Title")
|
||||
|
||||
assert result.success is True
|
||||
assert result.video_id == "abc123"
|
||||
assert "abc123" in result.video_url
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_title_truncated_to_100_chars(self, tmp_path):
|
||||
video = tmp_path / "ep.mp4"
|
||||
video.write_bytes(b"fake")
|
||||
long_title = "A" * 150
|
||||
|
||||
captured_args = {}
|
||||
|
||||
async def _capture_to_thread(fn, *args, **kwargs):
|
||||
captured_args["title"] = args[1] # title is second positional arg
|
||||
return YouTubeUploadResult(success=True, video_id="x")
|
||||
|
||||
with (
|
||||
patch("content.publishing.youtube._youtube_available", return_value=True),
|
||||
patch("content.publishing.youtube._daily_upload_count", return_value=0),
|
||||
patch("asyncio.to_thread", side_effect=_capture_to_thread),
|
||||
):
|
||||
await upload_episode(str(video), long_title)
|
||||
|
||||
assert len(captured_args["title"]) <= 100
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_default_tags_is_empty_list(self, tmp_path):
|
||||
video = tmp_path / "ep.mp4"
|
||||
video.write_bytes(b"fake")
|
||||
captured_args = {}
|
||||
|
||||
async def _capture(fn, *args, **kwargs):
|
||||
captured_args["tags"] = args[3]
|
||||
return YouTubeUploadResult(success=True, video_id="x")
|
||||
|
||||
with (
|
||||
patch("content.publishing.youtube._youtube_available", return_value=True),
|
||||
patch("content.publishing.youtube._daily_upload_count", return_value=0),
|
||||
patch("asyncio.to_thread", side_effect=_capture),
|
||||
):
|
||||
await upload_episode(str(video), "Title")
|
||||
|
||||
assert captured_args["tags"] == []
|
||||
Reference in New Issue
Block a user