"""Tests for the relink_media bulk path-rewrite operation (v0.8.0).""" import xml.etree.ElementTree as ET import pytest import server from fcpxml.writer import FCPXMLModifier RELINK_XML = ( '\n' '' '' '' '' '' '' '' '' '' '' '' '' ) @pytest.fixture def project_file(tmp_path): f = tmp_path / "relink.fcpxml" f.write_text(RELINK_XML) return f def test_relink_url_and_plain_src(project_file): modifier = FCPXMLModifier(str(project_file)) result = modifier.relink_media("/Volumes/OldDrive/Media", "/Volumes/NewDrive/Footage") assert result["relinked"] == 2 assert result["total_assets"] == 3 by_asset = {c["asset"]: c for c in result["changes"]} # file:// URL form preserved, percent-encoding intact assert by_asset["Interview"]["new"] == ( "file:///Volumes/NewDrive/Footage/My%20Footage/interview.mov" ) # plain path stays a plain path assert by_asset["Broll"]["new"] == "/Volumes/NewDrive/Footage/broll.mov" # non-matching asset untouched assert "Elsewhere" not in by_asset def test_relink_accepts_file_url_prefixes(project_file): modifier = FCPXMLModifier(str(project_file)) result = modifier.relink_media( "file:///Volumes/OldDrive/Media", "file:///Volumes/NewDrive" ) assert result["relinked"] == 2 def test_relink_whole_segment_matching(project_file): """/Volumes/OldDrive/Med must NOT match /Volumes/OldDrive/Media/...""" modifier = FCPXMLModifier(str(project_file)) result = modifier.relink_media("/Volumes/OldDrive/Med", "/tmp/x") assert result["relinked"] == 0 def test_relink_dry_run_does_not_mutate(project_file): modifier = FCPXMLModifier(str(project_file)) result = modifier.relink_media("/Volumes/OldDrive", "/Volumes/New", dry_run=True) assert result["relinked"] == 2 assert result["dry_run"] is True # tree unchanged media_rep = modifier.root.find(".//media-rep") assert "OldDrive" in media_rep.get("src") def test_relink_target_exists_flag(project_file, tmp_path): real_dir = tmp_path / "real" real_dir.mkdir() (real_dir / "broll.mov").write_bytes(b"x") modifier = FCPXMLModifier(str(project_file)) result = modifier.relink_media("/Volumes/OldDrive/Media", str(real_dir)) by_asset = {c["asset"]: c for c in result["changes"]} assert by_asset["Broll"]["target_exists"] is True assert by_asset["Interview"]["target_exists"] is False def test_relink_empty_find_rejected(project_file): modifier = FCPXMLModifier(str(project_file)) with pytest.raises(ValueError, match="non-empty"): modifier.relink_media("", "/tmp") def test_relink_persists_through_save(project_file, tmp_path): modifier = FCPXMLModifier(str(project_file)) modifier.relink_media("/Volumes/OldDrive/Media", "/Volumes/NewDrive") out = tmp_path / "relinked.fcpxml" modifier.save(str(out)) root = ET.parse(out).getroot() assert root.find(".//media-rep").get("src") == ( "file:///Volumes/NewDrive/My%20Footage/interview.mov" ) # ============================================================ # MCP handler # ============================================================ async def test_handler_relink(project_file): result = await server.handle_relink_media({ "filepath": str(project_file), "find": "/Volumes/OldDrive/Media", "replace": "/Volumes/NewDrive", }) text = result[0].text assert "Relinked 2 media reference(s)" in text assert "_relinked" in text async def test_handler_relink_dry_run(project_file): result = await server.handle_relink_media({ "filepath": str(project_file), "find": "/Volumes/OldDrive/Media", "replace": "/Volumes/NewDrive", "dry_run": True, }) text = result[0].text assert "Would relink 2" in text assert "Dry run" in text # No output file created assert not (project_file.parent / "relink_relinked.fcpxml").exists() async def test_handler_relink_no_match(project_file): result = await server.handle_relink_media({ "filepath": str(project_file), "find": "/Nonexistent/Prefix", "replace": "/tmp", }) assert "Nothing to relink" in result[0].text def test_relink_registered_in_dispatch(): assert "relink_media" in server.TOOL_HANDLERS