mirror of
https://github.com/nesquena/hermes-webui.git
synced 2026-05-26 19:50:15 +00:00
63251ad206
Opus pre-release findings on #1370 applied: SHOULD-FIX 1: Tightened parent_session_id exposure to only emit when the parent's end_reason is in {compression, cli_close}. Without this, two distinct WebUI sessions sharing a non-continuation parent (e.g. 'user_stop') would get clustered by frontend's _sessionLineageKey (which falls through to parent_session_id when _lineage_root_id is missing) and incorrectly collapsed into a single sidebar row. Updated assertions in: - tests/test_session_lineage_metadata_api.py:: test_non_compression_state_db_parent_does_not_create_sidebar_lineage - tests/test_pr1370_lineage_metadata_perf_and_orphan.py:: test_non_compression_parent_does_not_extend_lineage SHOULD-FIX 2: Chunked the IN-clause to 500 vars to stay under SQLITE_MAX_VARIABLE_NUMBER. Python 3.9 ships sqlite 3.31 with the default limit of 999. A power user with 2000+ sessions in the sidebar would hit OperationalError, the silent except-wrapper would swallow it, and lineage collapse would never work. Added test_in_clause_chunked_for_large_session_set with SQL interception to lock the invariant in source. PR addition (per user directive — Opus + my review, no second independent review round needed for combined batch): #1372 from @NocGeek — fix: persist manual cron run results. Self-contained 89 LOC fix split out from the held #1352. Mirrors the scheduled-cron path (cron/scheduler.py:1334-1364) exactly: saves output, marks job complete, treats empty response as soft failure with matching error string. 2 behavioral tests using sys.modules monkeypatch to mock cron.scheduler.run_job. CI not yet attached because branch is brand-new; ran the new tests + adjacent suites locally — all pass. Final test count: 3471 passing, 0 failed. Also adds 2 more regression tests for the perf-fix invariants: - test_in_clause_chunked_for_large_session_set - test_two_children_sharing_non_continuation_parent_not_collapsed
162 lines
5.3 KiB
Python
162 lines
5.3 KiB
Python
"""Regression tests for /api/sessions lineage metadata used by sidebar collapse."""
|
|
|
|
import sqlite3
|
|
import time
|
|
|
|
import pytest
|
|
|
|
import api.models as models
|
|
from api.models import SESSIONS, STREAMS, Session, all_sessions
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def _isolate(tmp_path, monkeypatch):
|
|
session_dir = tmp_path / "sessions"
|
|
session_dir.mkdir()
|
|
index_file = session_dir / "_index.json"
|
|
state_db = tmp_path / "state.db"
|
|
monkeypatch.setattr(models, "SESSION_DIR", session_dir)
|
|
monkeypatch.setattr(models, "SESSION_INDEX_FILE", index_file)
|
|
monkeypatch.setattr(models, "_active_state_db_path", lambda: state_db)
|
|
SESSIONS.clear()
|
|
STREAMS.clear()
|
|
yield state_db
|
|
SESSIONS.clear()
|
|
STREAMS.clear()
|
|
|
|
|
|
def _ensure_state_db(path):
|
|
conn = sqlite3.connect(str(path))
|
|
conn.executescript(
|
|
"""
|
|
CREATE TABLE sessions (
|
|
id TEXT PRIMARY KEY,
|
|
source TEXT,
|
|
title TEXT,
|
|
model TEXT,
|
|
started_at REAL NOT NULL,
|
|
message_count INTEGER DEFAULT 0,
|
|
parent_session_id TEXT,
|
|
ended_at REAL,
|
|
end_reason TEXT
|
|
);
|
|
"""
|
|
)
|
|
return conn
|
|
|
|
|
|
def _insert_state_row(conn, sid, *, parent=None, ended_at=None, end_reason=None, started_at=None):
|
|
conn.execute(
|
|
"""
|
|
INSERT INTO sessions
|
|
(id, source, title, model, started_at, message_count, parent_session_id, ended_at, end_reason)
|
|
VALUES (?, 'webui', ?, 'openai/gpt-5', ?, 2, ?, ?, ?)
|
|
""",
|
|
(sid, sid, started_at or time.time(), parent, ended_at, end_reason),
|
|
)
|
|
conn.commit()
|
|
|
|
|
|
def _save_webui_session(sid, *, title, updated_at):
|
|
session = Session(
|
|
session_id=sid,
|
|
title=title,
|
|
messages=[{"role": "user", "content": "hello"}, {"role": "assistant", "content": "hi"}],
|
|
updated_at=updated_at,
|
|
)
|
|
session.save(touch_updated_at=False)
|
|
return session
|
|
|
|
|
|
def test_all_sessions_exposes_state_db_lineage_metadata_for_webui_json_sessions(_isolate):
|
|
"""PR #1358 can only collapse rows when /api/sessions exposes lineage keys."""
|
|
conn = _ensure_state_db(_isolate)
|
|
t0 = time.time() - 100
|
|
try:
|
|
_save_webui_session("lineage_api_root", title="Hermes WebUI", updated_at=t0)
|
|
_save_webui_session("lineage_api_tip", title="Hermes WebUI #2", updated_at=t0 + 10)
|
|
_insert_state_row(
|
|
conn,
|
|
"lineage_api_root",
|
|
started_at=t0,
|
|
ended_at=t0 + 5,
|
|
end_reason="compression",
|
|
)
|
|
_insert_state_row(
|
|
conn,
|
|
"lineage_api_tip",
|
|
parent="lineage_api_root",
|
|
started_at=t0 + 6,
|
|
)
|
|
|
|
rows = {row["session_id"]: row for row in all_sessions()}
|
|
|
|
assert rows["lineage_api_tip"].get("parent_session_id") == "lineage_api_root"
|
|
assert rows["lineage_api_tip"].get("_lineage_root_id") == "lineage_api_root"
|
|
assert rows["lineage_api_tip"].get("_compression_segment_count") == 2
|
|
assert "_lineage_root_id" not in rows["lineage_api_root"]
|
|
finally:
|
|
conn.close()
|
|
|
|
|
|
def test_non_compression_state_db_parent_does_not_create_sidebar_lineage(_isolate):
|
|
conn = _ensure_state_db(_isolate)
|
|
t0 = time.time() - 100
|
|
try:
|
|
_save_webui_session("lineage_api_plain_parent", title="Parent", updated_at=t0)
|
|
_save_webui_session("lineage_api_plain_child", title="Child", updated_at=t0 + 10)
|
|
_insert_state_row(
|
|
conn,
|
|
"lineage_api_plain_parent",
|
|
started_at=t0,
|
|
ended_at=t0 + 5,
|
|
end_reason="user_stop",
|
|
)
|
|
_insert_state_row(
|
|
conn,
|
|
"lineage_api_plain_child",
|
|
parent="lineage_api_plain_parent",
|
|
started_at=t0 + 6,
|
|
)
|
|
|
|
rows = {row["session_id"]: row for row in all_sessions()}
|
|
|
|
# parent_session_id must NOT be exposed when the parent's end_reason
|
|
# is not a continuation (compression / cli_close). The frontend's
|
|
# _sessionLineageKey would otherwise group two children sharing a
|
|
# `user_stop` parent under the same key — incorrect collapse.
|
|
# (Tightened in v0.50.251 per Opus SHOULD-FIX 1.)
|
|
assert "parent_session_id" not in rows["lineage_api_plain_child"]
|
|
assert "_lineage_root_id" not in rows["lineage_api_plain_child"]
|
|
finally:
|
|
conn.close()
|
|
|
|
|
|
|
|
def test_cli_close_parent_preserves_cross_surface_continuation_lineage(_isolate):
|
|
conn = _ensure_state_db(_isolate)
|
|
t0 = time.time() - 100
|
|
try:
|
|
_save_webui_session("lineage_api_cli_parent", title="Hermes WebUI #8", updated_at=t0)
|
|
_save_webui_session("lineage_api_webui_child", title="Hermes WebUI #8", updated_at=t0 + 10)
|
|
_insert_state_row(
|
|
conn,
|
|
"lineage_api_cli_parent",
|
|
started_at=t0,
|
|
ended_at=t0 + 5,
|
|
end_reason="cli_close",
|
|
)
|
|
_insert_state_row(
|
|
conn,
|
|
"lineage_api_webui_child",
|
|
parent="lineage_api_cli_parent",
|
|
started_at=t0 + 6,
|
|
)
|
|
|
|
rows = {row["session_id"]: row for row in all_sessions()}
|
|
|
|
assert rows["lineage_api_webui_child"].get("parent_session_id") == "lineage_api_cli_parent"
|
|
assert rows["lineage_api_webui_child"].get("_lineage_root_id") == "lineage_api_cli_parent"
|
|
finally:
|
|
conn.close()
|