fix(sse): coerce non-int ui._event_id to None when stamping saves

When the active UI is a MagicMock test double, _ui_event_id() returned
the auto-vivified _event_id mock (getattr finds it, so the None default
never applies). That mock reached the conversations INSERT and failed
to bind ("type 'MagicMock' is not supported"), so save_message raised,
the row was dropped, and tests on the real-storage + mock-UI path broke
(CI: test_session_attachments::test_db_row_stores_text_only).

Coerce a non-int _event_id to None so mock UIs -- and counterless
CLI/eval/placeholder UIs -- stamp NULL (the synthetic-snapshot floor),
matching the documented contract. Production UIs always carry an int,
so behaviour there is unchanged.

Also drop two redundant local `import json` in the new /history
integration tests; the module-level import already covers them.
This commit is contained in:
Patrick Buckley
2026-05-30 04:08:57 -07:00
parent cdc1dbcc1d
commit 3070bc4eb5
2 changed files with 8 additions and 9 deletions
-4
View File
@@ -1304,8 +1304,6 @@ class TestHistoryInteractive:
``cursor`` = the resolved boundary's event_id. The client opens
its initial SSE with that cursor so the delta rebuilds the turn.
"""
import json
ws_id = "ws-cursor"
_inject_storage.register_workstream(ws_id, kind="interactive", user_id="test-user")
_inject_storage.save_message(ws_id, "user", "kick off", event_id=10)
@@ -1340,8 +1338,6 @@ class TestHistoryInteractive:
rendered block) and returns ``cursor: null`` — the client connects
fresh to the synthetic-snapshot floor, never leaving the turn
unrenderable."""
import json
ws_id = "ws-cursor-reload"
_inject_storage.register_workstream(ws_id, kind="interactive", user_id="test-user")
_inject_storage.save_message(ws_id, "user", "kick off", event_id=10)
+8 -5
View File
@@ -2134,12 +2134,15 @@ class ChatSession:
"""Current per-ws SSE ring-buffer high-water mark for stamping
saved messages with the ``Last-Event-ID`` resume cursor.
Returns ``getattr(self.ui, "_event_id", None)`` ``None`` for
UIs without the counter (CLI / eval / placeholder), whose rows
then stay NULL and are treated by ``/history`` as "no
fast-forward cursor available" (the synthetic-snapshot floor).
``None`` for UIs without an integer counter CLI / eval /
placeholder UIs (no ``_event_id``), and test doubles whose
``self.ui`` is a ``MagicMock`` (a non-int ``_event_id`` would
otherwise reach the INSERT and fail to bind). Those rows stay
NULL and are treated by ``/history`` as "no fast-forward cursor
available" (the synthetic-snapshot floor).
"""
return getattr(self.ui, "_event_id", None)
eid = getattr(self.ui, "_event_id", None)
return eid if isinstance(eid, int) else None
def _remaining_token_budget(self) -> int:
"""Estimate how many tokens are available for new content.