be85903234
The last piece of the macOS permissions campaign (#52010 follow-up): macOS prompts per-folder (Desktop, then Downloads, then Documents, ...) as the agent touches each one — a drip-feed of dialogs on first use. ONE Full Disk Access grant covers all of them permanently, and with the stable signing identities merged this week it survives every update. Nothing in Hermes taught users that. - hermes doctor: check_macos_full_disk_access() — prompt-free probe (the FDA-gated TCC db dir returns EPERM without a dialog; TCC only prompts on protected-CATEGORY paths), reports granted state or prints the one-switch setup with the Privacy_AllFiles deep link. - hermes setup: same probe at the end of onboarding — the moment users are primed to do system setup — silent when already granted, indeterminate, or non-macOS. - docs: desktop.md TCC section now leads with the one-switch guidance. - 7 tests (granted / denied / indeterminate / non-macOS, both surfaces).
98 lines
3.9 KiB
Python
98 lines
3.9 KiB
Python
"""macOS Full Disk Access onboarding guidance (issue #52010 follow-up).
|
|
|
|
One FDA grant silences every per-folder TCC prompt permanently. Doctor
|
|
reports the state and prints the one-switch setup; `hermes setup` surfaces
|
|
the same tip at onboarding. The probe must never itself trigger a prompt —
|
|
it reads the FDA-gated TCC db directory, which returns EPERM (no dialog)
|
|
without the grant.
|
|
"""
|
|
|
|
import io
|
|
import contextlib
|
|
|
|
import hermes_cli.doctor as doctor_mod
|
|
from hermes_cli.setup import _print_macos_fda_tip
|
|
|
|
|
|
def _capture(fn):
|
|
buf = io.StringIO()
|
|
with contextlib.redirect_stdout(buf):
|
|
fn()
|
|
return buf.getvalue()
|
|
|
|
|
|
class TestDoctorFdaCheck:
|
|
def test_silent_on_non_macos(self, monkeypatch):
|
|
monkeypatch.setattr(doctor_mod.sys, "platform", "linux")
|
|
out = _capture(doctor_mod.check_macos_full_disk_access)
|
|
assert out == ""
|
|
|
|
def test_granted_reports_ok(self, monkeypatch, tmp_path):
|
|
monkeypatch.setattr(doctor_mod.sys, "platform", "darwin")
|
|
tcc = tmp_path / "Library" / "Application Support" / "com.apple.TCC"
|
|
tcc.mkdir(parents=True)
|
|
monkeypatch.setattr(doctor_mod.Path, "home", classmethod(lambda cls: tmp_path))
|
|
out = _capture(doctor_mod.check_macos_full_disk_access)
|
|
assert "Full Disk Access granted" in out
|
|
assert "Privacy_AllFiles" not in out
|
|
|
|
def test_denied_prints_one_switch_guidance(self, monkeypatch, tmp_path):
|
|
monkeypatch.setattr(doctor_mod.sys, "platform", "darwin")
|
|
tcc = tmp_path / "Library" / "Application Support" / "com.apple.TCC"
|
|
tcc.mkdir(parents=True)
|
|
monkeypatch.setattr(doctor_mod.Path, "home", classmethod(lambda cls: tmp_path))
|
|
|
|
def _eperm(path):
|
|
raise PermissionError(13, "Operation not permitted", str(path))
|
|
|
|
monkeypatch.setattr(doctor_mod.os, "listdir", _eperm)
|
|
out = _capture(doctor_mod.check_macos_full_disk_access)
|
|
assert "Full Disk Access" in out
|
|
assert "Privacy_AllFiles" in out
|
|
assert "System Settings" in out
|
|
|
|
def test_indeterminate_probe_is_silent(self, monkeypatch, tmp_path):
|
|
"""Missing TCC dir (weird install) must not nag."""
|
|
monkeypatch.setattr(doctor_mod.sys, "platform", "darwin")
|
|
monkeypatch.setattr(doctor_mod.Path, "home", classmethod(lambda cls: tmp_path))
|
|
# tmp_path has no Library/Application Support/com.apple.TCC →
|
|
# FileNotFoundError (an OSError that is not PermissionError).
|
|
out = _capture(doctor_mod.check_macos_full_disk_access)
|
|
assert out == ""
|
|
|
|
|
|
class TestSetupFdaTip:
|
|
def test_silent_on_non_macos(self, monkeypatch):
|
|
import hermes_cli.setup as setup_mod
|
|
|
|
monkeypatch.setattr(setup_mod.sys, "platform", "linux")
|
|
out = _capture(_print_macos_fda_tip)
|
|
assert out == ""
|
|
|
|
def test_silent_when_already_granted(self, monkeypatch, tmp_path):
|
|
import hermes_cli.setup as setup_mod
|
|
|
|
monkeypatch.setattr(setup_mod.sys, "platform", "darwin")
|
|
tcc = tmp_path / "Library" / "Application Support" / "com.apple.TCC"
|
|
tcc.mkdir(parents=True)
|
|
monkeypatch.setattr(setup_mod.Path, "home", classmethod(lambda cls: tmp_path))
|
|
out = _capture(_print_macos_fda_tip)
|
|
assert out == ""
|
|
|
|
def test_tip_printed_when_denied(self, monkeypatch, tmp_path):
|
|
import hermes_cli.setup as setup_mod
|
|
|
|
monkeypatch.setattr(setup_mod.sys, "platform", "darwin")
|
|
tcc = tmp_path / "Library" / "Application Support" / "com.apple.TCC"
|
|
tcc.mkdir(parents=True)
|
|
monkeypatch.setattr(setup_mod.Path, "home", classmethod(lambda cls: tmp_path))
|
|
|
|
def _eperm(path):
|
|
raise PermissionError(13, "Operation not permitted", str(path))
|
|
|
|
monkeypatch.setattr(setup_mod.os, "listdir", _eperm)
|
|
out = _capture(_print_macos_fda_tip)
|
|
assert "Full Disk Access" in out
|
|
assert "Privacy_AllFiles" in out
|
|
assert "survives every Hermes update" in out
|