test(config): one parametrized every-seeder test + template parity, drop duplicates

The stack's regression tests pasted the same platform-parity loop into four
files and added eight cases for one invariant ("a seeded config must not
pin a global display key"), over the AGENTS.md invariant-test budget.

- assert_keeps_platform_display_defaults() in test_display_config.py is the
  single parity helper.
- test_shipped_template_keeps_every_platform_default moves out of
  TestYAMLNormalisation (it was attached there only by indentation) into
  TestInstallerSeededConfigThroughGatewayResolver, reuses _seed_like_installer
  and absorbs the _resolve_gateway_display_bool qqbot hop.
- test_config_edit_seed's test becomes the one parametrized every-seeder test:
  config edit (template / no template), `hermes setup agent` with Enter (the
  W1 leak fixed in this stack), _apply_default_agent_settings and blank slate.
  The per-file copies in test_setup_agent_settings / test_setup_blank_slate
  are removed.
- Dropped test_template_seeded_home_keeps_qqbot_reasoning_off (duplicate; its
  dead HERMES_HOME set + raising=False patch went with it) and
  test_show_reasoning_stays_a_known_config_key (change-detector for the
  rejected #121232 approach). The explicit opt-in control (#7148) stays.

On origin/main's production files the template test and all five seeder
cases fail; the control passes.
This commit is contained in:
kshitijk4poor
2026-09-24 17:10:14 +05:30
committed by kshitij
parent f59c5df755
commit 680fa8a92d
4 changed files with 73 additions and 86 deletions

View File

@@ -139,36 +139,25 @@ class TestYAMLNormalisation:
# ---------------------------------------------------------------------------
def test_shipped_template_keeps_every_platform_default(self, tmp_path):
"""The installers, the Docker first boot and ``doctor --fix`` copy
cli-config.yaml.example verbatim, so an uncommented ``display.<key>`` there
becomes an explicit global value that beats every platform tier."""
import shutil
from pathlib import Path
def assert_keeps_platform_display_defaults(cfg):
"""Every platform resolves every tier key (and tool_progress) exactly as with no config at all.
from gateway.display_config import _PLATFORM_DEFAULTS, resolve_display_setting, resolve_tool_progress
from gateway.run import _load_gateway_config
Shared by every config seeder's regression test: a seeded global ``display.<key>`` beats each
platform tier, because the gateway loader merges no DEFAULT_CONFIG (#121230)."""
from gateway.display_config import _PLATFORM_DEFAULTS, resolve_display_setting, resolve_tool_progress
template = Path(__file__).resolve().parents[2] / "cli-config.yaml.example"
shutil.copy(template, tmp_path / "config.yaml")
seeded = _load_gateway_config(tmp_path / "config.yaml")
assert "display" in seeded # the loader fails open to {}, which would pass vacuously
tier_keys = {key for tier in _PLATFORM_DEFAULTS.values() for key in tier}
for platform in _PLATFORM_DEFAULTS:
assert resolve_tool_progress(seeded, platform) == resolve_tool_progress({}, platform), platform
for key in tier_keys:
assert resolve_display_setting(seeded, platform, key) == resolve_display_setting({}, platform, key), (
platform, key)
tier_keys = {key for tier in _PLATFORM_DEFAULTS.values() for key in tier}
for platform in _PLATFORM_DEFAULTS:
assert resolve_tool_progress(cfg, platform) == resolve_tool_progress({}, platform), platform
for key in tier_keys:
assert resolve_display_setting(cfg, platform, key) == resolve_display_setting({}, platform, key), (
platform, key)
class TestInstallerSeededConfigThroughGatewayResolver:
"""Regression for #121230: a fresh install copies cli-config.yaml.example to config.yaml, and the
gateway then rendered reasoning into QQBot/Telegram/... because the template pinned a global
``display.show_reasoning: true`` over every platform's ``False`` default.
Goes through the resolver the gateway turn actually calls (``gateway/run.py``), with the same
arguments as ``gateway/run_turn.py``, not only ``resolve_display_setting``.
"""
@staticmethod
@@ -181,23 +170,21 @@ class TestInstallerSeededConfigThroughGatewayResolver:
shutil.copy(template, home / "config.yaml")
return home / "config.yaml"
def test_template_seeded_home_keeps_qqbot_reasoning_off(self, tmp_path, monkeypatch):
def test_shipped_template_keeps_every_platform_default(self, tmp_path):
"""The installers, the Docker first boot and ``doctor --fix`` copy cli-config.yaml.example
verbatim, so an uncommented ``display.<key>`` there becomes an explicit global value."""
from gateway.config import Platform
from gateway.run import _load_gateway_config, _resolve_gateway_display_bool
home = tmp_path / "hermes-home"
self._seed_like_installer(home)
monkeypatch.setenv("HERMES_HOME", str(home))
monkeypatch.setattr("gateway.run._gateway_config_home", lambda: home, raising=False)
seeded = _load_gateway_config(self._seed_like_installer(tmp_path / "hermes-home"))
assert "display" in seeded # the loader fails open to {}, which would pass vacuously
seeded = _load_gateway_config()
assert "display" in seeded # loader fails open to {}, which would make the assertion below vacuous
for platform in (Platform.QQBOT, Platform.TELEGRAM, Platform.DISCORD):
assert _resolve_gateway_display_bool(
seeded, platform.value, "show_reasoning", default=False, platform=platform,
require_platform_override_for={Platform.MATTERMOST},
) is False, platform
assert_keeps_platform_display_defaults(seeded)
# ...and through the resolver the gateway turn actually calls (same arguments as gateway/run_turn.py).
assert _resolve_gateway_display_bool(
seeded, "qqbot", "show_reasoning", default=False, platform=Platform.QQBOT,
require_platform_override_for={Platform.MATTERMOST},
) is False
def test_explicit_global_opt_in_still_reaches_gateway_platforms(self, tmp_path):
"""Control: an operator who deliberately writes ``display.show_reasoning: true`` still gets it (#7148)."""
@@ -211,13 +198,6 @@ class TestInstallerSeededConfigThroughGatewayResolver:
require_platform_override_for={Platform.MATTERMOST},
) is True
def test_show_reasoning_stays_a_known_config_key(self):
"""Control: the fix must not delete the key from DEFAULT_CONFIG, or
``hermes config set display.show_reasoning false`` gets rejected as unknown."""
from hermes_cli.config import _validate_config_key
assert _validate_config_key("display.show_reasoning") == (True, None)
# ---------------------------------------------------------------------------
# Config migration: tool_progress_overrides → display.platforms

View File

@@ -1,30 +1,64 @@
"""``hermes config edit`` on a home with no config.yaml seeds one. The seed must not pin a display value over
any messaging platform's own default (the gateway loader merges no DEFAULT_CONFIG, so every written key is explicit)."""
"""Every writer that seeds or first-configures config.yaml must leave each messaging platform's display defaults
alone (#121230). The gateway loader merges no DEFAULT_CONFIG, so any written global ``display.<key>`` beats every
platform tier (e.g. Telegram/Slack tool_progress ``off`` -> ``all``, QQBot show_reasoning ``False`` -> ``True``)."""
import pytest
from tests.gateway.test_display_config import assert_keeps_platform_display_defaults
@pytest.mark.parametrize("seed", ["template", "no-template"])
def test_config_edit_seed_keeps_every_platform_display_default(tmp_path, monkeypatch, seed):
def _config_edit(tmp_path, monkeypatch, cfg, *, template):
"""`hermes config edit` on a home with no config.yaml (seeds via seed_config_file, like `doctor --fix`)."""
monkeypatch.setenv("EDITOR", "true")
monkeypatch.setattr(cfg.subprocess, "run", lambda *a, **k: None)
if not template:
monkeypatch.setattr(cfg, "get_project_root", lambda: tmp_path / "no-checkout")
cfg.edit_config()
def _setup_agent_enter(tmp_path, monkeypatch, cfg):
"""`hermes setup agent` on a fresh home, pressing Enter (the offered default) on every prompt."""
import hermes_cli.setup as setup
monkeypatch.setattr(setup, "prompt", lambda question, default=None, *a, **k: default or "")
monkeypatch.setattr(setup, "prompt_yes_no", lambda *a, **k: False)
setup.setup_agent_settings(cfg.load_config())
def _apply_default_agent_settings(tmp_path, monkeypatch, cfg):
"""Quick and full first-time setup."""
from hermes_cli.setup import _apply_default_agent_settings
_apply_default_agent_settings(cfg.load_config())
def _blank_slate(tmp_path, monkeypatch, cfg):
from hermes_cli.setup_quick import _blank_slate_minimize_config
config = cfg.load_config()
_blank_slate_minimize_config(config)
cfg.save_config(config)
SEEDERS = {
"config-edit-template": lambda *a: _config_edit(*a, template=True),
"config-edit-no-template": lambda *a: _config_edit(*a, template=False),
"setup-agent-enter": _setup_agent_enter,
"apply-default-agent-settings": _apply_default_agent_settings,
"blank-slate": _blank_slate,
}
@pytest.mark.parametrize("seeder", list(SEEDERS))
def test_every_seeder_keeps_every_platform_display_default(tmp_path, monkeypatch, seeder):
import hermes_cli.config as cfg
from gateway.display_config import _PLATFORM_DEFAULTS, resolve_display_setting, resolve_tool_progress
from gateway.run import _load_gateway_config
home = tmp_path / "home"
home.mkdir()
monkeypatch.setenv("HERMES_HOME", str(home))
monkeypatch.setenv("EDITOR", "true")
monkeypatch.setattr(cfg.subprocess, "run", lambda *a, **k: None)
if seed == "no-template":
monkeypatch.setattr(cfg, "get_project_root", lambda: tmp_path / "no-checkout")
cfg.edit_config()
SEEDERS[seeder](tmp_path, monkeypatch, cfg)
config_path = home / "config.yaml"
assert config_path.exists() # the resolution checks below would pass on a missing file
seeded = _load_gateway_config(config_path)
tier_keys = {key for tier in _PLATFORM_DEFAULTS.values() for key in tier}
for platform in _PLATFORM_DEFAULTS:
assert resolve_tool_progress(seeded, platform) == resolve_tool_progress({}, platform), platform
for key in tier_keys:
assert resolve_display_setting(seeded, platform, key) == resolve_display_setting({}, platform, key), (
platform, key)
assert_keeps_platform_display_defaults(_load_gateway_config(config_path))

View File

@@ -46,21 +46,3 @@ def test_setup_agent_settings_prefers_config_over_stale_env(tmp_path, monkeypatc
assert "Press Enter to keep 60." not in out
# And the stale .env entry gets cleaned up
assert "HERMES_MAX_ITERATIONS" in removed_keys
def test_first_time_defaults_keep_every_platform_tool_progress_default(tmp_path, monkeypatch):
"""Quick and full first-time setup run this. A global display.tool_progress it
writes beats every platform tier, so Telegram and Slack went from off to all."""
from gateway.display_config import _PLATFORM_DEFAULTS, resolve_tool_progress
from gateway.run import _load_gateway_config
from hermes_cli.config import load_config
from hermes_cli.setup import _apply_default_agent_settings
monkeypatch.setenv("HERMES_HOME", str(tmp_path))
_apply_default_agent_settings(load_config())
on_disk = _load_gateway_config(tmp_path / "config.yaml")
assert on_disk.get("agent", {}).get("max_turns") == 150 # the save really landed
for platform in _PLATFORM_DEFAULTS:
assert resolve_tool_progress(on_disk, platform) == resolve_tool_progress({}, platform), platform

View File

@@ -83,15 +83,6 @@ class TestBlankSlateMinimizeConfig:
assert cfg["checkpoints"]["enabled"] is False
assert cfg["smart_model_routing"]["enabled"] is False
def test_messaging_platforms_keep_their_tool_progress_default(self):
"""A global display.tool_progress beats every platform tier (Telegram/Slack off)."""
from gateway.display_config import _PLATFORM_DEFAULTS, resolve_tool_progress
cfg = {}
_blank_slate_minimize_config(cfg)
for platform in _PLATFORM_DEFAULTS:
assert resolve_tool_progress(cfg, platform) == resolve_tool_progress({}, platform), platform
class TestBlankSlateFork:
"""The post-baseline fork: finish now vs walk through configurations."""