"""Confusable-tolerant path resolution (#833, the-hang-man). The library DB stored a track title with a curly apostrophe (U+2019); the file was written to disk with an ASCII one (U+0027). Delete rebuilt the unlink path from the DB value, so os.path.exists missed and the file survived. find_on_disk resolves the real on-disk name despite typographic confusables — with REAL temp files, not mocks, so it exercises the actual byte-level mismatch. """ from __future__ import annotations import os from core.library.path_resolve import fold_confusables, find_on_disk CURLY = chr(0x2019) # ’ right single quotation mark ASCII = chr(0x27) # ' ascii apostrophe # ── fold_confusables ──────────────────────────────────────────────────────── def test_curly_and_straight_apostrophe_fold_equal(): assert fold_confusables(f"I{CURLY}m Upset") == fold_confusables(f"I{ASCII}m Upset") assert fold_confusables(f"I{CURLY}m Upset") == "I'm Upset" def test_other_confusables_fold(): assert fold_confusables('Rock – Roll') == 'Rock - Roll' # en dash assert fold_confusables('Rock — Roll') == 'Rock - Roll' # em dash assert fold_confusables('A “B” C') == 'A "B" C' # smart quotes def test_fold_preserves_case_and_plain_text(): # Case must NOT be folded — case-sensitive datasets can hold names that # differ only by case, and folding could pick the wrong file. assert fold_confusables('Track NAME.mp3') == 'Track NAME.mp3' assert fold_confusables('') == '' # ── find_on_disk against real files ───────────────────────────────────────── def test_finds_ascii_file_from_curly_db_path(tmp_path): # On disk: ASCII apostrophe. DB/query: curly. This is the exact #833 case. album = tmp_path / 'Drake' / 'Scorpion' album.mkdir(parents=True) real = album / f"01 - I{ASCII}m Upset.mp3" real.write_text('audio') suffix = ['Drake', 'Scorpion', f"01 - I{CURLY}m Upset.mp3"] found = find_on_disk(str(tmp_path), suffix) assert found is not None assert os.path.samefile(found, real) def test_exact_match_still_works(tmp_path): real = tmp_path / 'Artist' / 'Album' / 'Track.mp3' real.parent.mkdir(parents=True) real.write_text('audio') found = find_on_disk(str(tmp_path), ['Artist', 'Album', 'Track.mp3']) assert found is not None and os.path.samefile(found, real) def test_confusable_in_folder_component(tmp_path): # The apostrophe can be in a folder name (album/artist), not just the file. folder = tmp_path / f"Guns N{ASCII} Roses" folder.mkdir() real = folder / 'track.mp3' real.write_text('audio') found = find_on_disk(str(tmp_path), [f"Guns N{CURLY} Roses", 'track.mp3']) assert found is not None and os.path.samefile(found, real) def test_returns_none_for_genuinely_missing_file(tmp_path): (tmp_path / 'Artist').mkdir() assert find_on_disk(str(tmp_path), ['Artist', 'Nope.mp3']) is None def test_does_not_match_a_different_track(tmp_path): # Folding apostrophes must not collapse two genuinely different names. (tmp_path / 'Some Other Song.mp3').write_text('x') assert find_on_disk(str(tmp_path), [f"I{CURLY}m Upset.mp3"]) is None def test_exact_wins_over_folded_when_both_present(tmp_path): # If the byte-exact file exists, it's chosen even when a folded sibling also # exists — no accidental cross-match. exact = tmp_path / f"I{CURLY}m Upset.mp3" other = tmp_path / f"I{ASCII}m Upset.mp3" exact.write_text('curly') other.write_text('ascii') found = find_on_disk(str(tmp_path), [f"I{CURLY}m Upset.mp3"]) assert found is not None and os.path.samefile(found, exact) def test_bad_base_dir_returns_none(tmp_path): assert find_on_disk(str(tmp_path / 'does-not-exist'), ['x.mp3']) is None