Implement changes to MSC2285 (hidden read receipts) (#12168)

* Changes hidden read receipts to be a separate receipt type
  (instead of a field on `m.read`).
* Updates the `/receipts` endpoint to accept `m.fully_read`.
This commit is contained in:
Šimon Brandner 2022-05-04 17:59:22 +02:00 committed by GitHub
parent 332cce8dcf
commit 116a4c8340
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 648 additions and 187 deletions

View file

@ -23,7 +23,6 @@ import synapse.rest.admin
from synapse.api.constants import (
EventContentFields,
EventTypes,
ReadReceiptEventFields,
ReceiptTypes,
RelationTypes,
)
@ -347,7 +346,7 @@ class SyncKnockTestCase(
# Knock on a room
channel = self.make_request(
"POST",
"/_matrix/client/r0/knock/%s" % (self.room_id,),
f"/_matrix/client/r0/knock/{self.room_id}",
b"{}",
self.knocker_tok,
)
@ -412,18 +411,79 @@ class ReadReceiptsTestCase(unittest.HomeserverTestCase):
# Send a message as the first user
res = self.helper.send(self.room_id, body="hello", tok=self.tok)
# Send a read receipt to tell the server the first user's message was read
body = json.dumps({ReadReceiptEventFields.MSC2285_HIDDEN: True}).encode("utf8")
# Send a private read receipt to tell the server the first user's message was read
channel = self.make_request(
"POST",
"/rooms/%s/receipt/m.read/%s" % (self.room_id, res["event_id"]),
body,
f"/rooms/{self.room_id}/receipt/org.matrix.msc2285.read.private/{res['event_id']}",
{},
access_token=self.tok2,
)
self.assertEqual(channel.code, 200)
# Test that the first user can't see the other user's hidden read receipt
self.assertEqual(self._get_read_receipt(), None)
# Test that the first user can't see the other user's private read receipt
self.assertIsNone(self._get_read_receipt())
@override_config({"experimental_features": {"msc2285_enabled": True}})
def test_public_receipt_can_override_private(self) -> None:
"""
Sending a public read receipt to the same event which has a private read
receipt should cause that receipt to become public.
"""
# Send a message as the first user
res = self.helper.send(self.room_id, body="hello", tok=self.tok)
# Send a private read receipt
channel = self.make_request(
"POST",
f"/rooms/{self.room_id}/receipt/{ReceiptTypes.READ_PRIVATE}/{res['event_id']}",
{},
access_token=self.tok2,
)
self.assertEqual(channel.code, 200)
self.assertIsNone(self._get_read_receipt())
# Send a public read receipt
channel = self.make_request(
"POST",
f"/rooms/{self.room_id}/receipt/{ReceiptTypes.READ}/{res['event_id']}",
{},
access_token=self.tok2,
)
self.assertEqual(channel.code, 200)
# Test that we did override the private read receipt
self.assertNotEqual(self._get_read_receipt(), None)
@override_config({"experimental_features": {"msc2285_enabled": True}})
def test_private_receipt_cannot_override_public(self) -> None:
"""
Sending a private read receipt to the same event which has a public read
receipt should cause no change.
"""
# Send a message as the first user
res = self.helper.send(self.room_id, body="hello", tok=self.tok)
# Send a public read receipt
channel = self.make_request(
"POST",
f"/rooms/{self.room_id}/receipt/{ReceiptTypes.READ}/{res['event_id']}",
{},
access_token=self.tok2,
)
self.assertEqual(channel.code, 200)
self.assertNotEqual(self._get_read_receipt(), None)
# Send a private read receipt
channel = self.make_request(
"POST",
f"/rooms/{self.room_id}/receipt/{ReceiptTypes.READ_PRIVATE}/{res['event_id']}",
{},
access_token=self.tok2,
)
self.assertEqual(channel.code, 200)
# Test that we didn't override the public read receipt
self.assertIsNone(self._get_read_receipt())
@parameterized.expand(
[
@ -455,7 +515,7 @@ class ReadReceiptsTestCase(unittest.HomeserverTestCase):
# Send a read receipt for this message with an empty body
channel = self.make_request(
"POST",
"/rooms/%s/receipt/m.read/%s" % (self.room_id, res["event_id"]),
f"/rooms/{self.room_id}/receipt/m.read/{res['event_id']}",
access_token=self.tok2,
custom_headers=[("User-Agent", user_agent)],
)
@ -479,6 +539,9 @@ class ReadReceiptsTestCase(unittest.HomeserverTestCase):
# Store the next batch for the next request.
self.next_batch = channel.json_body["next_batch"]
if channel.json_body.get("rooms", None) is None:
return None
# Return the read receipt
ephemeral_events = channel.json_body["rooms"]["join"][self.room_id][
"ephemeral"
@ -499,7 +562,10 @@ class UnreadMessagesTestCase(unittest.HomeserverTestCase):
def default_config(self) -> JsonDict:
config = super().default_config()
config["experimental_features"] = {"msc2654_enabled": True}
config["experimental_features"] = {
"msc2654_enabled": True,
"msc2285_enabled": True,
}
return config
def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None:
@ -564,7 +630,7 @@ class UnreadMessagesTestCase(unittest.HomeserverTestCase):
body = json.dumps({ReceiptTypes.READ: res["event_id"]}).encode("utf8")
channel = self.make_request(
"POST",
"/rooms/%s/read_markers" % self.room_id,
f"/rooms/{self.room_id}/read_markers",
body,
access_token=self.tok,
)
@ -578,11 +644,10 @@ class UnreadMessagesTestCase(unittest.HomeserverTestCase):
self._check_unread_count(1)
# Send a read receipt to tell the server we've read the latest event.
body = json.dumps({ReadReceiptEventFields.MSC2285_HIDDEN: True}).encode("utf8")
channel = self.make_request(
"POST",
"/rooms/%s/receipt/m.read/%s" % (self.room_id, res["event_id"]),
body,
f"/rooms/{self.room_id}/receipt/org.matrix.msc2285.read.private/{res['event_id']}",
{},
access_token=self.tok,
)
self.assertEqual(channel.code, 200, channel.json_body)
@ -644,13 +709,73 @@ class UnreadMessagesTestCase(unittest.HomeserverTestCase):
self._check_unread_count(4)
# Check that tombstone events changes increase the unread counter.
self.helper.send_state(
res1 = self.helper.send_state(
self.room_id,
EventTypes.Tombstone,
{"replacement_room": "!someroom:test"},
tok=self.tok2,
)
self._check_unread_count(5)
res2 = self.helper.send(self.room_id, "hello", tok=self.tok2)
# Make sure both m.read and org.matrix.msc2285.read.private advance
channel = self.make_request(
"POST",
f"/rooms/{self.room_id}/receipt/m.read/{res1['event_id']}",
{},
access_token=self.tok,
)
self.assertEqual(channel.code, 200, channel.json_body)
self._check_unread_count(1)
channel = self.make_request(
"POST",
f"/rooms/{self.room_id}/receipt/org.matrix.msc2285.read.private/{res2['event_id']}",
{},
access_token=self.tok,
)
self.assertEqual(channel.code, 200, channel.json_body)
self._check_unread_count(0)
# We test for both receipt types that influence notification counts
@parameterized.expand([ReceiptTypes.READ, ReceiptTypes.READ_PRIVATE])
def test_read_receipts_only_go_down(self, receipt_type: ReceiptTypes) -> None:
# Join the new user
self.helper.join(room=self.room_id, user=self.user2, tok=self.tok2)
# Send messages
res1 = self.helper.send(self.room_id, "hello", tok=self.tok2)
res2 = self.helper.send(self.room_id, "hello", tok=self.tok2)
# Read last event
channel = self.make_request(
"POST",
f"/rooms/{self.room_id}/receipt/{receipt_type}/{res2['event_id']}",
{},
access_token=self.tok,
)
self.assertEqual(channel.code, 200, channel.json_body)
self._check_unread_count(0)
# Make sure neither m.read nor org.matrix.msc2285.read.private make the
# read receipt go up to an older event
channel = self.make_request(
"POST",
f"/rooms/{self.room_id}/receipt/org.matrix.msc2285.read.private/{res1['event_id']}",
{},
access_token=self.tok,
)
self.assertEqual(channel.code, 200, channel.json_body)
self._check_unread_count(0)
channel = self.make_request(
"POST",
f"/rooms/{self.room_id}/receipt/m.read/{res1['event_id']}",
{},
access_token=self.tok,
)
self.assertEqual(channel.code, 200, channel.json_body)
self._check_unread_count(0)
def _check_unread_count(self, expected_count: int) -> None:
"""Syncs and compares the unread count with the expected value."""
@ -663,9 +788,11 @@ class UnreadMessagesTestCase(unittest.HomeserverTestCase):
self.assertEqual(channel.code, 200, channel.json_body)
room_entry = channel.json_body["rooms"]["join"][self.room_id]
room_entry = (
channel.json_body.get("rooms", {}).get("join", {}).get(self.room_id, {})
)
self.assertEqual(
room_entry["org.matrix.msc2654.unread_count"],
room_entry.get("org.matrix.msc2654.unread_count", 0),
expected_count,
room_entry,
)