/ tests / cli / test_cli_shutdown_memory_messages.py
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()