Files
unshackle/tests/core/test_music_folder_template.py
imSp4rky 68c7d95173 refactor(music)!: make Song an ordinary Title on the generic path
Delete the dead grouped-download block, the planner, and the music service hooks; Song now flows through the one download loop, gets tagged via tag_file, and answers -w with {disc}x{track} keys.

BREAKING CHANGE: GROUP_AUDIO_DOWNLOADS, get_music_track_options and get_music_collection_label are gone. A music service returns one Audio track per quality and lets the framework choose.
2026-08-18 15:05:30 -06:00

128 lines
4.6 KiB
Python

"""Tests for the dedicated album-folder template (`output_template.folder.albums`)."""
from __future__ import annotations
import warnings
import pytest
from unshackle.core.config import Config, config
from unshackle.core.titles.music import Song
from unshackle.core.utilities import sanitize_filename
class DummyService:
pass
class StubMediaInfo:
"""Minimal MediaInfo stand-in: the template context only reads track lists."""
video_tracks: list = []
audio_tracks: list = []
def make_song(**overrides) -> Song:
kwargs = dict(
id_="track-0001",
service=DummyService,
name="Example Track",
artist="Example Artist",
album="Example Album",
track=1,
disc=1,
year=2025,
album_artist="Example Artist",
)
kwargs.update(overrides)
return Song(**kwargs)
@pytest.fixture
def reset_folder_config():
"""Save/restore the global config's template attributes around each test."""
saved = (config.folder_templates, config.folder_template, config.output_template)
config.folder_templates = {}
config.folder_template = ""
config.output_template = {}
yield config
config.folder_templates, config.folder_template, config.output_template = saved
def test_folder_fallback_when_no_templates(reset_folder_config):
song = make_song()
result = song.get_filename(StubMediaInfo(), folder=True)
assert result == sanitize_filename("Example Artist - Example Album (2025)", " ")
def test_albums_template_used(reset_folder_config):
reset_folder_config.folder_templates = {"albums": "{album_artist} - {album} ({year})"}
result = make_song().get_filename(StubMediaInfo(), folder=True)
assert result == sanitize_filename("Example Artist - Example Album (2025)", " ")
# Album folder must NOT carry per-track info like the song file name does.
assert "01" not in result
def test_albums_preferred_over_songs(reset_folder_config):
reset_folder_config.folder_templates = {"albums": "AA-{album}", "songs": "SS-{album}"}
result = make_song().get_filename(StubMediaInfo(), folder=True)
assert result.startswith("AA-")
def test_songs_folder_used_when_no_albums(reset_folder_config):
# Backward compatibility: the legacy "songs" folder kind still names the album folder.
reset_folder_config.folder_templates = {"songs": "SS-{album}"}
result = make_song().get_filename(StubMediaInfo(), folder=True)
assert result.startswith("SS-")
def test_validation_accepts_music_variables_and_albums_kind():
with warnings.catch_warnings():
warnings.simplefilter("error") # any warning becomes a test failure
Config(
output_template={
"songs": "{track_number}. {title}",
"folder": {"albums": "{album_artist} - {album} ({year?})"},
}
)
def test_validation_warns_on_unknown_folder_kind():
with pytest.warns(UserWarning, match="Unknown folder template kind"):
# A non-folder key is required so output-template validation actually runs.
Config(output_template={"songs": "{track_number}. {title}", "folder": {"bogus": "{album}"}})
def test_undated_single_degrades_cleanly(reset_folder_config):
"""A single with no release year and no disc must not leave empty parens behind."""
song = Song(
id_="track-0001",
service=DummyService,
name="Example Track",
artist="Example Artist",
album="Example Album",
track=1,
album_artist="Example Artist",
) # no disc, no year: both are optional
assert str(song) == "Example Artist - Example Album (?) / 01. Example Track"
assert song.get_filename(StubMediaInfo(), folder=True) == sanitize_filename("Example Artist - Example Album", " ")
assert song.get_filename(StubMediaInfo()) == "01.Example.Track" # spacer applies to the title too
context = song.build_template_context(StubMediaInfo())
assert context["year"] == ""
assert context["disc"] == "" # a lone disc is never numbered
assert context["track_number"] == "01"
def test_undated_album_template_omits_the_year_group(reset_folder_config):
reset_folder_config.folder_templates = {"albums": "{album_artist} - {album} ({year?})"}
result = make_song(year=None).get_filename(StubMediaInfo(), folder=True)
assert result == "Example Artist - Example Album"
def test_songs_separator_survives_a_config_without_a_songs_template(reset_folder_config):
"""A user config with output_template but no `songs` key used to raise a KeyError."""
reset_folder_config.output_template = {"movies": "{title}.{year}"}
assert reset_folder_config.get_template_separator("songs") == "."