fix(pheby): restore paged history across session resets
This commit is contained in:
+69
-6
@@ -334,6 +334,16 @@ class TestConversations:
|
||||
ev = client.ws.events()[-1]
|
||||
assert ev["error"]["code"] == proto.ERR_BAD_REQUEST
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_invalid_history_cursor_rejected(self, tmp_path):
|
||||
server = make_server(tmp_path)
|
||||
client = FakeClientConnection()
|
||||
client.authenticated = True
|
||||
await server._handle_conversation_open(client, {
|
||||
"type": proto.C_CONVERSATION_OPEN,
|
||||
"conversation_id": "conv", "before_message_id": "m0 OR 1=1"}, "r1")
|
||||
assert client.ws.events()[-1]["error"]["code"] == proto.ERR_BAD_REQUEST
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_ids_survive_router_reload(self, tmp_path):
|
||||
server = make_server(tmp_path)
|
||||
@@ -443,8 +453,12 @@ class TestBridge:
|
||||
return "session-1"
|
||||
|
||||
class DB:
|
||||
def get_messages_as_conversation(self, _session_id, include_row_ids=False):
|
||||
def _read_all(self, sql, params):
|
||||
return [{"id": "session-1"}]
|
||||
|
||||
def get_messages_as_conversation(self, _session_id, include_row_ids=False, include_compacted=False):
|
||||
assert include_row_ids is True
|
||||
assert include_compacted is True
|
||||
return [
|
||||
{"_row_id": 10, "role": "user", "content": "hello", "timestamp": 1_789_000_000.25},
|
||||
{"_row_id": 11, "role": "assistant", "content": "reply", "timestamp": 1_789_000_001.5},
|
||||
@@ -453,13 +467,58 @@ class TestBridge:
|
||||
monkeypatch.setattr(hb, "_session_store", lambda: Store())
|
||||
monkeypatch.setattr(hb, "_session_db", lambda: DB())
|
||||
|
||||
history, found = await hb.conversation_history("conv", 20)
|
||||
history, found, older = await hb.conversation_history("conv", 20)
|
||||
|
||||
assert found is True
|
||||
assert older is False
|
||||
assert [message["message_id"] for message in history] == ["m10", "m11"]
|
||||
assert history[0]["ts"] == "2026-09-10T00:26:40.250000+00:00"
|
||||
assert history[1]["ts"] == "2026-09-10T00:26:41.500000+00:00"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_history_spans_reset_sessions_and_pages_without_duplicates(self, monkeypatch):
|
||||
from pheby import hermes_bridge as hb
|
||||
|
||||
class Store:
|
||||
def peek_session_id(self, _key):
|
||||
return "new"
|
||||
|
||||
class DB:
|
||||
def _read_all(self, sql, params):
|
||||
assert "session_key" in sql and params[1] == "pheby"
|
||||
return [{"id": "old"}, {"id": "new"}]
|
||||
|
||||
def get_messages_as_conversation(self, sid, **kwargs):
|
||||
assert kwargs == {"include_row_ids": True, "include_compacted": True}
|
||||
start = 1 if sid == "old" else 4
|
||||
return [{"_row_id": n, "role": "user", "content": f"turn {n}", "timestamp": float(n)}
|
||||
for n in range(start, start + 3)]
|
||||
|
||||
monkeypatch.setattr(hb, "_session_store", lambda: Store())
|
||||
monkeypatch.setattr(hb, "_session_db", lambda: DB())
|
||||
latest, found, older = await hb.conversation_history("conv", 2)
|
||||
assert found and older
|
||||
assert [m["message_id"] for m in latest] == ["m5", "m6"]
|
||||
previous, found, older = await hb.conversation_history("conv", 2, before_id=5)
|
||||
assert found and older
|
||||
assert [m["message_id"] for m in previous] == ["m3", "m4"]
|
||||
first, found, older = await hb.conversation_history("conv", 2, before_id=3)
|
||||
assert found and not older
|
||||
assert [m["message_id"] for m in first] == ["m1", "m2"]
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_history_read_failure_is_not_reported_as_empty_chat(self, monkeypatch):
|
||||
from pheby import hermes_bridge as hb
|
||||
|
||||
class DB:
|
||||
def _read_all(self, _sql, _params):
|
||||
raise OSError("database offline")
|
||||
|
||||
monkeypatch.setattr(hb, "_session_store", lambda: None)
|
||||
monkeypatch.setattr(hb, "_session_db", lambda: DB())
|
||||
with pytest.raises(RuntimeError, match="transcript"):
|
||||
await hb.conversation_history("conv", 20)
|
||||
|
||||
def test_iso_parses_by_instant_for_naive_local_datetimes(self):
|
||||
# gateway session-store timestamps are naive LOCAL datetimes
|
||||
# (gateway.session_lifecycle._now). last_active must come back with a
|
||||
@@ -776,9 +835,13 @@ class TestBridge:
|
||||
class DB:
|
||||
deleted = None
|
||||
|
||||
def delete_session(self, session_id):
|
||||
self.deleted = session_id
|
||||
return True
|
||||
def _read_all(self, sql, params):
|
||||
assert "session_key" in sql and params == (session_key, "pheby")
|
||||
return [{"id": "session-old"}, {"id": "session-delete"}]
|
||||
|
||||
def delete_sessions(self, session_ids):
|
||||
self.deleted = list(session_ids)
|
||||
return len(session_ids)
|
||||
|
||||
store, db = Store(), DB()
|
||||
from pheby import hermes_bridge as hb
|
||||
@@ -786,7 +849,7 @@ class TestBridge:
|
||||
monkeypatch.setattr(hb, "_session_db", lambda: db)
|
||||
|
||||
assert await hb.delete_conversation(cid) is True
|
||||
assert db.deleted == "session-delete"
|
||||
assert db.deleted == ["session-old", "session-delete"]
|
||||
assert session_key not in store._entries
|
||||
assert store.saved is True
|
||||
assert await server.router.get_name(cid) is None
|
||||
|
||||
Reference in New Issue
Block a user