Path builder: validate $year, never blind-slice release_date (#745)

The $year template variable was a blind release_date[:4] slice. When
something upstream poisoned release_date with a non-date value — the album
NAME — that slice emitted garbage: 'Mantras (Deluxe)'[:4] -> 'Mant', so
every download landed in 'Mantras (Deluxe) (Mant) [Album]/' instead of
'(2026)' (Tacobell444's screenshot).

Add _extract_year_from_release_date(): returns the leading 4 chars only
when they're a plausible year (isdigit, 1900 < y <= 2100), else ''. Matches
the guard the codebase already uses in soulid_worker._extract_year. A
non-year resolves to '' and the template's existing empty-() cleanup drops
it, so a poisoned release_date can never write rubbish into the path again.

This is the shared post-process path builder
(core/imports/paths.build_final_path_for_track) that DOWNLOADS, reorganize,
and imports all route through, so the guard covers every surface at once.

Defensive fix only — it stops the SYMPTOM regardless of which upstream
writes the album name into release_date. Pinning that upstream needs the
reporter's metadata source + the release_date value from app.log (the
Soulseek + AcoustID + future-dated-album combo is the discriminator);
tracked separately.

Tests (tests/imports/test_import_paths.py): unit coverage for the helper
(real dates kept, names/sentinels/short values rejected) + an integration
test reproducing #745 — a poisoned release_date yields 'Mantras (Deluxe)
[Album]' not '(Mant)' — differential-verified it produces the exact
'(Mant)' folder without the fix. Positive control keeps real (2026). 395
import + reorganize tests green.
This commit is contained in:
BoulderBadgeDad 2026-05-30 00:31:14 -07:00
parent 56c58c3afc
commit 443257915c
2 changed files with 174 additions and 3 deletions

View file

@ -38,6 +38,26 @@ def _get_config_manager():
return _FallbackConfig()
def _extract_year_from_release_date(release_date) -> str:
"""Return a validated 4-digit year from a release_date, or '' .
The ``$year`` template variable used to be a blind ``release_date[:4]``
slice. When something upstream poisons ``release_date`` with a non-date
value (e.g. the album NAME #745, where "Mantras (Deluxe)" produced a
"(Mant)" folder), that slice happily emitted garbage. Validate that the
leading 4 chars are a plausible year, matching the guard the rest of the
codebase already uses (see ``soulid_worker._extract_year``). Anything
that isn't a real year resolves to '' — the template's bracket-cleanup
then drops the empty ``()`` instead of writing rubbish into the path.
"""
if not release_date:
return ""
candidate = str(release_date)[:4]
if candidate.isdigit() and 1900 < int(candidate) <= 2100:
return candidate
return ""
def _get_itunes_client():
try:
from core.metadata_service import get_itunes_client
@ -426,9 +446,7 @@ def build_final_path_for_track(context, artist_context, album_info, file_ext):
year = ""
if album_context and album_context.get("release_date"):
release_date = album_context["release_date"]
if release_date and len(release_date) >= 4:
year = release_date[:4]
year = _extract_year_from_release_date(album_context["release_date"])
raw_album_type = ""
if album_context:

View file

@ -372,3 +372,156 @@ def test_build_final_path_for_track_with_cdnum_template_skips_disc_folder(monkey
)
# Verify the disc folder was not created either.
assert not (tmp_path / "Transfer" / "Artist One" / "Artist One - Album One" / "Disc 2").exists()
# ── #745: $year must validate, never blind-slice release_date ──────────────
def test_extract_year_accepts_real_dates():
assert import_paths._extract_year_from_release_date("2026-01-01") == "2026"
assert import_paths._extract_year_from_release_date("1999") == "1999"
assert import_paths._extract_year_from_release_date("2026") == "2026"
# Datetime-ish string — still leads with a valid year.
assert import_paths._extract_year_from_release_date("2010-12-31T00:00:00Z") == "2010"
def test_extract_year_rejects_non_date_values():
# #745 exact case: release_date poisoned with the album NAME.
assert import_paths._extract_year_from_release_date("Mantras (Deluxe)") == ""
assert import_paths._extract_year_from_release_date("Mant") == ""
# Implausible / sentinel years are rejected.
assert import_paths._extract_year_from_release_date("0000") == ""
assert import_paths._extract_year_from_release_date("1800") == ""
assert import_paths._extract_year_from_release_date("9999") == ""
# Empty / None.
assert import_paths._extract_year_from_release_date("") == ""
assert import_paths._extract_year_from_release_date(None) == ""
# Fewer than 4 leading digits.
assert import_paths._extract_year_from_release_date("202") == ""
def test_build_final_path_drops_garbage_year_from_folder(monkeypatch, tmp_path):
"""#745 reproduction: release_date carries the album NAME, not a date.
The $year slot must resolve to empty and the bracket cleanup must drop the
empty () producing 'Album One' NOT 'Album One (Mant)'."""
config = _Config(
{
"soulseek.transfer_path": str(tmp_path / "Transfer"),
"file_organization.enabled": True,
"file_organization.templates": {
# Template that uses $year, like the reporter's.
"album_path": "$albumartist/$album ($year) [$albumtype]/$track - $title",
"single_path": "$artist/$artist - $title",
},
"file_organization.collab_artist_mode": "first",
"file_organization.disc_label": "Disc",
}
)
monkeypatch.setattr(import_paths, "_get_config_manager", lambda: config)
monkeypatch.setattr(
import_paths, "_get_album_tracks_for_source",
lambda source, album_id: {"items": [{"disc_number": 1}]},
)
context = {
"artist": {"name": "Katie Pruitt"},
"album": {
"name": "Mantras (Deluxe)",
"id": "album-1",
# POISONED: the album name landed in release_date (the #745 bug).
"release_date": "Mantras (Deluxe)",
"total_tracks": 12,
"album_type": "album",
"artists": [{"name": "Katie Pruitt"}],
},
"track_info": {
"name": "White Lies, White Jesus And You",
"id": "track-1",
"track_number": 1,
"disc_number": 1,
"artists": [{"name": "Katie Pruitt"}],
},
"original_search_result": {
"title": "White Lies, White Jesus And You",
"clean_title": "White Lies, White Jesus And You",
"clean_album": "Mantras (Deluxe)",
"clean_artist": "Katie Pruitt",
"artists": [{"name": "Katie Pruitt"}],
},
"source": "deezer",
"is_album_download": False,
}
final_path, created = import_paths.build_final_path_for_track(
context,
{"name": "Katie Pruitt"},
{"is_album": True, "album_name": "Mantras (Deluxe)", "track_number": 1, "disc_number": 1},
".flac",
)
assert created is True
# The album folder must NOT contain "(Mant)" or any "(...)" year artifact.
album_folder = os.path.basename(os.path.dirname(final_path))
assert "(Mant" not in album_folder
assert "()" not in album_folder
# Empty () collapses; [Album] type stays.
assert album_folder == "Mantras (Deluxe) [Album]"
def test_build_final_path_keeps_real_year_in_folder(monkeypatch, tmp_path):
"""Positive control: a genuine release_date still produces the (YYYY)
folder proves the guard didn't break the happy path."""
config = _Config(
{
"soulseek.transfer_path": str(tmp_path / "Transfer"),
"file_organization.enabled": True,
"file_organization.templates": {
"album_path": "$albumartist/$album ($year) [$albumtype]/$track - $title",
"single_path": "$artist/$artist - $title",
},
"file_organization.collab_artist_mode": "first",
"file_organization.disc_label": "Disc",
}
)
monkeypatch.setattr(import_paths, "_get_config_manager", lambda: config)
monkeypatch.setattr(
import_paths, "_get_album_tracks_for_source",
lambda source, album_id: {"items": [{"disc_number": 1}]},
)
context = {
"artist": {"name": "Katie Pruitt"},
"album": {
"name": "Mantras (Deluxe)",
"id": "album-1",
"release_date": "2026-04-12",
"total_tracks": 12,
"album_type": "album",
"artists": [{"name": "Katie Pruitt"}],
},
"track_info": {
"name": "White Lies, White Jesus And You",
"id": "track-1",
"track_number": 1,
"disc_number": 1,
"artists": [{"name": "Katie Pruitt"}],
},
"original_search_result": {
"title": "White Lies, White Jesus And You",
"clean_title": "White Lies, White Jesus And You",
"clean_album": "Mantras (Deluxe)",
"clean_artist": "Katie Pruitt",
"artists": [{"name": "Katie Pruitt"}],
},
"source": "deezer",
"is_album_download": False,
}
final_path, _ = import_paths.build_final_path_for_track(
context,
{"name": "Katie Pruitt"},
{"is_album": True, "album_name": "Mantras (Deluxe)", "track_number": 1, "disc_number": 1},
".flac",
)
album_folder = os.path.basename(os.path.dirname(final_path))
assert album_folder == "Mantras (Deluxe) (2026) [Album]"