|
|
@@ -1170,6 +1170,33 @@ class TestReactionForwarding:
|
|
|
adapter.set_reaction_handler(AsyncMock(side_effect=RuntimeError("hook boom")))
|
|
|
await adapter._handle_realtime_event(self._event("reaction_added"))
|
|
|
|
|
|
+ async def test_presence_event_without_a_payload_user_id_does_not_raise(
|
|
|
+ self, adapter
|
|
|
+ ):
|
|
|
+ """The realtime PresenceChangedEvent carries only a status field — the
|
|
|
+ user is the event's own actor. A payload.user_id read used to raise
|
|
|
+ AttributeError on every presence change, and the event loop answered
|
|
|
+ that with a reconnect backoff that dropped messages arriving inside
|
|
|
+ the gap (a /restart was lost exactly that way)."""
|
|
|
+ event = self._event("presence_changed", actor_id="human-1")
|
|
|
+ # A real PresenceChangedEvent payload: no user_id attribute at all.
|
|
|
+ event.payload = MagicMock(spec=["status"])
|
|
|
+ event.payload.status = PresenceStatus.OFFLINE
|
|
|
+ adapter._user_cache["human-1"] = _make_user("human-1", "human")
|
|
|
+
|
|
|
+ await adapter._handle_realtime_event(event)
|
|
|
+
|
|
|
+ assert adapter._user_cache["human-1"].presence_status is PresenceStatus.OFFLINE
|
|
|
+
|
|
|
+ async def test_presence_of_an_uncached_user_is_skipped(self, adapter):
|
|
|
+ """Presence of someone the adapter has not cached is not an error —
|
|
|
+ there is nothing to patch, and the event is not interesting either."""
|
|
|
+ event = self._event("presence_changed", actor_id="stranger-9")
|
|
|
+ event.payload = MagicMock(spec=["status"])
|
|
|
+ event.payload.status = PresenceStatus.ONLINE
|
|
|
+
|
|
|
+ await adapter._handle_realtime_event(event)
|
|
|
+
|
|
|
|
|
|
# -- chat_type mapping --
|
|
|
|
|
|
@@ -2322,13 +2349,18 @@ def _make_edited_event(room_id="room-1", message_event_id="msg-1", actor_id="hum
|
|
|
def _make_presence_event(
|
|
|
user_id="user-1", actor_id=None, status=PresenceStatus.OFFLINE
|
|
|
):
|
|
|
- """A presence_changed envelope — actor is the user whose status moved."""
|
|
|
+ """A presence_changed envelope — actor is the user whose status moved.
|
|
|
+
|
|
|
+ The payload mirrors the wire shape (PresenceChangedEvent carries ONLY a
|
|
|
+ status field), so a spec'd payload here fails any regression to reading
|
|
|
+ payload.user_id — the bug that killed the realtime stream on every
|
|
|
+ presence change.
|
|
|
+ """
|
|
|
event = MagicMock()
|
|
|
event.id = f"evt-presence-{user_id}"
|
|
|
event.kind = "presence_changed"
|
|
|
event.actor_id = actor_id if actor_id is not None else user_id
|
|
|
- payload = MagicMock()
|
|
|
- payload.user_id = user_id
|
|
|
+ payload = MagicMock(spec=["status"])
|
|
|
payload.status = status
|
|
|
event.payload = payload
|
|
|
return event
|