atlas-iac/testing/tests/test_hermes_chat_sessions.py

393 lines
15 KiB
Python

"""Hermes chat sessions contracts."""
from __future__ import annotations
from test_hermes_chat_support import (
HERMES,
Path,
importlib,
json,
pytest,
sqlite3,
)
def test_telegram_api_session_migration_is_bounded_and_idempotent(tmp_path: Path):
"""Only named Telegram conversations receive presentation metadata."""
module_path = HERMES / "scripts" / "migrate_telegram_api_sessions.py"
spec = importlib.util.spec_from_file_location("migrate_telegram_sessions", module_path)
assert spec and spec.loader
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
state = tmp_path / "state.db"
responses = tmp_path / "response_store.db"
with sqlite3.connect(state) as connection:
connection.execute(
"""CREATE TABLE sessions (
id TEXT PRIMARY KEY, source TEXT, session_key TEXT, chat_type TEXT,
display_name TEXT, origin_json TEXT, title TEXT
)"""
)
connection.executemany(
"INSERT INTO sessions (id, source) VALUES (?, ?)",
(("telegram-session", "api_server"), ("other-session", "api_server")),
)
with sqlite3.connect(responses) as connection:
connection.execute(
"CREATE TABLE conversations (name TEXT PRIMARY KEY, response_id TEXT NOT NULL)"
)
connection.execute(
"CREATE TABLE responses (response_id TEXT PRIMARY KEY, data TEXT NOT NULL, accessed_at REAL NOT NULL)"
)
connection.executemany(
"INSERT INTO responses VALUES (?, ?, 0)",
(
("telegram-response", json.dumps({"session_id": "telegram-session"})),
("other-response", json.dumps({"session_id": "other-session"})),
),
)
connection.executemany(
"INSERT INTO conversations VALUES (?, ?)",
(("telegram", "telegram-response"), ("unrelated", "other-response")),
)
assert module.migrate(state, responses) == 1
assert module.migrate(state, responses) == 0
with sqlite3.connect(state) as connection:
telegram = connection.execute(
"SELECT session_key, chat_type, display_name, origin_json, title "
"FROM sessions WHERE id = 'telegram-session'"
).fetchone()
other = connection.execute(
"SELECT session_key, title FROM sessions WHERE id = 'other-session'"
).fetchone()
assert telegram[:3] == ("telegram", "private", "Telegram")
assert json.loads(telegram[3]) == {
"platform": "telegram",
"session_key": "telegram",
}
assert telegram[4] == "Telegram · General"
assert other == (None, None)
def test_web_session_activity_patch_projects_bounded_events(tmp_path: Path):
"""The DOM transcript includes events without polluting agent history."""
module_path = HERMES / "scripts" / "patch_web_session_activity.py"
spec = importlib.util.spec_from_file_location("patch_web_activity", module_path)
assert spec and spec.loader
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
source = tmp_path / "web_server.py"
destination = tmp_path / "patched.py"
source.write_text(
"prefix\n"
+ module.LATEST_ROWS_BEFORE
+ module.LATEST_SELECTION_BEFORE
+ module.HELPER_MARKER
+ " db = object()\n"
+ module.MESSAGES_BEFORE
+ "suffix\n",
encoding="utf-8",
)
module.patch(source, destination)
patched = destination.read_text(encoding="utf-8")
assert "def _run_activity_messages(" in patched
assert 'root = get_hermes_home() / "run-activity"' in patched
assert "path.stat().st_size > 600_000" in patched
assert "entries[-1_000:]" in patched
assert "*_run_activity_messages(sid)" in patched
assert "messages.sort(" in patched
assert "limit: Optional[int] = None" in patched
assert "total_messages = len(messages)" in patched
assert "min(int(limit), 10_000)" in patched
assert '"total_messages": total_messages' in patched
assert "SELECT id, parent_session_id, started_at, ended_at" in patched
assert "newest still-open member of the lineage" in patched
assert "immediate objective parent" in patched
assert "mixes unrelated workstreams" in patched
assert "orphaned children" in patched
assert 'item[0].get("ended_at") is None' in patched
def test_web_session_lineage_returns_to_resumed_parent():
"""An ended reviewer must not strand the live view away from its parent."""
module_path = HERMES / "scripts" / "patch_web_session_activity.py"
spec = importlib.util.spec_from_file_location("patch_web_lineage", module_path)
assert spec and spec.loader
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
namespace: dict[str, object] = {}
exec(
"def select_active(sid, db, rows):\n" + module.LATEST_SELECTION_AFTER,
namespace,
)
class FakeDB:
def get_session(self, session_id):
return next((row for row in rows if row["id"] == session_id), None)
rows = [
{
"id": "umbrella",
"parent_session_id": None,
"started_at": 0.0,
"ended_at": None,
},
{
"id": "root",
"parent_session_id": "umbrella",
"started_at": 1.0,
"ended_at": None,
},
{
"id": "review-1",
"parent_session_id": "root",
"started_at": 2.0,
"ended_at": 3.0,
},
{
"id": "review-2",
"parent_session_id": "root",
"started_at": 4.0,
"ended_at": None,
},
]
select_active = namespace["select_active"]
assert select_active("root", FakeDB(), rows) == (
"review-2",
["root", "review-2"],
)
assert select_active("review-1", FakeDB(), rows) == (
"review-2",
["root", "review-2"],
)
rows[2]["ended_at"] = 5.0
rows[3]["ended_at"] = 5.0
assert select_active("root", FakeDB(), rows) == ("root", ["root"])
rows[1]["ended_at"] = 9.0
rows.append(
{
"id": "orphaned-review",
"parent_session_id": "root",
"started_at": 6.0,
"ended_at": None,
}
)
rows.append(
{
"id": "accepted-review",
"parent_session_id": "root",
"started_at": 7.0,
"ended_at": 8.0,
}
)
assert select_active("review-1", FakeDB(), rows) == (
"accepted-review",
["root", "accepted-review"],
)
def test_api_activity_patch_coalesces_streaming_heartbeats(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
):
"""Token callbacks stay bounded while every tool transition is retained."""
module_path = HERMES / "scripts" / "patch_api_server_sessions.py"
spec = importlib.util.spec_from_file_location("patch_api_activity", module_path)
assert spec and spec.loader
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
activity_body = module.EVENT_CALLBACK_SIGNATURE_AFTER.split(
" def _make_run_event_callback(", 1
)[0]
namespace: dict[str, object] = {}
exec(
"import hashlib, json, logging, os, time\n"
"from pathlib import Path\n"
"logger = logging.getLogger(__name__)\n"
"def redact_sensitive_text(value): return value\n"
"class ActivityRecorder:\n"
+ activity_body,
namespace,
)
recorder = namespace["ActivityRecorder"]()
monkeypatch.setenv("HERMES_HOME", str(tmp_path))
recorder._record_run_activity("session", "_thinking")
recorder._record_run_activity("session", "reasoning.available")
recorder._record_run_activity("session", "subagent.thinking")
recorder._record_run_activity("session", "tool.started", tool_name="terminal")
recorder._record_run_activity("session", "tool.completed", tool_name="terminal")
journals = list((tmp_path / "run-activity").glob("*.jsonl"))
assert len(journals) == 1
entries = [json.loads(line) for line in journals[0].read_text().splitlines()]
assert [entry["activity_event"] for entry in entries] == [
"_thinking",
"tool.started",
"tool.completed",
]
def test_legacy_api_sessions_are_nested_idempotently(tmp_path: Path):
"""Known standalone API workers move under Cassandra without data loss."""
module_path = HERMES / "scripts" / "migrate_api_session_lineage.py"
spec = importlib.util.spec_from_file_location("migrate_api_sessions", module_path)
assert spec and spec.loader
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
database = tmp_path / "state.db"
with sqlite3.connect(database) as connection:
connection.execute(
"CREATE TABLE sessions (id TEXT PRIMARY KEY, source TEXT, "
"parent_session_id TEXT, title TEXT, transcript TEXT, archived INTEGER DEFAULT 0)"
)
connection.execute(
"INSERT INTO sessions (id, source, parent_session_id, title, transcript) "
"VALUES (?, 'tui', NULL, 'Cassandra', 'parent-data')",
(module.LEGACY_CASSANDRA_PARENT,),
)
worker_id = next(iter(module.LEGACY_CASSANDRA_WORKERS))
connection.execute(
"INSERT INTO sessions (id, source, parent_session_id, title, transcript) "
"VALUES (?, 'api_server', NULL, 'old', 'keep-me')",
(worker_id,),
)
connection.executemany(
"INSERT INTO sessions (id, source, parent_session_id, title, transcript) "
"VALUES (?, 'api_server', NULL, 'old smoke', 'keep-smoke')",
((orphan_id,) for orphan_id in module.LEGACY_ORPHANED_SMOKE_SESSIONS),
)
assert module.migrate(database) == 1 + len(module.LEGACY_ORPHANED_SMOKE_SESSIONS)
assert module.migrate(database) == 0
with sqlite3.connect(database) as connection:
row = connection.execute(
"SELECT parent_session_id, title, transcript FROM sessions WHERE id = ?",
(worker_id,),
).fetchone()
assert row == (
module.LEGACY_CASSANDRA_PARENT,
module.LEGACY_CASSANDRA_WORKERS[worker_id],
"keep-me",
)
with sqlite3.connect(database) as connection:
orphans = connection.execute(
"SELECT id, archived, title, transcript FROM sessions "
"WHERE id IN ({}) ORDER BY id".format(
",".join("?" for _ in module.LEGACY_ORPHANED_SMOKE_SESSIONS)
),
tuple(module.LEGACY_ORPHANED_SMOKE_SESSIONS),
).fetchall()
assert orphans == sorted(
(
orphan_id,
1,
module.LEGACY_ORPHANED_SMOKE_SESSIONS[orphan_id],
"keep-smoke",
)
for orphan_id in module.LEGACY_ORPHANED_SMOKE_SESSIONS
)
def test_automated_triage_sessions_are_grouped_without_touching_interactive_runs(tmp_path: Path):
"""Only the stable Ariadne contract moves below the triage parent."""
module_path = HERMES / "scripts" / "migrate_api_session_lineage.py"
spec = importlib.util.spec_from_file_location("migrate_triage_sessions", module_path)
assert spec and spec.loader
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
database = tmp_path / "state.db"
with sqlite3.connect(database) as connection:
connection.execute(
"CREATE TABLE sessions (id TEXT PRIMARY KEY, source TEXT, "
"parent_session_id TEXT, title TEXT, started_at REAL, archived INTEGER DEFAULT 0)"
)
connection.execute(
"CREATE TABLE messages (session_id TEXT, role TEXT, content TEXT)"
)
connection.executemany(
"INSERT INTO sessions (id, source, started_at) VALUES (?, 'api_server', ?)",
(("triage-run", 1.0), ("jenkins-run", 1.5), ("interactive-run", 2.0)),
)
connection.executemany(
"INSERT INTO messages (session_id, role, content) VALUES (?, 'user', ?)",
(
(
"triage-run",
module.TRIAGE_MESSAGE_PREFIXES[0]
+ " Fix for incident sonar/bstein_home/python:S2208/finding-key.",
),
(
"jenkins-run",
module.TRIAGE_MESSAGE_PREFIXES[1]
+ "\nAnalyze incident soteria/291 for the Jenkins job soteria.",
),
("interactive-run", "Please explain this alert to me."),
),
)
assert module.migrate(database, group_triage=True) == 2
assert module.migrate(database, group_triage=True) == 0
with sqlite3.connect(database) as connection:
parent = connection.execute(
"SELECT title FROM sessions WHERE id = ?", (module.TRIAGE_PARENT,)
).fetchone()
triage = connection.execute(
"SELECT parent_session_id, title FROM sessions WHERE id = 'triage-run'"
).fetchone()
interactive = connection.execute(
"SELECT parent_session_id FROM sessions WHERE id = 'interactive-run'"
).fetchone()
jenkins = connection.execute(
"SELECT parent_session_id, title FROM sessions WHERE id = 'jenkins-run'"
).fetchone()
assert parent == (module.TRIAGE_PARENT_TITLE,)
assert triage == (
module.TRIAGE_PARENT,
"Sonar · bstein_home · python:S2208 · iage-run",
)
assert interactive == (None,)
assert jenkins == (
module.TRIAGE_PARENT,
"Sonar · soteria/291 · kins-run",
)
def test_stale_parent_linked_api_workers_close_on_startup(tmp_path: Path):
"""A previous gateway lifetime cannot leave phantom active workers."""
module_path = HERMES / "scripts" / "migrate_api_session_lineage.py"
spec = importlib.util.spec_from_file_location("close_stale_api_workers", module_path)
assert spec and spec.loader
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
database = tmp_path / "state.db"
with sqlite3.connect(database) as connection:
connection.execute(
"CREATE TABLE sessions (id TEXT PRIMARY KEY, source TEXT, "
"parent_session_id TEXT, title TEXT, started_at REAL, ended_at REAL, "
"end_reason TEXT, archived INTEGER DEFAULT 0)"
)
connection.executemany(
"INSERT INTO sessions "
"(id, source, parent_session_id, started_at, ended_at, end_reason) "
"VALUES (?, ?, ?, 1, ?, ?)",
(
("stale", "api_server", "parent", None, None),
("root", "api_server", None, None, None),
("finished", "api_server", "parent", 2.0, "api_run_completed"),
("interactive", "tui", "parent", None, None),
),
)
assert module.migrate(database) == 1
assert module.migrate(database) == 0
with sqlite3.connect(database) as connection:
rows = connection.execute(
"SELECT id, ended_at, end_reason FROM sessions ORDER BY id"
).fetchall()
by_id = {row[0]: row[1:] for row in rows}
assert by_id["stale"][0] is not None
assert by_id["stale"][1] == "api_run_recovered_stale"
assert by_id["root"] == (None, None)
assert by_id["finished"] == (2.0, "api_run_completed")
assert by_id["interactive"] == (None, None)