fix(sessions): new sessions appear immediately in sidebar (#806)
Closes #789 Bug A. 60-second exemption in all_sessions() filter.
This commit is contained in:
194
tests/test_issue789.py
Normal file
194
tests/test_issue789.py
Normal file
@@ -0,0 +1,194 @@
|
||||
"""
|
||||
Regression tests for GitHub issue #789.
|
||||
|
||||
Bug: every brand-new session immediately disappeared from the sidebar because
|
||||
all_sessions() filtered out sessions where title == 'Untitled' AND
|
||||
message_count == 0. Since every new session starts with those values, it was
|
||||
filtered out of /api/sessions on the next refresh.
|
||||
|
||||
Fix: exempt sessions younger than 60 seconds from that filter. Sessions older
|
||||
than 60 seconds that are still Untitled with 0 messages are still suppressed
|
||||
(ghost sessions from test runs / accidental reloads).
|
||||
"""
|
||||
import json
|
||||
import time
|
||||
|
||||
import pytest
|
||||
|
||||
import api.models as models
|
||||
from api.models import Session, all_sessions
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _isolate(tmp_path, monkeypatch):
|
||||
"""Redirect SESSION_DIR and SESSION_INDEX_FILE to a temp dir."""
|
||||
session_dir = tmp_path / "sessions"
|
||||
session_dir.mkdir()
|
||||
index_file = session_dir / "_index.json"
|
||||
|
||||
monkeypatch.setattr(models, "SESSION_DIR", session_dir)
|
||||
monkeypatch.setattr(models, "SESSION_INDEX_FILE", index_file)
|
||||
|
||||
models.SESSIONS.clear()
|
||||
yield
|
||||
models.SESSIONS.clear()
|
||||
|
||||
|
||||
def _make_untitled_session(age_seconds, messages=None, session_id=None):
|
||||
"""Create a Session with title='Untitled', updated_at set to age_seconds ago."""
|
||||
now = time.time()
|
||||
s = Session(
|
||||
session_id=session_id or None,
|
||||
title="Untitled",
|
||||
messages=messages or [],
|
||||
updated_at=now - age_seconds,
|
||||
created_at=now - age_seconds,
|
||||
)
|
||||
# Persist to disk so the full-scan fallback can also find it
|
||||
s.path.write_text(
|
||||
json.dumps(s.__dict__, ensure_ascii=False, indent=2), encoding="utf-8"
|
||||
)
|
||||
return s
|
||||
|
||||
|
||||
def _make_titled_session(age_seconds, session_id=None):
|
||||
"""Create a Session with a real title and one message."""
|
||||
now = time.time()
|
||||
s = Session(
|
||||
session_id=session_id or None,
|
||||
title="My conversation",
|
||||
messages=[{"role": "user", "content": "hello"}],
|
||||
updated_at=now - age_seconds,
|
||||
created_at=now - age_seconds,
|
||||
)
|
||||
s.path.write_text(
|
||||
json.dumps(s.__dict__, ensure_ascii=False, indent=2), encoding="utf-8"
|
||||
)
|
||||
return s
|
||||
|
||||
|
||||
# ── Test 1: brand-new Untitled 0-message session IS included ─────────────────
|
||||
|
||||
def test_new_untitled_session_is_visible_in_sidebar():
|
||||
"""A session created just now (0 seconds old) must appear in all_sessions()."""
|
||||
new_session = _make_untitled_session(age_seconds=0)
|
||||
|
||||
result = all_sessions()
|
||||
ids = {s["session_id"] for s in result}
|
||||
|
||||
assert new_session.session_id in ids, (
|
||||
"Brand-new Untitled 0-message session must be visible in the sidebar "
|
||||
"(fix for issue #789)"
|
||||
)
|
||||
|
||||
|
||||
def test_recent_untitled_session_under_60s_is_visible():
|
||||
"""A session 30 seconds old should still be visible."""
|
||||
recent_session = _make_untitled_session(age_seconds=30)
|
||||
|
||||
result = all_sessions()
|
||||
ids = {s["session_id"] for s in result}
|
||||
|
||||
assert recent_session.session_id in ids, (
|
||||
"Untitled 0-message session younger than 60 s must be visible (#789)"
|
||||
)
|
||||
|
||||
|
||||
# ── Test 2: old Untitled 0-message session IS still filtered ─────────────────
|
||||
|
||||
def test_old_untitled_session_over_60s_is_filtered():
|
||||
"""A ghost session (Untitled, 0 messages, >60 s old) must be hidden."""
|
||||
old_session = _make_untitled_session(age_seconds=120)
|
||||
|
||||
result = all_sessions()
|
||||
ids = {s["session_id"] for s in result}
|
||||
|
||||
assert old_session.session_id not in ids, (
|
||||
"Ghost Untitled 0-message session older than 60 s must be filtered out"
|
||||
)
|
||||
|
||||
|
||||
def test_session_exactly_at_boundary_is_filtered():
|
||||
"""A session just over 60 seconds old should be filtered."""
|
||||
boundary_session = _make_untitled_session(age_seconds=61)
|
||||
|
||||
result = all_sessions()
|
||||
ids = {s["session_id"] for s in result}
|
||||
|
||||
assert boundary_session.session_id not in ids, (
|
||||
"Untitled 0-message session older than 60 s must be filtered out"
|
||||
)
|
||||
|
||||
|
||||
# ── Test 3: session with messages is always visible regardless of age ─────────
|
||||
|
||||
def test_session_with_messages_always_visible_new():
|
||||
"""A session with messages (even Untitled) is always visible when new."""
|
||||
s = Session(
|
||||
title="Untitled",
|
||||
messages=[{"role": "user", "content": "hello"}],
|
||||
)
|
||||
s.path.write_text(
|
||||
json.dumps(s.__dict__, ensure_ascii=False, indent=2), encoding="utf-8"
|
||||
)
|
||||
|
||||
result = all_sessions()
|
||||
ids = {r["session_id"] for r in result}
|
||||
assert s.session_id in ids, "Session with messages must always appear in sidebar"
|
||||
|
||||
|
||||
def test_session_with_messages_always_visible_old():
|
||||
"""An old session with messages is always visible."""
|
||||
now = time.time()
|
||||
s = Session(
|
||||
title="Untitled",
|
||||
messages=[{"role": "user", "content": "hello"}],
|
||||
updated_at=now - 3600,
|
||||
created_at=now - 3600,
|
||||
)
|
||||
s.path.write_text(
|
||||
json.dumps(s.__dict__, ensure_ascii=False, indent=2), encoding="utf-8"
|
||||
)
|
||||
|
||||
result = all_sessions()
|
||||
ids = {r["session_id"] for r in result}
|
||||
assert s.session_id in ids, (
|
||||
"Old session with messages must always appear in sidebar"
|
||||
)
|
||||
|
||||
|
||||
def test_titled_session_with_no_messages_old_is_visible():
|
||||
"""A titled session with 0 messages (old) should not be filtered — filter
|
||||
only targets Untitled sessions."""
|
||||
now = time.time()
|
||||
s = Session(
|
||||
title="Project Alpha",
|
||||
messages=[],
|
||||
updated_at=now - 3600,
|
||||
created_at=now - 3600,
|
||||
)
|
||||
s.path.write_text(
|
||||
json.dumps(s.__dict__, ensure_ascii=False, indent=2), encoding="utf-8"
|
||||
)
|
||||
|
||||
result = all_sessions()
|
||||
ids = {r["session_id"] for r in result}
|
||||
assert s.session_id in ids, (
|
||||
"A titled session must always appear regardless of message count"
|
||||
)
|
||||
|
||||
|
||||
# ── Test 4: mixed bag — only old Untitled empty sessions are filtered ─────────
|
||||
|
||||
def test_mixed_sessions_correct_visibility():
|
||||
"""With a mix of sessions, only old+Untitled+empty ones are suppressed."""
|
||||
new_ghost = _make_untitled_session(age_seconds=5, session_id="new_ghost")
|
||||
old_ghost = _make_untitled_session(age_seconds=200, session_id="old_ghost")
|
||||
real_session = _make_titled_session(age_seconds=500, session_id="real_session")
|
||||
|
||||
result = all_sessions()
|
||||
ids = {s["session_id"] for s in result}
|
||||
|
||||
assert "new_ghost" in ids, "New Untitled session (5s old) must be visible"
|
||||
assert "old_ghost" not in ids, "Old Untitled session (200s old) must be hidden"
|
||||
assert "real_session" in ids, "Titled session with messages must be visible"
|
||||
Reference in New Issue
Block a user