hermes-agent/tests/gateway/test_status_phrases.py

159 lines
5 KiB
Python

import random
from gateway.status_phrases import (
classify_status_context,
choose_status_phrase,
resolve_status_phrase_catalog,
)
def test_long_running_context_uses_status_bucket():
assert classify_status_context("status") == "status"
assert classify_status_context("heartbeat") == "status"
assert classify_status_context("long_running") == "status"
def test_non_status_context_falls_back_to_generic_bucket():
assert classify_status_context("tool", tool_name="terminal") == "generic"
assert classify_status_context("thinking") == "generic"
assert classify_status_context("interim_assistant") == "generic"
def test_status_phrase_does_not_leak_raw_preview_or_args():
msg = choose_status_phrase(
"status",
preview="actual private scratch text should not be sent",
args={"secret": "SECRET-123"},
rng=random.Random(4),
)
assert "actual private scratch" not in msg
assert "SECRET-123" not in msg
assert msg
def test_status_phrase_avoids_recent_repetition():
recent: list[str] = []
first = choose_status_phrase("status", rng=random.Random(2), recent=recent)
second = choose_status_phrase("status", rng=random.Random(2), recent=recent)
assert first != second
assert recent[-2:] == [first, second]
def test_builtin_catalog_is_loaded_from_external_asset_and_is_status_only():
catalog = resolve_status_phrase_catalog({}, "whatsapp")
assert set(catalog) == {"status", "generic"}
assert len(catalog["status"]) >= 25
assert len(catalog["generic"]) >= 10
def test_relative_status_phrase_path_loads_from_hermes_home(tmp_path, monkeypatch):
monkeypatch.setenv("HERMES_HOME", str(tmp_path))
phrase_file = tmp_path / "phrases.yaml"
phrase_file.write_text("mode: replace\nstatus:\n - relative safe status text\n", encoding="utf-8")
catalog = resolve_status_phrase_catalog(
{"display": {"status_phrases": {"path": "phrases.yaml"}}},
"whatsapp",
)
assert catalog["status"] == ["relative safe status text"]
def test_status_phrase_path_can_load_relative_directory(tmp_path, monkeypatch):
monkeypatch.setenv("HERMES_HOME", str(tmp_path))
phrase_dir = tmp_path / "phrase-catalog"
phrase_dir.mkdir()
(phrase_dir / "01-status.yaml").write_text("status:\n - relative dir status text\n", encoding="utf-8")
catalog = resolve_status_phrase_catalog(
{"display": {"status_phrases": {"path": "phrase-catalog"}}},
"whatsapp",
)
assert "relative dir status text" in catalog["status"]
def test_absolute_or_parent_phrase_paths_are_ignored(tmp_path, monkeypatch):
monkeypatch.setenv("HERMES_HOME", str(tmp_path))
outside = tmp_path.parent / "outside-phrases.yaml"
outside.write_text("mode: replace\nstatus:\n - should not load\n", encoding="utf-8")
catalog = resolve_status_phrase_catalog(
{"display": {"status_phrases": {"path": str(outside)}}},
"whatsapp",
)
escaped = resolve_status_phrase_catalog(
{"display": {"status_phrases": {"path": "../outside-phrases.yaml"}}},
"whatsapp",
)
assert catalog["status"] != ["should not load"]
assert escaped["status"] != ["should not load"]
def test_conventional_relative_status_phrase_file_is_loaded(tmp_path, monkeypatch):
monkeypatch.setenv("HERMES_HOME", str(tmp_path))
(tmp_path / "status_phrases.yaml").write_text(
"mode: replace\nstatus:\n - conventional status text\n",
encoding="utf-8",
)
catalog = resolve_status_phrase_catalog({}, "whatsapp")
assert catalog["status"] == ["conventional status text"]
def test_global_custom_status_phrase_catalog_appends_to_builtin():
catalog = resolve_status_phrase_catalog(
{
"display": {
"status_phrases": {
"status": ["custom long-running placeholder"],
}
}
},
"whatsapp",
)
assert "custom long-running placeholder" in catalog["status"]
assert len(catalog["status"]) > 1
def test_platform_custom_status_phrase_catalog_can_replace_surface():
catalog = resolve_status_phrase_catalog(
{
"display": {
"platforms": {
"whatsapp": {
"status_phrases": {
"mode": "replace",
"status": ["custom status placeholder"],
}
}
}
}
},
"whatsapp",
)
assert catalog["status"] == ["custom status placeholder"]
assert len(catalog["generic"]) > 1
def test_choose_status_phrase_uses_custom_catalog_without_leaking_args():
catalog = resolve_status_phrase_catalog(
{"display": {"status_phrases": {"mode": "replace", "status": ["custom safe status text"]}}},
"whatsapp",
)
msg = choose_status_phrase(
"status",
args={"query": "SECRET SEARCH"},
catalog=catalog,
)
assert msg == "custom safe status text"
assert "SECRET" not in msg