fix(session): use database session count for has_any_sessions (#351)
The previous implementation used `len(self._entries) > 1` to check if any sessions had ever been created. This failed for single-platform users because when sessions reset (via /reset, auto-reset, or gateway restart), the entry for the same session_key is replaced in _entries, not added. So len(_entries) stays at 1 for users who only use one platform. Fix: Query the SQLite database's session count instead. The database preserves historical session records (marked as ended), so session_count() correctly returns > 1 for returning users even after resets. This prevents the agent from reintroducing itself to returning users after every session reset. Fixes #351
This commit is contained in:
@@ -391,8 +391,16 @@ class SessionStore:
|
||||
|
||||
def has_any_sessions(self) -> bool:
|
||||
"""Check if any sessions have ever been created (across all platforms)."""
|
||||
if self._db:
|
||||
# Database tracks all sessions ever created (including ended ones).
|
||||
# This correctly handles session resets where the in-memory _entries
|
||||
# dict replaces the entry for the same session_key, but the DB
|
||||
# preserves the historical record.
|
||||
# > 1 because the current session is already created.
|
||||
return self._db.session_count() > 1
|
||||
# Fallback for when DB is not available (e.g., tests)
|
||||
self._ensure_loaded()
|
||||
return len(self._entries) > 1 # >1 because the current new session is already in _entries
|
||||
return len(self._entries) > 1
|
||||
|
||||
def get_or_create_session(
|
||||
self,
|
||||
|
||||
@@ -324,3 +324,53 @@ class TestSessionStoreEntriesAttribute:
|
||||
store._loaded = True
|
||||
assert hasattr(store, "_entries")
|
||||
assert not hasattr(store, "_sessions")
|
||||
|
||||
|
||||
class TestHasAnySessions:
|
||||
"""Tests for has_any_sessions() fix (issue #351)."""
|
||||
|
||||
@pytest.fixture
|
||||
def store_with_mock_db(self, tmp_path):
|
||||
"""SessionStore with a mocked database."""
|
||||
config = GatewayConfig()
|
||||
with patch("gateway.session.SessionStore._ensure_loaded"):
|
||||
s = SessionStore(sessions_dir=tmp_path, config=config)
|
||||
s._loaded = True
|
||||
s._entries = {}
|
||||
s._db = MagicMock()
|
||||
return s
|
||||
|
||||
def test_uses_database_count_when_available(self, store_with_mock_db):
|
||||
"""has_any_sessions should use database session_count, not len(_entries)."""
|
||||
store = store_with_mock_db
|
||||
# Simulate single-platform user with only 1 entry in memory
|
||||
store._entries = {"telegram:12345": MagicMock()}
|
||||
# But database has 3 sessions (current + 2 previous resets)
|
||||
store._db.session_count.return_value = 3
|
||||
|
||||
assert store.has_any_sessions() is True
|
||||
store._db.session_count.assert_called_once()
|
||||
|
||||
def test_first_session_ever_returns_false(self, store_with_mock_db):
|
||||
"""First session ever should return False (only current session in DB)."""
|
||||
store = store_with_mock_db
|
||||
store._entries = {"telegram:12345": MagicMock()}
|
||||
# Database has exactly 1 session (the current one just created)
|
||||
store._db.session_count.return_value = 1
|
||||
|
||||
assert store.has_any_sessions() is False
|
||||
|
||||
def test_fallback_without_database(self, tmp_path):
|
||||
"""Should fall back to len(_entries) when DB is not available."""
|
||||
config = GatewayConfig()
|
||||
with patch("gateway.session.SessionStore._ensure_loaded"):
|
||||
store = SessionStore(sessions_dir=tmp_path, config=config)
|
||||
store._loaded = True
|
||||
store._db = None
|
||||
store._entries = {"key1": MagicMock(), "key2": MagicMock()}
|
||||
|
||||
# > 1 entries means has sessions
|
||||
assert store.has_any_sessions() is True
|
||||
|
||||
store._entries = {"key1": MagicMock()}
|
||||
assert store.has_any_sessions() is False
|
||||
|
||||
Reference in New Issue
Block a user