fleet-memory/hindsight-integrations/hermes/tests/test_config.py
Nicolò Boschi 0ff36548e0
feat(hermes): file-based config + updated docs (#686)
* feat(hermes): file-based config + updated docs

Replace the old dataclass/configure() singleton with a plain dict
config loaded from ~/.hindsight/hermes.json — same field names and
conventions as the openclaw and claude-code integrations.

Loading order: defaults → config file → env var overrides.

- config.py: rewritten with load_config() returning a plain dict,
  DEFAULTS matching openclaw/claude-code fields, ENV_OVERRIDES with
  typed casting
- tools.py: register() uses load_config() instead of raw env vars
- __init__.py: clean exports (removed configure/get_config/reset_config)
- README.md: full rewrite with config file examples, tables by category
- docs/hermes.md: full rewrite with quick start, architecture, all
  config tables, gateway section, troubleshooting
- tests: updated for new config pattern, 46 tests pass

* ci: add test job for hermes integration

* chore: regenerate docs skill for hermes integration
2026-03-25 16:11:38 +01:00

98 lines
4.2 KiB
Python

"""Tests for hindsight_hermes.config module."""
import json
from hindsight_hermes.config import DEFAULTS, load_config
class TestLoadConfig:
def test_defaults_when_no_file(self, tmp_path, monkeypatch):
monkeypatch.delenv("HINDSIGHT_API_URL", raising=False)
monkeypatch.delenv("HINDSIGHT_API_KEY", raising=False)
monkeypatch.delenv("HINDSIGHT_BANK_ID", raising=False)
cfg = load_config(config_path=tmp_path / "nope.json")
assert cfg["hindsightApiUrl"] is None
assert cfg["bankId"] is None
assert cfg["recallBudget"] == "mid"
assert cfg["autoRecall"] is True
assert cfg["autoRetain"] is True
def test_reads_from_file(self, tmp_path, monkeypatch):
monkeypatch.delenv("HINDSIGHT_API_URL", raising=False)
monkeypatch.delenv("HINDSIGHT_API_KEY", raising=False)
monkeypatch.delenv("HINDSIGHT_BANK_ID", raising=False)
monkeypatch.delenv("HINDSIGHT_AUTO_RETAIN", raising=False)
f = tmp_path / "hermes.json"
f.write_text(json.dumps({
"hindsightApiUrl": "http://localhost:9077",
"hindsightApiToken": "file-token",
"bankId": "my-bank",
"recallBudget": "high",
"autoRetain": False,
"recallMaxTokens": 2048,
}))
cfg = load_config(config_path=f)
assert cfg["hindsightApiUrl"] == "http://localhost:9077"
assert cfg["hindsightApiToken"] == "file-token"
assert cfg["bankId"] == "my-bank"
assert cfg["recallBudget"] == "high"
assert cfg["autoRetain"] is False
assert cfg["recallMaxTokens"] == 2048
def test_env_overrides_file(self, tmp_path, monkeypatch):
f = tmp_path / "hermes.json"
f.write_text(json.dumps({
"hindsightApiUrl": "http://from-file:9077",
"bankId": "file-bank",
"recallBudget": "low",
}))
monkeypatch.setenv("HINDSIGHT_API_URL", "http://from-env:9077")
monkeypatch.setenv("HINDSIGHT_BANK_ID", "env-bank")
monkeypatch.delenv("HINDSIGHT_RECALL_BUDGET", raising=False)
cfg = load_config(config_path=f)
assert cfg["hindsightApiUrl"] == "http://from-env:9077"
assert cfg["bankId"] == "env-bank"
assert cfg["recallBudget"] == "low" # from file (env not set)
def test_api_key_env_maps_to_token(self, tmp_path, monkeypatch):
"""HINDSIGHT_API_KEY is an alias for hindsightApiToken."""
monkeypatch.setenv("HINDSIGHT_API_KEY", "my-key")
cfg = load_config(config_path=tmp_path / "nope.json")
assert cfg["hindsightApiToken"] == "my-key"
def test_bool_env_casting(self, tmp_path, monkeypatch):
monkeypatch.setenv("HINDSIGHT_AUTO_RETAIN", "false")
monkeypatch.setenv("HINDSIGHT_AUTO_RECALL", "1")
cfg = load_config(config_path=tmp_path / "nope.json")
assert cfg["autoRetain"] is False
assert cfg["autoRecall"] is True
def test_int_env_casting(self, tmp_path, monkeypatch):
monkeypatch.setenv("HINDSIGHT_RECALL_MAX_TOKENS", "2048")
cfg = load_config(config_path=tmp_path / "nope.json")
assert cfg["recallMaxTokens"] == 2048
def test_malformed_file_returns_defaults(self, tmp_path, monkeypatch):
monkeypatch.delenv("HINDSIGHT_API_URL", raising=False)
monkeypatch.delenv("HINDSIGHT_BANK_ID", raising=False)
f = tmp_path / "hermes.json"
f.write_text("not json {{{")
cfg = load_config(config_path=f)
assert cfg["recallBudget"] == "mid"
def test_null_values_in_file_ignored(self, tmp_path, monkeypatch):
"""null values in JSON should not override defaults."""
monkeypatch.delenv("HINDSIGHT_API_URL", raising=False)
monkeypatch.delenv("HINDSIGHT_BANK_ID", raising=False)
f = tmp_path / "hermes.json"
f.write_text(json.dumps({"bankId": None, "recallBudget": "high"}))
cfg = load_config(config_path=f)
assert cfg["bankId"] is None # stays default (None)
assert cfg["recallBudget"] == "high"
def test_all_defaults_present(self):
"""Every key in DEFAULTS should exist in a freshly loaded config."""
# Use a path that doesn't exist and clean env
cfg = DEFAULTS.copy()
for key in DEFAULTS:
assert key in cfg