soulsync/tests/imports/test_rematch_apply.py
BoulderBadgeDad f4c16ecc22 #889 Phase 4: the Re-identify modal + apply backend
The showpiece: a focused 'which release does this track belong to?' chooser.
Source tabs (default active), pre-seeded search, the same song surfaced across
single/EP/album with color-coded type badges, ISRC-ranked, replace-original
toggle (on by default). Glassy panel, blurred hero art, shimmer/spinner states,
hover-lift result cards — matched to the app's modal language.

Backend:
- core/imports/rematch_apply.py: pure staged_destination + build_reidentify_hint,
  injectable stage_file_for_reidentify (COPIES the file, never moves — original
  safe until re-import succeeds). 6 tests.
- POST /api/reidentify/apply (admin-only): resolve_hint_fields → stage file →
  create_hint → nudge the worker. Replace deletes the old row only on success.

Frontend: modal markup (index.html), full stylesheet (style.css), and the
openReidentifyModal/search/select/confirm flow (library.js). Not yet reachable
from a button — Phase 5 wires it.
2026-06-18 15:37:56 -07:00

71 lines
2.8 KiB
Python

"""#889 Phase 4/5: apply a re-identify — stage the file (copy, not move) + build
the hint. Locks down: the original is never touched, the staged name is unique +
keeps the extension, the hint carries the chosen release, and replace_track_id is
set ONLY when 'replace' is ticked.
"""
from __future__ import annotations
from pathlib import Path
import pytest
from core.imports.rematch_apply import (
build_reidentify_hint,
stage_file_for_reidentify,
staged_destination,
)
_FIELDS = {
"source": "spotify", "track_id": "trk_1", "album_id": "alb_album1",
"artist_id": "art_1", "track_title": "Song", "album_name": "Album1",
"artist_name": "Artist", "album_type": "album", "track_number": 5,
"disc_number": 1, "isrc": "US1234567890",
}
def test_staged_destination_keeps_ext_and_is_traceable():
dest = staged_destination("/staging", "/lib/EP1/05 - Song.flac", 42)
assert dest.endswith(".flac")
assert "[reid-42]" in dest # traceable to the track + unique per track
assert dest.startswith("/staging/") # loose file in staging root → single candidate
def test_stage_copies_not_moves(tmp_path: Path):
src = tmp_path / "lib" / "EP1" / "05 - Song.flac"
src.parent.mkdir(parents=True)
src.write_bytes(b"audio-bytes")
staging = tmp_path / "Staging"
out = stage_file_for_reidentify(str(src), str(staging), 42,
signature_fn=lambda p: "sig123")
staged = Path(out["staged_path"])
assert staged.is_file() and staged.read_bytes() == b"audio-bytes"
assert src.is_file() # ORIGINAL untouched (copy, never move)
assert out["content_hash"] == "sig123"
def test_stage_missing_source_raises(tmp_path: Path):
with pytest.raises(FileNotFoundError):
stage_file_for_reidentify(str(tmp_path / "gone.flac"), str(tmp_path / "S"), 1)
def test_build_hint_sets_replace_when_ticked():
h = build_reidentify_hint(42, _FIELDS, "/staging/x.flac", "sig", replace=True)
assert h.replace_track_id == 42
assert h.album_id == "alb_album1" and h.source == "spotify"
assert h.track_number == 5 and h.isrc == "US1234567890"
assert h.exempt_dedup is True
assert h.staged_path == "/staging/x.flac" and h.content_hash == "sig"
def test_build_hint_no_replace_when_unticked():
h = build_reidentify_hint(42, _FIELDS, "/staging/x.flac", "sig", replace=False)
assert h.replace_track_id is None # keep original → no deletion
assert h.exempt_dedup is True # still bypasses dedup-skip (explicit action)
def test_build_hint_handles_non_numeric_track_id():
# Jellyfin-style GUID track ids must still round-trip as replace target.
h = build_reidentify_hint("abc-guid", _FIELDS, "/s/x.flac", None, replace=True)
assert h.replace_track_id == "abc-guid"