"""Tests for Live mode v1 (v0.9.0) — push_to_fcp / list_fcp_libraries. osascript and pgrep are mocked throughout: unit tests must never launch Final Cut Pro. The import-options injection path is real (and DTD-checked in test_dtd_validation.py's environment when available). """ import subprocess import xml.etree.ElementTree as ET import pytest import server from fcpxml import live from fcpxml.live import inject_import_options, list_fcp_libraries, push_to_fcp SIMPLE_XML = ( '\n' '' '' '' '' '' '' '' ) @pytest.fixture def project_file(tmp_path): f = tmp_path / "live.fcpxml" f.write_text(SIMPLE_XML) return f def _fake_proc(returncode=0, stdout="", stderr=""): return subprocess.CompletedProcess( args=["osascript"], returncode=returncode, stdout=stdout, stderr=stderr ) # ============================================================ # inject_import_options # ============================================================ def test_inject_import_options_first_child(project_file, tmp_path): out = tmp_path / "live_import.fcpxml" inject_import_options( str(project_file), str(out), library_location="/Users/me/Movies/Test.fcplibrary", suppress_warnings=True, copy_assets=False, ) root = ET.parse(out).getroot() first = list(root)[0] assert first.tag == "import-options" opts = {o.get("key"): o.get("value") for o in first.findall("option")} # location is normalized to .fcpbundle so FCP auto-creates instead of # raising the modal "Open Library" picker (live-verified on FCP 12.2) assert opts["library location"].startswith("file:///Users/me/Movies/") assert opts["library location"].endswith("Test.fcpbundle") assert opts["suppress warnings"] == "1" assert opts["copy assets"] == "0" def test_inject_normalizes_to_fcpbundle(project_file, tmp_path): out = tmp_path / "n.fcpxml" inject_import_options( str(project_file), str(out), library_location="/tmp/Bare/MyLib" ) loc = ET.parse(out).getroot().find( "import-options/option[@key='library location']" ).get("value") assert loc.endswith("MyLib.fcpbundle") def test_inject_import_options_replaces_existing(project_file, tmp_path): out1 = tmp_path / "a.fcpxml" inject_import_options(str(project_file), str(out1), suppress_warnings=False) out2 = tmp_path / "b.fcpxml" inject_import_options(str(out1), str(out2), suppress_warnings=True) root = ET.parse(out2).getroot() assert len(root.findall("import-options")) == 1 def test_inject_percent_encodes_spaces(project_file, tmp_path): out = tmp_path / "c.fcpxml" inject_import_options( str(project_file), str(out), library_location="/Users/me/My Movies/Lib.fcplibrary", ) root = ET.parse(out).getroot() loc = root.find("import-options/option[@key='library location']").get("value") assert "%20" in loc and " " not in loc # ============================================================ # push_to_fcp (osascript mocked) # ============================================================ def test_push_sends_options_copy(project_file, tmp_path, monkeypatch): calls = [] def fake_run(script): calls.append(script) return _fake_proc() monkeypatch.setattr(live, "_run_osascript", fake_run) monkeypatch.setattr(live, "fcp_is_running", lambda: True) copy_path = str(tmp_path / "live_import.fcpxml") result = push_to_fcp( str(project_file), library_location="/tmp/Lib.fcplibrary", import_copy_path=copy_path, ) assert result["sent"] == copy_path assert result["launched_fcp"] is False assert 'tell application "Final Cut Pro"' in calls[0] assert "live_import.fcpxml" in calls[0] def test_push_reports_launch(project_file, tmp_path, monkeypatch): monkeypatch.setattr(live, "_run_osascript", lambda s: _fake_proc()) monkeypatch.setattr(live, "fcp_is_running", lambda: False) result = push_to_fcp( str(project_file), import_copy_path=str(tmp_path / "x.fcpxml") ) assert result["launched_fcp"] is True def test_push_permission_error_hint(project_file, tmp_path, monkeypatch): monkeypatch.setattr( live, "_run_osascript", lambda s: _fake_proc(returncode=1, stderr="Not authorized (-1743)"), ) monkeypatch.setattr(live, "fcp_is_running", lambda: True) with pytest.raises(RuntimeError, match="Automation permission"): push_to_fcp(str(project_file), import_copy_path=str(tmp_path / "x.fcpxml")) def test_push_escapes_applescript_quotes(tmp_path, monkeypatch): evil = tmp_path / 'a"b.fcpxml' evil.write_text(SIMPLE_XML) captured = {} monkeypatch.setattr( live, "_run_osascript", lambda s: captured.setdefault("script", s) and _fake_proc() or _fake_proc(), ) monkeypatch.setattr(live, "fcp_is_running", lambda: True) push_to_fcp(str(evil)) assert '\\"' in captured["script"] # ============================================================ # list_fcp_libraries (osascript mocked) # ============================================================ def test_list_refuses_to_launch(monkeypatch): monkeypatch.setattr(live, "fcp_is_running", lambda: False) with pytest.raises(RuntimeError, match="not running"): list_fcp_libraries() def test_list_parses_records(monkeypatch): monkeypatch.setattr(live, "fcp_is_running", lambda: True) payload = ( "LibA\x1fEvt1\x1fProj1\x1fProj2\x1f\x1e" "LibA\x1fEvt2\x1f\x1e" "LibB\x1f\x1e" ) monkeypatch.setattr(live, "_run_osascript", lambda s: _fake_proc(stdout=payload)) libs = list_fcp_libraries() by_name = {lib["name"]: lib for lib in libs} assert by_name["LibA"]["events"][0]["projects"] == ["Proj1", "Proj2"] assert by_name["LibA"]["events"][1]["name"] == "Evt2" assert by_name["LibB"]["events"] == [] # ============================================================ # MCP handlers # ============================================================ async def test_handler_push(project_file, monkeypatch): monkeypatch.setattr(live, "_run_osascript", lambda s: _fake_proc()) monkeypatch.setattr(live, "fcp_is_running", lambda: True) result = await server.handle_push_to_fcp({"filepath": str(project_file)}) text = result[0].text assert "Sent to Final Cut Pro" in text assert "_import" in text # options copy, original untouched assert "Export XML" in text # read-back asymmetry stated async def test_handler_list_not_running(monkeypatch): monkeypatch.setattr(live, "fcp_is_running", lambda: False) result = await server.handle_list_fcp_libraries({}) assert "not running" in result[0].text def test_live_tools_registered(): assert "push_to_fcp" in server.TOOL_HANDLERS assert "list_fcp_libraries" in server.TOOL_HANDLERS