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,540 @@
|
||||
"""TDD for secure explicit prebuilt signed app install bridge — no DerivedData scan, no secret leak."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import plistlib
|
||||
import stat
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
from typer.testing import CliRunner
|
||||
|
||||
from reyna_cli.cli import app
|
||||
|
||||
runner = CliRunner()
|
||||
|
||||
|
||||
def _mock_proc(rc=0, stdout="", stderr=""):
|
||||
from types import SimpleNamespace
|
||||
|
||||
return SimpleNamespace(returncode=rc, stdout=stdout, stderr=stderr)
|
||||
|
||||
|
||||
def _make_fake_app_bundle(base: Path, bundle_name: str = "Reyna CLI.app", bundle_id: str = "com.reyna.cli.privacy-host"):
|
||||
"""Create a minimal .app bundle directory with Info.plist and executable."""
|
||||
from reyna_cli import app_bundle as ab
|
||||
|
||||
bundle = base / bundle_name
|
||||
contents = bundle / "Contents"
|
||||
macos = contents / "MacOS"
|
||||
macos.mkdir(parents=True, exist_ok=True)
|
||||
exe = macos / "ReynaCLIHost"
|
||||
exe.write_bytes(b"fakebinarycontent")
|
||||
exe.chmod(0o755)
|
||||
plist_dict = ab.build_app_bundle_info_plist_dict()
|
||||
# allow custom bundle_id for negative tests
|
||||
if bundle_id != plist_dict.get("CFBundleIdentifier"):
|
||||
plist_dict = dict(plist_dict)
|
||||
plist_dict["CFBundleIdentifier"] = bundle_id
|
||||
with open(contents / "Info.plist", "wb") as f:
|
||||
plistlib.dump(plist_dict, f)
|
||||
return bundle
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# app_bundle layer
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_validate_app_bundle_at_path_success(tmp_path):
|
||||
from reyna_cli import app_bundle as ab
|
||||
|
||||
src_root = tmp_path / "src"
|
||||
src_root.mkdir()
|
||||
bundle = _make_fake_app_bundle(src_root)
|
||||
|
||||
def run_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="TeamIdentifier=TEAM123\nAuthority=Apple Development: Foo (TEAM123)\n")
|
||||
return _mock_proc(rc=0)
|
||||
|
||||
res = ab.validate_app_bundle_at_path(bundle, runner=run_ok)
|
||||
assert res["ok"] is True
|
||||
assert res["signature_verified"] is True
|
||||
assert res["is_ad_hoc"] is False
|
||||
assert res["bundle_identifier_matches"] is True
|
||||
|
||||
|
||||
def test_validate_app_bundle_at_path_rejects_relative(tmp_path):
|
||||
from reyna_cli import app_bundle as ab
|
||||
|
||||
rel = Path("relative/Reyna CLI.app")
|
||||
res = ab.validate_app_bundle_at_path(rel)
|
||||
assert res["ok"] is False
|
||||
assert any("absolute" in e.lower() for e in res["errors"])
|
||||
|
||||
|
||||
def test_validate_app_bundle_at_path_rejects_wrong_suffix(tmp_path):
|
||||
from reyna_cli import app_bundle as ab
|
||||
|
||||
p = tmp_path / "notapp"
|
||||
p.mkdir()
|
||||
res = ab.validate_app_bundle_at_path(p)
|
||||
assert res["ok"] is False
|
||||
assert any(".app" in e.lower() for e in res["errors"])
|
||||
|
||||
|
||||
def test_validate_app_bundle_at_path_rejects_symlink(tmp_path):
|
||||
from reyna_cli import app_bundle as ab
|
||||
|
||||
real_root = tmp_path / "real"
|
||||
real_root.mkdir()
|
||||
bundle = _make_fake_app_bundle(real_root)
|
||||
link = tmp_path / "Reyna CLI.app"
|
||||
link.symlink_to(bundle)
|
||||
|
||||
res = ab.validate_app_bundle_at_path(link)
|
||||
assert res["ok"] is False
|
||||
assert any("symlink" in e.lower() for e in res["errors"])
|
||||
|
||||
|
||||
def test_validate_app_bundle_at_path_rejects_ad_hoc(tmp_path):
|
||||
from reyna_cli import app_bundle as ab
|
||||
|
||||
src_root = tmp_path / "src"
|
||||
src_root.mkdir()
|
||||
bundle = _make_fake_app_bundle(src_root)
|
||||
|
||||
def run_adhoc(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_at_path(bundle, runner=run_adhoc)
|
||||
assert res["ok"] is False
|
||||
assert res["is_ad_hoc"] is True
|
||||
|
||||
|
||||
def test_install_prebuilt_app_bundle_success_no_xcodebuild(tmp_path):
|
||||
from reyna_cli import app_bundle as ab
|
||||
|
||||
src_root = tmp_path / "gui-build"
|
||||
src_root.mkdir()
|
||||
bundle = _make_fake_app_bundle(src_root)
|
||||
|
||||
repo_root = tmp_path / "repo"
|
||||
repo_root.mkdir()
|
||||
|
||||
calls = []
|
||||
|
||||
def runner(args, cwd=None, **kwargs):
|
||||
assert isinstance(args, list)
|
||||
calls.append(list(args))
|
||||
if args and args[0] == "xcodebuild":
|
||||
raise AssertionError("xcodebuild must NOT be called on prebuilt path")
|
||||
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.install_prebuilt_app_bundle(source_bundle_path=bundle, repo_root=repo_root, runner=runner)
|
||||
assert res["ok"] is True
|
||||
assert res["action"] == "install_prebuilt_app_bundle"
|
||||
assert "signature_verified" in res
|
||||
# Must have copied to dist
|
||||
dist_bundle = repo_root / "native" / "ReynaCLIHost" / "dist" / "Reyna CLI.app"
|
||||
assert dist_bundle.exists()
|
||||
assert (dist_bundle / "Contents" / "MacOS" / "ReynaCLIHost").exists()
|
||||
# No xcodebuild calls
|
||||
assert all(c[0] != "xcodebuild" for c in calls)
|
||||
# Must have called codesign validation for source and copy (at least 2 verifies)
|
||||
verify_calls = [c for c in calls if c[:3] == ["codesign", "--verify", "--deep"]]
|
||||
assert len(verify_calls) >= 2
|
||||
|
||||
|
||||
def test_install_prebuilt_app_bundle_invalid_prevents_copy(tmp_path, monkeypatch):
|
||||
from reyna_cli import app_bundle as ab
|
||||
|
||||
src_root = tmp_path / "gui-build"
|
||||
src_root.mkdir()
|
||||
bundle = _make_fake_app_bundle(src_root)
|
||||
|
||||
repo_root = tmp_path / "repo"
|
||||
repo_root.mkdir()
|
||||
|
||||
def runner_bad(args, cwd=None, **kwargs):
|
||||
if args[:3] == ["codesign", "--verify", "--deep"]:
|
||||
return _mock_proc(rc=1, stderr="verify fail")
|
||||
if args[:2] == ["codesign", "-dv"]:
|
||||
return _mock_proc(rc=0, stderr="TeamIdentifier=not set\n")
|
||||
return _mock_proc(rc=0)
|
||||
|
||||
# Patch copy to detect if called
|
||||
copied = {"called": False}
|
||||
original_copy = ab._copy_app_bundle_atomic
|
||||
|
||||
def tracking_copy(src, dst):
|
||||
copied["called"] = True
|
||||
return original_copy(src, dst)
|
||||
|
||||
monkeypatch.setattr(ab, "_copy_app_bundle_atomic", tracking_copy)
|
||||
|
||||
res = ab.install_prebuilt_app_bundle(source_bundle_path=bundle, repo_root=repo_root, runner=runner_bad)
|
||||
assert res["ok"] is False
|
||||
assert copied["called"] is False, "must not copy if source validation fails"
|
||||
dist_bundle = repo_root / "native" / "ReynaCLIHost" / "dist" / "Reyna CLI.app"
|
||||
assert not dist_bundle.exists()
|
||||
|
||||
|
||||
def test_install_prebuilt_app_bundle_rejects_symlink_and_relative(tmp_path):
|
||||
from reyna_cli import app_bundle as ab
|
||||
|
||||
real_root = tmp_path / "real"
|
||||
real_root.mkdir()
|
||||
bundle = _make_fake_app_bundle(real_root)
|
||||
link = tmp_path / "Reyna CLI.app"
|
||||
link.symlink_to(bundle)
|
||||
|
||||
repo_root = tmp_path / "repo"
|
||||
repo_root.mkdir()
|
||||
|
||||
def runner(args, cwd=None, **kwargs):
|
||||
return _mock_proc(rc=0)
|
||||
|
||||
res_link = ab.install_prebuilt_app_bundle(source_bundle_path=link, repo_root=repo_root, runner=runner)
|
||||
assert res_link["ok"] is False
|
||||
assert "symlink" in res_link["error"].lower()
|
||||
|
||||
# Relative
|
||||
rel = Path("relative/Reyna CLI.app")
|
||||
res_rel = ab.install_prebuilt_app_bundle(source_bundle_path=rel, repo_root=repo_root, runner=runner)
|
||||
assert res_rel["ok"] is False
|
||||
assert "absolute" in res_rel["error"].lower()
|
||||
|
||||
# Wrong suffix
|
||||
wrong = tmp_path / "wrong.appstuff"
|
||||
wrong.mkdir()
|
||||
res_suffix = ab.install_prebuilt_app_bundle(source_bundle_path=wrong, repo_root=repo_root, runner=runner)
|
||||
assert res_suffix["ok"] is False
|
||||
assert ".app" in res_suffix["error"].lower()
|
||||
|
||||
|
||||
def test_install_prebuilt_no_secret_leak_in_result(tmp_path):
|
||||
from reyna_cli import app_bundle as ab
|
||||
|
||||
src_root = tmp_path / "src"
|
||||
src_root.mkdir()
|
||||
bundle = _make_fake_app_bundle(src_root)
|
||||
|
||||
repo_root = tmp_path / "repo"
|
||||
repo_root.mkdir()
|
||||
|
||||
secret_team = "TEAM_SUPERSECRET123"
|
||||
|
||||
def runner(args, cwd=None, **kwargs):
|
||||
if args[:3] == ["codesign", "--verify", "--deep"]:
|
||||
return _mock_proc(rc=0, stdout=f"Authority=Secret {secret_team}")
|
||||
if args[:2] == ["codesign", "-dv"]:
|
||||
return _mock_proc(rc=0, stderr=f"TeamIdentifier={secret_team}\nAuthority=Apple Development: Foo ({secret_team})\n")
|
||||
return _mock_proc(rc=0)
|
||||
|
||||
res = ab.install_prebuilt_app_bundle(source_bundle_path=bundle, repo_root=repo_root, runner=runner)
|
||||
assert res["ok"] is True
|
||||
# Stringified result must not contain raw team identifier output (we filter generically)
|
||||
# Our runner purposely returns secret in stderr, but result should not echo stderr verbatim
|
||||
import json
|
||||
|
||||
res_str = json.dumps(res)
|
||||
# The implementation stores no raw codesign output, only booleans
|
||||
assert "Authority=Apple" not in res_str
|
||||
assert secret_team not in res_str or res.get("validation", {}).get("signature_verified") is True and secret_team not in str(res.get("validation", {}).get("errors", []))
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# privacy_host layer — prebuilt route
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_privacy_host_install_prebuilt_success_no_xcodebuild(tmp_path):
|
||||
from reyna_cli import privacy_host as ph_mod, app_bundle as ab_mod
|
||||
|
||||
src_root = tmp_path / "gui"
|
||||
src_root.mkdir()
|
||||
src_bundle = _make_fake_app_bundle(src_root)
|
||||
|
||||
repo_root = tmp_path / "repo"
|
||||
repo_root.mkdir()
|
||||
sock = tmp_path / "priv" / "reyna-cli.sock"
|
||||
plist = tmp_path / "LaunchAgents" / "com.reyna.cli.privacy-host.plist"
|
||||
logd = tmp_path / "Logs"
|
||||
|
||||
calls = []
|
||||
|
||||
def fake_runner(args, cwd=None, **kwargs):
|
||||
assert isinstance(args, list)
|
||||
calls.append(list(args))
|
||||
if args and args[0] == "xcodebuild":
|
||||
raise AssertionError("xcodebuild must not be called when prebuilt path supplied")
|
||||
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)
|
||||
|
||||
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,
|
||||
prebuilt_app_bundle_path=src_bundle,
|
||||
)
|
||||
|
||||
assert result["ok"] is True
|
||||
assert plist.exists()
|
||||
# No xcodebuild
|
||||
assert all(c[0] != "xcodebuild" for c in calls)
|
||||
# Must have bootout+bootstrap
|
||||
assert ["launchctl", "bootout", "gui/501/com.reyna.cli.privacy-host"] in calls
|
||||
assert ["launchctl", "bootstrap", "gui/501", str(plist)] in calls
|
||||
# Dist bundle exists
|
||||
dist_bundle = repo_root / "native" / "ReynaCLIHost" / "dist" / "Reyna CLI.app"
|
||||
assert dist_bundle.exists()
|
||||
|
||||
|
||||
def test_privacy_host_install_prebuilt_invalid_prevents_plist_and_launchctl(tmp_path):
|
||||
from reyna_cli import privacy_host as ph_mod
|
||||
|
||||
src_root = tmp_path / "gui"
|
||||
src_root.mkdir()
|
||||
src_bundle = _make_fake_app_bundle(src_root)
|
||||
|
||||
repo_root = tmp_path / "repo"
|
||||
repo_root.mkdir()
|
||||
sock = tmp_path / "priv" / "reyna-cli.sock"
|
||||
plist = tmp_path / "LaunchAgents" / "com.reyna.cli.privacy-host.plist"
|
||||
logd = tmp_path / "Logs"
|
||||
|
||||
calls = []
|
||||
|
||||
def fake_runner(args, cwd=None, **kwargs):
|
||||
calls.append(list(args))
|
||||
if args[:3] == ["codesign", "--verify", "--deep"]:
|
||||
return _mock_proc(rc=1, stderr="fail")
|
||||
if args[:2] == ["codesign", "-dv"]:
|
||||
return _mock_proc(rc=0, stderr="TeamIdentifier=not set\nSignature=adhoc\n")
|
||||
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,
|
||||
prebuilt_app_bundle_path=src_bundle,
|
||||
)
|
||||
|
||||
assert result["ok"] is False
|
||||
assert not plist.exists(), "plist must not be written if prebuilt validation fails"
|
||||
# No launchctl should have been called
|
||||
launch_calls = [c for c in calls if c and c[0] == "launchctl"]
|
||||
assert len(launch_calls) == 0, f"launchctl must not be called on invalid source, got {launch_calls}"
|
||||
|
||||
|
||||
def test_privacy_host_install_prebuilt_rejects_symlink_and_relative(tmp_path):
|
||||
from reyna_cli import privacy_host as ph_mod
|
||||
|
||||
real_root = tmp_path / "real"
|
||||
real_root.mkdir()
|
||||
real_bundle = _make_fake_app_bundle(real_root)
|
||||
|
||||
repo_root = tmp_path / "repo"
|
||||
repo_root.mkdir()
|
||||
plist = tmp_path / "LaunchAgents" / "com.reyna.cli.privacy-host.plist"
|
||||
|
||||
link = tmp_path / "Reyna CLI.app"
|
||||
link.symlink_to(real_bundle)
|
||||
|
||||
def runner(args, cwd=None, **kwargs):
|
||||
return _mock_proc(rc=0)
|
||||
|
||||
res_link = ph_mod.install_privacy_host_service(
|
||||
runner=runner,
|
||||
uid=501,
|
||||
repo_root=repo_root,
|
||||
plist_path=plist,
|
||||
prebuilt_app_bundle_path=link,
|
||||
)
|
||||
assert res_link["ok"] is False
|
||||
assert "symlink" in res_link["error"].lower()
|
||||
|
||||
# Relative
|
||||
rel = Path("relative/Reyna CLI.app")
|
||||
res_rel = ph_mod.install_privacy_host_service(
|
||||
runner=runner,
|
||||
uid=501,
|
||||
repo_root=repo_root,
|
||||
plist_path=plist,
|
||||
prebuilt_app_bundle_path=rel,
|
||||
)
|
||||
assert res_rel["ok"] is False
|
||||
assert "absolute" in res_rel["error"].lower()
|
||||
|
||||
|
||||
def test_privacy_host_install_default_still_calls_xcodebuild(tmp_path):
|
||||
from reyna_cli import privacy_host as ph_mod, app_bundle as ab_mod
|
||||
|
||||
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_mod.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"
|
||||
|
||||
calls = []
|
||||
|
||||
def fake_runner(args, cwd=None, **kwargs):
|
||||
calls.append(list(args))
|
||||
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)
|
||||
|
||||
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",
|
||||
prebuilt_app_bundle_path=None,
|
||||
)
|
||||
assert result["ok"] is True
|
||||
xcode_calls = [c for c in calls if c and c[0] == "xcodebuild"]
|
||||
assert len(xcode_calls) >= 1, "default install must still call xcodebuild"
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# CLI wiring
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_cli_install_has_app_bundle_option():
|
||||
res = runner.invoke(app, ["privacy-host", "install", "--help"])
|
||||
assert res.exit_code == 0
|
||||
out = res.stdout.lower()
|
||||
assert "app-bundle" in out
|
||||
|
||||
|
||||
def test_cli_install_app_bundle_prebuilt_success_mocked(tmp_path, monkeypatch):
|
||||
from reyna_cli import privacy_host as ph_mod
|
||||
|
||||
src_root = tmp_path / "gui"
|
||||
src_root.mkdir()
|
||||
src_bundle = _make_fake_app_bundle(src_root)
|
||||
|
||||
captured = {}
|
||||
|
||||
def fake_install(prebuilt_app_bundle_path=None, **kwargs):
|
||||
captured["prebuilt"] = prebuilt_app_bundle_path
|
||||
assert prebuilt_app_bundle_path is not None
|
||||
assert Path(prebuilt_app_bundle_path).is_absolute()
|
||||
assert str(prebuilt_app_bundle_path).endswith(".app")
|
||||
return {"ok": True, "action": "install", "app_bundle_path": str(prebuilt_app_bundle_path)}
|
||||
|
||||
monkeypatch.setattr(ph_mod, "install_privacy_host_service", lambda **kw: fake_install(**kw))
|
||||
|
||||
res = runner.invoke(app, ["privacy-host", "install", "--app-bundle", str(src_bundle), "--json"])
|
||||
assert res.exit_code == 0, res.stdout + res.stderr
|
||||
assert captured["prebuilt"] == src_bundle
|
||||
|
||||
|
||||
def test_cli_install_app_bundle_rejects_relative_and_symlink(tmp_path, monkeypatch):
|
||||
from reyna_cli import privacy_host as ph_mod
|
||||
|
||||
# Should fail at CLI layer before calling service, for relative
|
||||
def should_not_be_called(**kwargs):
|
||||
raise AssertionError("service must not be called when CLI rejects path")
|
||||
|
||||
monkeypatch.setattr(ph_mod, "install_privacy_host_service", lambda **kw: should_not_be_called(**kw))
|
||||
|
||||
# Relative
|
||||
res_rel = runner.invoke(app, ["privacy-host", "install", "--app-bundle", "relative/Reyna CLI.app", "--json"])
|
||||
assert res_rel.exit_code != 0
|
||||
assert "absolute" in res_rel.stdout.lower()
|
||||
|
||||
# Symlink
|
||||
real_root = tmp_path / "real"
|
||||
real_root.mkdir()
|
||||
real_bundle = _make_fake_app_bundle(real_root)
|
||||
link = tmp_path / "Reyna CLI.app"
|
||||
link.symlink_to(real_bundle)
|
||||
|
||||
res_link = runner.invoke(app, ["privacy-host", "install", "--app-bundle", str(link), "--json"])
|
||||
assert res_link.exit_code != 0
|
||||
assert "symlink" in res_link.stdout.lower()
|
||||
|
||||
|
||||
def test_cli_install_default_no_app_bundle_calls_build(tmp_path, monkeypatch):
|
||||
from reyna_cli import privacy_host as ph_mod
|
||||
|
||||
captured = {}
|
||||
|
||||
def fake_install(prebuilt_app_bundle_path=None, **kwargs):
|
||||
captured["prebuilt"] = prebuilt_app_bundle_path
|
||||
return {"ok": True, "action": "install", "app_bundle_path": "/fake/dist/Reyna CLI.app"}
|
||||
|
||||
monkeypatch.setattr(ph_mod, "install_privacy_host_service", lambda **kw: fake_install(**kw))
|
||||
|
||||
res = runner.invoke(app, ["privacy-host", "install", "--json"])
|
||||
assert res.exit_code == 0, res.stdout + res.stderr
|
||||
assert captured["prebuilt"] is None, "default should not pass prebuilt path"
|
||||
|
||||
|
||||
def test_no_deriveddata_scan_in_prebuilt_code():
|
||||
from pathlib import Path
|
||||
|
||||
src_app_bundle = Path("/Users/adolforeyna/Projects/reyna-cli/src/reyna_cli/app_bundle.py").read_text()
|
||||
src_privacy_host = Path("/Users/adolforeyna/Projects/reyna-cli/src/reyna_cli/privacy_host.py").read_text()
|
||||
# Prebuilt functions must not scan DerivedData automatically
|
||||
# They should not listdir or glob DerivedData without explicit path
|
||||
# Check that install_prebuilt_app_bundle does not reference DerivedData path discovery
|
||||
assert "install_prebuilt_app_bundle" in src_app_bundle
|
||||
# Ensure function body does not contain DerivedData scan (like os.walk or glob of DerivedData)
|
||||
# Simple heuristic: function definition area should not contain "DerivedData" search
|
||||
import re
|
||||
|
||||
# Extract install_prebuilt function
|
||||
m = re.search(r"def install_prebuilt_app_bundle.*?^def ", src_app_bundle, flags=re.DOTALL | re.MULTILINE)
|
||||
if m:
|
||||
func_text = m.group(0)
|
||||
# Should not contain "DerivedData" except maybe in comments about NOT using it
|
||||
# Allow at most trivial mention, but not listdir/glob
|
||||
assert "glob" not in func_text.lower() or "deriveddata" not in func_text.lower()
|
||||
assert "os.scandir" not in func_text.lower()
|
||||
assert "os.walk" not in func_text.lower()
|
||||
# privacy_host prebuilt path should not call build_app_bundle
|
||||
# It should have conditional: if prebuilt_app_bundle_path is not None -> install_prebuilt, else build
|
||||
assert "prebuilt_app_bundle_path" in src_privacy_host
|
||||
Reference in New Issue
Block a user