Files
hermes-agent/tests/hermes_cli/test_profile_listing_cache.py
teknium1 2a7658989f test: trim the roster-poll memo tests to the invariants
Keep staleness (edit seen on the next poll), missing-file pickup, parsed-once and the uncached-writer contracts; the per-file mutation-poison and cross-profile cases restate the copier.
2026-09-20 18:45:07 -07:00

87 lines
3.3 KiB
Python

"""``list_profiles`` re-reads a profile's YAML only when that file has changed.
The listing is the shared body of ``GET /api/profiles`` and JSON-RPC ``profiles.list``, which the
Bots roster polls every 5s per connection, and it parsed three YAML files per profile every time —
including an installer-seeded ``config.yaml`` (the annotated template, ~119KB) for two strings.
The memo must be invisible: every edit has to show up on the very next read, or `hermes profile
list` and the roster paint stale names and models. Only derived values are cached — the raw
readers keep their uncached contract, because callers write those documents back.
"""
from __future__ import annotations
from pathlib import Path
import pytest
import hermes_cli.profiles as profiles
@pytest.fixture(autouse=True)
def _clear_memo():
profiles._PROFILE_FILE_CACHE.clear()
yield
profiles._PROFILE_FILE_CACHE.clear()
@pytest.fixture
def profile_dir(tmp_path) -> Path:
p = tmp_path / "profiles" / "bob"
p.mkdir(parents=True)
(p / "config.yaml").write_text("model:\n provider: openai\n default: gpt-4o\n", encoding="utf-8")
(p / "profile.yaml").write_text("display_name: Bob\ndescription: first\n", encoding="utf-8")
(p / "distribution.yaml").write_text("name: starter\nversion: 1.0.0\nsource: registry\n", encoding="utf-8")
return p
def test_an_edited_config_model_is_seen_on_the_next_read(profile_dir):
assert profiles._read_config_model(profile_dir) == ("gpt-4o", "openai")
(profile_dir / "config.yaml").write_text(
"model:\n provider: anthropic\n default: claude-opus-4.6\n", encoding="utf-8")
assert profiles._read_config_model(profile_dir) == ("claude-opus-4.6", "anthropic")
def test_a_missing_file_is_not_cached_and_is_picked_up_when_created(tmp_path):
bare = tmp_path / "profiles" / "bare"
bare.mkdir(parents=True)
assert profiles._read_distribution_meta(bare) == (None, None, None)
assert not any(key[1] == "distribution" for key in profiles._PROFILE_FILE_CACHE)
(bare / "distribution.yaml").write_text("name: late\nversion: 9\nsource: registry\n", encoding="utf-8")
assert profiles._read_distribution_meta(bare) == ("late", 9, "registry")
def test_an_unchanged_file_is_parsed_once_across_repeated_reads(profile_dir, monkeypatch):
"""The saving itself — the roster poll stops re-parsing files that have not moved."""
parsed: list = []
real = profiles._load_yaml_dict
def _counting(path):
parsed.append(Path(path).name)
return real(path)
monkeypatch.setattr(profiles, "_load_yaml_dict", _counting)
for _ in range(3):
profiles.read_profile_meta(profile_dir)
profiles._read_distribution_meta(profile_dir)
assert parsed.count("profile.yaml") == 1
assert parsed.count("distribution.yaml") == 1
def test_the_raw_config_reader_keeps_its_uncached_contract(profile_dir):
"""``read_user_config_raw`` feeds write-back round-trips, so it must NOT be memoised here."""
from hermes_cli.config import read_user_config_raw
config_path = profile_dir / "config.yaml"
assert read_user_config_raw(config_path)["model"]["provider"] == "openai"
config_path.write_text("model:\n provider: xai\n default: grok-4.6\n", encoding="utf-8")
assert read_user_config_raw(config_path)["model"]["provider"] == "xai"