"""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_cli_reminders_edit_uses_remindctl(monkeypatch): captured = {} def fake_run(args): captured["args"] = args return {"updated": {"id": "r1", "title": "New title"}} monkeypatch.setattr("reyna_cli.cli.run_remindctl", fake_run) res = runner.invoke(app, ["macmini", "reminders", "edit", "r1", "--title", "New title", "--due", "tomorrow", "--json"]) assert res.exit_code == 0 assert captured["args"] == ["edit", "r1", "--title", "New title", "--due", "tomorrow", "--json", "--no-input"] assert json.loads(res.stdout)["source"] == "remindctl" def test_cli_reminders_delete_requires_force(monkeypatch): res = runner.invoke(app, ["macmini", "reminders", "delete", "r1", "--json"]) assert res.exit_code == 1 assert "--force" in res.stdout def test_cli_reminders_delete_uses_remindctl_with_force(monkeypatch): captured = {} def fake_run(args): captured["args"] = args return {"deleted": ["r1"]} monkeypatch.setattr("reyna_cli.cli.run_remindctl", fake_run) res = runner.invoke(app, ["macmini", "reminders", "delete", "r1", "--force", "--json"]) assert res.exit_code == 0 assert captured["args"] == ["delete", "r1", "--force", "--json", "--no-input"] 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