"""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("")
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('')
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("")
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("")
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("")
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("")
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("")
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("")
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("")
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}"