test_cli_shutdown_memory_messages.py
1 """Regression tests for #15165 (CLI sibling site) — CLI exit cleanup must 2 forward the agent's conversation transcript to ``shutdown_memory_provider`` 3 so memory providers' ``on_session_end`` hooks see the real messages. 4 5 Before the fix, ``_run_cleanup`` called 6 ``shutdown_memory_provider(getattr(agent, 'conversation_history', None) or [])``. 7 ``AIAgent`` has no ``conversation_history`` attribute — so the ``or []`` 8 branch always fired and providers got an empty list on CLI exit. This 9 mirrors the gateway bug fixed in the same commit (gateway/run.py uses 10 ``_session_messages``, which IS set on ``AIAgent``). 11 12 The fix reads ``_session_messages`` (same attribute the gateway path uses) 13 with an ``isinstance(..., list)`` guard so MagicMock-based agents in 14 other tests keep their existing no-arg behaviour. 15 """ 16 17 from __future__ import annotations 18 19 from unittest.mock import MagicMock, patch 20 21 22 @patch("hermes_cli.plugins.invoke_hook") 23 def test_cleanup_forwards_session_messages(mock_invoke_hook): 24 """_run_cleanup forwards a populated ``_session_messages`` list.""" 25 import cli as cli_mod 26 27 transcript = [ 28 {"role": "user", "content": "remember my dog is named Biscuit"}, 29 {"role": "assistant", "content": "Got it — Biscuit."}, 30 ] 31 32 agent = MagicMock() 33 agent.session_id = "cli-session-id" 34 agent._session_messages = transcript 35 36 cli_mod._active_agent_ref = agent 37 cli_mod._cleanup_done = False 38 try: 39 cli_mod._run_cleanup() 40 finally: 41 cli_mod._active_agent_ref = None 42 cli_mod._cleanup_done = False 43 44 agent.shutdown_memory_provider.assert_called_once_with(transcript) 45 46 47 @patch("hermes_cli.plugins.invoke_hook") 48 def test_cleanup_empty_list_still_forwarded(mock_invoke_hook): 49 """An agent that initialised but ran no turns has an empty list. 50 Forwarding it (rather than falling through) matches the gateway-side 51 behaviour and is explicit to providers.""" 52 import cli as cli_mod 53 54 agent = MagicMock() 55 agent.session_id = "cli-session-id" 56 agent._session_messages = [] 57 58 cli_mod._active_agent_ref = agent 59 cli_mod._cleanup_done = False 60 try: 61 cli_mod._run_cleanup() 62 finally: 63 cli_mod._active_agent_ref = None 64 cli_mod._cleanup_done = False 65 66 agent.shutdown_memory_provider.assert_called_once_with([]) 67 68 69 @patch("hermes_cli.plugins.invoke_hook") 70 def test_cleanup_non_list_attribute_falls_back_to_no_arg(mock_invoke_hook): 71 """A MagicMock agent auto-synthesises ``_session_messages`` as a 72 nested MagicMock. ``isinstance(mock, list)`` is False, so we fall 73 back to the no-arg path rather than passing a garbage value to 74 providers expecting ``List[Dict]``. This keeps existing CLI test 75 suites that use bare ``MagicMock()`` agents green.""" 76 import cli as cli_mod 77 78 agent = MagicMock() 79 agent.session_id = "cli-session-id" 80 # No explicit _session_messages — MagicMock synthesises one on access. 81 82 cli_mod._active_agent_ref = agent 83 cli_mod._cleanup_done = False 84 try: 85 cli_mod._run_cleanup() 86 finally: 87 cli_mod._active_agent_ref = None 88 cli_mod._cleanup_done = False 89 90 agent.shutdown_memory_provider.assert_called_once_with() 91 92 93 @patch("hermes_cli.plugins.invoke_hook") 94 def test_cleanup_provider_exception_is_swallowed(mock_invoke_hook): 95 """A raising ``shutdown_memory_provider`` must not crash CLI exit.""" 96 import cli as cli_mod 97 98 agent = MagicMock() 99 agent.session_id = "cli-session-id" 100 agent._session_messages = [{"role": "user", "content": "x"}] 101 agent.shutdown_memory_provider.side_effect = RuntimeError("boom") 102 103 cli_mod._active_agent_ref = agent 104 cli_mod._cleanup_done = False 105 try: 106 cli_mod._run_cleanup() # must not raise 107 finally: 108 cli_mod._active_agent_ref = None 109 cli_mod._cleanup_done = False 110 111 agent.shutdown_memory_provider.assert_called_once()