Import AITURK IDE 1.0.0-beta.1 from Hermes 63279301; preserve MIT license
This commit is contained in:
@@ -0,0 +1,176 @@
|
||||
"""Tests for hermes_cli.update_inventory — the plan phase (#91277 Phase 2)."""
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
import hermes_cli.update_inventory as ui
|
||||
|
||||
|
||||
def _write_state(home: Path, pid: int, sha: str | None = None, version: str | None = None):
|
||||
record = {"pid": pid}
|
||||
if sha:
|
||||
record["code_sha"] = sha
|
||||
if version:
|
||||
record["code_version"] = version
|
||||
(home / "gateway_state.json").write_text(json.dumps(record), encoding="utf-8")
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def fleet(monkeypatch, tmp_path):
|
||||
"""Two profiles with live gateways: default (systemd) + work (manual)."""
|
||||
default_home = tmp_path / "home"
|
||||
work_home = tmp_path / "home" / "profiles" / "work"
|
||||
work_home.mkdir(parents=True)
|
||||
_write_state(default_home, 100, sha="a" * 40, version="1.0")
|
||||
_write_state(work_home, 200) # pre-stamp gateway: no code identity
|
||||
|
||||
import re
|
||||
monkeypatch.setattr("hermes_cli.profiles._get_default_hermes_home", lambda: default_home)
|
||||
monkeypatch.setattr("hermes_cli.profiles._get_profiles_root", lambda: default_home / "profiles")
|
||||
monkeypatch.setattr("hermes_cli.profiles._PROFILE_ID_RE", re.compile(r"^[a-z0-9][a-z0-9_-]*$"), raising=False)
|
||||
monkeypatch.setattr("gateway.status._pid_exists", lambda pid: pid in (100, 200))
|
||||
monkeypatch.setattr("hermes_cli.gateway._get_service_pids", lambda all_profiles=False: {100})
|
||||
monkeypatch.setattr("hermes_cli.gateway.supports_systemd_services", lambda: True)
|
||||
monkeypatch.setattr("hermes_cli.gateway.find_profile_gateway_processes", lambda exclude_pids=None: [])
|
||||
monkeypatch.setattr(
|
||||
"hermes_cli.build_info.get_code_identity",
|
||||
lambda refresh=False: {"sha": "a" * 40, "short_sha": "a" * 8, "version": "1.0", "source": "git"},
|
||||
)
|
||||
monkeypatch.setattr("hermes_cli.config.detect_install_method", lambda *a, **k: "git")
|
||||
monkeypatch.setattr("hermes_cli.config.get_managed_system", lambda: None)
|
||||
return tmp_path
|
||||
|
||||
|
||||
class TestCollectInventory:
|
||||
def test_two_profile_fleet(self, fleet):
|
||||
plan = ui.collect_runtime_inventory()
|
||||
assert plan.install_method == "git"
|
||||
assert plan.updatable_in_place is True
|
||||
assert plan.expected_sha == "a" * 40
|
||||
assert plan.profiles == ["default", "work"]
|
||||
assert len(plan.runtimes) == 2
|
||||
by_profile = {r.profile: r for r in plan.runtimes}
|
||||
assert by_profile["default"].pid == 100
|
||||
assert by_profile["default"].supervisor == "systemd"
|
||||
assert by_profile["default"].code_sha == "a" * 40
|
||||
assert by_profile["work"].pid == 200
|
||||
assert by_profile["work"].supervisor == "manual"
|
||||
assert by_profile["work"].code_sha is None # pre-stamp gateway
|
||||
assert by_profile["work"].restart_via == "manual"
|
||||
from hermes_cli.update_inventory import describe_restart_mechanism
|
||||
|
||||
assert "hermes -p work gateway restart" in describe_restart_mechanism(
|
||||
by_profile["work"].restart_via, "work"
|
||||
)
|
||||
|
||||
def test_docker_install_not_updatable_in_place(self, fleet, monkeypatch):
|
||||
monkeypatch.setattr("hermes_cli.config.detect_install_method", lambda *a, **k: "docker")
|
||||
monkeypatch.setattr(
|
||||
"hermes_cli.config.recommended_update_command_for_method",
|
||||
lambda m: "docker pull nousresearch/hermes-agent:latest",
|
||||
)
|
||||
plan = ui.collect_runtime_inventory()
|
||||
assert plan.install_method == "docker"
|
||||
assert plan.updatable_in_place is False
|
||||
assert "docker pull" in plan.update_mechanism
|
||||
|
||||
def test_dead_pids_excluded(self, fleet, monkeypatch):
|
||||
monkeypatch.setattr("gateway.status._pid_exists", lambda pid: False)
|
||||
plan = ui.collect_runtime_inventory()
|
||||
assert plan.runtimes == []
|
||||
|
||||
def test_pid_file_fallback_covers_unstamped_profiles(self, fleet, monkeypatch):
|
||||
"""Gateways with a PID file but no runtime-status record still appear."""
|
||||
from hermes_cli.gateway import ProfileGatewayProcess
|
||||
|
||||
monkeypatch.setattr(
|
||||
"hermes_cli.gateway.find_profile_gateway_processes",
|
||||
lambda exclude_pids=None: [
|
||||
ProfileGatewayProcess(profile="legacy", path=Path("/x"), pid=300),
|
||||
# duplicate of an already-seen pid — must be deduped
|
||||
ProfileGatewayProcess(profile="default", path=Path("/y"), pid=100),
|
||||
],
|
||||
)
|
||||
monkeypatch.setattr("gateway.status._pid_exists", lambda pid: pid in (100, 200))
|
||||
plan = ui.collect_runtime_inventory()
|
||||
profiles = [r.profile for r in plan.runtimes]
|
||||
assert profiles.count("default") == 1 # deduped by pid
|
||||
assert "legacy" in profiles
|
||||
|
||||
def test_never_raises_when_everything_fails(self, monkeypatch):
|
||||
def _boom(*a, **k):
|
||||
raise RuntimeError("probe down")
|
||||
|
||||
for target in (
|
||||
"hermes_cli.config.detect_install_method",
|
||||
"hermes_cli.build_info.get_code_identity",
|
||||
"hermes_cli.profiles._get_default_hermes_home",
|
||||
"hermes_cli.gateway._get_service_pids",
|
||||
"hermes_cli.gateway.find_profile_gateway_processes",
|
||||
):
|
||||
monkeypatch.setattr(target, _boom)
|
||||
plan = ui.collect_runtime_inventory()
|
||||
assert plan.runtimes == []
|
||||
assert plan.install_method == "unknown"
|
||||
|
||||
def test_plan_serializes_for_receipt(self, fleet):
|
||||
plan = ui.collect_runtime_inventory()
|
||||
payload = plan.to_dict()
|
||||
# must be JSON-clean for the receipt
|
||||
text = json.dumps(payload)
|
||||
restored = json.loads(text)
|
||||
assert restored["install_method"] == "git"
|
||||
assert len(restored["runtimes"]) == 2
|
||||
assert restored["runtimes"][0]["kind"] == "gateway"
|
||||
|
||||
|
||||
class TestPrintPlan:
|
||||
def test_git_fleet_output(self, fleet, capsys):
|
||||
ui.print_update_plan(ui.collect_runtime_inventory())
|
||||
out = capsys.readouterr().out
|
||||
assert "Update plan:" in out
|
||||
assert "Install: git" in out
|
||||
assert "default, work" in out
|
||||
assert "pid 100" in out and "systemd" in out
|
||||
assert "pid 200" in out and "manual" in out
|
||||
|
||||
def test_docker_warns_not_in_place(self, fleet, monkeypatch, capsys):
|
||||
monkeypatch.setattr("hermes_cli.config.detect_install_method", lambda *a, **k: "docker")
|
||||
monkeypatch.setattr(
|
||||
"hermes_cli.config.recommended_update_command_for_method",
|
||||
lambda m: "docker pull nousresearch/hermes-agent:latest",
|
||||
)
|
||||
ui.print_update_plan(ui.collect_runtime_inventory())
|
||||
out = capsys.readouterr().out
|
||||
assert "NOT updatable in place" in out
|
||||
assert "docker pull" in out
|
||||
|
||||
def test_empty_fleet_message(self, fleet, monkeypatch, capsys):
|
||||
monkeypatch.setattr("gateway.status._pid_exists", lambda pid: False)
|
||||
ui.print_update_plan(ui.collect_runtime_inventory())
|
||||
assert "none detected" in capsys.readouterr().out
|
||||
|
||||
|
||||
class TestReceiptIntegration:
|
||||
def test_plan_recorded_into_active_receipt(self, fleet, monkeypatch, tmp_path):
|
||||
import hermes_cli.update_receipt as ur
|
||||
|
||||
home = tmp_path / "receipt_home"
|
||||
home.mkdir()
|
||||
monkeypatch.setattr("hermes_cli.config.get_hermes_home", lambda: home, raising=False)
|
||||
ur._current = None
|
||||
ur.begin_update_receipt()
|
||||
plan = ui.collect_runtime_inventory()
|
||||
ui.record_plan_in_receipt(plan)
|
||||
path = ur.finalize_update_receipt("success")
|
||||
payload = json.loads(path.read_text(encoding="utf-8"))
|
||||
assert payload["plan"]["install_method"] == "git"
|
||||
assert len(payload["plan"]["runtimes"]) == 2
|
||||
|
||||
def test_noop_without_active_receipt(self, fleet):
|
||||
import hermes_cli.update_receipt as ur
|
||||
|
||||
ur._current = None
|
||||
ui.record_plan_in_receipt(ui.collect_runtime_inventory()) # must not raise
|
||||
Reference in New Issue
Block a user