"""Regression checks for #2821 session pin/unpin state sync.""" from pathlib import Path ROOT = Path(__file__).resolve().parent.parent ROUTES_PY = (ROOT / "api" / "routes.py").read_text(encoding="utf-8") SESSIONS_JS = (ROOT / "static" / "sessions.js").read_text(encoding="utf-8") def _function_block(src: str, name: str) -> str: marker = f"function {name}" start = src.find(marker) assert start != -1, f"{name} not found" brace = src.find("{", start) assert brace != -1, f"{name} body not found" depth = 1 i = brace + 1 while i < len(src) and depth: if src[i] == "{": depth += 1 elif src[i] == "}": depth -= 1 i += 1 assert depth == 0, f"{name} body did not close" return src[start:i] def test_session_field_helper_reads_dicts_and_objects(): from api.routes import _session_field class SessionLike: session_id = "obj-1" pinned = True archived = False assert _session_field({"session_id": "dict-1", "pinned": True}, "pinned", False) is True assert _session_field({"session_id": "dict-1"}, "archived", False) is False assert _session_field(SessionLike(), "session_id", None) == "obj-1" assert _session_field(SessionLike(), "missing", "fallback") == "fallback" def test_pin_limit_snapshot_counts_index_dict_entries(): assert "def _session_counts_toward_pin_quota(session)" in ROUTES_PY assert "_session_counts_toward_pin_quota(existing)" in ROUTES_PY assert "_hide_from_default_sidebar(row)" in ROUTES_PY # #3288 replaced the set-of-ids snapshot with a visible-lineage row snapshot. # The load-bearing invariant this test guards is unchanged: the persisted pin # snapshot is computed BEFORE acquiring LOCK (all_sessions() acquires LOCK # internally, so snapshotting inside `with LOCK:` would deadlock). start = ROUTES_PY.find("persisted_rows = [") assert start != -1, "persisted pin snapshot not found" end = ROUTES_PY.find("with LOCK:", start) assert end != -1, "persisted pin snapshot should be computed before LOCK" persisted_snapshot = ROUTES_PY[start:end] # The snapshot must filter via the shared quota helper, not raw getattr checks. assert "_session_counts_toward_pin_quota(existing)" in persisted_snapshot assert 'getattr(existing, "pinned", False)' not in persisted_snapshot assert 'getattr(existing, "archived", False)' not in persisted_snapshot # The authoritative count collapses continuation siblings to visible lineages. assert "_visible_pinned_lineage_ids(" in ROUTES_PY def test_pin_action_does_not_short_circuit_on_stale_client_count(): body = _function_block(SESSIONS_JS, "_openSessionActionMenu") assert "const pinLimitReached=" not in body assert "if(pinLimitReached)" not in body assert "_pinnedSessionCount()>=_getPinnedSessionsLimit()" not in body assert "await api('/api/session/pin'" in body def test_pin_action_refreshes_session_list_after_pin_failure(): body = _function_block(SESSIONS_JS, "_openSessionActionMenu") catch_idx = body.find("}catch(err){") assert catch_idx != -1, "Pin/unpin action must have an error path" catch_block = body[catch_idx:body.find("}", catch_idx + len("}catch(err){")) + 1] assert "showToast(t('session_pin_failed')+err.message)" in catch_block assert "await renderSessionList()" in catch_block