mirror of
https://github.com/nesquena/hermes-webui.git
synced 2026-07-16 12:40:18 +00:00
d29da34c42
* fix(sessions): include target session in session events * chore(release): stamp v0.51.430 (Release OQ) for #4221 --------- Co-authored-by: Dennis Soong <dso2ng@gmail.com> Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
228 lines
8.7 KiB
Python
228 lines
8.7 KiB
Python
import queue
|
|
from pathlib import Path
|
|
|
|
|
|
ROUTES = Path("api/routes.py").read_text(encoding="utf-8")
|
|
SESSION_EVENTS = Path("api/session_events.py").read_text(encoding="utf-8")
|
|
PROFILES = Path("api/profiles.py").read_text(encoding="utf-8")
|
|
|
|
|
|
def test_session_events_endpoint_and_bus_are_defined():
|
|
assert "_SESSION_EVENTS_SUBSCRIBERS" in SESSION_EVENTS
|
|
assert "def publish_session_list_changed" in SESSION_EVENTS
|
|
assert "def _handle_session_events_stream" in ROUTES
|
|
assert "parsed.path == '/api/sessions/events'" in ROUTES
|
|
assert "Content-Type', 'text/event-stream; charset=utf-8'" in ROUTES
|
|
|
|
|
|
def test_session_events_publish_for_minimal_sidebar_mutations():
|
|
for reason in (
|
|
"session_new",
|
|
"session_delete",
|
|
"session_duplicate",
|
|
"session_import",
|
|
"session_import_cli",
|
|
"session_archive",
|
|
"session_move",
|
|
"session_pin",
|
|
"session_rename",
|
|
"session_title_regenerate",
|
|
"session_branch",
|
|
):
|
|
if reason == "session_import_cli":
|
|
assert f'publish_session_list_changed(\n "{reason}",' in ROUTES, reason
|
|
elif reason == "session_title_regenerate":
|
|
assert '_persist_generated_session_title(s, next_title, event_reason="session_title_regenerate")' in ROUTES
|
|
elif reason == "session_import":
|
|
assert f'publish_session_list_changed("{reason}")' in ROUTES, reason
|
|
else:
|
|
assert f'"{reason}",' in ROUTES, reason
|
|
|
|
assert 'if worktree_info:\n publish_session_list_changed(\n "session_new",' in ROUTES
|
|
assert "was_hidden_empty_session = _is_hidden_empty_session(s)" in ROUTES
|
|
assert 'if was_hidden_empty_session:\n publish_session_list_changed(\n "session_new",' in ROUTES
|
|
assert 'publish_session_list_changed(\n "session_duplicate",' in ROUTES
|
|
assert 'publish_session_list_changed(\n "session_rename",' in ROUTES
|
|
assert '_persist_generated_session_title(s, next_title, event_reason="session_title_regenerate")' in ROUTES
|
|
assert "session_id=sid" in ROUTES
|
|
assert 'event_profile = getattr(get_session(sid, metadata_only=True), "profile", None)' in ROUTES
|
|
assert "Failed to resolve profile for deleted session" in ROUTES
|
|
assert '_publish_session_list_changed("session_delete", profile=event_profile)' in ROUTES
|
|
assert 'publish_session_list_changed(\n "session_branch",' in ROUTES
|
|
assert 'publish_session_list_changed(\n "session_pin",' in ROUTES
|
|
assert 'publish_session_list_changed(\n "session_archive",' in ROUTES
|
|
assert 'publish_session_list_changed(\n "session_move",' in ROUTES
|
|
assert 'session_id=getattr(' in ROUTES
|
|
assert 'publish_session_list_changed("chat_start")' not in ROUTES
|
|
assert '_publish_session_list_changed("cron_complete",' in ROUTES
|
|
assert 'publish_session_list_changed("cron_complete",' in PROFILES
|
|
|
|
|
|
def test_session_event_queue_same_profile_is_bounded_and_latest_wins():
|
|
from api import session_events
|
|
|
|
q = session_events.subscribe_session_events()
|
|
try:
|
|
session_events.publish_session_list_changed("first", profile="profile-a")
|
|
session_events.publish_session_list_changed("second", profile="profile-a")
|
|
payload = q.get_nowait()
|
|
assert payload["type"] == "sessions_changed"
|
|
assert payload["reason"] == "second"
|
|
assert payload["profile"] == "profile-a"
|
|
assert q.empty()
|
|
finally:
|
|
session_events.unsubscribe_session_events(q)
|
|
|
|
|
|
def test_session_events_payload_tracks_session_id_when_available():
|
|
from api import session_events
|
|
|
|
q = session_events.subscribe_session_events()
|
|
try:
|
|
session_events.publish_session_list_changed(
|
|
"session_rename",
|
|
profile="profile-a",
|
|
session_id="session-123",
|
|
)
|
|
payload = q.get_nowait()
|
|
assert payload["type"] == "sessions_changed"
|
|
assert payload["reason"] == "session_rename"
|
|
assert payload["profile"] == "profile-a"
|
|
assert payload["session_id"] == "session-123"
|
|
finally:
|
|
session_events.unsubscribe_session_events(q)
|
|
|
|
|
|
def test_session_event_queue_same_profile_different_sessions_coalesces_to_profile_refresh():
|
|
from api import session_events
|
|
|
|
q = session_events.subscribe_session_events()
|
|
try:
|
|
session_events.publish_session_list_changed(
|
|
"session_rename",
|
|
profile="profile-a",
|
|
session_id="session-a",
|
|
)
|
|
session_events.publish_session_list_changed(
|
|
"session_pin",
|
|
profile="profile-a",
|
|
session_id="session-b",
|
|
)
|
|
payload = q.get_nowait()
|
|
assert payload["type"] == "sessions_changed"
|
|
assert payload["reason"] == "session_pin"
|
|
assert payload["profile"] == "profile-a"
|
|
assert "session_id" not in payload
|
|
assert q.empty()
|
|
finally:
|
|
session_events.unsubscribe_session_events(q)
|
|
|
|
|
|
def test_session_event_queue_profile_mismatch_coalesces_to_unscoped_refresh_all():
|
|
from api import session_events
|
|
|
|
q = session_events.subscribe_session_events()
|
|
try:
|
|
session_events.publish_session_list_changed(
|
|
"profile_a",
|
|
profile="profile-a",
|
|
session_id="session-a",
|
|
)
|
|
session_events.publish_session_list_changed(
|
|
"profile_b",
|
|
profile="profile-b",
|
|
session_id="session-b",
|
|
)
|
|
payload = q.get_nowait()
|
|
assert payload["type"] == "sessions_changed"
|
|
assert payload["reason"] == "profile_b"
|
|
assert "profile" not in payload
|
|
assert "session_id" not in payload
|
|
assert q.empty()
|
|
finally:
|
|
session_events.unsubscribe_session_events(q)
|
|
|
|
|
|
def test_session_event_queue_unscoped_pending_stays_unscoped_when_followed_by_scoped():
|
|
from api import session_events
|
|
|
|
q = session_events.subscribe_session_events()
|
|
try:
|
|
session_events.publish_session_list_changed("all_profiles")
|
|
session_events.publish_session_list_changed("profile_b", profile="profile-b")
|
|
payload = q.get_nowait()
|
|
assert payload["type"] == "sessions_changed"
|
|
assert payload["reason"] == "profile_b"
|
|
assert "profile" not in payload
|
|
assert q.empty()
|
|
finally:
|
|
session_events.unsubscribe_session_events(q)
|
|
|
|
|
|
def test_session_event_queue_drain_race_preserves_incoming_profile():
|
|
from api import session_events
|
|
|
|
class DrainedQueue:
|
|
def __init__(self):
|
|
self.payloads = []
|
|
self.put_attempts = 0
|
|
|
|
def put_nowait(self, payload):
|
|
self.put_attempts += 1
|
|
if self.put_attempts == 1:
|
|
raise queue.Full()
|
|
self.payloads.append(payload)
|
|
|
|
def get_nowait(self):
|
|
raise queue.Empty()
|
|
|
|
q = DrainedQueue()
|
|
with session_events._SESSION_EVENTS_LOCK:
|
|
session_events._SESSION_EVENTS_SUBSCRIBERS.add(q)
|
|
try:
|
|
session_events.publish_session_list_changed("profile_b", profile="profile-b")
|
|
assert len(q.payloads) == 1
|
|
payload = q.payloads[0]
|
|
assert payload["type"] == "sessions_changed"
|
|
assert payload["reason"] == "profile_b"
|
|
assert payload["profile"] == "profile-b"
|
|
finally:
|
|
with session_events._SESSION_EVENTS_LOCK:
|
|
session_events._SESSION_EVENTS_SUBSCRIBERS.discard(q)
|
|
|
|
|
|
def test_session_events_payload_tracks_profile_when_available():
|
|
from api import session_events
|
|
|
|
q = session_events.subscribe_session_events()
|
|
try:
|
|
session_events.publish_session_list_changed("no_profile")
|
|
payload = q.get_nowait()
|
|
assert payload["type"] == "sessions_changed"
|
|
assert payload["reason"] == "no_profile"
|
|
assert "profile" not in payload
|
|
|
|
session_events.publish_session_list_changed("with_profile", profile="profile-b")
|
|
payload = q.get_nowait()
|
|
assert payload["type"] == "sessions_changed"
|
|
assert payload["reason"] == "with_profile"
|
|
assert payload["profile"] == "profile-b"
|
|
finally:
|
|
session_events.unsubscribe_session_events(q)
|
|
|
|
|
|
def test_session_events_payload_omits_profile_for_default_root_alias(monkeypatch):
|
|
from api import session_events
|
|
|
|
monkeypatch.setattr(session_events, "_profile_is_root_alias", lambda profile: profile == "kinni")
|
|
|
|
q = session_events.subscribe_session_events()
|
|
try:
|
|
session_events.publish_session_list_changed("renamed_root", profile="kinni")
|
|
payload = q.get_nowait()
|
|
assert payload["type"] == "sessions_changed"
|
|
assert payload["reason"] == "renamed_root"
|
|
assert "profile" not in payload
|
|
finally:
|
|
session_events.unsubscribe_session_events(q)
|