/ tests / hermes_cli / test_terminal_menu_fallbacks.py
test_terminal_menu_fallbacks.py
  1  """Regression tests for numbered fallbacks when TerminalMenu cannot initialize."""
  2  
  3  import subprocess
  4  import sys
  5  import types
  6  
  7  from hermes_cli.config import load_config, save_config
  8  
  9  
 10  class _BrokenTerminalMenu:
 11      def __init__(self, *args, **kwargs):
 12          raise subprocess.CalledProcessError(2, ["tput", "clear"])
 13  
 14  
 15  def test_prompt_model_selection_falls_back_on_terminalmenu_runtime_error(monkeypatch):
 16      from hermes_cli.auth import _prompt_model_selection
 17  
 18      monkeypatch.setitem(
 19          sys.modules,
 20          "simple_term_menu",
 21          types.SimpleNamespace(TerminalMenu=_BrokenTerminalMenu),
 22      )
 23      responses = iter(["2"])
 24      monkeypatch.setattr("builtins.input", lambda _prompt="": next(responses))
 25  
 26      selected = _prompt_model_selection(["model-a", "model-b"])
 27  
 28      assert selected == "model-b"
 29  
 30  
 31  def test_prompt_reasoning_effort_falls_back_on_terminalmenu_runtime_error(monkeypatch):
 32      from hermes_cli.main import _prompt_reasoning_effort_selection
 33  
 34      monkeypatch.setitem(
 35          sys.modules,
 36          "simple_term_menu",
 37          types.SimpleNamespace(TerminalMenu=_BrokenTerminalMenu),
 38      )
 39      responses = iter(["3"])
 40      monkeypatch.setattr("builtins.input", lambda _prompt="": next(responses))
 41  
 42      selected = _prompt_reasoning_effort_selection(["low", "medium", "high"], current_effort="")
 43  
 44      assert selected == "high"
 45  
 46  
 47  def test_remove_custom_provider_falls_back_on_terminalmenu_runtime_error(tmp_path, monkeypatch):
 48      from hermes_cli.main import _remove_custom_provider
 49  
 50      monkeypatch.setenv("HERMES_HOME", str(tmp_path))
 51      monkeypatch.setitem(
 52          sys.modules,
 53          "simple_term_menu",
 54          types.SimpleNamespace(TerminalMenu=_BrokenTerminalMenu),
 55      )
 56  
 57      cfg = load_config()
 58      cfg["custom_providers"] = [
 59          {"name": "Local A", "base_url": "http://localhost:8001/v1"},
 60          {"name": "Local B", "base_url": "http://localhost:8002/v1"},
 61      ]
 62      save_config(cfg)
 63  
 64      responses = iter(["1"])
 65      monkeypatch.setattr("builtins.input", lambda _prompt="": next(responses))
 66  
 67      _remove_custom_provider(cfg)
 68  
 69      reloaded = load_config()
 70      assert reloaded["custom_providers"] == [
 71          {"name": "Local B", "base_url": "http://localhost:8002/v1"},
 72      ]
 73  
 74  
 75  def test_named_custom_provider_model_picker_falls_back_on_terminalmenu_runtime_error(tmp_path, monkeypatch):
 76      from hermes_cli.main import _model_flow_named_custom
 77  
 78      monkeypatch.setenv("HERMES_HOME", str(tmp_path))
 79      monkeypatch.setitem(
 80          sys.modules,
 81          "simple_term_menu",
 82          types.SimpleNamespace(TerminalMenu=_BrokenTerminalMenu),
 83      )
 84      monkeypatch.setattr("hermes_cli.models.fetch_api_models", lambda *args, **kwargs: ["model-a", "model-b"])
 85      monkeypatch.setattr("hermes_cli.auth.deactivate_provider", lambda: None)
 86  
 87      cfg = load_config()
 88      save_config(cfg)
 89  
 90      responses = iter(["2"])
 91      monkeypatch.setattr("builtins.input", lambda _prompt="": next(responses))
 92  
 93      _model_flow_named_custom(
 94          cfg,
 95          {
 96              "name": "Local",
 97              "base_url": "http://localhost:8000/v1",
 98              "api_key": "",
 99              "model": "",
100          },
101      )
102  
103      reloaded = load_config()
104      assert reloaded["model"]["provider"] == "custom"
105      assert reloaded["model"]["base_url"] == "http://localhost:8000/v1"
106      assert reloaded["model"]["default"] == "model-b"