Files
aiturk-hermes-ide/tests/gateway/test_session_degraded_db_continuity.py
T

145 lines
6.1 KiB
Python

"""Regression tests: session continuity when the default/global state.db is unavailable.
Root cause (live incident, 2026-08-17): when the default/global state.db is
corrupt or unreadable at gateway startup, ``SessionStore._db`` degrades to
None (JSONL routing fallback) and ``GatewayRunner._session_db`` is None.
Under multiplexed profile routes the AIAgent lazily opens the PROFILE's
state.db (``AIAgent._get_session_db_for_recall``) and its
``_ensure_db_session`` created the session row WITHOUT routing identity
(session_key/chat_id/chat_type/thread_id/user_id/origin_json/display_name
all NULL), so the row was unrecoverable by
``find_latest_gateway_session_for_peer``. The gateway's own
``record_gateway_session_peer`` self-heal never ran because ``_db`` was None.
On the next message, ``SessionStore.load_transcript`` returned [] even though
the transcript existed in the profile DB -> the agent lost prior context.
These tests pin fix 1 (routing identity on lazy row creation):
``AIAgent._ensure_db_session`` persists the full routing identity the
agent already carries, so the lazily-created row is recoverable. (The
transcript-recovery half of the original report is covered separately by
the scope-aware session DB resolution on main.)
"""
import json
import logging
import os
import tempfile
from pathlib import Path
from unittest.mock import patch
SESSION_ID = "degraded-db-continuity-session"
CHAT_ID = "-1001234567890"
THREAD_ID = "42"
SESSION_KEY = "agent:orion:telegram:group:-1001234567890:8148316720"
def _make_gateway_agent(session_db):
"""Build an AIAgent carrying gateway routing identity (multiplex profile route)."""
with patch.dict(os.environ, {"OPENROUTER_API_KEY": "test-key"}):
from run_agent import AIAgent
agent = AIAgent(
api_key="test-key",
base_url="https://openrouter.ai/api/v1",
model="test/model",
quiet_mode=True,
session_db=session_db,
session_id=SESSION_ID,
skip_context_files=True,
skip_memory=True,
platform="telegram",
user_id="8148316720",
user_name="testuser",
chat_id=CHAT_ID,
chat_name="Test Group",
chat_type="group",
thread_id=THREAD_ID,
gateway_session_key=SESSION_KEY,
)
# The multiplexed profile scope would resolve the active profile name to
# the route's profile (e.g. "orion"); pin it so the row + origin_json
# carry the profile deterministically.
with patch("hermes_cli.profiles.get_active_profile_name", return_value="orion"):
agent._ensure_db_session()
return agent
class TestEnsureDbSessionPersistsRoutingIdentity:
def test_lazy_creation_writes_session_key_and_chat_identity(self):
"""First-created row must carry gateway routing metadata (not identity-less)."""
from hermes_state import SessionDB
with tempfile.TemporaryDirectory() as tmpdir:
db = SessionDB(db_path=Path(tmpdir) / "t.db")
try:
_make_gateway_agent(db)
row = db.get_session(SESSION_ID)
assert row is not None
assert row["session_key"] == SESSION_KEY
assert row["chat_id"] == CHAT_ID
assert row["chat_type"] == "group"
assert row["thread_id"] == THREAD_ID
assert row["user_id"] == "8148316720"
assert row["display_name"] == "Test Group"
assert row["source"] == "telegram"
assert row["profile_name"] == "orion"
origin = json.loads(row["origin_json"])
assert origin["platform"] == "telegram"
assert origin["chat_id"] == CHAT_ID
assert origin["chat_name"] == "Test Group"
assert origin["chat_type"] == "group"
assert origin["user_id"] == "8148316720"
assert origin["user_name"] == "testuser"
assert origin["thread_id"] == THREAD_ID
assert origin["profile"] == "orion"
finally:
db.close()
def test_row_recoverable_by_peer_lookup(self):
"""find_latest_gateway_session_for_peer must find the lazily-created row."""
from hermes_state import SessionDB
with tempfile.TemporaryDirectory() as tmpdir:
db = SessionDB(db_path=Path(tmpdir) / "t.db")
try:
_make_gateway_agent(db)
# The peer lookup keys on session_key + source; the regression
# left session_key NULL so this returned None on every restart.
found = db.find_latest_gateway_session_for_peer(
session_key=SESSION_KEY,
source="telegram",
)
assert found is not None
assert found["id"] == SESSION_ID
finally:
db.close()
def test_cli_session_stays_identity_less(self):
"""A plain CLI agent (no gateway fields) keeps the old shape — no origin_json."""
from hermes_state import SessionDB
with tempfile.TemporaryDirectory() as tmpdir:
db = SessionDB(db_path=Path(tmpdir) / "t.db")
try:
with patch.dict(os.environ, {"OPENROUTER_API_KEY": "test-key"}):
from run_agent import AIAgent
agent = AIAgent(
api_key="test-key",
base_url="https://openrouter.ai/api/v1",
model="test/model",
quiet_mode=True,
session_db=db,
session_id=SESSION_ID + "-cli",
skip_context_files=True,
skip_memory=True,
)
agent._ensure_db_session()
row = db.get_session(SESSION_ID + "-cli")
assert row is not None
assert row["session_key"] is None
assert row["chat_id"] is None
assert row["origin_json"] is None
finally:
db.close()