63 lines
2.8 KiB
Python
63 lines
2.8 KiB
Python
|
|
"""Regression tests for #4167 review items — sidebar payload allowlist must
|
||
|
|
preserve read-only + gateway-routing fields, and the failed-refresh path must
|
||
|
|
not re-render a prior profile's sessions after a profile switch.
|
||
|
|
|
||
|
|
Item 1/2: _SIDEBAR_SESSION_RESPONSE_FIELDS must include read_only / is_read_only
|
||
|
|
(so the sidebar suppresses rename / action-menu / swipe on read-only sessions)
|
||
|
|
and gateway_routing (so the model label renders). Dropping these silently
|
||
|
|
regressed both surfaces.
|
||
|
|
|
||
|
|
Item 3: renderSessionList()'s catch path must scope-tag the cache and clear it
|
||
|
|
when the requested profile scope differs, instead of unconditionally
|
||
|
|
re-rendering the cached (prior-profile) rows.
|
||
|
|
"""
|
||
|
|
import re
|
||
|
|
import sys
|
||
|
|
from pathlib import Path
|
||
|
|
|
||
|
|
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
||
|
|
|
||
|
|
_ROOT = Path(__file__).resolve().parent.parent
|
||
|
|
|
||
|
|
|
||
|
|
def test_sidebar_allowlist_preserves_read_only_and_gateway_routing():
|
||
|
|
import api.routes as routes
|
||
|
|
|
||
|
|
allow = routes._SIDEBAR_SESSION_RESPONSE_FIELDS
|
||
|
|
assert "read_only" in allow, "read_only dropped -> read-only sessions render writable"
|
||
|
|
assert "is_read_only" in allow, "is_read_only dropped -> read-only detection misses"
|
||
|
|
assert "gateway_routing" in allow, "gateway_routing dropped -> sidebar model label degrades"
|
||
|
|
|
||
|
|
|
||
|
|
def test_sidebar_response_item_preserves_read_only_flag():
|
||
|
|
"""_sidebar_session_response_item() must carry read_only through."""
|
||
|
|
import api.routes as routes
|
||
|
|
|
||
|
|
fn = getattr(routes, "_sidebar_session_response_item", None)
|
||
|
|
if fn is None:
|
||
|
|
# Helper name differs across versions — fall back to allowlist assertion.
|
||
|
|
assert "read_only" in routes._SIDEBAR_SESSION_RESPONSE_FIELDS
|
||
|
|
return
|
||
|
|
row = fn({
|
||
|
|
"session_id": "s1",
|
||
|
|
"title": "t",
|
||
|
|
"read_only": True,
|
||
|
|
"gateway_routing": {"provider": "anthropic", "model": "claude"},
|
||
|
|
"not_allowed_field": "x",
|
||
|
|
})
|
||
|
|
assert row.get("read_only") is True
|
||
|
|
assert row.get("gateway_routing") == {"provider": "anthropic", "model": "claude"}
|
||
|
|
assert "not_allowed_field" not in row
|
||
|
|
|
||
|
|
|
||
|
|
def test_failed_refresh_clears_cache_on_profile_scope_change():
|
||
|
|
"""The catch path in renderSessionList must clear stale rows when the
|
||
|
|
requested scope differs from the cached scope (no cross-profile leak)."""
|
||
|
|
src = (_ROOT / "static" / "sessions.js").read_text(encoding="utf-8")
|
||
|
|
# The cache must be scope-tagged.
|
||
|
|
assert "_allSessionsScope" in src, "cache is not scope-tagged"
|
||
|
|
# The catch path must compare current scope to the cached scope and clear
|
||
|
|
# _allSessions when they differ rather than always rendering from cache.
|
||
|
|
assert re.search(r"_scopeMatches", src), "catch path does not gate fallback on scope match"
|
||
|
|
assert re.search(r"_allSessions\s*=\s*\[\]", src), "catch path does not clear stale rows on scope mismatch"
|