@@ -7,6 +7,7 @@ use std::{
7
7
use assert_matches:: assert_matches;
8
8
use assert_matches2:: assert_let;
9
9
use matrix_sdk:: {
10
+ ThreadingSupport ,
10
11
config:: SyncSettings ,
11
12
test_utils:: { logged_in_client_with_server, mocks:: MatrixMockServer } ,
12
13
} ;
@@ -23,8 +24,8 @@ use matrix_sdk_ui::{
23
24
} ;
24
25
use ruma:: {
25
26
RoomVersionId , event_id,
26
- events:: { TimelineEventType , room:: member:: MembershipState } ,
27
- mxc_uri, room_id, user_id,
27
+ events:: { Mentions , TimelineEventType , room:: member:: MembershipState } ,
28
+ mxc_uri, owned_user_id , room_id, user_id,
28
29
} ;
29
30
use serde_json:: json;
30
31
use wiremock:: {
@@ -114,6 +115,234 @@ async fn test_notification_client_with_context() {
114
115
assert_eq ! ( item. sender_avatar_url, Some ( sender_avatar_url. to_string( ) ) ) ;
115
116
}
116
117
118
+ #[ async_test]
119
+ async fn test_subscribed_threads_get_notifications ( ) {
120
+ let server = MatrixMockServer :: new ( ) . await ;
121
+ let client = server
122
+ . client_builder ( )
123
+ . on_builder ( |builder| {
124
+ builder. with_threading_support ( ThreadingSupport :: Enabled { with_subscriptions : true } )
125
+ } )
126
+ . build ( )
127
+ . await ;
128
+
129
+ let sender = user_id ! ( "@user:example.org" ) ;
130
+ let room_id = room_id ! ( "!a98sd12bjh:example.org" ) ;
131
+ let f = EventFactory :: new ( ) . room ( room_id) . sender ( sender) ;
132
+
133
+ // First, mock an empty sync so the room is known.
134
+ server. mock_room_state_encryption ( ) . plain ( ) . mount ( ) . await ;
135
+
136
+ // To have access to the push rules context, we must know the own's member
137
+ // event.
138
+ let room = server
139
+ . sync_room (
140
+ & client,
141
+ JoinedRoomBuilder :: new ( room_id) . add_state_event (
142
+ f. member ( client. user_id ( ) . unwrap ( ) )
143
+ . membership ( MembershipState :: Join )
144
+ . display_name ( "Jean-Michel Rouille" ) ,
145
+ ) ,
146
+ )
147
+ . await ;
148
+
149
+ // Sanity check: we can create push rules context.
150
+ room. push_context ( ) . await . unwrap ( ) . unwrap ( ) ;
151
+
152
+ // Create a notification client.
153
+ let sync_service = Arc :: new ( SyncService :: builder ( client. clone ( ) ) . build ( ) . await . unwrap ( ) ) ;
154
+ let process_setup = NotificationProcessSetup :: SingleProcess { sync_service } ;
155
+ let notification_client = NotificationClient :: new ( client, process_setup) . await . unwrap ( ) ;
156
+
157
+ // For a thread I'm subscribed to,
158
+ let thread_root = event_id ! ( "$thread_root" ) ;
159
+ server
160
+ . mock_get_thread_subscription ( )
161
+ . match_thread_id ( thread_root. to_owned ( ) )
162
+ . ok ( false )
163
+ . expect ( 2 )
164
+ . mount ( )
165
+ . await ;
166
+
167
+ let sender_member_event =
168
+ f. member ( sender) . membership ( MembershipState :: Join ) . display_name ( "John Diaspora" ) . into_raw ( ) ;
169
+
170
+ // Considering an in-thread message,
171
+ let in_thread_event = {
172
+ let event_id = event_id ! ( "$example_event_id" ) ;
173
+ let event = f
174
+ . text_msg ( "hello to you too!" )
175
+ . event_id ( event_id)
176
+ . server_ts ( 152049794 )
177
+ . in_thread ( thread_root, thread_root)
178
+ . into_event ( ) ;
179
+
180
+ server
181
+ . mock_room_event_context ( )
182
+ . match_event_id ( )
183
+ . ok ( event. clone ( ) , "" , "" , vec ! [ sender_member_event. clone( ) ] )
184
+ . mock_once ( )
185
+ . mount ( )
186
+ . await ;
187
+
188
+ // Then I get a notification for this message.
189
+ let item =
190
+ notification_client. get_notification_with_context ( room_id, event_id) . await . unwrap ( ) ;
191
+ assert_matches ! ( item, NotificationStatus :: Event ( ..) ) ;
192
+
193
+ event
194
+ } ;
195
+
196
+ // Considering the thread root event,
197
+ let event = f
198
+ . text_msg ( "hello world" )
199
+ . event_id ( thread_root)
200
+ . server_ts ( 152049793 )
201
+ . with_bundled_thread_summary ( in_thread_event. raw ( ) . clone ( ) . cast_unchecked ( ) , 1 , false )
202
+ . into_event ( ) ;
203
+
204
+ server
205
+ . mock_room_event_context ( )
206
+ . match_event_id ( )
207
+ . ok ( event. clone ( ) , "" , "" , vec ! [ sender_member_event] )
208
+ . mock_once ( )
209
+ . mount ( )
210
+ . await ;
211
+
212
+ // Then I get a notification for the thread root as well.
213
+ let item =
214
+ notification_client. get_notification_with_context ( room_id, thread_root) . await . unwrap ( ) ;
215
+ assert_matches ! ( item, NotificationStatus :: Event ( ..) ) ;
216
+ }
217
+
218
+ #[ async_test]
219
+ async fn test_unsubscribed_threads_get_notifications ( ) {
220
+ let server = MatrixMockServer :: new ( ) . await ;
221
+ let client = server
222
+ . client_builder ( )
223
+ . on_builder ( |builder| {
224
+ builder. with_threading_support ( ThreadingSupport :: Enabled { with_subscriptions : true } )
225
+ } )
226
+ . build ( )
227
+ . await ;
228
+
229
+ let sender = user_id ! ( "@user:example.org" ) ;
230
+ let room_id = room_id ! ( "!a98sd12bjh:example.org" ) ;
231
+ let f = EventFactory :: new ( ) . room ( room_id) . sender ( sender) ;
232
+
233
+ // First, mock an empty sync so the room is known.
234
+ server. mock_room_state_encryption ( ) . plain ( ) . mount ( ) . await ;
235
+
236
+ // To have access to the push rules context, we must know the own's member
237
+ // event.
238
+ let room = server
239
+ . sync_room (
240
+ & client,
241
+ JoinedRoomBuilder :: new ( room_id) . add_state_event (
242
+ f. member ( client. user_id ( ) . unwrap ( ) )
243
+ . membership ( MembershipState :: Join )
244
+ . display_name ( "Jean-Michel Rouille" ) ,
245
+ ) ,
246
+ )
247
+ . await ;
248
+
249
+ // Sanity check: we can create push rules context.
250
+ room. push_context ( ) . await . unwrap ( ) . unwrap ( ) ;
251
+
252
+ // Create a notification client.
253
+ let sync_service = Arc :: new ( SyncService :: builder ( client. clone ( ) ) . build ( ) . await . unwrap ( ) ) ;
254
+ let process_setup = NotificationProcessSetup :: SingleProcess { sync_service } ;
255
+ let notification_client = NotificationClient :: new ( client. clone ( ) , process_setup) . await . unwrap ( ) ;
256
+
257
+ // For a thread with an unknown subscription status (note: we're not mocking the
258
+ // get endpoint, since 404 is equivalent to no thread status),
259
+ let thread_root = event_id ! ( "$thread_root" ) ;
260
+
261
+ let sender_member_event =
262
+ f. member ( sender) . membership ( MembershipState :: Join ) . display_name ( "John Diaspora" ) . into_raw ( ) ;
263
+
264
+ // Considering a random in-thread message,
265
+ let first_thread_response = event_id ! ( "$thread_response1" ) ;
266
+ let in_thread_event = {
267
+ // Note: contains a mention, but not of me.
268
+ let event = f
269
+ . text_msg ( "hello to you too!" )
270
+ . event_id ( first_thread_response)
271
+ . server_ts ( 152049794 )
272
+ . in_thread ( thread_root, thread_root)
273
+ . mentions ( Mentions :: with_user_ids ( [ owned_user_id ! ( "@rando:example.org" ) ] ) )
274
+ . into_event ( ) ;
275
+
276
+ server
277
+ . mock_room_event_context ( )
278
+ . match_event_id ( )
279
+ . ok ( event. clone ( ) , "" , "" , vec ! [ sender_member_event. clone( ) ] )
280
+ . mock_once ( )
281
+ . mount ( )
282
+ . await ;
283
+
284
+ // Then I don't get a notification for it.
285
+ let item = notification_client
286
+ . get_notification_with_context ( room_id, first_thread_response)
287
+ . await
288
+ . unwrap ( ) ;
289
+ assert_matches ! ( item, NotificationStatus :: EventFilteredOut ) ;
290
+
291
+ event
292
+ } ;
293
+
294
+ // Considering the thread root event,
295
+ {
296
+ let event = f
297
+ . text_msg ( "hello world" )
298
+ . event_id ( thread_root)
299
+ . server_ts ( 152049793 )
300
+ . with_bundled_thread_summary ( in_thread_event. raw ( ) . clone ( ) . cast_unchecked ( ) , 1 , false )
301
+ . into_event ( ) ;
302
+
303
+ server
304
+ . mock_room_event_context ( )
305
+ . match_event_id ( )
306
+ . ok ( event. clone ( ) , "" , "" , vec ! [ sender_member_event. clone( ) ] )
307
+ . mock_once ( )
308
+ . mount ( )
309
+ . await ;
310
+
311
+ // I do get a notification about it, because it's not technically in the thread.
312
+ let item =
313
+ notification_client. get_notification_with_context ( room_id, thread_root) . await . unwrap ( ) ;
314
+ assert_matches ! ( item, NotificationStatus :: Event ( ..) ) ;
315
+ }
316
+
317
+ // But if a new in-thread event mentions me, then I would get subscribed,
318
+ {
319
+ let thread_response2 = event_id ! ( "$thread_response2" ) ;
320
+ // Note: event mentions me.
321
+ let event = f
322
+ . text_msg ( "hello world" )
323
+ . event_id ( thread_response2)
324
+ . server_ts ( 152049793 )
325
+ . in_thread ( thread_root, first_thread_response)
326
+ . mentions ( Mentions :: with_user_ids ( vec ! [ client. user_id( ) . unwrap( ) . to_owned( ) ] ) )
327
+ . into_event ( ) ;
328
+
329
+ server
330
+ . mock_room_event_context ( )
331
+ . match_event_id ( )
332
+ . ok ( event. clone ( ) , "" , "" , vec ! [ sender_member_event] )
333
+ . mock_once ( )
334
+ . mount ( )
335
+ . await ;
336
+
337
+ // Then I do get a notification for the thread root either.
338
+ let item = notification_client
339
+ . get_notification_with_context ( room_id, thread_response2)
340
+ . await
341
+ . unwrap ( ) ;
342
+ assert_matches ! ( item, NotificationStatus :: Event ( ..) ) ;
343
+ }
344
+ }
345
+
117
346
#[ async_test]
118
347
async fn test_notification_client_sliding_sync ( ) {
119
348
let room_id = room_id ! ( "!a98sd12bjh:example.org" ) ;
@@ -929,7 +1158,12 @@ async fn test_notification_client_context_filters_out_events_from_ignored_users(
929
1158
. into_event ( ) ;
930
1159
931
1160
// Mock the /context response
932
- server. mock_room_event_context ( ) . ok ( event, "start" , "end" ) . mock_once ( ) . mount ( ) . await ;
1161
+ server
1162
+ . mock_room_event_context ( )
1163
+ . ok ( event, "start" , "end" , Vec :: new ( ) )
1164
+ . mock_once ( )
1165
+ . mount ( )
1166
+ . await ;
933
1167
934
1168
let dummy_sync_service = Arc :: new ( SyncService :: builder ( client. clone ( ) ) . build ( ) . await . unwrap ( ) ) ;
935
1169
let process_setup =
0 commit comments