Test that a non-member's read receipt reaches their other devices

This commit is contained in:
Paul Chobert committed 2026-10-28 17:17:23 +01:00
1 parent 50b26e6cd5
commit 5cb675a5c7
2 files changed
+106 -2

No files matched your search

@@ -17,11 +17,11 @@ from parameterized import parameterized_class
from twisted.internet.testing import MemoryReactor
import synapse.rest.admin
from synapse.api.constants import EventTypes, Membership
from synapse.api.constants import EventTypes, Membership, ReceiptTypes
from synapse.config.server_announcements import ServerAnnouncementRoom
from synapse.handlers.server_announcements import SYNTHETIC_JOIN_EVENT_ID_PREFIX
from synapse.handlers.sliding_sync import StateValues
from synapse.rest.client import devices, login, room, sync
from synapse.rest.client import devices, login, receipts, room, sync
from synapse.server import HomeServer
from synapse.types import JsonDict
from synapse.util.clock import Clock
@@ -50,6 +50,7 @@ class SlidingSyncServerAnnouncementsTestCase(SlidingSyncBase):
room.register_servlets,
sync.register_servlets,
devices.register_servlets,
receipts.register_servlets,
]
def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None:
@@ -61,6 +62,7 @@ class SlidingSyncServerAnnouncementsTestCase(SlidingSyncBase):
self.former_tok = self.login("former", "pass")
self.bob = self.register_user("bob", "pass")
self.bob_tok = self.login("bob", "pass")
self.bob_other_tok = self.login("bob", "pass")
self.config = hs.config.serverannouncements
self.config.rooms = [
@@ -552,3 +554,52 @@ class SlidingSyncServerAnnouncementsTestCase(SlidingSyncBase):
.get("device_lists", {})
.get("changed", []),
)
def _send_read_receipt(self, event_id: str, tok: str) -> None:
channel = self.make_request(
"POST",
f"/rooms/{self.room_id}/receipt/{ReceiptTypes.READ}/{event_id}",
{},
access_token=tok,
)
self.assertEqual(channel.code, 200, channel.json_body)
def _receipts_sync_body(self) -> JsonDict:
sync_body = self._sync_body()
sync_body["extensions"] = {"receipts": {"enabled": True}}
return sync_body
def _receipt_readers(self, response: JsonDict, event_id: str) -> set[str]:
"""Return the users with an `m.read` receipt on `event_id` in the
announcement room of the receipts extension.
"""
receipt = (
response["extensions"]["receipts"].get("rooms", {}).get(self.room_id, {})
)
return set(
receipt.get("content", {}).get(event_id, {}).get(ReceiptTypes.READ, {})
)
def test_non_member_receipt_on_new_connection_of_other_device(self) -> None:
"""A non-member's read receipt is sent to a new connection of their
other devices.
"""
event_id = self._announce("hello")
self._send_read_receipt(event_id, self.bob_tok)
response, _ = self.do_sync(self._receipts_sync_body(), tok=self.bob_other_tok)
self.assertEqual(self._receipt_readers(response, event_id), {self.bob})
def test_non_member_receipt_on_existing_connection_of_other_device(self) -> None:
"""A non-member's read receipt is sent to an existing connection of their
other devices, even with no new announcement since.
"""
event_id = self._announce("hello")
_, pos = self.do_sync(self._receipts_sync_body(), tok=self.bob_other_tok)
self._send_read_receipt(event_id, self.bob_tok)
response, _ = self.do_sync(
self._receipts_sync_body(), since=pos, tok=self.bob_other_tok
)
self.assertEqual(self._receipt_readers(response, event_id), {self.bob})
+53
View File
@@ -441,6 +441,7 @@ class ServerAnnouncementsReceiptsTestCase(unittest.HomeserverTestCase):
self.announcer_tok = self.login("announcer", "pass")
self.bob = self.register_user("bob", "pass")
self.bob_tok = self.login("bob", "pass")
self.bob_other_tok = self.login("bob", "pass")
hs.config.serverannouncements.rooms = [
ServerAnnouncementRoom(
@@ -478,3 +479,55 @@ class ServerAnnouncementsReceiptsTestCase(unittest.HomeserverTestCase):
self.store.get_receipts_for_user(self.bob, [ReceiptTypes.READ])
)
self.assertEqual(receipts, {self.room_id: event_id})
def _announce(self, body: str) -> str:
return self.helper.send(self.room_id, body, tok=self.announcer_tok)["event_id"]
def _send_read_receipt(self, event_id: str, tok: str) -> None:
channel = self.make_request(
"POST",
f"/rooms/{self.room_id}/receipt/{ReceiptTypes.READ}/{event_id}",
{},
access_token=tok,
)
self.assertEqual(channel.code, HTTPStatus.OK, channel.json_body)
def _sync(self, tok: str, since: str | None = None) -> JsonDict:
url = "/sync" if since is None else f"/sync?since={since}"
channel = self.make_request("GET", url, access_token=tok)
self.assertEqual(channel.code, HTTPStatus.OK, channel.json_body)
return channel.json_body
def _receipt_readers(self, response: JsonDict, event_id: str) -> set[str]:
"""Return the users with an `m.read` receipt on `event_id` in the
announcement room's ephemeral events of a `/sync` response.
"""
room = response.get("rooms", {}).get("join", {}).get(self.room_id, {})
return {
user_id
for event in room.get("ephemeral", {}).get("events", [])
if event["type"] == EduTypes.RECEIPT
for user_id in event["content"].get(event_id, {}).get(ReceiptTypes.READ, {})
}
def test_non_member_receipt_on_initial_sync_of_other_device(self) -> None:
"""A non-member's read receipt is in the initial sync of their other
devices.
"""
event_id = self._announce("hello")
self._send_read_receipt(event_id, self.bob_tok)
response = self._sync(self.bob_other_tok)
self.assertEqual(self._receipt_readers(response, event_id), {self.bob})
def test_non_member_receipt_on_incremental_sync_of_other_device(self) -> None:
"""A non-member's read receipt is in the next incremental sync of their
other devices, even with no new announcement since.
"""
event_id = self._announce("hello")
since = self._sync(self.bob_other_tok)["next_batch"]
self._send_read_receipt(event_id, self.bob_tok)
response = self._sync(self.bob_other_tok, since)
self.assertEqual(self._receipt_readers(response, event_id), {self.bob})