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:
@@ -15,7 +15,7 @@
|
||||
|
||||
from typing import List
|
||||
|
||||
from synapse.api.constants import ReadReceiptEventFields, ReceiptTypes
|
||||
from synapse.api.constants import ReceiptTypes
|
||||
from synapse.types import JsonDict
|
||||
|
||||
from tests import unittest
|
||||
@@ -25,20 +25,15 @@ class ReceiptsTestCase(unittest.HomeserverTestCase):
|
||||
def prepare(self, reactor, clock, hs):
|
||||
self.event_source = hs.get_event_sources().sources.receipt
|
||||
|
||||
# In the first param of _test_filters_hidden we use "hidden" instead of
|
||||
# ReadReceiptEventFields.MSC2285_HIDDEN. We do this because we're mocking
|
||||
# the data from the database which doesn't use the prefix
|
||||
|
||||
def test_filters_out_hidden_receipt(self):
|
||||
self._test_filters_hidden(
|
||||
[
|
||||
{
|
||||
"content": {
|
||||
"$1435641916114394fHBLK:matrix.org": {
|
||||
ReceiptTypes.READ: {
|
||||
ReceiptTypes.READ_PRIVATE: {
|
||||
"@rikj:jki.re": {
|
||||
"ts": 1436451550453,
|
||||
"hidden": True,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -50,58 +45,23 @@ class ReceiptsTestCase(unittest.HomeserverTestCase):
|
||||
[],
|
||||
)
|
||||
|
||||
def test_does_not_filter_out_our_hidden_receipt(self):
|
||||
self._test_filters_hidden(
|
||||
[
|
||||
{
|
||||
"content": {
|
||||
"$1435641916hfgh4394fHBLK:matrix.org": {
|
||||
ReceiptTypes.READ: {
|
||||
"@me:server.org": {
|
||||
"ts": 1436451550453,
|
||||
"hidden": True,
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
||||
"type": "m.receipt",
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"content": {
|
||||
"$1435641916hfgh4394fHBLK:matrix.org": {
|
||||
ReceiptTypes.READ: {
|
||||
"@me:server.org": {
|
||||
"ts": 1436451550453,
|
||||
ReadReceiptEventFields.MSC2285_HIDDEN: True,
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
||||
"type": "m.receipt",
|
||||
}
|
||||
],
|
||||
)
|
||||
|
||||
def test_filters_out_hidden_receipt_and_ignores_rest(self):
|
||||
self._test_filters_hidden(
|
||||
[
|
||||
{
|
||||
"content": {
|
||||
"$1dgdgrd5641916114394fHBLK:matrix.org": {
|
||||
ReceiptTypes.READ: {
|
||||
ReceiptTypes.READ_PRIVATE: {
|
||||
"@rikj:jki.re": {
|
||||
"ts": 1436451550453,
|
||||
"hidden": True,
|
||||
},
|
||||
},
|
||||
ReceiptTypes.READ: {
|
||||
"@user:jki.re": {
|
||||
"ts": 1436451550453,
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
||||
"type": "m.receipt",
|
||||
@@ -130,10 +90,9 @@ class ReceiptsTestCase(unittest.HomeserverTestCase):
|
||||
{
|
||||
"content": {
|
||||
"$14356419edgd14394fHBLK:matrix.org": {
|
||||
ReceiptTypes.READ: {
|
||||
ReceiptTypes.READ_PRIVATE: {
|
||||
"@rikj:jki.re": {
|
||||
"ts": 1436451550453,
|
||||
"hidden": True,
|
||||
},
|
||||
}
|
||||
},
|
||||
@@ -223,7 +182,6 @@ class ReceiptsTestCase(unittest.HomeserverTestCase):
|
||||
[
|
||||
{
|
||||
"content": {
|
||||
"$143564gdfg6114394fHBLK:matrix.org": {},
|
||||
"$1435641916114394fHBLK:matrix.org": {
|
||||
ReceiptTypes.READ: {
|
||||
"@user:jki.re": {
|
||||
@@ -244,10 +202,9 @@ class ReceiptsTestCase(unittest.HomeserverTestCase):
|
||||
{
|
||||
"content": {
|
||||
"$14356419edgd14394fHBLK:matrix.org": {
|
||||
ReceiptTypes.READ: {
|
||||
ReceiptTypes.READ_PRIVATE: {
|
||||
"@rikj:jki.re": {
|
||||
"ts": 1436451550453,
|
||||
"hidden": True,
|
||||
},
|
||||
}
|
||||
},
|
||||
@@ -306,7 +263,73 @@ class ReceiptsTestCase(unittest.HomeserverTestCase):
|
||||
"type": "m.receipt",
|
||||
},
|
||||
],
|
||||
[],
|
||||
[
|
||||
{
|
||||
"content": {
|
||||
"$14356419edgd14394fHBLK:matrix.org": {
|
||||
ReceiptTypes.READ: {
|
||||
"@rikj:jki.re": "string",
|
||||
}
|
||||
},
|
||||
},
|
||||
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
||||
"type": "m.receipt",
|
||||
},
|
||||
],
|
||||
)
|
||||
|
||||
def test_leaves_our_hidden_and_their_public(self):
|
||||
self._test_filters_hidden(
|
||||
[
|
||||
{
|
||||
"content": {
|
||||
"$1dgdgrd5641916114394fHBLK:matrix.org": {
|
||||
ReceiptTypes.READ_PRIVATE: {
|
||||
"@me:server.org": {
|
||||
"ts": 1436451550453,
|
||||
},
|
||||
},
|
||||
ReceiptTypes.READ: {
|
||||
"@rikj:jki.re": {
|
||||
"ts": 1436451550453,
|
||||
},
|
||||
},
|
||||
"a.receipt.type": {
|
||||
"@rikj:jki.re": {
|
||||
"ts": 1436451550453,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
||||
"type": "m.receipt",
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"content": {
|
||||
"$1dgdgrd5641916114394fHBLK:matrix.org": {
|
||||
ReceiptTypes.READ_PRIVATE: {
|
||||
"@me:server.org": {
|
||||
"ts": 1436451550453,
|
||||
},
|
||||
},
|
||||
ReceiptTypes.READ: {
|
||||
"@rikj:jki.re": {
|
||||
"ts": 1436451550453,
|
||||
},
|
||||
},
|
||||
"a.receipt.type": {
|
||||
"@rikj:jki.re": {
|
||||
"ts": 1436451550453,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
||||
"type": "m.receipt",
|
||||
}
|
||||
],
|
||||
)
|
||||
|
||||
def _test_filters_hidden(
|
||||
|
||||
@@ -14,26 +14,246 @@
|
||||
|
||||
from synapse.api.constants import ReceiptTypes
|
||||
from synapse.replication.slave.storage.receipts import SlavedReceiptsStore
|
||||
from synapse.types import UserID, create_requester
|
||||
|
||||
from tests.test_utils.event_injection import create_event
|
||||
|
||||
from ._base import BaseSlavedStoreTestCase
|
||||
|
||||
USER_ID = "@feeling:blue"
|
||||
ROOM_ID = "!room:blue"
|
||||
EVENT_ID = "$event:blue"
|
||||
OTHER_USER_ID = "@other:test"
|
||||
OUR_USER_ID = "@our:test"
|
||||
|
||||
|
||||
class SlavedReceiptTestCase(BaseSlavedStoreTestCase):
|
||||
|
||||
STORE_TYPE = SlavedReceiptsStore
|
||||
|
||||
def test_receipt(self):
|
||||
self.check("get_receipts_for_user", [USER_ID, ReceiptTypes.READ], {})
|
||||
self.get_success(
|
||||
self.master_store.insert_receipt(
|
||||
ROOM_ID, ReceiptTypes.READ, USER_ID, [EVENT_ID], {}
|
||||
def prepare(self, reactor, clock, homeserver):
|
||||
super().prepare(reactor, clock, homeserver)
|
||||
self.room_creator = homeserver.get_room_creation_handler()
|
||||
self.persist_event_storage = self.hs.get_storage().persistence
|
||||
|
||||
# Create a test user
|
||||
self.ourUser = UserID.from_string(OUR_USER_ID)
|
||||
self.ourRequester = create_requester(self.ourUser)
|
||||
|
||||
# Create a second test user
|
||||
self.otherUser = UserID.from_string(OTHER_USER_ID)
|
||||
self.otherRequester = create_requester(self.otherUser)
|
||||
|
||||
# Create a test room
|
||||
info, _ = self.get_success(self.room_creator.create_room(self.ourRequester, {}))
|
||||
self.room_id1 = info["room_id"]
|
||||
|
||||
# Create a second test room
|
||||
info, _ = self.get_success(self.room_creator.create_room(self.ourRequester, {}))
|
||||
self.room_id2 = info["room_id"]
|
||||
|
||||
# Join the second user to the first room
|
||||
memberEvent, memberEventContext = self.get_success(
|
||||
create_event(
|
||||
self.hs,
|
||||
room_id=self.room_id1,
|
||||
type="m.room.member",
|
||||
sender=self.otherRequester.user.to_string(),
|
||||
state_key=self.otherRequester.user.to_string(),
|
||||
content={"membership": "join"},
|
||||
)
|
||||
)
|
||||
self.replicate()
|
||||
self.check(
|
||||
"get_receipts_for_user", [USER_ID, ReceiptTypes.READ], {ROOM_ID: EVENT_ID}
|
||||
self.get_success(
|
||||
self.persist_event_storage.persist_event(memberEvent, memberEventContext)
|
||||
)
|
||||
|
||||
# Join the second user to the second room
|
||||
memberEvent, memberEventContext = self.get_success(
|
||||
create_event(
|
||||
self.hs,
|
||||
room_id=self.room_id2,
|
||||
type="m.room.member",
|
||||
sender=self.otherRequester.user.to_string(),
|
||||
state_key=self.otherRequester.user.to_string(),
|
||||
content={"membership": "join"},
|
||||
)
|
||||
)
|
||||
self.get_success(
|
||||
self.persist_event_storage.persist_event(memberEvent, memberEventContext)
|
||||
)
|
||||
|
||||
def test_return_empty_with_no_data(self):
|
||||
res = self.get_success(
|
||||
self.master_store.get_receipts_for_user(
|
||||
OUR_USER_ID, [ReceiptTypes.READ, ReceiptTypes.READ_PRIVATE]
|
||||
)
|
||||
)
|
||||
self.assertEqual(res, {})
|
||||
|
||||
res = self.get_success(
|
||||
self.master_store.get_receipts_for_user_with_orderings(
|
||||
OUR_USER_ID,
|
||||
[ReceiptTypes.READ, ReceiptTypes.READ_PRIVATE],
|
||||
)
|
||||
)
|
||||
self.assertEqual(res, {})
|
||||
|
||||
res = self.get_success(
|
||||
self.master_store.get_last_receipt_event_id_for_user(
|
||||
OUR_USER_ID,
|
||||
self.room_id1,
|
||||
[ReceiptTypes.READ, ReceiptTypes.READ_PRIVATE],
|
||||
)
|
||||
)
|
||||
self.assertEqual(res, None)
|
||||
|
||||
def test_get_receipts_for_user(self):
|
||||
# Send some events into the first room
|
||||
event1_1_id = self.create_and_send_event(
|
||||
self.room_id1, UserID.from_string(OTHER_USER_ID)
|
||||
)
|
||||
event1_2_id = self.create_and_send_event(
|
||||
self.room_id1, UserID.from_string(OTHER_USER_ID)
|
||||
)
|
||||
|
||||
# Send public read receipt for the first event
|
||||
self.get_success(
|
||||
self.master_store.insert_receipt(
|
||||
self.room_id1, ReceiptTypes.READ, OUR_USER_ID, [event1_1_id], {}
|
||||
)
|
||||
)
|
||||
# Send private read receipt for the second event
|
||||
self.get_success(
|
||||
self.master_store.insert_receipt(
|
||||
self.room_id1, ReceiptTypes.READ_PRIVATE, OUR_USER_ID, [event1_2_id], {}
|
||||
)
|
||||
)
|
||||
|
||||
# Test we get the latest event when we want both private and public receipts
|
||||
res = self.get_success(
|
||||
self.master_store.get_receipts_for_user(
|
||||
OUR_USER_ID, [ReceiptTypes.READ, ReceiptTypes.READ_PRIVATE]
|
||||
)
|
||||
)
|
||||
self.assertEqual(res, {self.room_id1: event1_2_id})
|
||||
|
||||
# Test we get the older event when we want only public receipt
|
||||
res = self.get_success(
|
||||
self.master_store.get_receipts_for_user(OUR_USER_ID, [ReceiptTypes.READ])
|
||||
)
|
||||
self.assertEqual(res, {self.room_id1: event1_1_id})
|
||||
|
||||
# Test we get the latest event when we want only the public receipt
|
||||
res = self.get_success(
|
||||
self.master_store.get_receipts_for_user(
|
||||
OUR_USER_ID, [ReceiptTypes.READ_PRIVATE]
|
||||
)
|
||||
)
|
||||
self.assertEqual(res, {self.room_id1: event1_2_id})
|
||||
|
||||
# Test receipt updating
|
||||
self.get_success(
|
||||
self.master_store.insert_receipt(
|
||||
self.room_id1, ReceiptTypes.READ, OUR_USER_ID, [event1_2_id], {}
|
||||
)
|
||||
)
|
||||
res = self.get_success(
|
||||
self.master_store.get_receipts_for_user(OUR_USER_ID, [ReceiptTypes.READ])
|
||||
)
|
||||
self.assertEqual(res, {self.room_id1: event1_2_id})
|
||||
|
||||
# Send some events into the second room
|
||||
event2_1_id = self.create_and_send_event(
|
||||
self.room_id2, UserID.from_string(OTHER_USER_ID)
|
||||
)
|
||||
|
||||
# Test new room is reflected in what the method returns
|
||||
self.get_success(
|
||||
self.master_store.insert_receipt(
|
||||
self.room_id2, ReceiptTypes.READ_PRIVATE, OUR_USER_ID, [event2_1_id], {}
|
||||
)
|
||||
)
|
||||
res = self.get_success(
|
||||
self.master_store.get_receipts_for_user(
|
||||
OUR_USER_ID, [ReceiptTypes.READ, ReceiptTypes.READ_PRIVATE]
|
||||
)
|
||||
)
|
||||
self.assertEqual(res, {self.room_id1: event1_2_id, self.room_id2: event2_1_id})
|
||||
|
||||
def test_get_last_receipt_event_id_for_user(self):
|
||||
# Send some events into the first room
|
||||
event1_1_id = self.create_and_send_event(
|
||||
self.room_id1, UserID.from_string(OTHER_USER_ID)
|
||||
)
|
||||
event1_2_id = self.create_and_send_event(
|
||||
self.room_id1, UserID.from_string(OTHER_USER_ID)
|
||||
)
|
||||
|
||||
# Send public read receipt for the first event
|
||||
self.get_success(
|
||||
self.master_store.insert_receipt(
|
||||
self.room_id1, ReceiptTypes.READ, OUR_USER_ID, [event1_1_id], {}
|
||||
)
|
||||
)
|
||||
# Send private read receipt for the second event
|
||||
self.get_success(
|
||||
self.master_store.insert_receipt(
|
||||
self.room_id1, ReceiptTypes.READ_PRIVATE, OUR_USER_ID, [event1_2_id], {}
|
||||
)
|
||||
)
|
||||
|
||||
# Test we get the latest event when we want both private and public receipts
|
||||
res = self.get_success(
|
||||
self.master_store.get_last_receipt_event_id_for_user(
|
||||
OUR_USER_ID,
|
||||
self.room_id1,
|
||||
[ReceiptTypes.READ, ReceiptTypes.READ_PRIVATE],
|
||||
)
|
||||
)
|
||||
self.assertEqual(res, event1_2_id)
|
||||
|
||||
# Test we get the older event when we want only public receipt
|
||||
res = self.get_success(
|
||||
self.master_store.get_last_receipt_event_id_for_user(
|
||||
OUR_USER_ID, self.room_id1, [ReceiptTypes.READ]
|
||||
)
|
||||
)
|
||||
self.assertEqual(res, event1_1_id)
|
||||
|
||||
# Test we get the latest event when we want only the private receipt
|
||||
res = self.get_success(
|
||||
self.master_store.get_last_receipt_event_id_for_user(
|
||||
OUR_USER_ID, self.room_id1, [ReceiptTypes.READ_PRIVATE]
|
||||
)
|
||||
)
|
||||
self.assertEqual(res, event1_2_id)
|
||||
|
||||
# Test receipt updating
|
||||
self.get_success(
|
||||
self.master_store.insert_receipt(
|
||||
self.room_id1, ReceiptTypes.READ, OUR_USER_ID, [event1_2_id], {}
|
||||
)
|
||||
)
|
||||
res = self.get_success(
|
||||
self.master_store.get_last_receipt_event_id_for_user(
|
||||
OUR_USER_ID, self.room_id1, [ReceiptTypes.READ]
|
||||
)
|
||||
)
|
||||
self.assertEqual(res, event1_2_id)
|
||||
|
||||
# Send some events into the second room
|
||||
event2_1_id = self.create_and_send_event(
|
||||
self.room_id2, UserID.from_string(OTHER_USER_ID)
|
||||
)
|
||||
|
||||
# Test new room is reflected in what the method returns
|
||||
self.get_success(
|
||||
self.master_store.insert_receipt(
|
||||
self.room_id2, ReceiptTypes.READ_PRIVATE, OUR_USER_ID, [event2_1_id], {}
|
||||
)
|
||||
)
|
||||
res = self.get_success(
|
||||
self.master_store.get_last_receipt_event_id_for_user(
|
||||
OUR_USER_ID,
|
||||
self.room_id2,
|
||||
[ReceiptTypes.READ, ReceiptTypes.READ_PRIVATE],
|
||||
)
|
||||
)
|
||||
self.assertEqual(res, event2_1_id)
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user