Competing installers and checkout-local venv assumptions bypassed PM selection, install consent, and generation lifetimes. Route consumers through PM and installation-bound launchers. Refresh source launchers before obsolete Python entries can be collected. Remove Node, browser, and CUA acquisition engines, obsolete venv-holder handling, detached sync, and unused PM APIs. Keep historical updater exports inert and preserve external tool ownership and native integration. Share product freshness and prepared inputs across builders. Align plugin admission, Docker provisioning, setup instructions, and behavioral tests. Verified targeted Python and JavaScript tests, desktop and web typechecks, scoped lint, real product builds, and the Docker frontend smoke test. The missed post-setup test cleanup is included and verified. Native Windows/macOS execution, full Rust compilation, and the complete repository suite remain unverified. Historical compatibility requirements were preserved and extended, not fully rescanned.
124 lines
4.4 KiB
Python
124 lines
4.4 KiB
Python
from types import SimpleNamespace
|
|
from unittest.mock import MagicMock
|
|
|
|
import hermes_cli.memory_setup as memory_setup
|
|
from hermes_cli.memory_setup import _CANCELLED, _curses_select
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_cmd_setup_generic_choice_cancel_writes_nothing(tmp_path, monkeypatch):
|
|
class ChoiceProvider:
|
|
def __init__(self):
|
|
self.save_config = MagicMock()
|
|
|
|
def get_config_schema(self):
|
|
return [{
|
|
"key": "mode",
|
|
"description": "Mode",
|
|
"default": "one",
|
|
"choices": ["one", "two"],
|
|
}]
|
|
|
|
provider = ChoiceProvider()
|
|
selections = iter([0, _CANCELLED])
|
|
save_config = MagicMock()
|
|
install_dependencies = MagicMock()
|
|
|
|
monkeypatch.setattr(memory_setup, "_get_available_providers", lambda: [("fake", "local", provider)])
|
|
monkeypatch.setattr(memory_setup, "_curses_select", lambda *args, **kwargs: next(selections))
|
|
monkeypatch.setattr(memory_setup, "_install_dependencies", install_dependencies)
|
|
monkeypatch.setattr(memory_setup, "get_hermes_home", lambda: tmp_path)
|
|
monkeypatch.setattr("hermes_cli.config.load_config", lambda: {"memory": {}})
|
|
monkeypatch.setattr("hermes_cli.config.save_config", save_config)
|
|
|
|
memory_setup.cmd_setup(SimpleNamespace())
|
|
|
|
install_dependencies.assert_called_once_with("fake")
|
|
save_config.assert_not_called()
|
|
provider.save_config.assert_not_called()
|
|
assert not (tmp_path / ".env").exists()
|
|
|
|
|
|
# _write_env_vars's CR/LF-stripping, denylist, and plain-value-roundtrip
|
|
# behavior is covered by tests/hermes_cli/test_memory_setup_env_denylist.py,
|
|
# which exercises the current save_env_value-routed signature
|
|
# (env_writes, hermes_home=None) \u2014 these three tests pinned the prior direct
|
|
# Path.write_text(env_path, env_writes) signature/implementation and were
|
|
# removed along with it (#60587).
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# provider extras — mode-aware expansion (#70636)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
def test_install_dependencies_prepares_declared_extra_even_if_importable(tmp_path, monkeypatch):
|
|
"""PM, not ambient importability, decides whether constraints are current."""
|
|
import hermes_yaml as _yaml
|
|
|
|
plugin_dir = tmp_path / "mem0"
|
|
plugin_dir.mkdir()
|
|
(plugin_dir / "plugin.yaml").write_text(
|
|
_yaml.safe_dump({"extra": "mem0"}), encoding="utf-8"
|
|
)
|
|
monkeypatch.setattr(
|
|
"plugins.memory.find_provider_dir", lambda name: plugin_dir
|
|
)
|
|
|
|
synced = []
|
|
|
|
import pm
|
|
|
|
monkeypatch.setattr(pm, "available", lambda extra: True)
|
|
monkeypatch.setattr(
|
|
pm, "sync_venv",
|
|
lambda extras=None, explicit=False: synced.append((list(extras or []), explicit)),
|
|
)
|
|
|
|
memory_setup._install_dependencies("mem0")
|
|
|
|
assert synced == [(["mem0"], True)]
|
|
|
|
|
|
def test_cmd_status_memory_tool_gate_disabled(capsys, monkeypatch):
|
|
"""When both memory stores are disabled, Memory status reports memory tool as disabled."""
|
|
_cfg = {"memory": {"memory_enabled": False, "user_profile_enabled": False}}
|
|
monkeypatch.setattr("hermes_cli.config.load_config", lambda: _cfg)
|
|
# check_memory_requirements() reads the readonly loader, not load_config.
|
|
monkeypatch.setattr(
|
|
"hermes_cli.config.load_config_readonly", lambda: _cfg, raising=False
|
|
)
|
|
monkeypatch.setattr(memory_setup, "_get_available_providers", lambda: [])
|
|
|
|
memory_setup.cmd_status(SimpleNamespace())
|
|
|
|
captured = capsys.readouterr().out
|
|
assert "Memory tool: disabled ✗" in captured
|
|
assert "Memory injection: disabled ✗" in captured
|
|
assert "User profile: disabled ✗" in captured
|
|
|
|
|
|
def test_cmd_status_memory_tool_gate_enabled(capsys, monkeypatch):
|
|
"""When at least one memory store is enabled, Memory status reports memory tool as enabled."""
|
|
_cfg = {"memory": {"memory_enabled": True, "user_profile_enabled": False}}
|
|
monkeypatch.setattr("hermes_cli.config.load_config", lambda: _cfg)
|
|
monkeypatch.setattr(
|
|
"hermes_cli.config.load_config_readonly", lambda: _cfg, raising=False
|
|
)
|
|
monkeypatch.setattr(memory_setup, "_get_available_providers", lambda: [])
|
|
|
|
memory_setup.cmd_status(SimpleNamespace())
|
|
|
|
captured = capsys.readouterr().out
|
|
assert "Memory tool: enabled ✓" in captured
|
|
assert "Memory injection: enabled ✓" in captured
|
|
assert "User profile: disabled ✗" in captured
|