Skip to content

Commit b585963

Browse files
committed
test(notification client): check msc4306 behavior in the notification client too
1 parent 5719fde commit b585963

File tree

3 files changed

+247
-6
lines changed

3 files changed

+247
-6
lines changed

crates/matrix-sdk-ui/tests/integration/notification_client.rs

Lines changed: 237 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use std::{
77
use assert_matches::assert_matches;
88
use assert_matches2::assert_let;
99
use matrix_sdk::{
10+
ThreadingSupport,
1011
config::SyncSettings,
1112
test_utils::{logged_in_client_with_server, mocks::MatrixMockServer},
1213
};
@@ -23,8 +24,8 @@ use matrix_sdk_ui::{
2324
};
2425
use ruma::{
2526
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,
2829
};
2930
use serde_json::json;
3031
use wiremock::{
@@ -114,6 +115,234 @@ async fn test_notification_client_with_context() {
114115
assert_eq!(item.sender_avatar_url, Some(sender_avatar_url.to_string()));
115116
}
116117

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+
117346
#[async_test]
118347
async fn test_notification_client_sliding_sync() {
119348
let room_id = room_id!("!a98sd12bjh:example.org");
@@ -929,7 +1158,12 @@ async fn test_notification_client_context_filters_out_events_from_ignored_users(
9291158
.into_event();
9301159

9311160
// 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;
9331167

9341168
let dummy_sync_service = Arc::new(SyncService::builder(client.clone()).build().await.unwrap());
9351169
let process_setup =

crates/matrix-sdk/src/test_utils/mocks/mod.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2505,15 +2505,16 @@ impl<'a> MockEndpoint<'a, RoomEventContextEndpoint> {
25052505
self
25062506
}
25072507

2508-
/// Returns an endpoint that emulates success
2508+
/// Returns an endpoint that emulates success.
25092509
pub fn ok(
25102510
self,
25112511
event: TimelineEvent,
25122512
start: impl Into<String>,
25132513
end: impl Into<String>,
2514+
state_events: Vec<Raw<AnyStateEvent>>,
25142515
) -> MatrixMock<'a> {
25152516
let event_path = if self.endpoint.match_event_id {
2516-
let event_id = event.kind.event_id().expect("an event id is required");
2517+
let event_id = event.event_id().expect("an event id is required");
25172518
// The event id should begin with `$`, which would be taken as the end of the
25182519
// regex so we need to escape it
25192520
event_id.as_str().replace("$", "\\$")
@@ -2531,7 +2532,7 @@ impl<'a> MockEndpoint<'a, RoomEventContextEndpoint> {
25312532
"event": event.into_raw().json(),
25322533
"end": end.into(),
25332534
"start": start.into(),
2534-
"state": []
2535+
"state": state_events
25352536
})));
25362537
MatrixMock { server: self.server, mock }
25372538
}

testing/matrix-sdk-test/src/event_factory.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,12 @@ impl EventBuilder<RoomMessageEventContent> {
371371
self
372372
}
373373

374+
/// Adds the given mentions to the current event.
375+
pub fn mentions(mut self, mentions: Mentions) -> Self {
376+
self.content.mentions = Some(mentions);
377+
self
378+
}
379+
374380
/// Adds a replacement relation to the current event, with the new content
375381
/// passed.
376382
pub fn edit(

0 commit comments

Comments
 (0)