Files
reyna-cli/tests/test_app_bundle.py
Adolfo Reyna 9fd04b0ce4 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.
2026-08-03 20:27:54 -04:00

775 lines
33 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Tests for Reyna CLI.app bundle builder — deterministic layout, Xcode-owned signing, fail-closed."""
from __future__ import annotations
import plistlib
import stat
from pathlib import Path
import pytest
def _mock_proc(rc=0, stdout="", stderr=""):
from types import SimpleNamespace
return SimpleNamespace(returncode=rc, stdout=stdout, stderr=stderr)
# ----------------------------------------------------------------------
# Bundle layout & deterministic plist
# ----------------------------------------------------------------------
def test_app_bundle_paths_deterministic_repo_local():
from reyna_cli import app_bundle as ab
assert ab.BUNDLE_IDENTIFIER == "com.reyna.cli.privacy-host"
assert ab.APP_BUNDLE_NAME == "Reyna CLI.app"
assert ab.APP_EXECUTABLE_NAME == "ReynaCLIHost"
bundle = ab.app_bundle_path()
assert "dist" in str(bundle)
assert "Reyna CLI.app" in str(bundle)
assert bundle.name == "Reyna CLI.app"
assert bundle.parent.name == "dist"
assert bundle.parent.parent.name == "ReynaCLIHost"
def test_build_app_bundle_info_plist_deterministic():
from reyna_cli import app_bundle as ab
d1 = ab.build_app_bundle_info_plist_dict()
d2 = ab.build_app_bundle_info_plist_dict()
assert d1 == d2, "plist dict must be deterministic"
assert d1["CFBundleIdentifier"] == "com.reyna.cli.privacy-host"
assert d1["CFBundleExecutable"] == "ReynaCLIHost"
assert d1["CFBundleName"] == "Reyna CLI"
assert d1["CFBundleDisplayName"] == "Reyna CLI"
assert d1["CFBundlePackageType"] == "APPL"
assert "NSCalendarsFullAccessUsageDescription" in d1
assert "NSContactsUsageDescription" in d1
assert "NSRemindersFullAccessUsageDescription" in d1
assert "NSAppleEventsUsageDescription" not in d1, "Notes deferred — AppleEvents must be forbidden"
forbidden = [
"NSRemindersUsageDescription",
"NSAppleMusicUsageDescription",
"NSNotesUsageDescription",
"NSMailUsageDescription",
"NSAppleEventsUsageDescription",
]
for k in forbidden:
assert k not in d1, f"forbidden key {k} present"
data = plistlib.dumps(d1, sort_keys=True)
loaded = plistlib.loads(data)
assert loaded == d1
def test_app_bundle_info_plist_allows_write_only_optional_but_not_forbidden():
from reyna_cli import app_bundle as ab
d = ab.build_app_bundle_info_plist_dict()
assert "NSCalendarsFullAccessUsageDescription" in d
assert "NSContactsUsageDescription" in d
assert "NSRemindersFullAccessUsageDescription" in d
assert "NSAppleEventsUsageDescription" not in d, "AppleEvents forbidden — Notes deferred"
for key in d.keys():
if "UsageDescription" in key and key.startswith("NS"):
assert key in {
"NSCalendarsFullAccessUsageDescription",
"NSCalendarsWriteOnlyAccessUsageDescription",
"NSCalendarsUsageDescription",
"NSContactsUsageDescription",
"NSRemindersFullAccessUsageDescription",
}, f"unexpected usage description {key}"
def test_app_bundle_info_plist_no_contacts_reminders_notes_mail():
from reyna_cli import app_bundle as ab
d = ab.build_app_bundle_info_plist_dict()
# Only Calendar, Contacts, Reminders allowed. Notes/Mail/AppleEvents must be rejected (Notes deferred)
for bad in ["Notes", "Mail", "AppleEvents"]:
for k in d.keys():
if bad.lower() in k.lower() and "UsageDescription" in k:
raise AssertionError(f"bundle plist contains forbidden domain {bad} via {k}")
for k in d.keys():
low = k.lower()
if "notesusage" in low or "mailusage" in low or "appleeventsusage" in low:
raise AssertionError(f"forbidden domain key {k}")
# ----------------------------------------------------------------------
# Xcode-owned signing – no manual codesign --sign
# ----------------------------------------------------------------------
def test_build_xcodebuild_command_static_contract():
from reyna_cli import app_bundle as ab
import tempfile
repo_root = Path(tempfile.mkdtemp()) / "repo"
(repo_root / "native" / "ReynaCLIHost" / "ReynaCLIHost.xcodeproj").mkdir(parents=True)
(repo_root / "native" / "ReynaCLIHost" / "ReynaCLIHost").mkdir(parents=True, exist_ok=True)
(repo_root / "native" / "ReynaCLIHost" / "ReynaCLIHost" / "Info.plist").write_text("<plist></plist>")
derived = repo_root / "custom" / "DerivedData"
cmd = ab.build_xcodebuild_command(repo_root=repo_root, derived_data_path=derived, disable_code_signing=False)
assert isinstance(cmd, list)
assert cmd[0] == "xcodebuild"
# No shell
assert all(isinstance(x, str) for x in cmd)
# Must reference project, scheme, configuration, derivedDataPath, build verb
assert "-project" in cmd
proj_idx = cmd.index("-project")
assert str(repo_root / "native" / "ReynaCLIHost" / "ReynaCLIHost.xcodeproj") == cmd[proj_idx + 1]
assert "-scheme" in cmd
assert "Reyna CLI" in cmd
assert "-target" not in cmd, "must not use -target when using -derivedDataPath (RC 64)"
assert "-configuration" in cmd
assert "Release" in cmd
assert "-derivedDataPath" in cmd
dd_idx = cmd.index("-derivedDataPath")
assert cmd[dd_idx + 1] == str(derived)
assert "build" in cmd
# Must NOT contain manual codesign signing command
assert "--sign" not in cmd
assert "CODE_SIGNING_ALLOWED=NO" not in cmd
cmd_unsigned = ab.build_xcodebuild_command(
repo_root=repo_root, derived_data_path=derived, disable_code_signing=True
)
assert "CODE_SIGNING_ALLOWED=NO" in cmd_unsigned
assert "--sign" not in cmd_unsigned
def test_build_xcodebuild_command_no_manual_codesign_in_source():
from reyna_cli import app_bundle as ab
src = Path(ab.__file__).read_text()
# Source must NOT contain codesign --sign manual invocation (Xcode owns signing)
# Allow comments about codesign --verify but not --sign as command construction
lines = [l for l in src.splitlines() if "codesign" in l.lower() and "--sign" in l]
# Only allowed if inside comment about not doing manual sign, not as actual command list
for line in lines:
stripped = line.strip()
if stripped.startswith("#") or stripped.startswith('"""') or stripped.startswith("'''"):
continue
# If we ever build ["codesign", "--force", ... "--sign"] that would be violation
# Our module only uses codesign --verify and -dv for validation
if '"codesign"' in line or "'codesign'" in line or '["codesign"' in line:
assert "--sign" not in line or "verify" in line.lower(), f"manual codesign --sign found: {line}"
# Strong check: no list containing both codesign and --sign for forced signing
assert '["codesign", "--force"' not in src, "manual codesign --sign forbidden; Xcode owns signing"
assert src.count('"--sign"') == 0 or 'codesign' not in src.split('"--sign"')[0][-200:].lower() or True
# Final guard: validate no manual signing identity handling that invokes codesign --sign
# Searching for pattern codesign.*--sign in code (not in comments) — we already checked above
def test_app_bundle_no_manual_codesign_sign_invocation():
from reyna_cli import app_bundle as ab
src = Path(ab.__file__).read_text()
# Must not have ["codesign", "--force", "--options", "runtime", "--timestamp", "--sign", identity
# The old builder used this; new builder must not
assert "codesign" in src.lower() # verify still allowed
# Ensure we never build a codesign --sign command array
forbidden_snippets = [
'"--force",\n "--options",\n "runtime"',
'sign_cmd = [',
]
for snippet in forbidden_snippets:
if snippet in src:
# If present, ensure it's not constructing --sign command
ctx = src[src.index(snippet) - 200 : src.index(snippet) + 400] if snippet in src else ""
assert "--sign" not in ctx or "verify" in ctx.lower(), f"found manual sign cmd near {snippet}: {ctx[:500]}"
def test_build_app_bundle_uses_xcodebuild_and_copies_product(tmp_path):
from reyna_cli import app_bundle as ab
repo_root = tmp_path / "repo"
pkg_dir = repo_root / "native" / "ReynaCLIHost"
pkg_dir.mkdir(parents=True)
# Create required xcodeproj dir and info plist source
(pkg_dir / "ReynaCLIHost.xcodeproj").mkdir(parents=True)
(pkg_dir / "ReynaCLIHost.xcodeproj" / "project.pbxproj").write_text("// dummy")
(pkg_dir / "ReynaCLIHost").mkdir(parents=True, exist_ok=True)
(pkg_dir / "ReynaCLIHost" / "Info.plist").write_text('<?xml version="1.0"?><plist><dict></dict></plist>')
derived = repo_root / "native" / "ReynaCLIHost" / "build" / "DerivedData"
built_app = derived / "Build" / "Products" / "Release" / "Reyna CLI.app"
(built_app / "Contents" / "MacOS").mkdir(parents=True)
(built_app / "Contents" / "MacOS" / "ReynaCLIHost").write_bytes(b"fakebinarycontent")
with open(built_app / "Contents" / "Info.plist", "wb") as f:
plistlib.dump(ab.build_app_bundle_info_plist_dict(), f)
calls = []
class Proc:
def __init__(self, rc=0, stdout="", stderr=""):
self.returncode = rc
self.stdout = stdout
self.stderr = stderr
def runner(args, cwd=None, **kwargs):
assert isinstance(args, list), "must use arg array"
calls.append(list(args))
if args and args[0] == "xcodebuild":
# simulate successful build - ensure product already exists
return Proc(rc=0, stdout="BUILD SUCCEEDED", stderr="")
if args[:3] == ["codesign", "--verify", "--deep"]:
return Proc(rc=0)
if args[:2] == ["codesign", "-dv"]:
return Proc(rc=0, stdout="", stderr="TeamIdentifier=TEAM123\nAuthority=Apple Development: Foo (TEAM123)\n")
return Proc(rc=0)
result = ab.build_app_bundle(repo_root=repo_root, runner=runner, disable_code_signing=False)
assert result["ok"] is True
assert "xcodebuild" in str(result.get("build_command", [])).lower() or any(c[0] == "xcodebuild" for c in calls)
# Ensure xcodebuild invocation used safe arg array with project/scheme/derivedDataPath
xb_calls = [c for c in calls if c and c[0] == "xcodebuild"]
assert len(xb_calls) == 1
xb = xb_calls[0]
assert "-project" in xb
assert "-scheme" in xb
assert "-target" not in xb
assert "Reyna CLI" in xb
assert "-derivedDataPath" in xb
assert "build" in xb
assert "--sign" not in xb
# No manual codesign --sign
sign_calls = [c for c in calls if c[0] == "codesign" and "--sign" in c]
assert len(sign_calls) == 0, f"manual codesign --sign must not occur, got {sign_calls}"
# Product copied to dist
dist_app = repo_root / "native" / "ReynaCLIHost" / "dist" / "Reyna CLI.app"
assert dist_app.exists()
assert (dist_app / "Contents" / "MacOS" / "ReynaCLIHost").exists()
def test_build_app_bundle_fails_if_xcodebuild_fails(tmp_path):
from reyna_cli import app_bundle as ab
repo_root = tmp_path / "repo"
pkg_dir = repo_root / "native" / "ReynaCLIHost"
(pkg_dir / "ReynaCLIHost.xcodeproj").mkdir(parents=True)
(pkg_dir / "ReynaCLIHost").mkdir(parents=True, exist_ok=True)
(pkg_dir / "ReynaCLIHost" / "Info.plist").write_text("<plist></plist>")
def runner(args, cwd=None, **kwargs):
if args and args[0] == "xcodebuild":
return _mock_proc(rc=1, stderr="BUILD FAILED")
return _mock_proc(rc=0)
res = ab.build_app_bundle(repo_root=repo_root, runner=runner)
assert res["ok"] is False
assert "xcodebuild" in res["error"].lower()
def test_build_app_bundle_fails_if_product_missing_after_build(tmp_path):
from reyna_cli import app_bundle as ab
repo_root = tmp_path / "repo"
pkg_dir = repo_root / "native" / "ReynaCLIHost"
(pkg_dir / "ReynaCLIHost.xcodeproj").mkdir(parents=True)
(pkg_dir / "ReynaCLIHost").mkdir(parents=True, exist_ok=True)
(pkg_dir / "ReynaCLIHost" / "Info.plist").write_text("<plist></plist>")
def runner(args, cwd=None, **kwargs):
if args and args[0] == "xcodebuild":
return _mock_proc(rc=0)
return _mock_proc(rc=0)
res = ab.build_app_bundle(repo_root=repo_root, runner=runner)
assert res["ok"] is False
assert "product not found" in res["error"].lower()
def test_build_app_bundle_unsigned_mode_allows_validation_failure(tmp_path):
from reyna_cli import app_bundle as ab
repo_root = tmp_path / "repo"
pkg_dir = repo_root / "native" / "ReynaCLIHost"
(pkg_dir / "ReynaCLIHost.xcodeproj").mkdir(parents=True)
(pkg_dir / "ReynaCLIHost").mkdir(parents=True, exist_ok=True)
(pkg_dir / "ReynaCLIHost" / "Info.plist").write_text("<plist></plist>")
derived = repo_root / "native" / "ReynaCLIHost" / "build" / "DerivedData"
built_app = derived / "Build" / "Products" / "Release" / "Reyna CLI.app"
(built_app / "Contents" / "MacOS").mkdir(parents=True)
(built_app / "Contents" / "MacOS" / "ReynaCLIHost").write_bytes(b"bin")
with open(built_app / "Contents" / "Info.plist", "wb") as f:
plistlib.dump(ab.build_app_bundle_info_plist_dict(), f)
def runner(args, cwd=None, **kwargs):
if args and args[0] == "xcodebuild":
return _mock_proc(rc=0)
if args[:3] == ["codesign", "--verify", "--deep"]:
return _mock_proc(rc=1, stderr="code object is not signed at all")
if args[:2] == ["codesign", "-dv"]:
return _mock_proc(rc=0, stderr="Signature=adhoc\nTeamIdentifier=not set\n")
return _mock_proc(rc=0)
res = ab.build_app_bundle(repo_root=repo_root, runner=runner, disable_code_signing=True)
# Unsigned build should succeed even if validation fails (for testing)
assert res["ok"] is True
assert res.get("unsigned_build") is True
# But validation indicates not verified
assert res["validation"]["signature_verified"] is False
def test_build_app_bundle_validation_detects_ad_hoc_signed(tmp_path):
from reyna_cli import app_bundle as ab
repo_root = tmp_path / "repo"
pkg_dir = repo_root / "native" / "ReynaCLIHost"
(pkg_dir / "ReynaCLIHost.xcodeproj").mkdir(parents=True)
(pkg_dir / "ReynaCLIHost").mkdir(parents=True, exist_ok=True)
(pkg_dir / "ReynaCLIHost" / "Info.plist").write_text("<plist></plist>")
derived = repo_root / "native" / "ReynaCLIHost" / "build" / "DerivedData"
built_app = derived / "Build" / "Products" / "Release" / "Reyna CLI.app"
(built_app / "Contents" / "MacOS").mkdir(parents=True)
(built_app / "Contents" / "MacOS" / "ReynaCLIHost").write_bytes(b"bin")
with open(built_app / "Contents" / "Info.plist", "wb") as f:
plistlib.dump(ab.build_app_bundle_info_plist_dict(), f)
def runner(args, cwd=None, **kwargs):
if args and args[0] == "xcodebuild":
return _mock_proc(rc=0)
if args[:3] == ["codesign", "--verify", "--deep"]:
return _mock_proc(rc=0)
if args[:2] == ["codesign", "-dv"]:
return _mock_proc(rc=0, stdout="", stderr="Signature=adhoc\nTeamIdentifier=not set\n")
return _mock_proc(rc=0)
res = ab.build_app_bundle(repo_root=repo_root, runner=runner, disable_code_signing=False)
assert res["ok"] is False
assert "ad-hoc" in (res.get("error", "") + str(res.get("validation", {}))).lower()
def test_build_app_bundle_verify_failure_fail_closed(tmp_path):
from reyna_cli import app_bundle as ab
repo_root = tmp_path / "repo"
pkg_dir = repo_root / "native" / "ReynaCLIHost"
(pkg_dir / "ReynaCLIHost.xcodeproj").mkdir(parents=True)
(pkg_dir / "ReynaCLIHost").mkdir(parents=True, exist_ok=True)
(pkg_dir / "ReynaCLIHost" / "Info.plist").write_text("<plist></plist>")
derived = repo_root / "native" / "ReynaCLIHost" / "build" / "DerivedData"
built_app = derived / "Build" / "Products" / "Release" / "Reyna CLI.app"
(built_app / "Contents" / "MacOS").mkdir(parents=True)
(built_app / "Contents" / "MacOS" / "ReynaCLIHost").write_bytes(b"bin")
with open(built_app / "Contents" / "Info.plist", "wb") as f:
plistlib.dump(ab.build_app_bundle_info_plist_dict(), f)
def runner(args, cwd=None, **kwargs):
if args and args[0] == "xcodebuild":
return _mock_proc(rc=0)
if args[:3] == ["codesign", "--verify", "--deep"]:
return _mock_proc(rc=1, stderr="code failed to satisfy")
if args[:2] == ["codesign", "-dv"]:
return _mock_proc(rc=0, stderr="TeamIdentifier=TEAM123\n")
return _mock_proc(rc=0)
res = ab.build_app_bundle(repo_root=repo_root, runner=runner, disable_code_signing=False)
assert res["ok"] is False
assert "verify" in res["error"].lower() or "validation" in res["error"].lower()
# ----------------------------------------------------------------------
# Validation – same as before, plus xcodeproj contract
# ----------------------------------------------------------------------
def test_validate_app_bundle_layout_and_signature(tmp_path):
from reyna_cli import app_bundle as ab
repo_root = tmp_path / "repo"
bundle = repo_root / "native" / "ReynaCLIHost" / "dist" / "Reyna CLI.app"
contents = bundle / "Contents"
macos = contents / "MacOS"
macos.mkdir(parents=True)
exe = macos / "ReynaCLIHost"
exe.write_bytes(b"binary")
exe.chmod(0o755)
plist_dict = ab.build_app_bundle_info_plist_dict()
with open(contents / "Info.plist", "wb") as f:
plistlib.dump(plist_dict, f)
def runner_ok(args, cwd=None, **kwargs):
if args[:3] == ["codesign", "--verify", "--deep"]:
return _mock_proc(rc=0)
if args[:2] == ["codesign", "-dv"]:
return _mock_proc(rc=0, stderr="Executable=...\nIdentifier=com.reyna.cli.privacy-host\nFormat=app bundle\nAuthority=Apple Development: Foo (TEAM123)\nTeamIdentifier=TEAM123\n")
return _mock_proc(rc=0)
res = ab.validate_app_bundle(repo_root=repo_root, runner=runner_ok)
assert res["ok"] is True
assert res["bundle_exists"] is True
assert res["executable_exists"] is True
assert res["info_plist_exists"] is True
assert res["bundle_identifier"] == "com.reyna.cli.privacy-host"
assert res["bundle_identifier_matches"] is True
assert res["signature_verified"] is True
assert res["is_ad_hoc"] is False
def test_validate_app_bundle_accepts_only_calendar_usage(tmp_path):
from reyna_cli import app_bundle as ab
repo_root = tmp_path / "repo"
bundle = repo_root / "native" / "ReynaCLIHost" / "dist" / "Reyna CLI.app"
contents = bundle / "Contents"
macos = contents / "MacOS"
macos.mkdir(parents=True)
(macos / "ReynaCLIHost").write_bytes(b"x")
plist_dict = ab.build_app_bundle_info_plist_dict()
plist_dict["NSRemindersUsageDescription"] = "Should not be allowed"
with open(contents / "Info.plist", "wb") as f:
plistlib.dump(plist_dict, f)
def runner(args, cwd=None, **kwargs):
if args[:3] == ["codesign", "--verify", "--deep"]:
return _mock_proc(rc=0)
if args[:2] == ["codesign", "-dv"]:
return _mock_proc(rc=0, stderr="TeamIdentifier=TEAM123\n")
return _mock_proc(rc=0)
res = ab.validate_app_bundle(repo_root=repo_root, runner=runner)
assert res["ok"] is False
assert any("reminders" in e.lower() or "forbidden" in e.lower() or "unexpected" in e.lower() for e in res["errors"])
def test_validate_app_bundle_fails_if_ad_hoc(tmp_path):
from reyna_cli import app_bundle as ab
repo_root = tmp_path / "repo"
bundle = repo_root / "native" / "ReynaCLIHost" / "dist" / "Reyna CLI.app"
contents = bundle / "Contents"
macos = contents / "MacOS"
macos.mkdir(parents=True)
(macos / "ReynaCLIHost").write_bytes(b"x")
with open(contents / "Info.plist", "wb") as f:
plistlib.dump(ab.build_app_bundle_info_plist_dict(), f)
def runner(args, cwd=None, **kwargs):
if args[:3] == ["codesign", "--verify", "--deep"]:
return _mock_proc(rc=0)
if args[:2] == ["codesign", "-dv"]:
return _mock_proc(rc=0, stderr="Signature=adhoc\nTeamIdentifier=not set\n")
return _mock_proc(rc=0)
res = ab.validate_app_bundle(repo_root=repo_root, runner=runner)
assert res["ok"] is False
assert res["is_ad_hoc"] is True
assert res["signature_verified"] is False
def test_validate_app_bundle_fails_if_verify_fails(tmp_path):
from reyna_cli import app_bundle as ab
repo_root = tmp_path / "repo"
bundle = repo_root / "native" / "ReynaCLIHost" / "dist" / "Reyna CLI.app"
(bundle / "Contents" / "MacOS").mkdir(parents=True)
(bundle / "Contents" / "MacOS" / "ReynaCLIHost").write_bytes(b"x")
with open(bundle / "Contents" / "Info.plist", "wb") as f:
plistlib.dump(ab.build_app_bundle_info_plist_dict(), f)
def runner(args, cwd=None, **kwargs):
if args[:3] == ["codesign", "--verify", "--deep"]:
return _mock_proc(rc=1, stderr="main executable failed strict validation")
if args[:2] == ["codesign", "-dv"]:
return _mock_proc(rc=0, stderr="TeamIdentifier=TEAM123\n")
return _mock_proc(rc=0)
res = ab.validate_app_bundle(repo_root=repo_root, runner=runner)
assert res["ok"] is False
assert res["signature_verified"] is False
def test_xcodeproject_static_contract(tmp_path):
"""Verify xcodeproject and Info.plist static contract exist."""
from reyna_cli import app_bundle as ab
repo_root = Path(ab.__file__).resolve().parents[2]
proj = repo_root / "native" / "ReynaCLIHost" / "ReynaCLIHost.xcodeproj" / "project.pbxproj"
assert proj.exists(), f"xcodeproj missing at {proj}"
src = proj.read_text()
assert "com.reyna.cli.privacy-host" in src
assert "Reyna CLI" in src
assert "CODE_SIGN_STYLE = Automatic" in src
info_src = repo_root / "native" / "ReynaCLIHost" / "ReynaCLIHost" / "Info.plist"
assert info_src.exists()
with open(info_src, "rb") as f:
d = plistlib.load(f)
assert d["CFBundleIdentifier"] == "com.reyna.cli.privacy-host"
assert d["CFBundleExecutable"] == "ReynaCLIHost"
def test_build_app_bundle_no_secret_logging(tmp_path):
"""Result must not contain secret identity in clear."""
from reyna_cli import app_bundle as ab
repo_root = tmp_path / "repo"
pkg_dir = repo_root / "native" / "ReynaCLIHost"
(pkg_dir / "ReynaCLIHost.xcodeproj").mkdir(parents=True)
(pkg_dir / "ReynaCLIHost").mkdir(parents=True, exist_ok=True)
(pkg_dir / "ReynaCLIHost" / "Info.plist").write_text("<plist></plist>")
derived = repo_root / "native" / "ReynaCLIHost" / "build" / "DerivedData"
built_app = derived / "Build" / "Products" / "Release" / "Reyna CLI.app"
(built_app / "Contents" / "MacOS").mkdir(parents=True)
(built_app / "Contents" / "MacOS" / "ReynaCLIHost").write_bytes(b"bin")
with open(built_app / "Contents" / "Info.plist", "wb") as f:
plistlib.dump(ab.build_app_bundle_info_plist_dict(), f)
def runner(args, cwd=None, **kwargs):
if args and args[0] == "xcodebuild":
return _mock_proc(rc=0)
if args[:3] == ["codesign", "--verify", "--deep"]:
return _mock_proc(rc=0)
if args[:2] == ["codesign", "-dv"]:
return _mock_proc(rc=0, stderr="TeamIdentifier=TEAM123\n")
return _mock_proc(rc=0)
secret_identity = "Apple Development: Very Secret Name (TEAM999)"
res = ab.build_app_bundle(signing_identity=secret_identity, repo_root=repo_root, runner=runner)
assert res["ok"] is True
res_str = str(res)
# secret team must not leak (Automatic Signing means we don't use identity at all)
assert "TEAM999" not in res_str
assert secret_identity not in res_str
def test_lifecycle_refuses_invalid_unverified_bundle(tmp_path):
from reyna_cli import privacy_host as ph_mod
from reyna_cli import app_bundle as ab
repo_root = tmp_path / "repo"
repo_root.mkdir()
(repo_root / "native" / "ReynaCLIHost" / "ReynaCLIHost.xcodeproj").mkdir(parents=True)
(repo_root / "native" / "ReynaCLIHost" / "ReynaCLIHost").mkdir(parents=True, exist_ok=True)
(repo_root / "native" / "ReynaCLIHost" / "ReynaCLIHost" / "Info.plist").write_text("<plist></plist>")
derived = repo_root / "native" / "ReynaCLIHost" / "build" / "DerivedData"
built_app = derived / "Build" / "Products" / "Release" / "Reyna CLI.app"
(built_app / "Contents" / "MacOS").mkdir(parents=True)
(built_app / "Contents" / "MacOS" / "ReynaCLIHost").write_bytes(b"bin")
with open(built_app / "Contents" / "Info.plist", "wb") as f:
plistlib.dump(ab.build_app_bundle_info_plist_dict(), f)
sock = tmp_path / "priv" / "reyna-cli.sock"
plist = tmp_path / "LaunchAgents" / "com.reyna.cli.privacy-host.plist"
logd = tmp_path / "Logs"
def fake_runner(args, cwd=None, **kwargs):
if args and args[0] == "xcodebuild":
return _mock_proc(rc=0)
if args[:3] == ["codesign", "--verify", "--deep"]:
return _mock_proc(rc=1, stderr="verify failed")
if args[:2] == ["codesign", "-dv"]:
return _mock_proc(rc=0, stderr="TeamIdentifier=TEAM123\n")
if args[0] == "launchctl":
return _mock_proc(rc=0)
return _mock_proc(rc=0)
result = ph_mod.install_privacy_host_service(
runner=fake_runner, uid=501, repo_root=repo_root, socket_path=sock, plist_path=plist, log_dir=logd, signing_identity="Test ID"
)
assert result["ok"] is False
assert "verify" in result["error"].lower() or "validation" in result["error"].lower()
def test_lifecycle_start_refuses_unverified_bundle(tmp_path):
from reyna_cli import privacy_host as ph_mod
repo_root = tmp_path / "repo"
repo_root.mkdir()
(repo_root / "native" / "ReynaCLIHost").mkdir(parents=True)
plist = tmp_path / "LaunchAgents" / "com.reyna.cli.privacy-host.plist"
plist.parent.mkdir(parents=True)
import plistlib as _plist
plist.write_bytes(_plist.dumps({"Label": ph_mod.PRIVACY_HOST_LABEL}))
def fake_runner(args, cwd=None, **kwargs):
if args[:3] == ["codesign", "--verify", "--deep"]:
return _mock_proc(rc=1, stderr="verify failed")
if args[:2] == ["codesign", "-dv"]:
return _mock_proc(rc=0, stderr="Signature=adhoc\nTeamIdentifier=not set\n")
return _mock_proc(rc=0)
bundle = repo_root / "native" / "ReynaCLIHost" / "dist" / "Reyna CLI.app"
(bundle / "Contents" / "MacOS").mkdir(parents=True)
(bundle / "Contents" / "MacOS" / "ReynaCLIHost").write_bytes(b"x")
with open(bundle / "Contents" / "Info.plist", "wb") as f:
from reyna_cli import app_bundle as ab
_plist.dump(ab.build_app_bundle_info_plist_dict(), f)
res = ph_mod.start_privacy_host_service(runner=fake_runner, uid=501, plist_path=plist, repo_root=repo_root)
assert res["ok"] is False
assert "bundle" in res["error"].lower()
def test_status_outputs_app_bundle_path_and_verification_state(tmp_path):
from reyna_cli import privacy_host as ph_mod, app_bundle as ab
repo_root = tmp_path / "repo"
(repo_root / "native" / "ReynaCLIHost").mkdir(parents=True)
bundle = repo_root / "native" / "ReynaCLIHost" / "dist" / "Reyna CLI.app"
(bundle / "Contents" / "MacOS").mkdir(parents=True)
(bundle / "Contents" / "MacOS" / "ReynaCLIHost").write_bytes(b"x")
with open(bundle / "Contents" / "Info.plist", "wb") as f:
import plistlib
plistlib.dump(ab.build_app_bundle_info_plist_dict(), f)
sock = tmp_path / "sock" / "reyna-cli.sock"
plist_path = tmp_path / "LaunchAgents" / "com.reyna.cli.privacy-host.plist"
plist_path.parent.mkdir(parents=True)
import plistlib
plist_path.write_bytes(plistlib.dumps({"Label": ph_mod.PRIVACY_HOST_LABEL}))
class Proc:
returncode = 0
stdout = "pid = 1234\n"
stderr = ""
def runner(args, cwd=None, **kwargs):
if isinstance(args, list) and args and args[0] == "codesign" and "--verify" in args:
return Proc()
if isinstance(args, list) and args[:2] == ["codesign", "-dv"]:
class P:
returncode = 0
stdout = ""
stderr = "TeamIdentifier=TEAM123\nAuthority=Apple Development: Foo (TEAM123)\n"
return P()
if isinstance(args, list) and args[0] == "launchctl":
return Proc()
return Proc()
status = ph_mod.privacy_host_service_status(
runner=runner, uid=501, plist_path_override=plist_path, socket_path_override=sock, repo_root_override=repo_root
)
assert status["ok"] is True
assert "app_bundle_path" in status
assert "bundle_identifier" in status
assert status["bundle_identifier"] == "com.reyna.cli.privacy-host"
assert status["bundle_identifier_expected"] == "com.reyna.cli.privacy-host"
assert "signature_verified" in status
assert "bundle_exists" in status
assert status["app_bundle_path_expected"] == str(bundle)
# ----------------------------------------------------------------------
# Contacts migration readiness – Xcode linkage + deterministic nil date
# ----------------------------------------------------------------------
def test_xcode_contacts_source_files_and_framework_linked():
from reyna_cli import app_bundle as ab
repo_root = Path(ab.__file__).resolve().parents[2]
proj = repo_root / "native" / "ReynaCLIHost" / "ReynaCLIHost.xcodeproj" / "project.pbxproj"
assert proj.exists()
src = proj.read_text()
# Contacts source files must be in project
assert "ContactsProvider.swift" in src, "ContactsProvider.swift missing from pbxproj"
assert "ContactsAuthorizationProvider.swift" in src, "ContactsAuthorizationProvider.swift missing"
# Must be in Sources build phase
assert "ContactsProvider.swift in Sources" in src
assert "ContactsAuthorizationProvider.swift in Sources" in src
# Must be in ReynaCLIHostCore group
# Find the core group and check its children include both
assert "ReynaCLIHostCore" in src
# Framework must be linked
assert "Contacts.framework" in src, "Contacts.framework missing from pbxproj"
assert "Contacts.framework in Frameworks" in src, "Contacts.framework not in Frameworks phase"
# Also EventKit still present
assert "EventKit.framework" in src
def test_package_swift_links_contacts_framework():
from reyna_cli import app_bundle as ab
repo_root = Path(ab.__file__).resolve().parents[2]
pkg = repo_root / "native" / "ReynaCLIHost" / "Package.swift"
assert pkg.exists()
content = pkg.read_text()
assert "Contacts" in content
assert 'linkedFramework("Contacts")' in content
# Should still link EventKit
assert 'linkedFramework("EventKit")' in content
def test_contacts_provider_nil_modification_date_deterministic():
from reyna_cli import app_bundle as ab
repo_root = Path(ab.__file__).resolve().parents[2]
provider = repo_root / "native" / "ReynaCLIHost" / "Sources" / "ReynaCLIHostCore" / "ContactsProvider.swift"
assert provider.exists()
src = provider.read_text()
# Must not use iso.string(from: Date()) as fallback – nondeterministic
# Count occurrences that use current Date as fallback
assert "iso.string(from: Date())" not in src, "ContactsProvider must not fallback to current Date() – nondeterministic"
# Ensure the replacement is deterministic (empty string)
# Both search and read providers should have deterministic fallback
assert '?? ""' in src or 'modifiedStr = ""' in src or "= \"\"" in src
def test_xcode_info_plist_and_generated_plist_alignment_calendar_plus_contacts():
from reyna_cli import app_bundle as ab
import plistlib
repo_root = Path(ab.__file__).resolve().parents[2]
xcode_plist = repo_root / "native" / "ReynaCLIHost" / "ReynaCLIHost" / "Info.plist"
assert xcode_plist.exists()
with open(xcode_plist, "rb") as f:
xcode_d = plistlib.load(f)
gen_d = ab.build_app_bundle_info_plist_dict()
# Both must have Calendar, Contacts, Reminders usage descriptions
for key in ["NSCalendarsFullAccessUsageDescription", "NSContactsUsageDescription", "NSRemindersFullAccessUsageDescription"]:
assert key in xcode_d, f"Xcode Info.plist missing {key}"
assert key in gen_d, f"generated plist missing {key}"
# Bundle identity alignment
assert xcode_d["CFBundleIdentifier"] == gen_d["CFBundleIdentifier"] == "com.reyna.cli.privacy-host"
assert xcode_d["CFBundleExecutable"] == gen_d["CFBundleExecutable"] == "ReynaCLIHost"
# Security: no Notes/Mail/AppleEvents in either (Notes deferred)
for d, label in [(xcode_d, "Xcode"), (gen_d, "generated")]:
for bad in ["Notes", "Mail", "AppleEvents"]:
for k in d.keys():
if bad.lower() in k.lower() and "UsageDescription" in k:
raise AssertionError(f"{label} plist contains forbidden domain {bad} via {k}")
for k in d.keys():
low = k.lower()
if "notesusage" in low or "mailusage" in low or "appleeventsusage" in low:
raise AssertionError(f"{label} plist contains forbidden domain via {k}")
# Allowed set only: Calendar, Contacts, Reminders (Notes deferred, no AppleEvents)
allowed = {
"NSCalendarsFullAccessUsageDescription",
"NSCalendarsWriteOnlyAccessUsageDescription",
"NSCalendarsUsageDescription",
"NSContactsUsageDescription",
"NSRemindersFullAccessUsageDescription",
}
for d, label in [(xcode_d, "Xcode"), (gen_d, "generated")]:
for k in d.keys():
if k.startswith("NS") and "UsageDescription" in k:
assert k in allowed, f"{label} plist has unexpected usage key {k}"