diff --git a/gateway/session.py b/gateway/session.py index b59196b81..77aff0592 100644 --- a/gateway/session.py +++ b/gateway/session.py @@ -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, diff --git a/tests/gateway/test_session.py b/tests/gateway/test_session.py index 97e610da1..152346458 100644 --- a/tests/gateway/test_session.py +++ b/tests/gateway/test_session.py @@ -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