refactor: unify gateway session title sync flow

This commit is contained in:
aaron 2026-04-23 09:09:33 +00:00
parent 59d45346ba
commit 3438de3623
8 changed files with 421 additions and 61 deletions

View file

@ -1,9 +1,10 @@
"""Tests for /title gateway slash command.
"""Tests for gateway session-title flows.
Tests the _handle_title_command handler (set/show session titles)
across all gateway messenger platforms.
Tests the /title handler plus native gateway session-title propagation
for manual and auto-generated titles.
"""
import asyncio
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
@ -33,6 +34,7 @@ def _make_runner(session_db=None):
runner.adapters = {}
runner._voice_mode = {}
runner._session_db = session_db
runner._background_tasks = set()
# Mock session_store that returns a session entry with a known session_id
mock_session_entry = MagicMock()
@ -72,7 +74,7 @@ class TestHandleTitleCommand:
@pytest.mark.asyncio
async def test_set_title_renames_telegram_topic_when_in_thread(self, tmp_path):
"""Telegram /title should also rename the active topic thread when possible."""
"""Telegram /title should schedule thread-title sync via the native callback path."""
from hermes_state import SessionDB
db = SessionDB(db_path=tmp_path / "state.db")
db.create_session("test_session_123", "telegram")
@ -85,14 +87,18 @@ class TestHandleTitleCommand:
event = _make_event(text="/title Indicative Topic", thread_id="470094")
result = await runner._handle_title_command(event)
adapter.register_post_delivery_callback.assert_called_once()
callback = adapter.register_post_delivery_callback.call_args.args[1]
callback()
await asyncio.sleep(0)
adapter.update_thread_title.assert_awaited_once_with("67890", "470094", "Indicative Topic")
assert "Telegram topic renamed too" in result
assert "Telegram topic renamed too" not in result
assert db.get_session_title("test_session_123") == "Indicative Topic"
db.close()
@pytest.mark.asyncio
async def test_set_title_renames_telegram_general_topic_when_thread_is_one(self, tmp_path):
"""Telegram General topic thread_id=1 should still trigger a rename attempt."""
"""Telegram General topic thread_id=1 should also use the deferred sync path."""
from hermes_state import SessionDB
db = SessionDB(db_path=tmp_path / "state.db")
db.create_session("test_session_123", "telegram")
@ -105,8 +111,12 @@ class TestHandleTitleCommand:
event = _make_event(text="/title Lobby", thread_id="1")
result = await runner._handle_title_command(event)
adapter.register_post_delivery_callback.assert_called_once()
callback = adapter.register_post_delivery_callback.call_args.args[1]
callback()
await asyncio.sleep(0)
adapter.update_thread_title.assert_awaited_once_with("67890", "1", "Lobby")
assert "Telegram topic renamed too" in result
assert "Telegram topic renamed too" not in result
assert db.get_session_title("test_session_123") == "Lobby"
db.close()
@ -125,11 +135,91 @@ class TestHandleTitleCommand:
event = _make_event(text="/title Plain Chat Title")
result = await runner._handle_title_command(event)
adapter.update_thread_title.assert_not_called()
assert "Telegram topic renamed too" not in result
adapter.register_post_delivery_callback.assert_not_called()
assert db.get_session_title("test_session_123") == "Plain Chat Title"
db.close()
class TestGatewayAutoTitleSync:
@pytest.mark.asyncio
async def test_auto_title_flow_uses_same_session_title_path(self, tmp_path):
"""Gateway auto-title should persist title and sync Telegram thread title."""
from hermes_state import SessionDB
db = SessionDB(db_path=tmp_path / "state.db")
db.create_session("test_session_123", "telegram")
runner = _make_runner(session_db=db)
adapter = MagicMock()
adapter.update_thread_title = AsyncMock(return_value=True)
runner.adapters[Platform.TELEGRAM] = adapter
source = _make_event(thread_id="470094").source
with patch("agent.title_generator.generate_title_if_missing", return_value="Auto Topic"):
await runner._auto_title_gateway_session(
session_id="test_session_123",
session_key="telegram:12345:67890",
source=source,
user_message="hello",
assistant_response="hi there",
)
assert db.get_session_title("test_session_123") == "Auto Topic"
adapter.update_thread_title.assert_awaited_once_with("67890", "470094", "Auto Topic")
db.close()
@pytest.mark.asyncio
async def test_auto_title_skips_platform_sync_when_no_thread(self, tmp_path):
"""Gateway auto-title without a thread should remain DB-only."""
from hermes_state import SessionDB
db = SessionDB(db_path=tmp_path / "state.db")
db.create_session("test_session_123", "telegram")
runner = _make_runner(session_db=db)
adapter = MagicMock()
adapter.update_thread_title = AsyncMock(return_value=True)
runner.adapters[Platform.TELEGRAM] = adapter
source = _make_event().source
with patch("agent.title_generator.generate_title_if_missing", return_value="Auto Session"):
await runner._auto_title_gateway_session(
session_id="test_session_123",
session_key="telegram:12345:67890",
source=source,
user_message="hello",
assistant_response="hi there",
)
assert db.get_session_title("test_session_123") == "Auto Session"
adapter.update_thread_title.assert_not_called()
db.close()
@pytest.mark.asyncio
async def test_auto_title_skips_overwriting_existing_manual_title(self, tmp_path):
"""Gateway auto-title should not clobber a title set while generation was in flight."""
from hermes_state import SessionDB
db = SessionDB(db_path=tmp_path / "state.db")
db.create_session("test_session_123", "telegram")
db.set_session_title("test_session_123", "Manual Title")
runner = _make_runner(session_db=db)
adapter = MagicMock()
adapter.update_thread_title = AsyncMock(return_value=True)
runner.adapters[Platform.TELEGRAM] = adapter
source = _make_event(thread_id="470094").source
with patch("agent.title_generator.generate_title_if_missing", return_value="Auto Topic"):
await runner._auto_title_gateway_session(
session_id="test_session_123",
session_key="telegram:12345:67890",
source=source,
user_message="hello",
assistant_response="hi there",
)
assert db.get_session_title("test_session_123") == "Manual Title"
adapter.update_thread_title.assert_not_called()
db.close()
@pytest.mark.asyncio
async def test_show_title_when_set(self, tmp_path):
"""Showing title when one is set returns the title."""