Files
Timmy-time-dashboard/tests/unit/test_content_episode.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

149 lines
5.1 KiB
Python

"""Unit tests for content.composition.episode."""
from __future__ import annotations
from unittest.mock import patch
import pytest
from content.composition.episode import (
EpisodeResult,
EpisodeSpec,
_moviepy_available,
_slugify,
build_episode,
)
# ── _slugify ──────────────────────────────────────────────────────────────────
class TestSlugify:
def test_basic(self):
assert _slugify("Hello World") == "hello-world"
def test_special_chars_removed(self):
assert _slugify("Top Highlights — March 2026") == "top-highlights--march-2026"
def test_truncates_long_strings(self):
long = "a" * 100
assert len(_slugify(long)) <= 80
def test_empty_string_returns_episode(self):
assert _slugify("") == "episode"
def test_no_leading_or_trailing_dashes(self):
result = _slugify(" hello ")
assert not result.startswith("-")
assert not result.endswith("-")
# ── EpisodeSpec ───────────────────────────────────────────────────────────────
class TestEpisodeSpec:
def test_default_transition_from_settings(self):
spec = EpisodeSpec(title="EP")
from config import settings
assert spec.resolved_transition == settings.video_transition_duration
def test_custom_transition_overrides_settings(self):
spec = EpisodeSpec(title="EP", transition_duration=2.5)
assert spec.resolved_transition == pytest.approx(2.5)
def test_resolved_output_contains_slug(self):
spec = EpisodeSpec(title="My Episode")
assert "my-episode" in spec.resolved_output
def test_explicit_output_path_preserved(self):
spec = EpisodeSpec(title="EP", output_path="/tmp/custom.mp4")
assert spec.resolved_output == "/tmp/custom.mp4"
# ── _moviepy_available ────────────────────────────────────────────────────────
class TestMoviepyAvailable:
def test_returns_bool(self):
assert isinstance(_moviepy_available(), bool)
def test_false_when_spec_missing(self):
with patch("importlib.util.find_spec", return_value=None):
assert _moviepy_available() is False
# ── build_episode ─────────────────────────────────────────────────────────────
class TestBuildEpisode:
@pytest.mark.asyncio
async def test_returns_failure_when_moviepy_missing(self):
with patch("content.composition.episode._moviepy_available", return_value=False):
result = await build_episode(
clip_paths=[],
title="Test Episode",
)
assert result.success is False
assert "moviepy" in result.error.lower()
@pytest.mark.asyncio
async def test_returns_failure_when_compose_raises(self):
with (
patch("content.composition.episode._moviepy_available", return_value=True),
patch(
"content.composition.episode._compose_sync",
side_effect=RuntimeError("compose error"),
),
):
result = await build_episode(
clip_paths=[],
title="Test Episode",
)
assert result.success is False
assert "compose error" in result.error
@pytest.mark.asyncio
async def test_returns_episode_result_on_success(self):
fake_result = EpisodeResult(
success=True,
output_path="/tmp/ep.mp4",
duration=42.0,
clip_count=3,
)
with (
patch("content.composition.episode._moviepy_available", return_value=True),
patch(
"asyncio.to_thread",
return_value=fake_result,
),
):
result = await build_episode(
clip_paths=["/tmp/a.mp4"],
title="Test Episode",
output_path="/tmp/ep.mp4",
)
assert result.success is True
assert result.output_path == "/tmp/ep.mp4"
assert result.duration == pytest.approx(42.0)
assert result.clip_count == 3
@pytest.mark.asyncio
async def test_spec_receives_custom_transition(self):
captured_spec = {}
def _capture_compose(spec):
captured_spec["spec"] = spec
return EpisodeResult(success=True, output_path="/tmp/ep.mp4")
with (
patch("content.composition.episode._moviepy_available", return_value=True),
patch("asyncio.to_thread", side_effect=lambda fn, spec: _capture_compose(spec)),
):
await build_episode(
clip_paths=[],
title="EP",
transition_duration=3.0,
)
assert captured_spec["spec"].resolved_transition == pytest.approx(3.0)