Files
Timmy-time-dashboard/tests/unit/test_content_youtube.py
Claude (Opus 4.6) f0841bd34e
Some checks failed
Tests / lint (push) Has been cancelled
Tests / test (push) Has been cancelled
[claude] Automated Episode Compiler — Highlights to Published Video (#880) (#1318)
2026-03-24 02:05:14 +00:00

160 lines
6.0 KiB
Python

"""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"] == []