Files
hermes-webui/tests/test_cron_session_title.py
2026-05-05 01:52:42 +00:00

159 lines
5.1 KiB
Python

"""Tests for cron session title fallback in get_cli_sessions().
When a CLI session originates from cron and has no title in state.db, the
WebUI sidebar should display the human-friendly job name from cron/jobs.json
instead of a generic "Cron Session" label.
Session ID format produced by hermes-agent: cron_<job_id>_<YYYYMMDD>_<HHMMSS>
"""
import json
import sqlite3
import pytest
import api.models as models
def _make_state_db(path, sessions):
"""Create a state.db with the schema get_cli_sessions() expects.
`sessions` is a list of (id, title, source) tuples.
"""
conn = sqlite3.connect(str(path))
conn.execute("""
CREATE TABLE sessions (
id TEXT PRIMARY KEY,
title TEXT,
model TEXT,
message_count INTEGER,
started_at REAL,
source TEXT
)
""")
conn.execute("""
CREATE TABLE messages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
session_id TEXT,
timestamp REAL
)
""")
for sid, title, source in sessions:
conn.execute(
"INSERT INTO sessions (id, title, model, message_count, started_at, source) "
"VALUES (?, ?, ?, ?, ?, ?)",
(sid, title, "gpt-x", 1, 1700000000.0, source),
)
conn.execute(
"INSERT INTO messages (session_id, timestamp) VALUES (?, ?)",
(sid, 1700000001.0),
)
conn.commit()
conn.close()
def _write_jobs_json(hermes_home, jobs):
"""Write cron/jobs.json with the given jobs list."""
cron_dir = hermes_home / "cron"
cron_dir.mkdir(parents=True, exist_ok=True)
(cron_dir / "jobs.json").write_text(
json.dumps({"jobs": jobs}), encoding="utf-8"
)
@pytest.fixture
def fake_hermes_home(tmp_path, monkeypatch):
"""Point get_cli_sessions() at a temporary HERMES_HOME and disable
profile lookups so the test runs hermetically."""
home = tmp_path / "hermes"
home.mkdir()
# Both profile helpers are imported lazily inside get_cli_sessions(),
# so patching the api.profiles module reaches them.
import api.profiles as profiles
monkeypatch.setattr(profiles, "get_active_hermes_home", lambda: home)
monkeypatch.setattr(profiles, "get_active_profile_name", lambda: None)
return home
def test_cron_session_uses_job_name_when_title_missing(fake_hermes_home):
"""A cron session with no title should display the friendly job name."""
_write_jobs_json(fake_hermes_home, [
{"id": "cd65df6fc1a8", "name": "wiki-auto-ingest"},
])
_make_state_db(fake_hermes_home / "state.db", [
("cron_cd65df6fc1a8_20260417_191049", None, "cron"),
])
sessions = models.get_cli_sessions()
assert len(sessions) == 1
assert sessions[0]["title"] == "wiki-auto-ingest"
def test_cron_session_falls_back_when_jobs_json_missing(fake_hermes_home):
"""No jobs.json should not crash; title falls back to 'Cron Session'."""
_make_state_db(fake_hermes_home / "state.db", [
("cron_abc123_20260417_191049", None, "cron"),
])
sessions = models.get_cli_sessions()
assert sessions[0]["title"] == "Cron Session"
def test_cron_session_falls_back_when_job_id_not_in_jobs_json(fake_hermes_home):
"""Stale session whose job has been deleted falls back gracefully."""
_write_jobs_json(fake_hermes_home, [
{"id": "different_job", "name": "Some Other Job"},
])
_make_state_db(fake_hermes_home / "state.db", [
("cron_orphan_20260417_191049", None, "cron"),
])
sessions = models.get_cli_sessions()
assert sessions[0]["title"] == "Cron Session"
def test_explicit_title_is_preserved(fake_hermes_home):
"""If state.db already has a title, the cron job lookup should not
override it."""
_write_jobs_json(fake_hermes_home, [
{"id": "cd65df6fc1a8", "name": "wiki-auto-ingest"},
])
_make_state_db(fake_hermes_home / "state.db", [
("cron_cd65df6fc1a8_20260417_191049", "User-edited title", "cron"),
])
sessions = models.get_cli_sessions()
assert sessions[0]["title"] == "User-edited title"
def test_non_cron_sessions_unaffected(fake_hermes_home):
"""The cron-name lookup must not run for cli-source sessions, so the
generic 'Cli Session' fallback still applies when title is empty."""
_write_jobs_json(fake_hermes_home, [
{"id": "cd65df6fc1a8", "name": "wiki-auto-ingest"},
])
# A 'cli' session whose ID coincidentally starts with 'cron_' must not
# pick up the job name — the source check guards against this.
_make_state_db(fake_hermes_home / "state.db", [
("cron_cd65df6fc1a8_xx", None, "cli"),
])
# PR #1587 hides one-off default-titled CLI rows. Keep this fixture visible
# so the test remains focused on the cron-name guard rather than sidebar
# filtering.
conn = sqlite3.connect(str(fake_hermes_home / "state.db"))
conn.execute(
"INSERT INTO messages (session_id, timestamp) VALUES (?, ?)",
("cron_cd65df6fc1a8_xx", 1700000002.0),
)
conn.commit()
conn.close()
sessions = models.get_cli_sessions()
assert sessions[0]["title"] == "Cli Session"