Something went wrong. Try again.
The agent that grows with you
Something went wrong. Try again.
6.4 kB · 161 lines
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162"""Tests for non-interactive setup and first-run headless behavior."""
from argparse import Namespacefrom unittest.mock import MagicMock, patch
import pytestfrom hermes_cli.config import DEFAULT_CONFIG, load_config, save_config
def _make_setup_args(**overrides): return Namespace( non_interactive=overrides.get("non_interactive", False), section=overrides.get("section", None), reset=overrides.get("reset", False), )
def _make_chat_args(**overrides): return Namespace( continue_last=overrides.get("continue_last", None), resume=overrides.get("resume", None), model=overrides.get("model", None), provider=overrides.get("provider", None), toolsets=overrides.get("toolsets", None), verbose=overrides.get("verbose", False), query=overrides.get("query", None), worktree=overrides.get("worktree", False), yolo=overrides.get("yolo", False), pass_session_id=overrides.get("pass_session_id", False), quiet=overrides.get("quiet", False), checkpoints=overrides.get("checkpoints", False), )
class TestNonInteractiveSetup: """Verify setup paths exit cleanly in headless/non-interactive environments."""
def test_cmd_setup_allows_noninteractive_flag_without_tty(self): """The CLI entrypoint should not block --non-interactive before setup.py handles it.""" from hermes_cli.main import cmd_setup
args = _make_setup_args(non_interactive=True)
with ( patch("hermes_cli.setup.run_setup_wizard") as mock_run_setup, patch("sys.stdin") as mock_stdin, ): mock_stdin.isatty.return_value = False cmd_setup(args)
mock_run_setup.assert_called_once_with(args)
def test_cmd_setup_defers_no_tty_handling_to_setup_wizard(self): """Bare `hermes setup` should reach setup.py, which prints headless guidance.""" from hermes_cli.main import cmd_setup
args = _make_setup_args(non_interactive=False)
with ( patch("hermes_cli.setup.run_setup_wizard") as mock_run_setup, patch("sys.stdin") as mock_stdin, ): mock_stdin.isatty.return_value = False cmd_setup(args)
mock_run_setup.assert_called_once_with(args)
def test_non_interactive_flag_skips_wizard(self, capsys): """--non-interactive should print guidance and not enter the wizard.""" from hermes_cli.setup import run_setup_wizard
args = _make_setup_args(non_interactive=True)
with ( patch("hermes_cli.setup.ensure_hermes_home"), patch("hermes_cli.setup.load_config", return_value={}), patch("hermes_cli.setup.get_hermes_home", return_value="/tmp/.hermes"), patch("hermes_cli.auth.get_active_provider", side_effect=AssertionError("wizard continued")), patch("builtins.input", side_effect=AssertionError("input should not be called")), ): run_setup_wizard(args)
out = capsys.readouterr().out assert "hermes config set model.provider custom" in out
def test_no_tty_skips_wizard(self, capsys): """When stdin has no TTY, the setup wizard should print guidance and return.""" from hermes_cli.setup import run_setup_wizard
args = _make_setup_args(non_interactive=False)
with ( patch("hermes_cli.setup.ensure_hermes_home"), patch("hermes_cli.setup.load_config", return_value={}), patch("hermes_cli.setup.get_hermes_home", return_value="/tmp/.hermes"), patch("hermes_cli.auth.get_active_provider", side_effect=AssertionError("wizard continued")), patch("sys.stdin") as mock_stdin, patch("builtins.input", side_effect=AssertionError("input should not be called")), ): mock_stdin.isatty.return_value = False run_setup_wizard(args)
out = capsys.readouterr().out assert "hermes config set model.provider custom" in out
def test_reset_flag_rewrites_config_before_noninteractive_exit(self, tmp_path, monkeypatch, capsys): """--reset should rewrite config.yaml even when the wizard cannot run interactively.""" from hermes_cli.setup import run_setup_wizard
monkeypatch.setenv("HERMES_HOME", str(tmp_path)) cfg = load_config() cfg["model"] = {"provider": "custom", "base_url": "http://localhost:8080/v1", "default": "llama3"} cfg["agent"]["max_turns"] = 12 save_config(cfg)
args = _make_setup_args(non_interactive=True, reset=True)
run_setup_wizard(args)
reloaded = load_config() assert reloaded["model"] == DEFAULT_CONFIG["model"] assert reloaded["agent"]["max_turns"] == DEFAULT_CONFIG["agent"]["max_turns"] out = capsys.readouterr().out assert "Configuration reset to defaults." in out
def test_chat_first_run_headless_skips_setup_prompt(self, capsys): """Bare `hermes` should not prompt for input when no provider exists and stdin is headless.""" from hermes_cli.main import cmd_chat
args = _make_chat_args()
with ( patch("hermes_cli.main._has_any_provider_configured", return_value=False), patch("hermes_cli.main.cmd_setup") as mock_setup, patch("sys.stdin") as mock_stdin, patch("builtins.input", side_effect=AssertionError("input should not be called")), ): mock_stdin.isatty.return_value = False with pytest.raises(SystemExit) as exc: cmd_chat(args)
assert exc.value.code == 1 mock_setup.assert_not_called() out = capsys.readouterr().out assert "hermes config set model.provider custom" in out
def test_main_accepts_tts_setup_section(self, monkeypatch): """`hermes setup tts` should parse and dispatch like other setup sections.""" from hermes_cli import main as main_mod
received = {}
def fake_cmd_setup(args): received["section"] = args.section
monkeypatch.setattr(main_mod, "cmd_setup", fake_cmd_setup) monkeypatch.setattr("sys.argv", ["hermes", "setup", "tts"])
main_mod.main()
assert received["section"] == "tts"