hermes-agent/tests/cli/test_cli_save_config_value.py
Teknium e3ffe5b75f
fix: remove legacy compression.summary_* config and env var fallbacks (#8992)
Remove the backward-compat code paths that read compression provider/model
settings from legacy config keys and env vars, which caused silent failures
when auto-detection resolved to incompatible backends.

What changed:
- Remove compression.summary_model, summary_provider, summary_base_url from
  DEFAULT_CONFIG and cli.py defaults
- Remove backward-compat block in _resolve_task_provider_model() that read
  from the legacy compression section
- Remove _get_auxiliary_provider() and _get_auxiliary_env_override() helper
  functions (AUXILIARY_*/CONTEXT_* env var readers)
- Remove env var fallback chain for per-task overrides
- Update hermes config show to read from auxiliary.compression
- Add config migration (v16→17) that moves non-empty legacy values to
  auxiliary.compression and strips the old keys
- Update example config and openclaw migration script
- Remove/update tests for deleted code paths

Compression model/provider is now configured exclusively via:
  auxiliary.compression.provider / auxiliary.compression.model

Closes #8923
2026-04-13 04:59:26 -07:00

80 lines
3.1 KiB
Python

"""Tests for save_config_value() in cli.py — atomic write behavior."""
import os
import yaml
from pathlib import Path
from unittest.mock import patch, MagicMock
import pytest
class TestSaveConfigValueAtomic:
"""save_config_value() must use atomic_yaml_write to avoid data loss."""
@pytest.fixture
def config_env(self, tmp_path, monkeypatch):
"""Isolated config environment with a writable config.yaml."""
hermes_home = tmp_path / ".hermes"
hermes_home.mkdir()
config_path = hermes_home / "config.yaml"
config_path.write_text(yaml.dump({
"model": {"default": "test-model", "provider": "openrouter"},
"display": {"skin": "default"},
}))
monkeypatch.setattr("cli._hermes_home", hermes_home)
return config_path
def test_calls_atomic_yaml_write(self, config_env, monkeypatch):
"""save_config_value must route through atomic_yaml_write, not bare open()."""
mock_atomic = MagicMock()
monkeypatch.setattr("utils.atomic_yaml_write", mock_atomic)
from cli import save_config_value
save_config_value("display.skin", "mono")
mock_atomic.assert_called_once()
written_path, written_data = mock_atomic.call_args[0]
assert Path(written_path) == config_env
assert written_data["display"]["skin"] == "mono"
def test_preserves_existing_keys(self, config_env):
"""Writing a new key must not clobber existing config entries."""
from cli import save_config_value
save_config_value("agent.max_turns", 50)
result = yaml.safe_load(config_env.read_text())
assert result["model"]["default"] == "test-model"
assert result["model"]["provider"] == "openrouter"
assert result["display"]["skin"] == "default"
assert result["agent"]["max_turns"] == 50
def test_creates_nested_keys(self, config_env):
"""Dot-separated paths create intermediate dicts as needed."""
from cli import save_config_value
save_config_value("auxiliary.compression.model", "google/gemini-3-flash-preview")
result = yaml.safe_load(config_env.read_text())
assert result["auxiliary"]["compression"]["model"] == "google/gemini-3-flash-preview"
def test_overwrites_existing_value(self, config_env):
"""Updating an existing key replaces the value."""
from cli import save_config_value
save_config_value("display.skin", "ares")
result = yaml.safe_load(config_env.read_text())
assert result["display"]["skin"] == "ares"
def test_file_not_truncated_on_error(self, config_env, monkeypatch):
"""If atomic_yaml_write raises, the original file is untouched."""
original_content = config_env.read_text()
def exploding_write(*args, **kwargs):
raise OSError("disk full")
monkeypatch.setattr("utils.atomic_yaml_write", exploding_write)
from cli import save_config_value
result = save_config_value("display.skin", "broken")
assert result is False
assert config_env.read_text() == original_content