1111# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212# See the License for the specific language governing permissions and
1313# limitations under the License.
14+ from http import HTTPStatus
15+ from typing import Optional
16+
1417from twisted .test .proto_helpers import MemoryReactor
1518
1619import synapse .rest .admin
17- from synapse .rest .client import login , receipts , register
20+ from synapse .api .constants import EduTypes , EventTypes , HistoryVisibility , ReceiptTypes
21+ from synapse .rest .client import login , receipts , room , sync
1822from synapse .server import HomeServer
23+ from synapse .types import JsonDict
1924from synapse .util import Clock
2025
2126from tests import unittest
2429class ReceiptsTestCase (unittest .HomeserverTestCase ):
2530 servlets = [
2631 login .register_servlets ,
27- register .register_servlets ,
2832 receipts .register_servlets ,
2933 synapse .rest .admin .register_servlets ,
34+ room .register_servlets ,
35+ sync .register_servlets ,
3036 ]
3137
3238 def prepare (self , reactor : MemoryReactor , clock : Clock , hs : HomeServer ) -> None :
33- self .owner = self .register_user ("owner" , "pass" )
34- self .owner_tok = self .login ("owner" , "pass" )
39+ self .url = "/sync?since=%s"
40+ self .next_batch = "s0"
41+
42+ # Register the first user
43+ self .user_id = self .register_user ("kermit" , "monkey" )
44+ self .tok = self .login ("kermit" , "monkey" )
45+
46+ # Create the room
47+ self .room_id = self .helper .create_room_as (self .user_id , tok = self .tok )
48+
49+ # Register the second user
50+ self .user2 = self .register_user ("kermit2" , "monkey" )
51+ self .tok2 = self .login ("kermit2" , "monkey" )
52+
53+ # Join the second user
54+ self .helper .join (room = self .room_id , user = self .user2 , tok = self .tok2 )
3555
3656 def test_send_receipt (self ) -> None :
57+ # Send a message.
58+ res = self .helper .send (self .room_id , body = "hello" , tok = self .tok )
59+
60+ # Send a read receipt
61+ channel = self .make_request (
62+ "POST" ,
63+ f"/rooms/{ self .room_id } /receipt/{ ReceiptTypes .READ } /{ res ['event_id' ]} " ,
64+ {},
65+ access_token = self .tok2 ,
66+ )
67+ self .assertEqual (channel .code , 200 )
68+ self .assertNotEqual (self ._get_read_receipt (), None )
69+
70+ def test_send_receipt_unknown_event (self ) -> None :
71+ """Receipts sent for unknown events are ignored to not break message retention."""
72+ # Attempt to send a receipt to an unknown room.
3773 channel = self .make_request (
3874 "POST" ,
3975 "/rooms/!abc:beep/receipt/m.read/$def" ,
4076 content = {},
41- access_token = self .owner_tok ,
77+ access_token = self .tok2 ,
78+ )
79+ self .assertEqual (channel .code , 200 , channel .result )
80+ self .assertIsNone (self ._get_read_receipt ())
81+
82+ # Attempt to send a receipt to an unknown event.
83+ channel = self .make_request (
84+ "POST" ,
85+ f"/rooms/{ self .room_id } /receipt/m.read/$def" ,
86+ content = {},
87+ access_token = self .tok2 ,
4288 )
4389 self .assertEqual (channel .code , 200 , channel .result )
90+ self .assertIsNone (self ._get_read_receipt ())
91+
92+ def test_send_receipt_unviewable_event (self ) -> None :
93+ """Receipts sent for unviewable events are errors."""
94+ # Create a room where new users can't see events from before their join
95+ # & send events into it.
96+ room_id = self .helper .create_room_as (
97+ self .user_id ,
98+ tok = self .tok ,
99+ extra_content = {
100+ "preset" : "private_chat" ,
101+ "initial_state" : [
102+ {
103+ "content" : {"history_visibility" : HistoryVisibility .JOINED },
104+ "state_key" : "" ,
105+ "type" : EventTypes .RoomHistoryVisibility ,
106+ }
107+ ],
108+ },
109+ )
110+ res = self .helper .send (room_id , body = "hello" , tok = self .tok )
111+
112+ # Attempt to send a receipt from the wrong user.
113+ channel = self .make_request (
114+ "POST" ,
115+ f"/rooms/{ room_id } /receipt/{ ReceiptTypes .READ } /{ res ['event_id' ]} " ,
116+ content = {},
117+ access_token = self .tok2 ,
118+ )
119+ self .assertEqual (channel .code , 403 , channel .result )
120+
121+ # Join the user to the room, but they still can't see the event.
122+ self .helper .invite (room_id , self .user_id , self .user2 , tok = self .tok )
123+ self .helper .join (room = room_id , user = self .user2 , tok = self .tok2 )
124+
125+ channel = self .make_request (
126+ "POST" ,
127+ f"/rooms/{ room_id } /receipt/{ ReceiptTypes .READ } /{ res ['event_id' ]} " ,
128+ content = {},
129+ access_token = self .tok2 ,
130+ )
131+ self .assertEqual (channel .code , 403 , channel .result )
44132
45133 def test_send_receipt_invalid_room_id (self ) -> None :
46134 channel = self .make_request (
47135 "POST" ,
48136 "/rooms/not-a-room-id/receipt/m.read/$def" ,
49137 content = {},
50- access_token = self .owner_tok ,
138+ access_token = self .tok ,
51139 )
52140 self .assertEqual (channel .code , 400 , channel .result )
53141 self .assertEqual (
@@ -59,7 +147,7 @@ def test_send_receipt_invalid_event_id(self) -> None:
59147 "POST" ,
60148 "/rooms/!abc:beep/receipt/m.read/not-an-event-id" ,
61149 content = {},
62- access_token = self .owner_tok ,
150+ access_token = self .tok ,
63151 )
64152 self .assertEqual (channel .code , 400 , channel .result )
65153 self .assertEqual (
@@ -71,6 +159,123 @@ def test_send_receipt_invalid_receipt_type(self) -> None:
71159 "POST" ,
72160 "/rooms/!abc:beep/receipt/invalid-receipt-type/$def" ,
73161 content = {},
74- access_token = self .owner_tok ,
162+ access_token = self .tok ,
75163 )
76164 self .assertEqual (channel .code , 400 , channel .result )
165+
166+ def test_private_read_receipts (self ) -> None :
167+ # Send a message as the first user
168+ res = self .helper .send (self .room_id , body = "hello" , tok = self .tok )
169+
170+ # Send a private read receipt to tell the server the first user's message was read
171+ channel = self .make_request (
172+ "POST" ,
173+ f"/rooms/{ self .room_id } /receipt/{ ReceiptTypes .READ_PRIVATE } /{ res ['event_id' ]} " ,
174+ {},
175+ access_token = self .tok2 ,
176+ )
177+ self .assertEqual (channel .code , 200 )
178+
179+ # Test that the first user can't see the other user's private read receipt
180+ self .assertIsNone (self ._get_read_receipt ())
181+
182+ def test_public_receipt_can_override_private (self ) -> None :
183+ """
184+ Sending a public read receipt to the same event which has a private read
185+ receipt should cause that receipt to become public.
186+ """
187+ # Send a message as the first user
188+ res = self .helper .send (self .room_id , body = "hello" , tok = self .tok )
189+
190+ # Send a private read receipt
191+ channel = self .make_request (
192+ "POST" ,
193+ f"/rooms/{ self .room_id } /receipt/{ ReceiptTypes .READ_PRIVATE } /{ res ['event_id' ]} " ,
194+ {},
195+ access_token = self .tok2 ,
196+ )
197+ self .assertEqual (channel .code , 200 )
198+ self .assertIsNone (self ._get_read_receipt ())
199+
200+ # Send a public read receipt
201+ channel = self .make_request (
202+ "POST" ,
203+ f"/rooms/{ self .room_id } /receipt/{ ReceiptTypes .READ } /{ res ['event_id' ]} " ,
204+ {},
205+ access_token = self .tok2 ,
206+ )
207+ self .assertEqual (channel .code , 200 )
208+
209+ # Test that we did override the private read receipt
210+ self .assertNotEqual (self ._get_read_receipt (), None )
211+
212+ def test_private_receipt_cannot_override_public (self ) -> None :
213+ """
214+ Sending a private read receipt to the same event which has a public read
215+ receipt should cause no change.
216+ """
217+ # Send a message as the first user
218+ res = self .helper .send (self .room_id , body = "hello" , tok = self .tok )
219+
220+ # Send a public read receipt
221+ channel = self .make_request (
222+ "POST" ,
223+ f"/rooms/{ self .room_id } /receipt/{ ReceiptTypes .READ } /{ res ['event_id' ]} " ,
224+ {},
225+ access_token = self .tok2 ,
226+ )
227+ self .assertEqual (channel .code , 200 )
228+ self .assertNotEqual (self ._get_read_receipt (), None )
229+
230+ # Send a private read receipt
231+ channel = self .make_request (
232+ "POST" ,
233+ f"/rooms/{ self .room_id } /receipt/{ ReceiptTypes .READ_PRIVATE } /{ res ['event_id' ]} " ,
234+ {},
235+ access_token = self .tok2 ,
236+ )
237+ self .assertEqual (channel .code , 200 )
238+
239+ # Test that we didn't override the public read receipt
240+ self .assertIsNone (self ._get_read_receipt ())
241+
242+ def test_read_receipt_with_empty_body_is_rejected (self ) -> None :
243+ # Send a message as the first user
244+ res = self .helper .send (self .room_id , body = "hello" , tok = self .tok )
245+
246+ # Send a read receipt for this message with an empty body
247+ channel = self .make_request (
248+ "POST" ,
249+ f"/rooms/{ self .room_id } /receipt/m.read/{ res ['event_id' ]} " ,
250+ access_token = self .tok2 ,
251+ )
252+ self .assertEqual (channel .code , HTTPStatus .BAD_REQUEST )
253+ self .assertEqual (channel .json_body ["errcode" ], "M_NOT_JSON" , channel .json_body )
254+
255+ def _get_read_receipt (self ) -> Optional [JsonDict ]:
256+ """Syncs and returns the read receipt."""
257+
258+ # Checks if event is a read receipt
259+ def is_read_receipt (event : JsonDict ) -> bool :
260+ return event ["type" ] == EduTypes .RECEIPT
261+
262+ # Sync
263+ channel = self .make_request (
264+ "GET" ,
265+ self .url % self .next_batch ,
266+ access_token = self .tok ,
267+ )
268+ self .assertEqual (channel .code , 200 )
269+
270+ # Store the next batch for the next request.
271+ self .next_batch = channel .json_body ["next_batch" ]
272+
273+ if channel .json_body .get ("rooms" , None ) is None :
274+ return None
275+
276+ # Return the read receipt
277+ ephemeral_events = channel .json_body ["rooms" ]["join" ][self .room_id ][
278+ "ephemeral"
279+ ]["events" ]
280+ receipt_event = filter (is_read_receipt , ephemeral_events )
281+ return next (receipt_event , None )
0 commit comments