from __future__ import annotations import plistlib from pathlib import Path import pytest from k1link.local_service_launchd import ( MissionCoreLaunchAgentError, plan_mission_core_launch_agent, ) def _write_agent( *, path: Path, repository: Path, uv_entrypoint: Path, environment: dict[str, str] | None = None, ) -> None: path.write_bytes( plistlib.dumps( { "Label": "com.nodedc.mission-core.local", "ProgramArguments": [str(uv_entrypoint), "run", "k1link", "serve"], "WorkingDirectory": str(repository), "EnvironmentVariables": environment or {"PATH": "/usr/bin:/bin"}, } ) ) path.chmod(0o600) def test_launch_agent_plan_disables_sync_and_enables_watchdog(tmp_path: Path) -> None: repository = tmp_path / "repo" repository.mkdir() uv_entrypoint = tmp_path / "uv" uv_entrypoint.write_text("#!/bin/sh\n") uv_entrypoint.chmod(0o700) agent = tmp_path / "agent.plist" _write_agent(path=agent, repository=repository, uv_entrypoint=uv_entrypoint) plan = plan_mission_core_launch_agent( repository_root=repository, agent_path=agent, ) desired = plistlib.loads(plan.desired_payload) assert desired["ProgramArguments"] == [ str(uv_entrypoint), "run", "--no-sync", "k1link", "serve", ] assert desired["EnvironmentVariables"]["MISSIONCORE_SERVICE_WATCHDOG"] == "1" assert desired["KeepAlive"] is True assert desired["RunAtLoad"] is True assert desired["AbandonProcessGroup"] is False assert desired["ExitTimeOut"] == 20 assert plan.current_sha256 != plan.desired_sha256 assert plan.current_working_directory == repository assert plan.desired_working_directory == repository assert plan.preserved_data_directory is None assert "MISSIONCORE_DATA_DIR" not in desired["EnvironmentVariables"] assert plan.to_dict()["changes"]["repository_migration"] is False assert plan.to_dict()["changes"]["data_directory_preserved"] is False def test_launch_agent_plan_rejects_cross_repository_migration_by_default( tmp_path: Path, ) -> None: current_repository = tmp_path / "current-repo" current_repository.mkdir() desired_repository = tmp_path / "desired-repo" desired_repository.mkdir() uv_entrypoint = tmp_path / "uv" uv_entrypoint.write_text("#!/bin/sh\n") uv_entrypoint.chmod(0o700) agent = tmp_path / "agent.plist" _write_agent( path=agent, repository=current_repository, uv_entrypoint=uv_entrypoint, ) with pytest.raises( MissionCoreLaunchAgentError, match="current launch agent targets another repository", ): plan_mission_core_launch_agent( repository_root=desired_repository, agent_path=agent, ) def test_launch_agent_plan_binds_explicit_cross_repository_migration( tmp_path: Path, ) -> None: current_repository = tmp_path / "current-repo" current_repository.mkdir() desired_repository = tmp_path / "desired-repo" desired_repository.mkdir() other_repository = tmp_path / "other-repo" other_repository.mkdir() data_directory = current_repository / ".runtime" / "mission-core" data_directory.mkdir(parents=True, mode=0o700) uv_entrypoint = tmp_path / "uv" uv_entrypoint.write_text("#!/bin/sh\n") uv_entrypoint.chmod(0o700) agent = tmp_path / "agent.plist" _write_agent( path=agent, repository=current_repository, uv_entrypoint=uv_entrypoint, ) with pytest.raises( MissionCoreLaunchAgentError, match="does not target the expected current repository", ): plan_mission_core_launch_agent( repository_root=desired_repository, agent_path=agent, expected_current_repository_root=other_repository, ) plan = plan_mission_core_launch_agent( repository_root=desired_repository, agent_path=agent, expected_current_repository_root=current_repository, ) desired = plistlib.loads(plan.desired_payload) document = plan.to_dict() assert plan.current_working_directory == current_repository assert plan.desired_working_directory == desired_repository assert plan.preserved_data_directory == data_directory assert desired["WorkingDirectory"] == str(desired_repository) assert desired["EnvironmentVariables"]["MISSIONCORE_DATA_DIR"] == str(data_directory) assert document["current_working_directory"] == str(current_repository) assert document["desired_working_directory"] == str(desired_repository) assert document["preserved_data_directory"] == str(data_directory) assert document["changes"]["repository_migration"] is True assert document["changes"]["data_directory_preserved"] is True assert document["changes"]["preserved_data_directory"] == str(data_directory) def test_launch_agent_migration_retains_existing_private_data_directory( tmp_path: Path, ) -> None: current_repository = tmp_path / "current-repo" current_repository.mkdir() desired_repository = tmp_path / "desired-repo" desired_repository.mkdir() configured_data_directory = tmp_path / "canonical-data" configured_data_directory.mkdir(mode=0o700) uv_entrypoint = tmp_path / "uv" uv_entrypoint.write_text("#!/bin/sh\n") uv_entrypoint.chmod(0o700) agent = tmp_path / "agent.plist" _write_agent( path=agent, repository=current_repository, uv_entrypoint=uv_entrypoint, environment={ "PATH": "/usr/bin:/bin", "MISSIONCORE_DATA_DIR": str(configured_data_directory), }, ) plan = plan_mission_core_launch_agent( repository_root=desired_repository, agent_path=agent, expected_current_repository_root=current_repository, ) desired = plistlib.loads(plan.desired_payload) assert plan.preserved_data_directory == configured_data_directory assert ( desired["EnvironmentVariables"]["MISSIONCORE_DATA_DIR"] == str(configured_data_directory) ) def test_launch_agent_migration_rejects_symlinked_data_directory(tmp_path: Path) -> None: current_repository = tmp_path / "current-repo" current_repository.mkdir() desired_repository = tmp_path / "desired-repo" desired_repository.mkdir() private_directory = tmp_path / "private-data" private_directory.mkdir(mode=0o700) symlinked_directory = tmp_path / "data-link" symlinked_directory.symlink_to(private_directory, target_is_directory=True) uv_entrypoint = tmp_path / "uv" uv_entrypoint.write_text("#!/bin/sh\n") uv_entrypoint.chmod(0o700) agent = tmp_path / "agent.plist" _write_agent( path=agent, repository=current_repository, uv_entrypoint=uv_entrypoint, environment={ "PATH": "/usr/bin:/bin", "MISSIONCORE_DATA_DIR": str(symlinked_directory), }, ) with pytest.raises( MissionCoreLaunchAgentError, match="data directory is not a private canonical directory", ): plan_mission_core_launch_agent( repository_root=desired_repository, agent_path=agent, expected_current_repository_root=current_repository, ) def test_launch_agent_migration_rejects_nonprivate_default_data_directory( tmp_path: Path, ) -> None: current_repository = tmp_path / "current-repo" current_repository.mkdir() desired_repository = tmp_path / "desired-repo" desired_repository.mkdir() data_directory = current_repository / ".runtime" / "mission-core" data_directory.mkdir(parents=True, mode=0o755) data_directory.chmod(0o755) uv_entrypoint = tmp_path / "uv" uv_entrypoint.write_text("#!/bin/sh\n") uv_entrypoint.chmod(0o700) agent = tmp_path / "agent.plist" _write_agent( path=agent, repository=current_repository, uv_entrypoint=uv_entrypoint, ) with pytest.raises( MissionCoreLaunchAgentError, match="data directory is not a private canonical directory", ): plan_mission_core_launch_agent( repository_root=desired_repository, agent_path=agent, expected_current_repository_root=current_repository, )