Selaa lähdekoodia

fix: Bot room-lifecycle timeline events use room_id, not a nested room

Bot._on_timeline_upsert called sub.HasField('room') for room-lifecycle
timeline events (room_created, user_joined_room, ...), but
RoomTimelineRoomEvent only has a plain proto3 room_id string field, not a
nested room message. HasField on a non-optional proto3 scalar raises
ValueError, which crashed the realtime task and caused a crash-restart loop
during retained-timeline replay (found during robochatto 0.5 upgrade testing).

Build the Room from the room_id string instead (Room.parse({'id': ...})),
testing the plain string by truthiness rather than HasField. Added a
regression test for the room_created upsert path.
Federico Feliziani 2 kuukautta sitten
vanhempi
commit
a39cb6df9d
2 muutettua tiedostoa jossa 33 lisäystä ja 2 poistoa
  1. 4 2
      src/chattolib/bot.py
  2. 29 0
      tests/test_bot.py

+ 4 - 2
src/chattolib/bot.py

@@ -602,8 +602,10 @@ class Bot:
         ):
             room = None
             sub = getattr(event, case, None)
-            if sub is not None and sub.HasField("room"):
-                room = Room.parse(_pb_to_dict(sub.room))
+            # RoomTimelineRoomEvent carries a plain proto3 `room_id` string
+            # (no presence), so test it by truthiness rather than HasField.
+            if sub is not None and sub.room_id:
+                room = Room.parse({"id": sub.room_id})
             await self._dispatch(BotRoomEvent(bot=self, kind="room", room=room, detail=case))
 
 

+ 29 - 0
tests/test_bot.py

@@ -19,6 +19,7 @@ from chattolib.bot import (
     Bot,
     BotMessageEvent,
     BotPresenceEvent,
+    BotRoomEvent,
     BotTypingEvent,
 )
 from chattolib.client import ChattoClient
@@ -135,6 +136,34 @@ async def test_dispatch_message_event():
     assert seen[0].is_mention  # body mentions the bot's login
 
 
+async def test_dispatch_room_lifecycle_event():
+    # Regression: room-lifecycle timeline events (room_created, user_joined_room,
+    # ...) carry a RoomTimelineRoomEvent with a `room_id` string, not a nested
+    # `room` message. The handler must build the Room from the id and must not
+    # raise (the old code called sub.HasField("room") on a field that doesn't
+    # exist, crashing the server task on retained-timeline replay).
+    bot = _bot()
+    seen: list[BotRoomEvent] = []
+
+    async def on_room(event: BotRoomEvent) -> None:
+        seen.append(event)
+
+    bot.on("room", on_room)
+
+    frame = realtime_pb2.RealtimeProjectionEvent()
+    frame.id = "p1"
+    op = frame.operations.add()
+    op.room_timeline_event_upsert.room_id = "r1"
+    evt = op.room_timeline_event_upsert.event
+    evt.room_created.room_id = "r1"
+
+    await bot._handle_projection(_wrap_projection_event(frame))
+    assert len(seen) == 1
+    assert seen[0].detail == "room_created"
+    assert seen[0].room is not None
+    assert seen[0].room.id == "r1"
+
+
 async def test_dispatch_presence_event():
     bot = _bot()
     seen: list[BotPresenceEvent] = []