"""Regression checks for configurable pinned session limits.""" import json import pathlib import urllib.error import urllib.request from api import config as webui_config from tests._pytest_port import BASE ROOT = pathlib.Path(__file__).resolve().parent.parent CONFIG_PY = (ROOT / "api" / "config.py").read_text(encoding="utf-8") INDEX_HTML = (ROOT / "static" / "index.html").read_text(encoding="utf-8") PANELS_JS = (ROOT / "static" / "panels.js").read_text(encoding="utf-8") BOOT_JS = (ROOT / "static" / "boot.js").read_text(encoding="utf-8") SESSIONS_JS = (ROOT / "static" / "sessions.js").read_text(encoding="utf-8") def post(path, body=None): data = json.dumps(body or {}).encode() req = urllib.request.Request( BASE + path, data=data, headers={"Content-Type": "application/json"}, ) try: with urllib.request.urlopen(req, timeout=10) as r: return json.loads(r.read()), r.status except urllib.error.HTTPError as e: return json.loads(e.read()), e.code def make_session(created, title): payload = { "title": title, "messages": [{"role": "user", "content": "keep this conversation handy"}], "model": "test/pin-limit-setting", } d, status = post("/api/session/import", payload) assert status == 200 sid = d["session"]["session_id"] created.append(sid) return sid def test_settings_file_path_can_be_scoped_per_instance(tmp_path, monkeypatch): default_state_dir = tmp_path / "default-state" monkeypatch.delenv("HERMES_WEBUI_SETTINGS_FILE", raising=False) assert webui_config._resolve_settings_file(default_state_dir) == default_state_dir / "settings.json" staging_settings = tmp_path / "staging" / "settings.json" monkeypatch.setenv("HERMES_WEBUI_SETTINGS_FILE", str(staging_settings)) assert webui_config._resolve_settings_file(default_state_dir) == staging_settings.resolve() def test_pin_limit_setting_is_exposed_and_wired_through_ui(): assert '"pinned_sessions_limit": 3' in CONFIG_PY assert '"pinned_sessions_limit": (1, 99)' in CONFIG_PY assert 'id="settingsPinnedSessionsLimit"' in INDEX_HTML assert 'type="number"' in INDEX_HTML assert 'min="1"' in INDEX_HTML assert 'max="99"' in INDEX_HTML assert 'payload.pinned_sessions_limit=parseInt(pinnedLimitField.value,10)' in PANELS_JS assert "settings.pinned_sessions_limit" in PANELS_JS assert "window._pinnedSessionsLimit=parseInt(s.pinned_sessions_limit||3,10)||3" in BOOT_JS assert "function _getPinnedSessionsLimit()" in SESSIONS_JS assert "function _pinnedSessionsLimit()" not in SESSIONS_JS assert "_pinnedSessionCount()>=_getPinnedSessionsLimit()" not in SESSIONS_JS assert "await api('/api/session/pin'" in SESSIONS_JS def test_settings_api_persists_integer_pin_limit_and_rejects_invalid_values(): try: d, status = post("/api/settings", {"pinned_sessions_limit": 5}) assert status == 200 assert d["pinned_sessions_limit"] == 5 d, status = post("/api/settings", {"pinned_sessions_limit": "7"}) assert status == 200 assert d["pinned_sessions_limit"] == 7 d, status = post("/api/settings", {"pinned_sessions_limit": 0}) assert status == 200 assert d["pinned_sessions_limit"] == 7 d, status = post("/api/settings", {"pinned_sessions_limit": 100}) assert status == 200 assert d["pinned_sessions_limit"] == 7 finally: post("/api/settings", {"pinned_sessions_limit": 3}) def test_session_pin_endpoint_uses_configured_limit(): created = [] try: d, status = post("/api/settings", {"pinned_sessions_limit": 4}) assert status == 200 assert d["pinned_sessions_limit"] == 4 pinned = [make_session(created, f"Configured pin cap {i}") for i in range(4)] for sid in pinned: d, status = post("/api/session/pin", {"session_id": sid, "pinned": True}) assert status == 200 assert d["session"]["pinned"] is True fifth = make_session(created, "Configured pin cap overflow") d, status = post("/api/session/pin", {"session_id": fifth, "pinned": True}) assert status == 400 assert "4 sessions" in d.get("error", "") finally: post("/api/settings", {"pinned_sessions_limit": 3}) for sid in created: post("/api/session/delete", {"session_id": sid})