mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-25 00:51:20 +00:00
test(gateway): on_session_finalize fires on idle-expiry + AUTHOR_MAP
Regression test for #14981. Verifies that _session_expiry_watcher fires on_session_finalize for each session swept out of the store, matching the contract documented for /new, /reset, CLI shutdown, and gateway stop. Verified the test fails cleanly on pre-fix code (hook call list missing sess-expired) and passes with the fix applied.
This commit is contained in:
parent
260ae62134
commit
25465fd8d7
2 changed files with 78 additions and 0 deletions
|
|
@ -214,6 +214,7 @@ AUTHOR_MAP = {
|
|||
"ntconguit@gmail.com": "0xharryriddle",
|
||||
"agent@wildcat.local": "ericnicolaides",
|
||||
"georgex8001@gmail.com": "georgex8001",
|
||||
"stefan@dimagents.ai": "dimitrovi",
|
||||
"hermes@noushq.ai": "benbarclay",
|
||||
"chinmingcock@gmail.com": "ChimingLiu",
|
||||
"openclaw@sparklab.ai": "openclaw",
|
||||
|
|
|
|||
|
|
@ -166,3 +166,80 @@ async def test_hook_error_does_not_break_reset(mock_invoke_hook):
|
|||
|
||||
# Should still return a success message despite hook errors
|
||||
assert "Session reset" in result or "New session" in result
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@patch("hermes_cli.plugins.invoke_hook")
|
||||
async def test_idle_expiry_fires_finalize_hook(mock_invoke_hook):
|
||||
"""Regression test for #14981.
|
||||
|
||||
When ``_session_expiry_watcher`` sweeps a session that has aged past
|
||||
its reset policy (idle timeout, scheduled reset), it must fire
|
||||
``on_session_finalize`` so plugin providers get the same final-pass
|
||||
extraction opportunity they'd get from /new or CLI shutdown. Before
|
||||
the fix, the expiry path flushed memories and evicted the agent but
|
||||
silently skipped the hook.
|
||||
"""
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from gateway.run import GatewayRunner
|
||||
|
||||
runner = object.__new__(GatewayRunner)
|
||||
runner._running = True
|
||||
runner._running_agents = {}
|
||||
runner._agent_cache = {}
|
||||
runner._agent_cache_lock = None
|
||||
runner._last_session_store_prune_ts = 0.0
|
||||
|
||||
session_key = "agent:main:telegram:dm:42"
|
||||
expired_entry = SessionEntry(
|
||||
session_key=session_key,
|
||||
session_id="sess-expired",
|
||||
created_at=datetime.now() - timedelta(hours=2),
|
||||
updated_at=datetime.now() - timedelta(hours=2),
|
||||
platform=Platform.TELEGRAM,
|
||||
chat_type="dm",
|
||||
)
|
||||
expired_entry.memory_flushed = False
|
||||
|
||||
runner.session_store = MagicMock()
|
||||
runner.session_store._ensure_loaded = MagicMock()
|
||||
runner.session_store._entries = {session_key: expired_entry}
|
||||
runner.session_store._is_session_expired = MagicMock(return_value=True)
|
||||
runner.session_store._lock = MagicMock()
|
||||
runner.session_store._lock.__enter__ = MagicMock(return_value=None)
|
||||
runner.session_store._lock.__exit__ = MagicMock(return_value=None)
|
||||
runner.session_store._save = MagicMock()
|
||||
|
||||
runner._async_flush_memories = AsyncMock()
|
||||
runner._evict_cached_agent = MagicMock()
|
||||
runner._cleanup_agent_resources = MagicMock()
|
||||
runner._sweep_idle_cached_agents = MagicMock(return_value=0)
|
||||
|
||||
# The watcher starts with `await asyncio.sleep(60)` and loops while
|
||||
# `self._running`. Patch sleep so the 60s initial delay is instant, then
|
||||
# flip `_running` false inside the flush call so the loop exits cleanly
|
||||
# after one pass.
|
||||
_orig_sleep = __import__("asyncio").sleep
|
||||
|
||||
async def _fast_sleep(_):
|
||||
await _orig_sleep(0)
|
||||
|
||||
async def _flush_and_stop(session_id, key):
|
||||
runner._running = False # terminate the loop after this iteration
|
||||
|
||||
runner._async_flush_memories = AsyncMock(side_effect=_flush_and_stop)
|
||||
|
||||
with patch("gateway.run.asyncio.sleep", side_effect=_fast_sleep):
|
||||
await runner._session_expiry_watcher(interval=0)
|
||||
|
||||
# Look for the finalize call targeting the expired session.
|
||||
finalize_calls = [
|
||||
c for c in mock_invoke_hook.call_args_list
|
||||
if c[0] and c[0][0] == "on_session_finalize"
|
||||
]
|
||||
session_ids = {c[1].get("session_id") for c in finalize_calls}
|
||||
assert "sess-expired" in session_ids, (
|
||||
f"on_session_finalize was not fired during idle expiry; "
|
||||
f"got session_ids={session_ids} (regression of #14981)"
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue