11
11
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
12
# See the License for the specific language governing permissions and
13
13
# limitations under the License.
14
+ from http import HTTPStatus
15
+ from typing import Optional
16
+
14
17
from twisted .test .proto_helpers import MemoryReactor
15
18
16
19
import 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
18
22
from synapse .server import HomeServer
23
+ from synapse .types import JsonDict
19
24
from synapse .util import Clock
20
25
21
26
from tests import unittest
24
29
class ReceiptsTestCase (unittest .HomeserverTestCase ):
25
30
servlets = [
26
31
login .register_servlets ,
27
- register .register_servlets ,
28
32
receipts .register_servlets ,
29
33
synapse .rest .admin .register_servlets ,
34
+ room .register_servlets ,
35
+ sync .register_servlets ,
30
36
]
31
37
32
38
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 )
35
55
36
56
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.
37
73
channel = self .make_request (
38
74
"POST" ,
39
75
"/rooms/!abc:beep/receipt/m.read/$def" ,
40
76
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 ,
42
88
)
43
89
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 )
44
132
45
133
def test_send_receipt_invalid_room_id (self ) -> None :
46
134
channel = self .make_request (
47
135
"POST" ,
48
136
"/rooms/not-a-room-id/receipt/m.read/$def" ,
49
137
content = {},
50
- access_token = self .owner_tok ,
138
+ access_token = self .tok ,
51
139
)
52
140
self .assertEqual (channel .code , 400 , channel .result )
53
141
self .assertEqual (
@@ -59,7 +147,7 @@ def test_send_receipt_invalid_event_id(self) -> None:
59
147
"POST" ,
60
148
"/rooms/!abc:beep/receipt/m.read/not-an-event-id" ,
61
149
content = {},
62
- access_token = self .owner_tok ,
150
+ access_token = self .tok ,
63
151
)
64
152
self .assertEqual (channel .code , 400 , channel .result )
65
153
self .assertEqual (
@@ -71,6 +159,123 @@ def test_send_receipt_invalid_receipt_type(self) -> None:
71
159
"POST" ,
72
160
"/rooms/!abc:beep/receipt/invalid-receipt-type/$def" ,
73
161
content = {},
74
- access_token = self .owner_tok ,
162
+ access_token = self .tok ,
75
163
)
76
164
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