/ tests / hermes_cli / test_update_config_clears_custom_fields.py
test_update_config_clears_custom_fields.py
 1  """Tests for hermes_cli.auth._update_config_for_provider clearing stale fields.
 2  
 3  When the user switches from a custom provider (e.g. MiniMax with
 4  ``api_mode: anthropic_messages``, ``api_key: mxp-...``) to a built-in
 5  provider (e.g. OpenRouter), the stale ``api_key`` and ``api_mode`` would
 6  otherwise override the new provider's credentials and transport choice.
 7  
 8  Built-in providers that legitimately need a specific ``api_mode`` (copilot,
 9  xai) compute it at request-resolution time in
10  ``_copilot_runtime_api_mode`` / ``_detect_api_mode_for_url``, so removing
11  the persisted value here is safe.
12  """
13  
14  from __future__ import annotations
15  
16  import yaml
17  
18  from hermes_cli.auth import _update_config_for_provider
19  from hermes_cli.config import get_config_path
20  
21  
22  def _read_model_cfg() -> dict:
23      path = get_config_path()
24      if not path.exists():
25          return {}
26      data = yaml.safe_load(path.read_text()) or {}
27      model = data.get("model", {})
28      return model if isinstance(model, dict) else {}
29  
30  
31  def _seed_custom_provider_config(api_mode: str = "anthropic_messages") -> None:
32      """Write a config.yaml mimicking a user on a MiniMax-style custom provider."""
33      path = get_config_path()
34      path.parent.mkdir(parents=True, exist_ok=True)
35      path.write_text(
36          yaml.safe_dump(
37              {
38                  "model": {
39                      "provider": "custom",
40                      "base_url": "https://api.minimax.io/anthropic",
41                      "api_key": "mxp-stale-key",
42                      "api_mode": api_mode,
43                      "default": "claude-sonnet-4-6",
44                  }
45              },
46              sort_keys=False,
47          )
48      )
49  
50  
51  class TestUpdateConfigForProviderClearsStaleCustomFields:
52      def test_switching_to_openrouter_clears_api_key_and_api_mode(self):
53          _seed_custom_provider_config()
54  
55          _update_config_for_provider(
56              "openrouter",
57              "https://openrouter.ai/api/v1",
58              default_model="anthropic/claude-sonnet-4.6",
59          )
60  
61          model_cfg = _read_model_cfg()
62          assert model_cfg.get("provider") == "openrouter"
63          assert model_cfg.get("base_url") == "https://openrouter.ai/api/v1"
64          assert "api_key" not in model_cfg, (
65              "Stale custom api_key would leak into OpenRouter requests — must be cleared"
66          )
67          assert "api_mode" not in model_cfg, (
68              "Stale api_mode=anthropic_messages from MiniMax would mis-route "
69              "OpenRouter requests to the Anthropic SDK — must be cleared"
70          )
71  
72      def test_switching_to_nous_clears_stale_api_mode(self):
73          _seed_custom_provider_config()
74          _update_config_for_provider("nous", "https://inference-api.nousresearch.com/v1")
75          model_cfg = _read_model_cfg()
76          assert model_cfg.get("provider") == "nous"
77          assert "api_mode" not in model_cfg
78          assert "api_key" not in model_cfg
79  
80      def test_switching_clears_codex_responses_api_mode(self):
81          """Also covers codex_responses, not just anthropic_messages."""
82          _seed_custom_provider_config(api_mode="codex_responses")
83          _update_config_for_provider("openrouter", "https://openrouter.ai/api/v1")
84          assert "api_mode" not in _read_model_cfg()