/ tests / hermes_cli / test_mcp_reload_confirm_gate.py
test_mcp_reload_confirm_gate.py
 1  """Tests for the approvals.mcp_reload_confirm config gate.
 2  
 3  When the user runs /reload-mcp, the MCP tool set is rebuilt which
 4  invalidates the provider prompt cache for the active session.  That's
 5  expensive on long-context / high-reasoning models.  The config gate
 6  adds a three-option confirmation (Approve Once / Always Approve /
 7  Cancel); "Always Approve" flips this key to false so subsequent reloads
 8  run silently.
 9  """
10  
11  from __future__ import annotations
12  
13  from copy import deepcopy
14  
15  from hermes_cli.config import DEFAULT_CONFIG
16  
17  
18  class TestMcpReloadConfirmDefault:
19      def test_default_config_has_the_key(self):
20          approvals = DEFAULT_CONFIG.get("approvals")
21          assert isinstance(approvals, dict)
22          assert "mcp_reload_confirm" in approvals
23  
24      def test_default_is_true(self):
25          # New installs confirm by default — this is the safe behavior.
26          assert DEFAULT_CONFIG["approvals"]["mcp_reload_confirm"] is True
27  
28      def test_shape_matches_other_approval_keys(self):
29          # Same flat dict level as `mode` / `timeout` / `cron_mode`.
30          approvals = DEFAULT_CONFIG["approvals"]
31          assert isinstance(approvals.get("mode"), str)
32          assert isinstance(approvals.get("timeout"), int)
33          assert isinstance(approvals.get("cron_mode"), str)
34          assert isinstance(approvals.get("mcp_reload_confirm"), bool)
35  
36  
37  class TestUserConfigMerge:
38      """If a user has a pre-existing config without this key, load_config
39      should fill it in from DEFAULT_CONFIG (deep merge preserves keys the
40      user didn't override).
41      """
42  
43      def test_existing_user_config_without_key_gets_default(self, tmp_path, monkeypatch):
44          import yaml
45  
46          # Simulate a legacy user config without the new key.
47          home = tmp_path / ".hermes"
48          home.mkdir()
49          cfg_path = home / "config.yaml"
50          legacy = {
51              "approvals": {"mode": "manual", "timeout": 60, "cron_mode": "deny"},
52          }
53          cfg_path.write_text(yaml.safe_dump(legacy))
54  
55          monkeypatch.setenv("HERMES_HOME", str(home))
56          # Force a fresh reimport of config.py so the HERMES_HOME is honored.
57          import importlib
58          import hermes_cli.config as cfg_mod
59          importlib.reload(cfg_mod)
60  
61          cfg = cfg_mod.load_config()
62          assert cfg["approvals"]["mcp_reload_confirm"] is True
63  
64      def test_existing_user_config_with_false_key_survives_merge(
65          self, tmp_path, monkeypatch,
66      ):
67          """A user who has clicked "Always Approve" (key=false) must keep
68          that setting across reloads — the default_true value must not win.
69          """
70          import yaml
71  
72          home = tmp_path / ".hermes"
73          home.mkdir()
74          cfg_path = home / "config.yaml"
75          user_cfg = {
76              "approvals": {
77                  "mode": "manual",
78                  "timeout": 60,
79                  "cron_mode": "deny",
80                  "mcp_reload_confirm": False,
81              },
82          }
83          cfg_path.write_text(yaml.safe_dump(user_cfg))
84  
85          monkeypatch.setenv("HERMES_HOME", str(home))
86          import importlib
87          import hermes_cli.config as cfg_mod
88          importlib.reload(cfg_mod)
89  
90          cfg = cfg_mod.load_config()
91          assert cfg["approvals"]["mcp_reload_confirm"] is False