Files
hermes-agent/tests/gateway/test_webhook_dynamic_routes.py

88 lines
3.1 KiB
Python
Raw Normal View History

"""Tests for webhook adapter dynamic route loading."""
import json
import os
import pytest
from pathlib import Path
from gateway.config import PlatformConfig
from gateway.platforms.webhook import WebhookAdapter, _DYNAMIC_ROUTES_FILENAME
def _make_adapter(routes=None, extra=None):
_extra = extra or {}
if routes:
_extra["routes"] = routes
_extra.setdefault("secret", "test-global-secret")
config = PlatformConfig(enabled=True, extra=_extra)
return WebhookAdapter(config)
@pytest.fixture(autouse=True)
def _isolate(tmp_path, monkeypatch):
monkeypatch.setenv("HERMES_HOME", str(tmp_path))
class TestDynamicRouteLoading:
def test_no_dynamic_file(self):
adapter = _make_adapter(routes={"static": {"secret": "s"}})
adapter._reload_dynamic_routes()
assert "static" in adapter._routes
assert len(adapter._dynamic_routes) == 0
def test_loads_dynamic_routes(self, tmp_path):
subs = {"my-hook": {"secret": "dynamic-secret", "prompt": "test", "events": []}}
(tmp_path / _DYNAMIC_ROUTES_FILENAME).write_text(json.dumps(subs))
adapter = _make_adapter(routes={"static": {"secret": "s"}})
adapter._reload_dynamic_routes()
assert "my-hook" in adapter._routes
assert "static" in adapter._routes
def test_static_takes_precedence(self, tmp_path):
(tmp_path / _DYNAMIC_ROUTES_FILENAME).write_text(
json.dumps({"conflict": {"secret": "dynamic", "prompt": "dyn"}})
)
adapter = _make_adapter(routes={"conflict": {"secret": "static", "prompt": "stat"}})
adapter._reload_dynamic_routes()
assert adapter._routes["conflict"]["secret"] == "static"
def test_mtime_gated(self, tmp_path):
import time
path = tmp_path / _DYNAMIC_ROUTES_FILENAME
path.write_text(json.dumps({"v1": {"secret": "s"}}))
adapter = _make_adapter()
adapter._reload_dynamic_routes()
assert "v1" in adapter._dynamic_routes
# Same mtime — no reload
adapter._dynamic_routes["injected"] = True
adapter._reload_dynamic_routes()
assert "injected" in adapter._dynamic_routes
# New write — reloads
time.sleep(0.05)
path.write_text(json.dumps({"v2": {"secret": "s"}}))
adapter._reload_dynamic_routes()
assert "v2" in adapter._dynamic_routes
assert "v1" not in adapter._dynamic_routes
def test_file_removal_clears(self, tmp_path):
path = tmp_path / _DYNAMIC_ROUTES_FILENAME
path.write_text(json.dumps({"temp": {"secret": "s"}}))
adapter = _make_adapter()
adapter._reload_dynamic_routes()
assert "temp" in adapter._dynamic_routes
path.unlink()
adapter._reload_dynamic_routes()
assert len(adapter._dynamic_routes) == 0
def test_corrupted_file(self, tmp_path):
(tmp_path / _DYNAMIC_ROUTES_FILENAME).write_text("not json")
adapter = _make_adapter(routes={"static": {"secret": "s"}})
adapter._reload_dynamic_routes()
assert "static" in adapter._routes
assert len(adapter._dynamic_routes) == 0