feat(privacy-host): add signed native calendar contacts and reminders host
Add the owner-only AF_UNIX Reyna CLI privacy host, strict signed-app installation, and typed native routing for Calendar, Contacts, and Reminders.\n\nAdd bounded system-status paths and config-only direct local-service wrappers. Preserve MacMiniMCP pending explicit cutover approval.\n\nApple Notes is intentionally deferred: no native Notes operations, Apple Events declaration, or Automation helper are included; legacy Notes handling remains untouched.
This commit is contained in:
@@ -0,0 +1,279 @@
|
||||
"""Tests for reminders native wrappers and CLI – TDD fakes only, no live Reminders access."""
|
||||
from pathlib import Path
|
||||
import json
|
||||
import pytest
|
||||
from typer.testing import CliRunner
|
||||
from reyna_cli.cli import app
|
||||
|
||||
runner = CliRunner()
|
||||
|
||||
|
||||
def test_native_reminders_request_full_access_explicit_op(monkeypatch):
|
||||
from reyna_cli import privacy_host as ph_mod
|
||||
|
||||
captured = {}
|
||||
|
||||
class FakeClient:
|
||||
def __init__(self, timeout):
|
||||
captured["timeout"] = timeout
|
||||
|
||||
def call(self, op, args):
|
||||
captured["op"] = op
|
||||
captured["args"] = args
|
||||
return {"id": "x", "ok": True, "result": {"protocol_version": "1.0.0", "operation": "reminders.request_full_access", "status": "authorized"}}
|
||||
|
||||
monkeypatch.setattr(ph_mod, "PrivacyClient", FakeClient)
|
||||
result = ph_mod.native_reminders_request_full_access()
|
||||
assert captured["op"] == "reminders.request_full_access"
|
||||
assert captured["args"] == {}
|
||||
assert captured["timeout"] == 35
|
||||
assert result["ok"] is True
|
||||
assert result["result"]["status"] == "authorized"
|
||||
|
||||
|
||||
def test_native_reminders_lists_direct_op(monkeypatch):
|
||||
from reyna_cli import privacy_host as ph_mod
|
||||
|
||||
captured = {}
|
||||
|
||||
class FakeClient:
|
||||
def call(self, op, args):
|
||||
captured["op"] = op
|
||||
captured["args"] = args
|
||||
return {"id": "1", "ok": True, "result": {"protocol_version": "1.0.0", "operation": "reminders.lists", "reminder_lists": [{"id": "a", "title": "Groceries", "source": "iCloud", "type": "caldav"}]}}
|
||||
|
||||
monkeypatch.setattr(ph_mod, "PrivacyClient", FakeClient)
|
||||
result = ph_mod.native_reminders_lists()
|
||||
assert captured["op"] == "reminders.lists"
|
||||
assert captured["args"] == {}
|
||||
assert result["ok"] is True
|
||||
assert result["result"]["reminder_lists"][0]["title"] == "Groceries"
|
||||
|
||||
|
||||
def test_native_reminders_list_success(monkeypatch):
|
||||
from reyna_cli import privacy_host as ph_mod
|
||||
|
||||
captured = {}
|
||||
|
||||
class FakeClient:
|
||||
def call(self, op, args):
|
||||
captured["op"] = op
|
||||
captured["args"] = args
|
||||
return {"id": "2", "ok": True, "result": {"reminders": [{"id": "r1", "list_id": "a", "list_title": "Groceries", "title": "Milk", "completed": False, "due": None, "priority": 0}]}}
|
||||
|
||||
monkeypatch.setattr(ph_mod, "PrivacyClient", FakeClient)
|
||||
result = ph_mod.native_reminders_list(list_name="Groceries", limit=25)
|
||||
assert captured["op"] == "reminders.list"
|
||||
assert captured["args"]["list"] == "Groceries"
|
||||
assert captured["args"]["limit"] == 25
|
||||
assert result["result"]["reminders"][0]["title"] == "Milk"
|
||||
|
||||
|
||||
def test_native_reminders_list_with_id_and_completed_filter(monkeypatch):
|
||||
from reyna_cli import privacy_host as ph_mod
|
||||
|
||||
captured = {}
|
||||
|
||||
class FakeClient:
|
||||
def call(self, op, args):
|
||||
captured["args"] = args
|
||||
return {"id": "2", "ok": True, "result": {"reminders": []}}
|
||||
|
||||
monkeypatch.setattr(ph_mod, "PrivacyClient", FakeClient)
|
||||
ph_mod.native_reminders_list(list_id="stable-id", completed=True, limit=50)
|
||||
assert captured["args"]["list_id"] == "stable-id"
|
||||
assert captured["args"]["completed"] is True
|
||||
assert captured["args"]["limit"] == 50
|
||||
|
||||
|
||||
def test_native_reminders_create_success_with_list_title(monkeypatch):
|
||||
from reyna_cli import privacy_host as ph_mod
|
||||
|
||||
captured = {}
|
||||
|
||||
class FakeClient:
|
||||
def call(self, op, args):
|
||||
captured["op"] = op
|
||||
captured["args"] = args
|
||||
return {"id": "3", "ok": True, "result": {"created_reminder": {"id": "new", "list_id": "a", "list_title": "Groceries", "title": "Buy eggs"}}}
|
||||
|
||||
monkeypatch.setattr(ph_mod, "PrivacyClient", FakeClient)
|
||||
result = ph_mod.native_reminders_create(title="Buy eggs", list_name="Groceries", notes="organic", due="2026-08-10T10:00:00Z", priority=1)
|
||||
assert captured["op"] == "reminders.create"
|
||||
assert captured["args"]["title"] == "Buy eggs"
|
||||
assert captured["args"]["list"] == "Groceries"
|
||||
assert captured["args"]["notes"] == "organic"
|
||||
assert captured["args"]["due"] == "2026-08-10T10:00:00Z"
|
||||
assert captured["args"]["priority"] == 1
|
||||
assert result["ok"] is True
|
||||
|
||||
|
||||
def test_native_reminders_create_requires_list():
|
||||
from reyna_cli import privacy_host as ph_mod
|
||||
|
||||
with pytest.raises(ValueError, match="list must be specified"):
|
||||
ph_mod.native_reminders_create(title="No list")
|
||||
|
||||
|
||||
def test_native_reminders_create_validates_title():
|
||||
from reyna_cli import privacy_host as ph_mod
|
||||
|
||||
with pytest.raises(ValueError, match="title must be nonempty"):
|
||||
ph_mod.native_reminders_create(title="", list_name="X")
|
||||
|
||||
with pytest.raises(ValueError, match="title exceeds"):
|
||||
ph_mod.native_reminders_create(title="A" * 1025, list_name="X")
|
||||
|
||||
|
||||
def test_native_reminders_create_validates_limit():
|
||||
from reyna_cli import privacy_host as ph_mod
|
||||
|
||||
with pytest.raises(ValueError, match="limit"):
|
||||
ph_mod.native_reminders_list(limit=0)
|
||||
|
||||
with pytest.raises(ValueError, match="limit"):
|
||||
ph_mod.native_reminders_list(limit=999)
|
||||
|
||||
|
||||
def test_native_reminders_no_mcp_import():
|
||||
from reyna_cli import privacy_host as ph_mod
|
||||
import pathlib
|
||||
src = pathlib.Path(ph_mod.__file__).read_text()
|
||||
assert "call_macmini_tool" not in src
|
||||
assert "macmini_client" not in src
|
||||
assert "def native_reminders_request_full_access" in src
|
||||
assert "reminders.request_full_access" in src
|
||||
assert "def native_reminders_lists" in src
|
||||
assert "def native_reminders_list" in src
|
||||
assert "def native_reminders_create" in src
|
||||
|
||||
|
||||
def test_cli_reminders_lists_uses_native(monkeypatch):
|
||||
def fake_lists():
|
||||
return {"ok": True, "source": "native_privacy_host", "result": {"reminder_lists": [{"id": "a", "title": "Groceries", "source": "iCloud", "type": "caldav"}]}}
|
||||
|
||||
monkeypatch.setattr("reyna_cli.privacy_host.native_reminders_lists", fake_lists)
|
||||
res = runner.invoke(app, ["macmini", "reminders", "lists", "--json"])
|
||||
assert res.exit_code == 0, res.stdout + res.stderr
|
||||
payload = json.loads(res.stdout)
|
||||
assert payload["ok"] is True
|
||||
assert payload["result"]["reminder_lists"][0]["title"] == "Groceries"
|
||||
|
||||
|
||||
def test_cli_reminders_list_uses_native(monkeypatch):
|
||||
captured = {}
|
||||
|
||||
def fake_list(list_id=None, list_name=None, completed=None, limit=50):
|
||||
captured["list_id"] = list_id
|
||||
captured["list_name"] = list_name
|
||||
captured["completed"] = completed
|
||||
captured["limit"] = limit
|
||||
return {"ok": True, "source": "native_privacy_host", "result": {"reminders": []}}
|
||||
|
||||
monkeypatch.setattr("reyna_cli.privacy_host.native_reminders_list", fake_list)
|
||||
res = runner.invoke(app, ["macmini", "reminders", "list", "--list", "Groceries", "--limit", "10", "--json"])
|
||||
assert res.exit_code == 0, res.stdout + res.stderr
|
||||
assert captured["list_name"] == "Groceries"
|
||||
assert captured["limit"] == 10
|
||||
|
||||
|
||||
def test_cli_reminders_list_with_completed_flags(monkeypatch):
|
||||
captured = {}
|
||||
|
||||
def fake_list(list_id=None, list_name=None, completed=None, limit=25):
|
||||
captured["completed"] = completed
|
||||
return {"ok": True, "source": "native_privacy_host", "result": {"reminders": []}}
|
||||
|
||||
monkeypatch.setattr("reyna_cli.privacy_host.native_reminders_list", fake_list)
|
||||
res = runner.invoke(app, ["macmini", "reminders", "list", "--list", "Groceries", "--completed", "--json"])
|
||||
assert res.exit_code == 0, res.stdout + res.stderr
|
||||
assert captured["completed"] is True
|
||||
|
||||
res2 = runner.invoke(app, ["macmini", "reminders", "list", "--list", "Groceries", "--incomplete", "--json"])
|
||||
assert res2.exit_code == 0, res2.stdout + res2.stderr
|
||||
# --incomplete should set completed=False
|
||||
assert captured["completed"] is False
|
||||
|
||||
|
||||
def test_cli_reminders_create_requires_list():
|
||||
res = runner.invoke(app, ["macmini", "reminders", "create", "Buy milk", "--json"])
|
||||
assert res.exit_code != 0
|
||||
|
||||
|
||||
def test_cli_reminders_create_with_title_and_list(monkeypatch):
|
||||
captured = {}
|
||||
|
||||
def fake_create(title, list_id=None, list_name=None, notes=None, due=None, priority=None):
|
||||
captured["title"] = title
|
||||
captured["list_name"] = list_name
|
||||
captured["list_id"] = list_id
|
||||
captured["notes"] = notes
|
||||
captured["due"] = due
|
||||
captured["priority"] = priority
|
||||
return {"ok": True, "source": "native_privacy_host", "result": {"created_reminder": {"id": "new", "list_id": "a", "list_title": "Groceries", "title": title}}}
|
||||
|
||||
monkeypatch.setattr("reyna_cli.privacy_host.native_reminders_create", fake_create)
|
||||
res = runner.invoke(app, ["macmini", "reminders", "create", "Buy milk", "--list", "Groceries", "--notes", "2% please", "--json"])
|
||||
assert res.exit_code == 0, res.stdout + res.stderr
|
||||
assert captured["title"] == "Buy milk"
|
||||
assert captured["list_name"] == "Groceries"
|
||||
assert captured["notes"] == "2% please"
|
||||
|
||||
|
||||
def test_cli_reminders_create_with_list_id_and_due_priority(monkeypatch):
|
||||
captured = {}
|
||||
|
||||
def fake_create(title, list_id=None, list_name=None, notes=None, due=None, priority=None):
|
||||
captured["list_id"] = list_id
|
||||
captured["due"] = due
|
||||
captured["priority"] = priority
|
||||
return {"ok": True, "source": "native_privacy_host", "result": {"created_reminder": {"id": "new", "list_id": list_id or "", "list_title": "", "title": title}}}
|
||||
|
||||
monkeypatch.setattr("reyna_cli.privacy_host.native_reminders_create", fake_create)
|
||||
res = runner.invoke(app, ["macmini", "reminders", "create", "Task", "--list-id", "abc-123", "--due", "2026-08-10T10:00:00Z", "--priority", "1", "--json"])
|
||||
assert res.exit_code == 0, res.stdout + res.stderr
|
||||
assert captured["list_id"] == "abc-123"
|
||||
assert captured["due"] == "2026-08-10T10:00:00Z"
|
||||
assert captured["priority"] == 1
|
||||
|
||||
|
||||
def test_cli_reminders_no_mcp_tool_call_remaining():
|
||||
from reyna_cli import cli as cli_mod
|
||||
src = Path(cli_mod.__file__).read_text()
|
||||
assert "native_reminders_lists" in src
|
||||
assert "native_reminders_list" in src
|
||||
assert "native_reminders_create" in src
|
||||
assert 'call_macmini_tool("reminders_list_lists"' not in src
|
||||
assert 'call_macmini_tool("reminders_list"' not in src
|
||||
assert 'call_macmini_tool("reminders_create"' not in src
|
||||
|
||||
|
||||
def test_privacy_host_cli_has_reminders_authorize():
|
||||
res = runner.invoke(app, ["privacy-host", "--help"])
|
||||
assert res.exit_code == 0
|
||||
assert "reminders-authorize" in res.stdout
|
||||
|
||||
|
||||
def test_privacy_host_reminders_authorize_cli_help_mentions_prompt():
|
||||
res = runner.invoke(app, ["privacy-host", "reminders-authorize", "--help"])
|
||||
assert res.exit_code == 0
|
||||
out = res.stdout.lower()
|
||||
assert "reminders" in out
|
||||
assert "permission" in out or "prompt" in out or "privacy" in out
|
||||
|
||||
|
||||
def test_reminders_authorize_cli_no_generic_fallback(monkeypatch):
|
||||
calls = {"count": 0}
|
||||
|
||||
def fake_native():
|
||||
calls["count"] += 1
|
||||
return {"ok": True, "source": "native_privacy_host", "result": {"protocol_version": "1.0.0", "operation": "reminders.request_full_access", "status": "authorized"}}
|
||||
|
||||
monkeypatch.setattr("reyna_cli.privacy_host.native_reminders_request_full_access", fake_native)
|
||||
|
||||
res = runner.invoke(app, ["privacy-host", "reminders-authorize", "--json"])
|
||||
assert res.exit_code == 0, res.stdout + res.stderr
|
||||
payload = json.loads(res.stdout)
|
||||
assert payload["ok"] is True
|
||||
assert payload["result"]["status"] == "authorized"
|
||||
assert calls["count"] == 1
|
||||
Reference in New Issue
Block a user