chore: adiciona .gitignore e commit.command
This commit is contained in:
Executable
+202
@@ -0,0 +1,202 @@
|
||||
"""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 = (
|
||||
'<?xml version="1.0" encoding="UTF-8"?>\n'
|
||||
'<fcpxml version="1.13"><resources>'
|
||||
'<format id="r1" frameDuration="1/24s" width="1920" height="1080"/>'
|
||||
'</resources>'
|
||||
'<library><event name="Evt"><project name="Live Test">'
|
||||
'<sequence format="r1" duration="240/24s"><spine>'
|
||||
'<gap name="Gap" offset="0s" duration="240/24s"/>'
|
||||
'</spine></sequence></project></event></library></fcpxml>'
|
||||
)
|
||||
|
||||
|
||||
@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
|
||||
Reference in New Issue
Block a user