Files
pi_mcps/mcp/bigmind/tests/test_feature7_live_sessions.py
T
Patrick Plate 155d56e8e8 chore: reorganize into polyglot monorepo (workshop)
- Move bigmind/ -> mcp/bigmind/
- Move webscraper/ -> mcp/webscraper/
- Move mss-failsafe/ -> java/mss-failsafe/
- Move Wellmann-Shop/ -> java/wellmann-shop/ (normalize to kebab-case)
- Add .roo/ IDE config files to tracking
- Add plans/REPO_STRATEGY.md (monorepo strategy document)
- Expand .gitignore: Java/Maven, Node/TS, coverage, uv.lock
- Rewrite README.md as navigation index
- Update .roo/mcp.json webscraper path to mcp/webscraper/
2026-04-04 08:51:15 +02:00

275 lines
12 KiB
Python

"""Tests for Feature 7 — Live Session Awareness.
Covers:
- announce_focus() — atomic conflict detection, focus writes
- get_active_sessions() — idle_minutes, field values, exclusion of closed
- close_session() — must NULL focus columns
- Schema v6 — sessions focus columns + token_saves table
- log_token_save() — insert, accumulate, stats
- get_token_efficiency_stats() — totals, session filter, best, by_method
"""
import json
import pytest
from bigmind import memory_store
from bigmind.db import db
@pytest.fixture
def user(temp_db):
return memory_store.get_or_create_user("testuser", "Test User")
# ── announce_focus ─────────────────────────────────────────────────────────────
class TestAnnounceFocus:
def test_sets_focus_and_files(self, temp_db, user):
sid = memory_store.create_session(user["id"])
result = memory_store.announce_focus(sid, "Working on db.py", ["bigmind/db.py"])
assert result["updated"] is True
assert result["conflicts"] == []
sessions = memory_store.get_active_sessions(user["id"])
assert len(sessions) == 1
assert sessions[0]["focus"] == "Working on db.py"
assert "bigmind/db.py" in sessions[0]["files"]
def test_sets_ide_hint(self, temp_db, user):
sid = memory_store.create_session(user["id"])
memory_store.announce_focus(sid, "task", ["file.py"], ide_hint="PyCharm")
sessions = memory_store.get_active_sessions(user["id"])
assert sessions[0]["ide_hint"] == "PyCharm"
def test_detects_file_conflict(self, temp_db, user):
sid1 = memory_store.create_session(user["id"])
sid2 = memory_store.create_session(user["id"])
memory_store.announce_focus(sid1, "Working on server.py", ["src/server.py"])
result = memory_store.announce_focus(sid2, "Also server.py", ["src/server.py"])
assert len(result["conflicts"]) == 1
assert result["conflicts"][0]["session_id"] == sid1[:8]
assert "src/server.py" in result["conflicts"][0]["overlapping_files"]
def test_no_conflict_for_different_files(self, temp_db, user):
sid1 = memory_store.create_session(user["id"])
sid2 = memory_store.create_session(user["id"])
memory_store.announce_focus(sid1, "Editing db.py", ["bigmind/db.py"])
result = memory_store.announce_focus(sid2, "Editing server.py", ["src/server.py"])
assert result["conflicts"] == []
def test_second_call_overwrites_focus(self, temp_db, user):
sid = memory_store.create_session(user["id"])
memory_store.announce_focus(sid, "First task", ["a.py"])
memory_store.announce_focus(sid, "Second task", ["b.py"])
sessions = memory_store.get_active_sessions(user["id"])
assert sessions[0]["focus"] == "Second task"
assert "b.py" in sessions[0]["files"]
assert "a.py" not in sessions[0]["files"]
def test_empty_files_list_no_conflict(self, temp_db, user):
sid1 = memory_store.create_session(user["id"])
sid2 = memory_store.create_session(user["id"])
memory_store.announce_focus(sid1, "Task A", [])
result = memory_store.announce_focus(sid2, "Task B", [])
assert result["conflicts"] == []
def test_own_session_not_a_conflict(self, temp_db, user):
sid = memory_store.create_session(user["id"])
memory_store.announce_focus(sid, "First focus", ["server.py"])
result = memory_store.announce_focus(sid, "Updated focus", ["server.py"])
assert result["conflicts"] == []
def test_conflict_lists_overlapping_files_only(self, temp_db, user):
sid1 = memory_store.create_session(user["id"])
sid2 = memory_store.create_session(user["id"])
memory_store.announce_focus(sid1, "Multiple files", ["a.py", "b.py", "c.py"])
result = memory_store.announce_focus(sid2, "Partial overlap", ["b.py", "z.py"])
assert len(result["conflicts"]) == 1
assert result["conflicts"][0]["overlapping_files"] == ["b.py"]
def test_conflict_not_raised_for_closed_session(self, temp_db, user):
sid1 = memory_store.create_session(user["id"])
memory_store.announce_focus(sid1, "Old work", ["x.py"])
memory_store.close_session(sid1, "done")
sid2 = memory_store.create_session(user["id"])
result = memory_store.announce_focus(sid2, "New work", ["x.py"])
assert result["conflicts"] == []
# ── get_active_sessions ────────────────────────────────────────────────────────
class TestGetActiveSessions:
def test_returns_all_open_sessions(self, temp_db, user):
sid1 = memory_store.create_session(user["id"])
sid2 = memory_store.create_session(user["id"])
sessions = memory_store.get_active_sessions(user["id"])
ids = [s["session_id"] for s in sessions]
assert sid1 in ids
assert sid2 in ids
def test_excludes_closed_sessions(self, temp_db, user):
sid = memory_store.create_session(user["id"])
memory_store.close_session(sid, "done")
sessions = memory_store.get_active_sessions(user["id"])
ids = [s["session_id"] for s in sessions]
assert sid not in ids
def test_idle_minutes_non_negative(self, temp_db, user):
sid = memory_store.create_session(user["id"])
memory_store.announce_focus(sid, "task", [])
sessions = memory_store.get_active_sessions(user["id"])
assert sessions[0]["idle_minutes"] is not None
assert sessions[0]["idle_minutes"] >= 0
def test_focus_null_when_not_announced(self, temp_db, user):
sid = memory_store.create_session(user["id"])
sessions = memory_store.get_active_sessions(user["id"])
s = next(x for x in sessions if x["session_id"] == sid)
assert s["focus"] is None
assert s["files"] == []
assert s["ide_hint"] is None
def test_focus_reflects_announce(self, temp_db, user):
sid = memory_store.create_session(user["id"])
memory_store.announce_focus(sid, "Live task", ["db.py"], ide_hint="VS Code")
sessions = memory_store.get_active_sessions(user["id"])
s = next(x for x in sessions if x["session_id"] == sid)
assert s["focus"] == "Live task"
assert "db.py" in s["files"]
assert s["ide_hint"] == "VS Code"
def test_empty_when_no_open_sessions(self, temp_db, user):
sid = memory_store.create_session(user["id"])
memory_store.close_session(sid, "done")
sessions = memory_store.get_active_sessions(user["id"])
assert sessions == []
# ── close_session clears focus ────────────────────────────────────────────────
class TestCloseClearsFocus:
def test_focus_columns_nulled_on_close(self, temp_db, user):
sid = memory_store.create_session(user["id"])
memory_store.announce_focus(sid, "some work", ["x.py"], ide_hint="PyCharm")
# Verify focus was set
with db() as conn:
row = conn.execute(
"SELECT current_focus, focus_files, focus_updated_at FROM sessions WHERE id=?",
(sid,),
).fetchone()
assert row["current_focus"] == "some work"
memory_store.close_session(sid, "finished", topics="test")
# All focus columns must be NULL after close
with db() as conn:
row = conn.execute(
"SELECT current_focus, focus_files, focus_updated_at FROM sessions WHERE id=?",
(sid,),
).fetchone()
assert row["current_focus"] is None
assert row["focus_files"] is None
assert row["focus_updated_at"] is None
# ── Schema v6 ─────────────────────────────────────────────────────────────────
class TestSchemaV6:
def test_sessions_have_focus_columns(self, temp_db, user):
sid = memory_store.create_session(user["id"])
with db() as conn:
row = conn.execute("SELECT * FROM sessions WHERE id=?", (sid,)).fetchone()
col_names = row.keys()
assert "current_focus" in col_names
assert "focus_files" in col_names
assert "focus_updated_at" in col_names
assert "ide_hint" in col_names
def test_token_saves_table_exists(self, temp_db):
with db() as conn:
count = conn.execute("SELECT COUNT(*) FROM token_saves").fetchone()[0]
assert count == 0 # table exists, just empty
def test_schema_version_is_7(self, temp_db):
with db() as conn:
version = conn.execute(
"SELECT version FROM schema_version"
).fetchone()["version"]
assert version == 7
# ── Token Efficiency Tracker (Feature 6) ──────────────────────────────────────
class TestTokenSaves:
def test_log_returns_row_id(self, temp_db, user):
sid = memory_store.create_session(user["id"])
row_id = memory_store.log_token_save(
sid, user["id"], "grep instead of read", 50_000
)
assert isinstance(row_id, int) and row_id > 0
def test_total_accumulates(self, temp_db, user):
sid = memory_store.create_session(user["id"])
memory_store.log_token_save(sid, user["id"], "save 1", 10_000, method_used="grep")
memory_store.log_token_save(sid, user["id"], "save 2", 20_000, method_used="memory_hit")
stats = memory_store.get_token_efficiency_stats(user["id"])
assert stats["total_tokens_saved"] == 30_000
def test_session_total_correct(self, temp_db, user):
sid1 = memory_store.create_session(user["id"])
sid2 = memory_store.create_session(user["id"])
memory_store.log_token_save(sid1, user["id"], "s1 save", 5_000)
memory_store.log_token_save(sid2, user["id"], "s2 save", 8_000)
stats = memory_store.get_token_efficiency_stats(user["id"], session_id=sid1)
assert stats["session_tokens_saved"] == 5_000
def test_best_save_returns_largest(self, temp_db, user):
sid = memory_store.create_session(user["id"])
memory_store.log_token_save(sid, user["id"], "small", 1_000)
memory_store.log_token_save(sid, user["id"], "big one", 999_000)
memory_store.log_token_save(sid, user["id"], "medium", 50_000)
stats = memory_store.get_token_efficiency_stats(user["id"])
assert stats["best_save"]["tokens_saved_estimate"] == 999_000
assert stats["best_save"]["description"] == "big one"
def test_by_method_aggregation(self, temp_db, user):
sid = memory_store.create_session(user["id"])
memory_store.log_token_save(sid, user["id"], "g1", 10_000, method_used="grep")
memory_store.log_token_save(sid, user["id"], "g2", 10_000, method_used="grep")
memory_store.log_token_save(sid, user["id"], "m1", 5_000, method_used="memory_hit")
stats = memory_store.get_token_efficiency_stats(user["id"])
by_method = {r["method_used"]: r["total"] for r in stats["by_method"]}
assert by_method["grep"] == 20_000
assert by_method["memory_hit"] == 5_000
def test_empty_stats_when_no_saves(self, temp_db, user):
stats = memory_store.get_token_efficiency_stats(user["id"])
assert stats["total_tokens_saved"] == 0
assert stats["best_save"] is None
assert stats["recent_saves"] == []
def test_method_stored_correctly(self, temp_db, user):
sid = memory_store.create_session(user["id"])
memory_store.log_token_save(
sid, user["id"], "tail log file", 200_000, method_used="tail"
)
stats = memory_store.get_token_efficiency_stats(user["id"])
assert stats["best_save"]["method_used"] == "tail"