"""confirm_and_apply: a candidate whose delete target is already gone must be marked applied (cleared from the queue), not silently no-op'd and left stuck. This is the 'can't delete this one' case that stranded a stale fuzzy candidate.""" import asyncio import time from app.db import SessionLocal from app.models import DedupCandidate, DedupRun from app.services import dedup_review_service as d def _make_candidate(keep_path: str, delete_path: str) -> int: db = SessionLocal() try: run = DedupRun(started_at=time.time(), mode="dry_run") db.add(run) db.commit() db.refresh(run) c = DedupCandidate( dedup_run_id=run.id, pass_name="fuzzy_audio", keep_path=keep_path, delete_path=delete_path, delete_id=None, ) db.add(c) db.commit() db.refresh(c) return c.id finally: db.close() def test_already_gone_delete_path_is_resolved(tmp_path): keep = tmp_path / "keep.flac" keep.write_bytes(b"x") # keep still exists gone = tmp_path / "gone.mp3" # delete target already removed cid = _make_candidate(str(keep), str(gone)) # No actionable file to delete -> no subprocess/lock; the async call resolves # the stale candidate directly. result = asyncio.run(d.confirm_and_apply([cid], "tester")) assert result is not None # a run row was produced (not a silent no-op) db = SessionLocal() try: c = db.get(DedupCandidate, cid) assert c.applied is True # cleared from the pending queue finally: db.close() # and it no longer appears in the pending list assert cid not in [c.id for c in d.list_pending_candidates()] def test_ignore_then_unignore(tmp_path): cid = _make_candidate(str(tmp_path / "a.flac"), str(tmp_path / "b.mp3")) assert d.ignore_candidate(cid, "tester").ignored is True assert cid not in [c.id for c in d.list_pending_candidates()] assert d.unignore_candidate(cid).ignored is False assert cid in [c.id for c in d.list_pending_candidates()]