mirror of
https://github.com/matrix-org/matrix-rust-sdk.git
synced 2026-08-04 03:52:19 -04:00
429 lines
15 KiB
Rust
429 lines
15 KiB
Rust
use assert_matches2::assert_matches;
|
|
use matrix_sdk::{
|
|
notification_settings::RoomNotificationMode,
|
|
room::ThreadSubscription,
|
|
test_utils::mocks::{MatrixMockServer, PushRuleIdSpec},
|
|
};
|
|
use matrix_sdk_test::{ALICE, JoinedRoomBuilder, async_test, event_factory::EventFactory};
|
|
use ruma::{event_id, owned_event_id, push::RuleKind, room_id};
|
|
|
|
#[async_test]
|
|
async fn test_subscribe_thread() {
|
|
let server = MatrixMockServer::new().await;
|
|
let client = server.client_builder().build().await;
|
|
|
|
let room_id = room_id!("!test:example.org");
|
|
let room = server.sync_joined_room(&client, room_id).await;
|
|
|
|
let root_id = owned_event_id!("$root");
|
|
|
|
server
|
|
.mock_room_put_thread_subscription()
|
|
.match_room_id(room_id.to_owned())
|
|
.match_thread_id(root_id.clone())
|
|
.ok()
|
|
.mock_once()
|
|
.mount()
|
|
.await;
|
|
|
|
// I can subscribe to a thread.
|
|
room.subscribe_thread(root_id.clone(), Some(root_id.clone())).await.unwrap();
|
|
|
|
server
|
|
.mock_room_get_thread_subscription()
|
|
.match_room_id(room_id.to_owned())
|
|
.match_thread_id(root_id.clone())
|
|
.ok(true)
|
|
.mock_once()
|
|
.mount()
|
|
.await;
|
|
|
|
// I can get the subscription for that same thread.
|
|
let subscription = room.fetch_thread_subscription(root_id.clone()).await.unwrap().unwrap();
|
|
assert_eq!(subscription, ThreadSubscription { automatic: true });
|
|
|
|
// If I try to get a subscription for a thread event that's unknown, I get no
|
|
// `ThreadSubscription`, not an error.
|
|
let subscription =
|
|
room.fetch_thread_subscription(owned_event_id!("$another_root")).await.unwrap();
|
|
assert!(subscription.is_none());
|
|
|
|
// I can also unsubscribe from a thread.
|
|
server
|
|
.mock_room_delete_thread_subscription()
|
|
.match_room_id(room_id.to_owned())
|
|
.match_thread_id(root_id.clone())
|
|
.ok()
|
|
.mock_once()
|
|
.mount()
|
|
.await;
|
|
|
|
room.unsubscribe_thread(root_id.clone()).await.unwrap();
|
|
|
|
// Now, if I retry to get the subscription for this thread, it doesn't exist
|
|
// anymore.
|
|
let subscription = room.fetch_thread_subscription(root_id.clone()).await.unwrap();
|
|
assert_matches!(subscription, None);
|
|
|
|
// Subscribing automatically to the thread may also return a `M_SKIPPED` error
|
|
// that should be non-fatal.
|
|
server
|
|
.mock_room_put_thread_subscription()
|
|
.match_room_id(room_id.to_owned())
|
|
.match_thread_id(root_id.clone())
|
|
.conflicting_unsubscription()
|
|
.mock_once()
|
|
.mount()
|
|
.await;
|
|
|
|
room.subscribe_thread(root_id.clone(), Some(root_id.clone())).await.unwrap();
|
|
|
|
// And in this case, the thread is still unsubscribed.
|
|
let subscription = room.fetch_thread_subscription(root_id).await.unwrap();
|
|
assert_matches!(subscription, None);
|
|
}
|
|
|
|
#[async_test]
|
|
async fn test_subscribe_thread_if_needed() {
|
|
let server = MatrixMockServer::new().await;
|
|
let client = server.client_builder().build().await;
|
|
|
|
let room_id = room_id!("!test:example.org");
|
|
let room = server.sync_joined_room(&client, room_id).await;
|
|
|
|
// If there's no prior subscription, the function `subscribe_thread_if_needed`
|
|
// will automatically subscribe to the thread, whether the new subscription
|
|
// is automatic or not.
|
|
for (root_id, automatic) in [
|
|
(owned_event_id!("$root"), None),
|
|
(owned_event_id!("$woot"), Some(owned_event_id!("$woot"))),
|
|
] {
|
|
server
|
|
.mock_room_put_thread_subscription()
|
|
.match_room_id(room_id.to_owned())
|
|
.match_thread_id(root_id.clone())
|
|
.ok()
|
|
.mock_once()
|
|
.mount()
|
|
.await;
|
|
|
|
room.subscribe_thread_if_needed(&root_id, automatic).await.unwrap();
|
|
}
|
|
|
|
// If there's a prior automatic subscription, the function
|
|
// `subscribe_thread_if_needed` will only subscribe to the thread if the new
|
|
// subscription is manual.
|
|
{
|
|
let root_id = owned_event_id!("$toot");
|
|
|
|
server
|
|
.mock_room_get_thread_subscription()
|
|
.match_room_id(room_id.to_owned())
|
|
.match_thread_id(root_id.clone())
|
|
.ok(true)
|
|
.mock_once()
|
|
.mount()
|
|
.await;
|
|
|
|
server
|
|
.mock_room_put_thread_subscription()
|
|
.match_room_id(room_id.to_owned())
|
|
.match_thread_id(root_id.clone())
|
|
.ok()
|
|
.mock_once()
|
|
.mount()
|
|
.await;
|
|
|
|
room.subscribe_thread_if_needed(&root_id, None).await.unwrap();
|
|
}
|
|
|
|
// Otherwise, it will be a no-op.
|
|
{
|
|
let root_id = owned_event_id!("$foot");
|
|
|
|
server
|
|
.mock_room_get_thread_subscription()
|
|
.match_room_id(room_id.to_owned())
|
|
.match_thread_id(root_id.clone())
|
|
.ok(true)
|
|
.mock_once()
|
|
.mount()
|
|
.await;
|
|
|
|
room.subscribe_thread_if_needed(&root_id, Some(owned_event_id!("$foot"))).await.unwrap();
|
|
}
|
|
|
|
// The function `subscribe_thread_if_needed` is a no-op if there's a prior
|
|
// manual subscription, whether the new subscription is automatic or not.
|
|
for (root_id, automatic) in [
|
|
(owned_event_id!("$root"), None),
|
|
(owned_event_id!("$woot"), Some(owned_event_id!("$woot"))),
|
|
] {
|
|
server
|
|
.mock_room_get_thread_subscription()
|
|
.match_room_id(room_id.to_owned())
|
|
.match_thread_id(root_id.clone())
|
|
.ok(false)
|
|
.mock_once()
|
|
.mount()
|
|
.await;
|
|
|
|
// No-op! (The PUT endpoint hasn't been mocked, so this would result in a 404 if
|
|
// it were trying to hit it.)
|
|
room.subscribe_thread_if_needed(&root_id, automatic).await.unwrap();
|
|
}
|
|
}
|
|
|
|
#[async_test]
|
|
async fn test_thread_push_rule_is_triggered_for_subscribed_threads() {
|
|
// This test checks that the evaluation of push rules for threads will correctly
|
|
// call `Room::fetch_thread_subscription` for threads.
|
|
|
|
let server = MatrixMockServer::new().await;
|
|
let client = server
|
|
.client_builder()
|
|
.no_server_versions()
|
|
.on_builder(|builder| {
|
|
builder.with_threading_support(matrix_sdk::ThreadingSupport::Enabled {
|
|
with_subscriptions: true,
|
|
})
|
|
})
|
|
.build()
|
|
.await;
|
|
|
|
// Make sure to advertise support for thread subscriptions.
|
|
server.mock_versions().with_thread_subscriptions().ok().mount().await;
|
|
|
|
let room_id = room_id!("!test:example.org");
|
|
let room = server.sync_joined_room(&client, room_id).await;
|
|
|
|
let thread_root_id = owned_event_id!("$root");
|
|
let f = EventFactory::new().room(room_id).sender(*ALICE);
|
|
|
|
// Make it so that the client has a member event for the current user.
|
|
server
|
|
.sync_room(
|
|
&client,
|
|
JoinedRoomBuilder::new(room_id).add_state_event(f.member(client.user_id().unwrap())),
|
|
)
|
|
.await;
|
|
|
|
// Sanity check: we can get a push context.
|
|
let push_context = room
|
|
.push_context()
|
|
.await
|
|
.expect("getting a push context works")
|
|
.expect("the push context should exist");
|
|
|
|
// Mock the thread subscriptions endpoint so the user is subscribed to the
|
|
// thread.
|
|
server
|
|
.mock_room_get_thread_subscription()
|
|
.match_room_id(room_id.to_owned())
|
|
.match_thread_id(thread_root_id.clone())
|
|
.ok(true)
|
|
.mock_once()
|
|
.mount()
|
|
.await;
|
|
|
|
// Given an event in the thread I'm subscribed to, the push rule evaluation will
|
|
// trigger the thread subscription endpoint,
|
|
let event =
|
|
f.text_msg("hello to you too!").in_thread(&thread_root_id, &thread_root_id).into_raw_sync();
|
|
|
|
// And the event will trigger a notification.
|
|
let actions = push_context.for_event(&event).await;
|
|
assert!(actions.iter().any(|action| action.should_notify()));
|
|
|
|
// But for a thread that I haven't subscribed to (i.e. the endpoint returns 404,
|
|
// because it's not set up), no actions are returned.
|
|
let another_thread_root_id = event_id!("$another_root");
|
|
let event = f
|
|
.text_msg("bonjour à vous également !")
|
|
.in_thread(another_thread_root_id, another_thread_root_id)
|
|
.into_raw_sync();
|
|
|
|
let actions = push_context.for_event(&event).await;
|
|
assert!(actions.is_empty());
|
|
}
|
|
|
|
#[async_test]
|
|
async fn test_thread_push_rules_and_notification_modes() {
|
|
// This test checks that, given a combination of a global notification mode, and
|
|
// a room notification mode, we do get notifications for thread events according
|
|
// to the subscriptions.
|
|
|
|
let server = MatrixMockServer::new().await;
|
|
let client = server
|
|
.client_builder()
|
|
.no_server_versions()
|
|
.on_builder(|builder| {
|
|
builder.with_threading_support(matrix_sdk::ThreadingSupport::Enabled {
|
|
with_subscriptions: true,
|
|
})
|
|
})
|
|
.build()
|
|
.await;
|
|
|
|
// Make sure to advertise support for thread subscriptions.
|
|
server.mock_versions().with_thread_subscriptions().ok().mount().await;
|
|
|
|
let room_id = room_id!("!test:example.org");
|
|
let f = EventFactory::new().room(room_id).sender(*ALICE);
|
|
// Make it so that the client has a member event for the current user.
|
|
let room = server
|
|
.sync_room(
|
|
&client,
|
|
JoinedRoomBuilder::new(room_id).add_state_event(f.member(client.user_id().unwrap())),
|
|
)
|
|
.await;
|
|
|
|
// Sanity check: we can get a push context.
|
|
room.push_context()
|
|
.await
|
|
.expect("getting a push context works")
|
|
.expect("the push context should exist");
|
|
|
|
// Mock push rules endpoints, allowing any modification to any rule.
|
|
server.mock_set_push_rules_actions(RuleKind::Underride, PushRuleIdSpec::Any).ok().mount().await;
|
|
server.mock_set_push_rules_actions(RuleKind::Room, PushRuleIdSpec::Any).ok().mount().await;
|
|
server.mock_set_push_rules(RuleKind::Underride, PushRuleIdSpec::Any).ok().mount().await;
|
|
server.mock_set_push_rules(RuleKind::Room, PushRuleIdSpec::Any).ok().mount().await;
|
|
server.mock_set_push_rules(RuleKind::Override, PushRuleIdSpec::Any).ok().mount().await;
|
|
server.mock_delete_push_rules(RuleKind::Room, PushRuleIdSpec::Any).ok().mount().await;
|
|
server.mock_delete_push_rules(RuleKind::Override, PushRuleIdSpec::Any).ok().mount().await;
|
|
|
|
// Given a thread I'm subscribed to,
|
|
let thread_root_id = owned_event_id!("$root");
|
|
|
|
server
|
|
.mock_room_get_thread_subscription()
|
|
.match_room_id(room_id.to_owned())
|
|
.match_thread_id(thread_root_id.clone())
|
|
.ok(true)
|
|
.mount()
|
|
.await;
|
|
|
|
// Given an event in the thread I'm subscribed to,
|
|
let event =
|
|
f.text_msg("hello to you too!").in_thread(&thread_root_id, &thread_root_id).into_raw_sync();
|
|
|
|
// If global mode = AllMessages,
|
|
let settings = client.notification_settings().await;
|
|
|
|
let is_encrypted = false;
|
|
let is_one_to_one = false;
|
|
settings
|
|
.set_default_room_notification_mode(
|
|
is_encrypted.into(),
|
|
is_one_to_one.into(),
|
|
RoomNotificationMode::AllMessages,
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
// If room mode = AllMessages,
|
|
settings.set_room_notification_mode(room_id, RoomNotificationMode::AllMessages).await.unwrap();
|
|
// Ack the push rules change via sync, for it to be applied in the push context.
|
|
let ruleset = settings.ruleset().await;
|
|
server
|
|
.mock_sync()
|
|
.ok_and_run(&client, |builder| {
|
|
builder.add_global_account_data(f.push_rules(ruleset));
|
|
})
|
|
.await;
|
|
|
|
// The thread event will trigger a notification.
|
|
let actions = room.push_context().await.unwrap().unwrap().traced_for_event(&event).await;
|
|
assert!(actions.iter().any(|action| action.should_notify()));
|
|
|
|
// If room mode = mentions and keywords only,
|
|
settings
|
|
.set_room_notification_mode(room_id, RoomNotificationMode::MentionsAndKeywordsOnly)
|
|
.await
|
|
.unwrap();
|
|
let ruleset = settings.ruleset().await;
|
|
server
|
|
.mock_sync()
|
|
.ok_and_run(&client, |builder| {
|
|
builder.add_global_account_data(f.push_rules(ruleset));
|
|
})
|
|
.await;
|
|
|
|
// The thread event will trigger a notification.
|
|
let actions = room.push_context().await.unwrap().unwrap().traced_for_event(&event).await;
|
|
assert!(actions.iter().any(|action| action.should_notify()));
|
|
|
|
// If room mode = mute,
|
|
settings.set_room_notification_mode(room_id, RoomNotificationMode::Mute).await.unwrap();
|
|
let ruleset = settings.ruleset().await;
|
|
server
|
|
.mock_sync()
|
|
.ok_and_run(&client, |builder| {
|
|
builder.add_global_account_data(f.push_rules(ruleset));
|
|
})
|
|
.await;
|
|
|
|
// The thread event will not trigger a notification, as the room has been muted.
|
|
let actions = room.push_context().await.unwrap().unwrap().traced_for_event(&event).await;
|
|
assert!(!actions.iter().any(|action| action.should_notify()));
|
|
|
|
// Now, if global mode = mentions only,
|
|
settings
|
|
.set_default_room_notification_mode(
|
|
is_encrypted.into(),
|
|
is_one_to_one.into(),
|
|
RoomNotificationMode::MentionsAndKeywordsOnly,
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
// If room mode = AllMessages,
|
|
settings.set_room_notification_mode(room_id, RoomNotificationMode::AllMessages).await.unwrap();
|
|
// Ack the push rules change via sync, for it to be applied in the push context.
|
|
let ruleset = settings.ruleset().await;
|
|
server
|
|
.mock_sync()
|
|
.ok_and_run(&client, |builder| {
|
|
builder.add_global_account_data(f.push_rules(ruleset));
|
|
})
|
|
.await;
|
|
|
|
// The thread event will trigger a notification.
|
|
let actions = room.push_context().await.unwrap().unwrap().traced_for_event(&event).await;
|
|
assert!(actions.iter().any(|action| action.should_notify()));
|
|
|
|
// If room mode = Mentions only,
|
|
settings
|
|
.set_room_notification_mode(room_id, RoomNotificationMode::MentionsAndKeywordsOnly)
|
|
.await
|
|
.unwrap();
|
|
// Ack the push rules change via sync, for it to be applied in the push context.
|
|
let ruleset = settings.ruleset().await;
|
|
server
|
|
.mock_sync()
|
|
.ok_and_run(&client, |builder| {
|
|
builder.add_global_account_data(f.push_rules(ruleset));
|
|
})
|
|
.await;
|
|
|
|
// The thread event will trigger a notification.
|
|
let actions = room.push_context().await.unwrap().unwrap().traced_for_event(&event).await;
|
|
assert!(actions.iter().any(|action| action.should_notify()));
|
|
|
|
// If room mode = mute,
|
|
settings.set_room_notification_mode(room_id, RoomNotificationMode::Mute).await.unwrap();
|
|
// Ack the push rules change via sync, for it to be applied in the push context.
|
|
let ruleset = settings.ruleset().await;
|
|
server
|
|
.mock_sync()
|
|
.ok_and_run(&client, |builder| {
|
|
builder.add_global_account_data(f.push_rules(ruleset));
|
|
})
|
|
.await;
|
|
|
|
// The thread event will not trigger a notification, as the room has been muted.
|
|
let actions = room.push_context().await.unwrap().unwrap().traced_for_event(&event).await;
|
|
assert!(!actions.iter().any(|action| action.should_notify()));
|
|
}
|