Files
isparkclaw-webui/tests/test_issue789.py
nesquena-hermes 312a493a72 fix(sessions): new sessions appear immediately in sidebar (#806)
Closes #789 Bug A. 60-second exemption in all_sessions() filter.
2026-04-21 17:08:52 +00:00

195 lines
6.5 KiB
Python

"""
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"