mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-31 19:16:29 +00:00
Main's 243c9182b1/a16fd675df/7142dc4580 added load_config_readonly sibling stubs across 38 files; our pruned versions of 11 of those files kept only the load_config stubs. Re-applied the pairing at every surviving site (26 patch()/setattr sites) — same return_value/ side_effect as the adjacent load_config stub. 494 tests green across the 11 files.
146 lines
5.2 KiB
Python
146 lines
5.2 KiB
Python
"""Tests for OpenRouter response caching header injection."""
|
|
|
|
from types import SimpleNamespace
|
|
from unittest.mock import patch
|
|
|
|
import pytest
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# build_or_headers
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestBuildOrHeaders:
|
|
"""Test the build_or_headers() helper in agent/auxiliary_client.py."""
|
|
|
|
|
|
|
|
|
|
|
|
def test_ttl_default(self):
|
|
"""Default TTL (300) is included when cache is enabled."""
|
|
from agent.auxiliary_client import build_or_headers
|
|
|
|
headers = build_or_headers(or_config={"response_cache": True, "response_cache_ttl": 300})
|
|
assert headers["X-OpenRouter-Cache-TTL"] == "300"
|
|
|
|
|
|
|
|
|
|
|
|
def test_ttl_negative(self):
|
|
"""Negative TTL is ignored."""
|
|
from agent.auxiliary_client import build_or_headers
|
|
|
|
headers = build_or_headers(or_config={"response_cache": True, "response_cache_ttl": -5})
|
|
assert "X-OpenRouter-Cache-TTL" not in headers
|
|
|
|
|
|
|
|
def test_returns_fresh_dict(self):
|
|
"""Each call returns a new dict so mutations don't leak."""
|
|
from agent.auxiliary_client import build_or_headers
|
|
|
|
cfg = {"response_cache": True}
|
|
h1 = build_or_headers(or_config=cfg)
|
|
h2 = build_or_headers(or_config=cfg)
|
|
assert h1 is not h2
|
|
assert h1 == h2
|
|
|
|
|
|
def test_none_config_load_config_fails_gracefully(self):
|
|
"""When load_config() fails, build_or_headers still returns base headers."""
|
|
from agent.auxiliary_client import build_or_headers
|
|
|
|
with patch("hermes_cli.config.load_config", side_effect=RuntimeError("boom")), patch("hermes_cli.config.load_config_readonly", side_effect=RuntimeError("boom")):
|
|
headers = build_or_headers(or_config=None)
|
|
# Should have base attribution but no cache headers
|
|
assert "HTTP-Referer" in headers
|
|
assert "X-OpenRouter-Cache" not in headers
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Environment variable overrides
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestEnvVarOverrides:
|
|
"""Test env var precedence over config.yaml for response caching."""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize("ttl", ["0", "86401", "abc", "-1", "12.5"])
|
|
def test_invalid_env_ttl_dropped(self, monkeypatch, ttl):
|
|
"""Invalid TTL env values are ignored; cache still enabled without TTL."""
|
|
from agent.auxiliary_client import build_or_headers
|
|
|
|
monkeypatch.setenv("HERMES_OPENROUTER_CACHE", "1")
|
|
monkeypatch.setenv("HERMES_OPENROUTER_CACHE_TTL", ttl)
|
|
headers = build_or_headers(or_config={})
|
|
assert headers["X-OpenRouter-Cache"] == "true"
|
|
assert "X-OpenRouter-Cache-TTL" not in headers
|
|
|
|
@pytest.mark.parametrize("ttl", ["1", "300", "86400"])
|
|
def test_valid_env_ttl_boundaries(self, monkeypatch, ttl):
|
|
"""Boundary TTL values (1, 300, 86400) are accepted."""
|
|
from agent.auxiliary_client import build_or_headers
|
|
|
|
monkeypatch.setenv("HERMES_OPENROUTER_CACHE", "yes")
|
|
monkeypatch.setenv("HERMES_OPENROUTER_CACHE_TTL", ttl)
|
|
assert build_or_headers(or_config={})["X-OpenRouter-Cache-TTL"] == ttl
|
|
|
|
def test_no_env_vars_falls_through_to_config(self, monkeypatch):
|
|
"""Without env vars, config.yaml controls behavior."""
|
|
from agent.auxiliary_client import build_or_headers
|
|
|
|
monkeypatch.delenv("HERMES_OPENROUTER_CACHE", raising=False)
|
|
monkeypatch.delenv("HERMES_OPENROUTER_CACHE_TTL", raising=False)
|
|
headers = build_or_headers(or_config={"response_cache": True, "response_cache_ttl": 600})
|
|
assert headers["X-OpenRouter-Cache"] == "true"
|
|
assert headers["X-OpenRouter-Cache-TTL"] == "600"
|
|
|
|
class TestDefaultConfig:
|
|
"""Verify the openrouter config section is in DEFAULT_CONFIG."""
|
|
|
|
def test_openrouter_section_exists(self):
|
|
from hermes_cli.config import DEFAULT_CONFIG
|
|
|
|
assert "openrouter" in DEFAULT_CONFIG
|
|
or_cfg = DEFAULT_CONFIG["openrouter"]
|
|
assert or_cfg["response_cache"] is True
|
|
assert or_cfg["response_cache_ttl"] == 300
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# _check_openrouter_cache_status
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestCheckOpenrouterCacheStatus:
|
|
"""Test the _check_openrouter_cache_status method on AIAgent."""
|
|
|
|
def _make_agent(self):
|
|
"""Create a minimal AIAgent-like object with just the method under test."""
|
|
from run_agent import AIAgent
|
|
|
|
# Use object.__new__ to skip __init__, then set the attributes we need
|
|
agent = object.__new__(AIAgent)
|
|
agent._or_cache_hits = 0
|
|
return agent
|
|
|
|
|
|
|
|
def test_no_header_is_noop(self):
|
|
agent = self._make_agent()
|
|
resp = SimpleNamespace(headers={})
|
|
agent._check_openrouter_cache_status(resp)
|
|
assert getattr(agent, "_or_cache_hits", 0) == 0
|
|
|
|
|
|
|
|
def test_case_insensitive(self):
|
|
agent = self._make_agent()
|
|
resp = SimpleNamespace(headers={"x-openrouter-cache-status": "hit"})
|
|
agent._check_openrouter_cache_status(resp)
|
|
assert agent._or_cache_hits == 1
|