"""Tests for tagsync.platform_macos. The plist encoding test runs anywhere. The xattr round-trip runs only on systems where the extended-attribute syscalls are available (macOS and most Linux filesystems). """ from __future__ import annotations import os import plistlib import sys from pathlib import Path import pytest from tagsync.platform_macos import FINDER_TAGS_XATTR, MacWriter def test_mac_writer_supports_all_file_types(): w = MacWriter() for path in ("a.jpg", "b.zip", "c.txt", "noextension"): assert w.supports(Path(path)) def test_mac_writer_is_online_only_is_false(): w = MacWriter() assert w.is_online_only(Path("/any/file")) is False def test_plist_encoding_matches_finder_format(): """Finder expects a binary plist of a string array.""" from tagsync.platform_macos import plistlib # re-export tags = ["alpha", "beta", "gamma"] encoded = plistlib.dumps(tags, fmt=plistlib.FMT_BINARY) # Binary plists start with 'bplist'. assert encoded.startswith(b"bplist") decoded = plistlib.loads(encoded) assert decoded == tags def _xattr_supported(path: Path) -> bool: """Can we setxattr / getxattr on files in this directory?""" if not hasattr(os, "setxattr") or not hasattr(os, "getxattr"): return False try: # Use a neutral namespace; on Linux, user.* always works on ext4. os.setxattr(path, "user.tagsync_probe", b"ok") val = os.getxattr(path, "user.tagsync_probe") os.removexattr(path, "user.tagsync_probe") return val == b"ok" except OSError: return False @pytest.fixture def xattr_capable_file(tmp_path): f = tmp_path / "target" f.touch() if not _xattr_supported(f): pytest.skip("filesystem does not support extended attributes") return f def test_write_and_read_tags_via_xattr(xattr_capable_file): """Round-trip the actual xattr the script uses in production. On Linux, the 'com.apple.metadata:*' namespace may not be allowed, so this test uses the MacWriter only on Darwin. """ if sys.platform != "darwin": pytest.skip("Finder xattr namespace only writable on macOS") w = MacWriter() w.write_tags(xattr_capable_file, ["alpha", "beta"]) raw = os.getxattr(xattr_capable_file, FINDER_TAGS_XATTR) assert plistlib.loads(raw) == ["alpha", "beta"] def test_clear_tags_via_xattr(xattr_capable_file): if sys.platform != "darwin": pytest.skip("Finder xattr namespace only writable on macOS") w = MacWriter() w.write_tags(xattr_capable_file, ["alpha"]) w.write_tags(xattr_capable_file, []) with pytest.raises(OSError): os.getxattr(xattr_capable_file, FINDER_TAGS_XATTR) def test_clear_tags_when_none_set_is_noop(tmp_path): """Removing a non-existent xattr should not raise.""" if sys.platform != "darwin": pytest.skip("requires macOS xattr semantics") f = tmp_path / "fresh" f.touch() MacWriter().write_tags(f, []) # should not raise # --- launchd plist generation ----------------------------------------------- def test_install_schedule_writes_plist(tmp_path, monkeypatch): """We don't want to actually call launchctl; intercept it and verify the plist contents.""" import tagsync.platform_macos as pm calls = [] class FakeResult: returncode = 0 stderr = "" stdout = "" monkeypatch.setattr(pm.subprocess, "run", lambda args, **kw: calls.append(args) or FakeResult()) # Redirect plist + log paths into tmp to avoid touching real HOME. fake_plist = tmp_path / "agent.plist" fake_log = tmp_path / "log" monkeypatch.setattr(pm, "LAUNCHD_PLIST", fake_plist) monkeypatch.setattr(pm, "LOG_PATH", fake_log) entry = tmp_path / "tagsync.py" entry.write_text("# placeholder") result_path = pm.install_schedule(entry, interval_minutes=30) assert result_path == fake_plist content = fake_plist.read_text() assert "StartInterval" in content assert "1800" in content # 30 min -> 1800 sec assert str(entry) in content assert "launchctl" in " ".join(" ".join(c) for c in calls) def test_uninstall_schedule_when_not_installed(tmp_path, monkeypatch): import tagsync.platform_macos as pm monkeypatch.setattr(pm, "LAUNCHD_PLIST", tmp_path / "missing.plist") assert pm.uninstall_schedule() is False