Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions gateway/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,7 @@ def __init__(self, sessions_dir: Path, config: GatewayConfig,
self.config = config
self._entries: Dict[str, SessionEntry] = {}
self._loaded = False
self._loaded_from_disk = False
self._has_active_processes_fn = has_active_processes_fn
self._on_auto_reset = on_auto_reset # callback(old_entry) before auto-reset

Expand All @@ -321,9 +322,11 @@ def _ensure_loaded(self) -> None:
data = json.load(f)
for key, entry_data in data.items():
self._entries[key] = SessionEntry.from_dict(entry_data)
if data:
self._loaded_from_disk = True
except Exception as e:
print(f"[gateway] Warning: Failed to load sessions: {e}")

self._loaded = True

def _save(self) -> None:
Expand Down Expand Up @@ -392,7 +395,7 @@ def _should_reset(self, entry: SessionEntry, source: SessionSource) -> bool:
def has_any_sessions(self) -> bool:
"""Check if any sessions have ever been created (across all platforms)."""
self._ensure_loaded()
return len(self._entries) > 1 # >1 because the current new session is already in _entries
return self._loaded_from_disk or len(self._entries) > 1

def get_or_create_session(
self,
Expand Down
88 changes: 88 additions & 0 deletions tests/gateway/test_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,94 @@ def test_rewrite_with_empty_list(self, store):
assert reloaded == []


class TestHasAnySessions:
"""Regression: has_any_sessions() must detect prior sessions even for
single-platform users where _entries has only one key after a reset."""

@pytest.fixture()
def store(self, tmp_path):
config = GatewayConfig()
with patch("gateway.session.SessionStore._ensure_loaded"):
s = SessionStore(sessions_dir=tmp_path, config=config)
s._db = None
s._loaded = True
return s

def test_fresh_install_no_sessions(self, store):
"""Brand-new install with no entries → False."""
store._entries = {}
store._loaded_from_disk = False
assert store.has_any_sessions() is False

def test_single_platform_first_session(self, store):
"""First session just created, not yet saved to disk → False."""
store._entries = {"agent:main:telegram:dm": MagicMock()}
store._loaded_from_disk = False
assert store.has_any_sessions() is False

def test_single_platform_after_reset(self, store):
"""Single-platform user after session reset — sessions.json was loaded
from disk so we know they have used the bot before → True."""
store._entries = {"agent:main:telegram:dm": MagicMock()}
store._loaded_from_disk = True
assert store.has_any_sessions() is True

def test_multi_platform_always_detected(self, store):
"""Multiple session keys → True regardless of disk state."""
store._entries = {
"agent:main:telegram:dm": MagicMock(),
"agent:main:discord:dm": MagicMock(),
}
store._loaded_from_disk = False
assert store.has_any_sessions() is True

def test_loaded_from_disk_flag_set_on_load(self, tmp_path):
"""_ensure_loaded sets _loaded_from_disk when sessions.json exists."""
sessions_file = tmp_path / "sessions.json"
sessions_file.write_text(json.dumps({
"agent:main:telegram:dm": {
"session_key": "agent:main:telegram:dm",
"session_id": "20260304_120000_abc",
"created_at": "2026-03-04T12:00:00",
"updated_at": "2026-03-04T12:05:00",
}
}))
config = GatewayConfig()
store = SessionStore.__new__(SessionStore)
store.sessions_dir = tmp_path
store.config = config
store._entries = {}
store._loaded = False
store._loaded_from_disk = False
store._has_active_processes_fn = None
store._on_auto_reset = None
store._db = None

store._ensure_loaded()

assert store._loaded_from_disk is True
assert store.has_any_sessions() is True

def test_loaded_from_disk_flag_false_on_empty_file(self, tmp_path):
"""Empty sessions.json → _loaded_from_disk stays False."""
sessions_file = tmp_path / "sessions.json"
sessions_file.write_text(json.dumps({}))
config = GatewayConfig()
store = SessionStore.__new__(SessionStore)
store.sessions_dir = tmp_path
store.config = config
store._entries = {}
store._loaded = False
store._loaded_from_disk = False
store._has_active_processes_fn = None
store._on_auto_reset = None
store._db = None

store._ensure_loaded()

assert store._loaded_from_disk is False


class TestSessionStoreEntriesAttribute:
"""Regression: /reset must access _entries, not _sessions."""

Expand Down