soulsync/tests/downloads/test_file_finder.py
BoulderBadgeDad b36392c62b Fix: a '/' in a song title was treated as a path separator (#835)
YouTube/Tidal/Qobuz results encode the name as ``id||title``. When the title
itself contains a '/' (e.g. the Sawano AoT track "YouSeeBIGGIRL/T:T"), two places
wrongly basename-split it on the slash and kept only the last segment ("T:T"):

- core/downloads/file_finder.py — the completed-download finder truncated its
  search target to "T:T", so the real on-disk file (slash sanitised by the
  writer) never matched → "not found after processing" → the download got
  QUARANTINED. Now an encoded ``id||title`` keeps the whole title as the target
  and contributes no remote-directory components; real Soulseek PATHS still get
  basename + dir extraction unchanged.
- webui/static/downloads.js — the manual-search FILE column showed only "T:T".
  Added a ``||``-aware short-label helper (mirrors the correct handling already
  used elsewhere in the file); real file paths still show their basename.

Tests: the finder locates "YouSeeBIGGIRL∕T: T.mp3" from the encoded title
"…||YouSeeBIGGIRL/T:T" (the screenshot case), doesn't match an unrelated file,
and a genuine Soulseek path still resolves to its last segment. 21 finder tests
+ 64 script-split integrity tests pass.
2026-06-10 16:29:09 -07:00

398 lines
14 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Tests for ``core/downloads/file_finder.py``.
Real-world regression these tests pin: the Soulseek album-bundle
post-process previously had its own three-candidate probe for the
local downloaded file. When slskd was configured to nest downloads
under a username subdir (a common config) NONE of the three
candidates matched, the poll silently timed out 22 minutes later,
and the batch went to "failed" even though slskd had successfully
downloaded every track of the album. The per-track download path
already used the recursive-walk finder and worked fine — these
tests pin the lifted shared finder so both paths now find files
no matter what layout slskd writes.
Issue: #715 (Billy Ocean — Download album task fails after slskd
finishes downloading release).
"""
from __future__ import annotations
import os
from pathlib import Path
import pytest
from core.downloads.file_finder import find_completed_audio_file
def _touch(path: Path, content: bytes = b'\x00\x00'):
"""Create a small placeholder file at ``path``."""
path.parent.mkdir(parents=True, exist_ok=True)
path.write_bytes(content)
# ---------------------------------------------------------------------------
# Layout coverage — slskd writes downloads to many different paths
# depending on its config. The bundle resolver must find the file in
# all of these layouts; the pre-lift code only handled the first two.
# ---------------------------------------------------------------------------
def test_finds_file_in_flat_slskd_layout(tmp_path):
"""Default slskd config: ``<download_dir>/<basename>``."""
downloads = tmp_path / 'downloads'
target = downloads / '01 - Suddenly.flac'
_touch(target)
found, location = find_completed_audio_file(
str(downloads), r'shared\Billy Ocean\Very Best Of\01 - Suddenly.flac',
)
assert found == str(target)
assert location == 'downloads'
def test_finds_file_under_username_subdir(tmp_path):
"""Regression for #715. slskd configured with
``directories.downloads.username = true`` writes to
``<download_dir>/<username>/<filename>``. Pre-lift the bundle
resolver missed this layout because it only probed
``download_dir/filename`` / ``download_dir/basename`` /
``download_dir/normalized_remote_path`` — none included a
username segment, so every file looked missing."""
downloads = tmp_path / 'downloads'
target = downloads / '3opgkrpokgreg' / '01 - Suddenly.flac'
_touch(target)
found, location = find_completed_audio_file(
str(downloads), r'shared\Billy Ocean\Very Best Of\01 - Suddenly.flac',
)
assert found == str(target)
assert location == 'downloads'
def test_finds_file_when_slskd_preserves_remote_tree(tmp_path):
"""slskd config where the remote sharer's folder tree is
mirrored locally — ``<download_dir>/shared/<artist>/<album>/<file>``."""
downloads = tmp_path / 'downloads'
target = downloads / 'shared' / 'Billy Ocean' / 'Very Best Of' / '01 - Suddenly.flac'
_touch(target)
found, location = find_completed_audio_file(
str(downloads), r'shared\Billy Ocean\Very Best Of\01 - Suddenly.flac',
)
assert found == str(target)
assert location == 'downloads'
def test_finds_file_in_deeply_nested_user_tree(tmp_path):
"""Some slskd setups combine username + preserved tree. Finder
walks recursively so depth doesn't matter."""
downloads = tmp_path / 'downloads'
target = (downloads / '3opgkrpokgreg' / 'Music'
/ 'Billy Ocean' / 'Very Best Of'
/ '01 - Suddenly.flac')
_touch(target)
found, location = find_completed_audio_file(
str(downloads), r'shared\Billy Ocean\Very Best Of\01 - Suddenly.flac',
)
assert found == str(target)
def test_returns_none_when_file_missing(tmp_path):
"""File genuinely not on disk → both elements ``None``."""
downloads = tmp_path / 'downloads'
downloads.mkdir()
_touch(downloads / '02 - Caribbean Queen.flac') # different file
found, location = find_completed_audio_file(
str(downloads), r'shared\Billy Ocean\Very Best Of\01 - Suddenly.flac',
)
assert found is None
assert location is None
# ---------------------------------------------------------------------------
# Disambiguation — when multiple files share a basename, the path
# whose components mirror the remote tree wins.
# ---------------------------------------------------------------------------
def test_path_confirms_against_remote_dirs_when_basename_collides(tmp_path):
"""Two albums both contain ``01 - Intro.flac``. The finder
picks the one whose path carries the most remote-dir components
— keeps two simultaneous bundle downloads from claiming each
other's file."""
downloads = tmp_path / 'downloads'
other_album = downloads / 'Some Other Artist' / 'Other Album' / '01 - Intro.flac'
target_album = downloads / 'Billy Ocean' / 'Very Best Of' / '01 - Intro.flac'
_touch(other_album)
_touch(target_album)
found, _ = find_completed_audio_file(
str(downloads), r'shared\Billy Ocean\Very Best Of\01 - Intro.flac',
)
assert found == str(target_album)
def test_returns_only_match_when_no_disambiguation_possible(tmp_path):
"""Single basename match in the tree → return it regardless of
whether the remote dir components line up."""
downloads = tmp_path / 'downloads'
target = downloads / 'random' / 'subdir' / '01 - Suddenly.flac'
_touch(target)
found, _ = find_completed_audio_file(
str(downloads), r'shared\Billy Ocean\Very Best Of\01 - Suddenly.flac',
)
assert found == str(target)
# ---------------------------------------------------------------------------
# slskd dedup suffix — when a file with the same name already exists,
# slskd appends ``_<timestamp>`` to the new file. The finder must
# strip this and still match the original API filename.
# ---------------------------------------------------------------------------
def test_matches_slskd_dedup_suffix(tmp_path):
"""``Song.flac`` requested → ``Song_639067852665564677.flac``
on disk (slskd dedup). Finder strips the timestamp suffix and
returns the deduped file."""
downloads = tmp_path / 'downloads'
target = downloads / '01 - Suddenly_639067852665564677.flac'
_touch(target)
found, _ = find_completed_audio_file(
str(downloads), r'shared\Billy Ocean\Very Best Of\01 - Suddenly.flac',
)
assert found == str(target)
def test_dedup_suffix_short_digits_not_treated_as_dedup(tmp_path):
"""Year-style ``_2007`` (4 digits) is NOT slskd's dedup format —
that's a 10+ digit timestamp. A file like ``Greatest Hits_2007.flac``
that exists alongside the requested ``Greatest Hits.flac`` must
not be returned as a tier-1 dedup match. (Fuzzy may still grab
it as a lower-confidence fallback when no real match exists,
which is intentional — the strict-dedup tier is what we're
pinning here.)"""
downloads = tmp_path / 'downloads'
# Real match exists too — so the dedup-incorrect file doesn't
# win by default. Tests the priority order.
real_match = downloads / 'Billy Ocean' / '01 - Suddenly.flac'
near_miss = downloads / '01 - Suddenly_2007.flac'
_touch(real_match)
_touch(near_miss)
found, _ = find_completed_audio_file(
str(downloads), r'shared\Billy Ocean\Very Best Of\01 - Suddenly.flac',
)
# Real exact-basename + path-confirmed match must win over the
# year-suffixed file.
assert found == str(real_match)
# ---------------------------------------------------------------------------
# Special inputs — YouTube/Tidal encoded filenames, quarantine,
# empty paths, transfer-dir fallback.
# ---------------------------------------------------------------------------
def test_skips_quarantine_subdir(tmp_path):
"""Files under ``ss_quarantine/`` are known-wrong AcoustID
rejects — the finder must ignore them so a quarantined-but-still-
present file doesn't get re-claimed."""
downloads = tmp_path / 'downloads'
quarantined = downloads / 'ss_quarantine' / '01 - Suddenly.flac'
real = downloads / 'Billy Ocean' / '01 - Suddenly.flac'
_touch(quarantined)
_touch(real)
found, _ = find_completed_audio_file(
str(downloads), r'shared\Billy Ocean\Very Best Of\01 - Suddenly.flac',
)
assert found == str(real)
def test_handles_youtube_tidal_encoded_filename(tmp_path):
"""YouTube / Tidal downloads encode the API filename as
``id||title``. The finder strips the id and matches against
the title half."""
downloads = tmp_path / 'downloads'
target = downloads / 'My Song.mp3'
_touch(target)
found, _ = find_completed_audio_file(str(downloads), 'abc123||My Song.mp3')
assert found == str(target)
def test_falls_back_to_transfer_dir_when_download_dir_misses(tmp_path):
"""File has already moved into the transfer dir. The finder
falls through to the second search root rather than returning
None — covers the post-process race where a file is mid-move."""
downloads = tmp_path / 'downloads'
downloads.mkdir()
transfer = tmp_path / 'transfer'
moved = transfer / 'Billy Ocean' / '01 - Suddenly.flac'
_touch(moved)
found, location = find_completed_audio_file(
str(downloads),
r'shared\Billy Ocean\Very Best Of\01 - Suddenly.flac',
transfer_dir=str(transfer),
)
assert found == str(moved)
assert location == 'transfer'
def test_returns_none_when_neither_dir_has_file(tmp_path):
"""Missing in both download AND transfer → ``(None, None)``."""
downloads = tmp_path / 'downloads'
transfer = tmp_path / 'transfer'
downloads.mkdir()
transfer.mkdir()
found, location = find_completed_audio_file(
str(downloads),
r'shared\Billy Ocean\Very Best Of\01 - Suddenly.flac',
transfer_dir=str(transfer),
)
assert found is None
assert location is None
def test_ignores_non_audio_files(tmp_path):
"""Cover art, NFO, and text files in the slskd dir must not be
surfaced as audio matches even when their stem aligns."""
downloads = tmp_path / 'downloads'
_touch(downloads / '01 - Suddenly.txt') # wrong extension
_touch(downloads / '01 - Suddenly.nfo')
_touch(downloads / 'cover.jpg')
target = downloads / '01 - Suddenly.flac'
_touch(target)
found, _ = find_completed_audio_file(
str(downloads), r'shared\Billy Ocean\Very Best Of\01 - Suddenly.flac',
)
assert found == str(target)
def test_empty_api_filename_returns_none(tmp_path):
"""Defensive — empty / None filename can't resolve to anything."""
downloads = tmp_path / 'downloads'
downloads.mkdir()
_touch(downloads / '01 - Suddenly.flac')
found, location = find_completed_audio_file(str(downloads), '')
assert found is None
assert location is None
# ---------------------------------------------------------------------------
# Fuzzy fallback — when no exact / dedup match lands, the finder
# falls back to the closest-basename fuzzy match above 0.85.
# ---------------------------------------------------------------------------
def test_fuzzy_matches_punctuation_variant(tmp_path):
"""Soulseek shares vary in separator style — underscore vs
dash vs period. The normaliser collapses all three to spaces
so the fuzzy comparator can match across the variation."""
downloads = tmp_path / 'downloads'
# On disk: underscore. API filename: dash. Both normalise to
# the same token stream.
target = downloads / "01_Caribbean_Queen.flac"
_touch(target)
found, _ = find_completed_audio_file(
str(downloads),
r"shared\Billy Ocean\Very Best Of\01 - Caribbean Queen.flac",
)
assert found == str(target)
def test_fuzzy_rejects_low_similarity(tmp_path):
"""A completely different filename in the same dir must not
fuzzy-match — the 0.85 floor keeps unrelated files out."""
downloads = tmp_path / 'downloads'
_touch(downloads / 'random-unrelated-file.flac')
found, _ = find_completed_audio_file(
str(downloads), r'shared\Billy Ocean\Very Best Of\01 - Suddenly.flac',
)
assert found is None
if __name__ == '__main__':
pytest.main([__file__, '-v'])
# ---------------------------------------------------------------------------
# Issue #835 — a '/' in a YouTube/Tidal title is part of the NAME, not a path
# separator. The encoded ``id||title`` finder previously basename-split the
# title ("YouSeeBIGGIRL/T:T" -> "T:T"), so the real on-disk file (with the
# slash sanitised) never matched and valid downloads got quarantined.
# ---------------------------------------------------------------------------
from core.downloads.file_finder import _extract_basename
def test_encoded_title_with_slash_is_not_basename_split():
# The Sawano AoT track. The id||title encoding must keep the whole title.
assert _extract_basename('vy63u2hKoPE||YouSeeBIGGIRL/T:T') == 'YouSeeBIGGIRL/T:T'
def test_finds_youtube_file_whose_title_contains_a_slash(tmp_path):
downloads = tmp_path / 'downloads'
# On disk the slash is sanitised to a look-alike and the colon spaced out,
# exactly as in the issue screenshot.
target = downloads / 'YouSeeBIGGIRLT: T.mp3'
_touch(target)
found, location = find_completed_audio_file(
str(downloads), 'vy63u2hKoPE||YouSeeBIGGIRL/T:T',
)
assert found == str(target), 'pre-#835 this truncated the target to "T:T" and missed the file'
assert location == 'downloads'
def test_slash_title_does_not_match_an_unrelated_file(tmp_path):
# Guard against the fix being too loose: a different track must NOT match.
downloads = tmp_path / 'downloads'
_touch(downloads / 'Some Totally Different Song.mp3')
found, _ = find_completed_audio_file(
str(downloads), 'vy63u2hKoPE||YouSeeBIGGIRL/T:T',
)
assert found is None
def test_real_soulseek_path_still_basenamed(tmp_path):
# Regression: a genuine remote PATH must still resolve to its last segment.
downloads = tmp_path / 'downloads'
target = downloads / '01 - Suddenly.flac'
_touch(target)
assert _extract_basename(r'shared\Billy Ocean\Very Best Of\01 - Suddenly.flac') == '01 - Suddenly.flac'
found, _ = find_completed_audio_file(
str(downloads), r'shared\Billy Ocean\Very Best Of\01 - Suddenly.flac',
)
assert found == str(target)