mirror of
https://github.com/unshackle-dl/unshackle.git
synced 2026-11-12 12:05:07 +00:00
159 lines
5.5 KiB
Python
159 lines
5.5 KiB
Python
"""Tests for date-based ("daily"/sports) episode naming via Episode.air_date."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import date, datetime
|
|
|
|
import pytest
|
|
|
|
from unshackle.core.config import config
|
|
from unshackle.core.titles.episode import Episode
|
|
|
|
|
|
class DummyService:
|
|
pass
|
|
|
|
|
|
class StubMediaInfo:
|
|
"""Minimal MediaInfo stand-in: the template context only reads track lists."""
|
|
|
|
video_tracks: list = []
|
|
audio_tracks: list = []
|
|
|
|
|
|
def make_episode(**overrides) -> Episode:
|
|
kwargs = dict(
|
|
id_="ep-0001",
|
|
service=DummyService,
|
|
title="The Show",
|
|
season=2024,
|
|
number=181,
|
|
name="The Big Race",
|
|
year=2024,
|
|
)
|
|
kwargs.update(overrides)
|
|
return Episode(**kwargs)
|
|
|
|
|
|
@pytest.fixture
|
|
def reset_template():
|
|
saved = (config.output_template, config.folder_templates, config.folder_template)
|
|
config.output_template = {"series": "{title}.{year?}.{season_episode}.{quality}-{tag}"}
|
|
config.folder_templates = {}
|
|
config.folder_template = ""
|
|
yield config
|
|
config.output_template, config.folder_templates, config.folder_template = saved
|
|
|
|
|
|
def test_air_date_replaces_season_episode_dotted(reset_template):
|
|
ep = make_episode(air_date=date(2024, 6, 30))
|
|
ctx = ep.build_template_context(StubMediaInfo())
|
|
assert ctx["season_episode"] == "2024.06.30"
|
|
assert ctx["season"] == "2024.06.30"
|
|
assert ctx["episode"] == ""
|
|
assert ctx["year"] == "" # date carries the year
|
|
assert ctx["date"] == "2024-06-30" # ISO regardless of separator
|
|
|
|
|
|
def test_air_date_uses_space_separator(reset_template):
|
|
reset_template.output_template = {"series": "{title} {season_episode} {quality}-{tag}"}
|
|
ep = make_episode(air_date=date(2024, 6, 30))
|
|
assert ep.build_template_context(StubMediaInfo())["season_episode"] == "2024 06 30"
|
|
|
|
|
|
def test_iso_string_air_date_coerced(reset_template):
|
|
ep = make_episode(air_date="2024-06-30")
|
|
assert isinstance(ep.air_date, date)
|
|
assert ep.build_template_context(StubMediaInfo())["season_episode"] == "2024.06.30"
|
|
|
|
|
|
def test_year_always_dropped_in_file_when_dated(reset_template):
|
|
"""The air date is the sole date in the filename; a distinct year is dropped too (deterministic)."""
|
|
ep = make_episode(year=2010, air_date=date(2013, 10, 30))
|
|
ctx = ep.build_template_context(StubMediaInfo())
|
|
assert ctx["year"] == ""
|
|
assert ctx["season_episode"] == "2013.10.30"
|
|
|
|
|
|
def test_yearless_title_takes_year_from_date(reset_template):
|
|
"""A title with no year still names cleanly; the date supplies the year."""
|
|
ep = make_episode(year=None, air_date=date(2026, 6, 28))
|
|
ctx = ep.build_template_context(StubMediaInfo())
|
|
assert ctx["year"] == ""
|
|
assert ctx["season_episode"] == "2026.06.28"
|
|
|
|
|
|
def test_datetime_normalized_to_date(reset_template):
|
|
"""A datetime (subclass of date) must not leak its time into the {date} token."""
|
|
ep = make_episode(air_date=datetime(2024, 6, 30, 13, 0, 0))
|
|
assert type(ep.air_date) is date
|
|
assert ep.build_template_context(StubMediaInfo())["date"] == "2024-06-30"
|
|
|
|
|
|
def test_no_air_date_is_unchanged(reset_template):
|
|
"""Regression: without air_date, naming is exactly as before (presentation-only field)."""
|
|
ep = make_episode()
|
|
ctx = ep.build_template_context(StubMediaInfo())
|
|
assert ctx["season_episode"] == "S2024E181"
|
|
assert ctx["season"] == "S2024"
|
|
assert ctx["episode"] == "E181"
|
|
assert ctx["year"] == 2024
|
|
assert ctx["date"] == ""
|
|
|
|
|
|
def test_dated_folder_groups_by_year(reset_template):
|
|
reset_template.folder_templates = {}
|
|
reset_template.folder_template = ""
|
|
folder = make_episode(air_date=date(2024, 6, 30)).get_filename(StubMediaInfo(), folder=True)
|
|
assert "2024" in folder
|
|
assert "S2024" not in folder # season folder is the year, not the faked season
|
|
|
|
|
|
def test_dated_folder_drops_year_matching_air_year(reset_template):
|
|
"""{year?} would repeat the air-year season label (Title.2024.2024); it must render once."""
|
|
folder = make_episode(air_date=date(2024, 6, 30)).get_filename(StubMediaInfo(), folder=True)
|
|
assert "2024.2024" not in folder
|
|
|
|
|
|
def test_dated_folder_keeps_distinct_year(reset_template):
|
|
"""A series year different from the air year is real information and stays."""
|
|
folder = make_episode(year=2010, air_date=date(2013, 10, 30)).get_filename(StubMediaInfo(), folder=True)
|
|
assert "2010" in folder
|
|
assert "2013" in folder
|
|
|
|
|
|
def test_a_dated_episode_matches_its_iso_date_key():
|
|
assert make_episode(air_date=date(2024, 6, 30)).matches_wanted({"2024-06-30"})
|
|
|
|
|
|
def test_a_dated_episode_still_matches_its_season_episode_key():
|
|
assert make_episode(air_date=date(2024, 6, 30)).matches_wanted({"2024x181"})
|
|
|
|
|
|
def test_a_date_key_does_not_match_another_day():
|
|
assert not make_episode(air_date=date(2024, 6, 30)).matches_wanted({"2024-07-01"})
|
|
|
|
|
|
def test_a_season_episode_exclusion_beats_the_date_key():
|
|
assert not make_episode(air_date=date(2024, 6, 30)).matches_wanted({"2024-06-30", "!2024x181"})
|
|
|
|
|
|
def test_an_undated_episode_is_unaffected_by_date_keys():
|
|
episode = make_episode()
|
|
assert not episode.matches_wanted({"2024-06-30"})
|
|
assert episode.matches_wanted({"2024x181"})
|
|
|
|
|
|
def test_a_pre_1970_air_date_is_rejected():
|
|
with pytest.raises(ValueError):
|
|
make_episode(air_date=date(1969, 12, 31))
|
|
|
|
|
|
def test_a_future_air_date_is_accepted():
|
|
future = date.today().replace(year=date.today().year + 1)
|
|
assert make_episode(air_date=future).air_date == future
|
|
|
|
|
|
def test_an_unparseable_air_date_string_is_kept_as_is():
|
|
assert make_episode(air_date="soon").air_date == "soon"
|