matrix_sdk/notification_settings/
mod.rs

1// Copyright 2024 The Matrix.org Foundation C.I.C.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for that specific language governing permissions and
13// limitations under the License.
14
15//! High-level push notification settings API
16
17use std::{ops::Deref, sync::Arc};
18
19use indexmap::IndexSet;
20use ruma::{
21    RoomId,
22    api::client::push::{
23        delete_pushrule, set_pushrule, set_pushrule_actions, set_pushrule_enabled,
24    },
25    events::push_rules::PushRulesEvent,
26    push::{Action, NewPushRule, PredefinedUnderrideRuleId, RuleKind, Ruleset, Tweak},
27};
28use tokio::sync::{
29    RwLock,
30    broadcast::{self, Receiver},
31};
32use tracing::{debug, error};
33
34use self::{command::Command, rule_commands::RuleCommands, rules::Rules};
35
36mod command;
37mod rule_commands;
38mod rules;
39
40pub use matrix_sdk_base::notification_settings::RoomNotificationMode;
41
42use crate::{
43    Client, Result, config::RequestConfig, error::NotificationSettingsError,
44    event_handler::EventHandlerDropGuard,
45};
46
47/// Whether or not a room is encrypted
48#[derive(Debug, Clone, Copy)]
49pub enum IsEncrypted {
50    /// The room is encrypted
51    Yes,
52    /// The room is not encrypted
53    No,
54}
55
56impl From<bool> for IsEncrypted {
57    fn from(value: bool) -> Self {
58        if value { Self::Yes } else { Self::No }
59    }
60}
61
62/// Whether or not a room is a `one-to-one`
63#[derive(Debug, Clone, Copy)]
64pub enum IsOneToOne {
65    /// A room is a `one-to-one` room if it has exactly two members.
66    Yes,
67    /// The room doesn't have exactly two members.
68    No,
69}
70
71impl From<bool> for IsOneToOne {
72    fn from(value: bool) -> Self {
73        if value { Self::Yes } else { Self::No }
74    }
75}
76
77/// A high-level API to manage the client owner's push notification settings.
78#[derive(Debug, Clone)]
79pub struct NotificationSettings {
80    /// The underlying HTTP client.
81    client: Client,
82    /// Owner's account push rules. They will be updated on sync.
83    rules: Arc<RwLock<Rules>>,
84    /// Drop guard of event handler for push rules event.
85    _push_rules_event_handler_guard: Arc<EventHandlerDropGuard>,
86    /// Notified every time the push rules change, either due to sync or local
87    /// changes.
88    changes_sender: broadcast::Sender<()>,
89}
90
91impl NotificationSettings {
92    /// Build a new [`NotificationSettings`].
93    ///
94    /// # Arguments
95    ///
96    /// * `client` - A [`Client`] used to perform API calls.
97    /// * `ruleset` - A [`Ruleset`] containing account's owner push rules.
98    pub(crate) fn new(client: Client, ruleset: Ruleset) -> Self {
99        let changes_sender = broadcast::Sender::new(100);
100        let rules = Arc::new(RwLock::new(Rules::new(ruleset)));
101
102        // Listen for PushRulesEvent.
103        let push_rules_event_handler_handle = client.add_event_handler({
104            let changes_sender = changes_sender.clone();
105            let rules = rules.clone();
106            move |ev: PushRulesEvent| async move {
107                *rules.write().await = Rules::new(ev.content.global);
108                let _ = changes_sender.send(());
109            }
110        });
111
112        let _push_rules_event_handler_guard =
113            Arc::new(client.event_handler_drop_guard(push_rules_event_handler_handle));
114
115        Self { client, rules, _push_rules_event_handler_guard, changes_sender }
116    }
117
118    /// Subscribe to changes to the [`NotificationSettings`] (i.e. changes to
119    /// push rules).
120    ///
121    /// Changes can happen due to local changes or changes in another session.
122    pub fn subscribe_to_changes(&self) -> Receiver<()> {
123        self.changes_sender.subscribe()
124    }
125
126    /// Get the user defined notification mode for a room.
127    pub async fn get_user_defined_room_notification_mode(
128        &self,
129        room_id: &RoomId,
130    ) -> Option<RoomNotificationMode> {
131        self.rules.read().await.get_user_defined_room_notification_mode(room_id)
132    }
133
134    /// Get the default notification mode for a room.
135    ///
136    /// # Arguments
137    ///
138    /// * `is_encrypted` - `Yes` if the room is encrypted
139    /// * `is_one_to_one` - `Yes` if the room is a direct chat involving two
140    ///   people
141    pub async fn get_default_room_notification_mode(
142        &self,
143        is_encrypted: IsEncrypted,
144        is_one_to_one: IsOneToOne,
145    ) -> RoomNotificationMode {
146        self.rules.read().await.get_default_room_notification_mode(is_encrypted, is_one_to_one)
147    }
148
149    /// Get all room IDs for which a user-defined rule exists.
150    pub async fn get_rooms_with_user_defined_rules(&self, enabled: Option<bool>) -> Vec<String> {
151        self.rules.read().await.get_rooms_with_user_defined_rules(enabled)
152    }
153
154    /// Get whether the given ruleset contains some enabled keywords rules.
155    pub async fn contains_keyword_rules(&self) -> bool {
156        self.rules.read().await.contains_keyword_rules()
157    }
158
159    /// Get whether a push rule is enabled.
160    pub async fn is_push_rule_enabled(
161        &self,
162        kind: RuleKind,
163        rule_id: impl AsRef<str>,
164    ) -> Result<bool, NotificationSettingsError> {
165        self.rules.read().await.is_enabled(kind, rule_id.as_ref())
166    }
167
168    /// Set whether a push rule is enabled.
169    pub async fn set_push_rule_enabled(
170        &self,
171        kind: RuleKind,
172        rule_id: impl AsRef<str>,
173        enabled: bool,
174    ) -> Result<(), NotificationSettingsError> {
175        let rules = self.rules.read().await.clone();
176
177        let mut rule_commands = RuleCommands::new(rules.ruleset);
178        rule_commands.set_rule_enabled(kind, rule_id.as_ref(), enabled)?;
179
180        self.run_server_commands(&rule_commands).await?;
181
182        let rules = &mut *self.rules.write().await;
183        rules.apply(rule_commands);
184
185        Ok(())
186    }
187
188    /// Set the default notification mode for a type of room.
189    ///
190    /// # Arguments
191    ///
192    /// * `is_encrypted` - `Yes` if the mode is for encrypted rooms
193    /// * `is_one_to_one` - `Yes` if the mode if for `one-to-one` rooms (rooms
194    ///   with exactly two members)
195    /// * `mode` - the new default mode
196    pub async fn set_default_room_notification_mode(
197        &self,
198        is_encrypted: IsEncrypted,
199        is_one_to_one: IsOneToOne,
200        mode: RoomNotificationMode,
201    ) -> Result<(), NotificationSettingsError> {
202        let actions = match mode {
203            RoomNotificationMode::AllMessages => {
204                vec![Action::Notify, Action::SetTweak(Tweak::Sound("default".into()))]
205            }
206            _ => {
207                vec![]
208            }
209        };
210
211        let room_rule_id =
212            rules::get_predefined_underride_room_rule_id(is_encrypted, is_one_to_one);
213        self.set_underride_push_rule_actions(room_rule_id, actions.clone()).await?;
214
215        let poll_start_rule_id = rules::get_predefined_underride_poll_start_rule_id(is_one_to_one);
216        if let Err(error) =
217            self.set_underride_push_rule_actions(poll_start_rule_id, actions.clone()).await
218        {
219            // The poll start event rules are currently unstable so they might not be found
220            // on every homeserver. Let's ignore this error for the moment.
221            if let NotificationSettingsError::RuleNotFound(rule_id) = &error {
222                debug!("Unable to update poll start push rule: rule `{rule_id}` not found");
223            } else {
224                return Err(error);
225            }
226        }
227
228        Ok(())
229    }
230
231    /// Sets the push rule actions for a given underride push rule. It also
232    /// enables the push rule if it is disabled. [Underride rules] are the
233    /// lowest priority push rules
234    ///
235    /// # Arguments
236    ///
237    /// * `rule_id` - the identifier of the push rule
238    /// * `actions` - the actions to set for the push rule
239    ///
240    /// [Underride rules]: https://spec.matrix.org/v1.8/client-server-api/#push-rules
241    pub async fn set_underride_push_rule_actions(
242        &self,
243        rule_id: PredefinedUnderrideRuleId,
244        actions: Vec<Action>,
245    ) -> Result<(), NotificationSettingsError> {
246        let rules = self.rules.read().await.clone();
247        let rule_kind = RuleKind::Underride;
248        let mut rule_commands = RuleCommands::new(rules.clone().ruleset);
249
250        rule_commands.set_rule_actions(rule_kind.clone(), rule_id.as_str(), actions)?;
251
252        if !rules.is_enabled(rule_kind.clone(), rule_id.as_str())? {
253            rule_commands.set_rule_enabled(rule_kind, rule_id.as_str(), true)?
254        }
255
256        self.run_server_commands(&rule_commands).await?;
257
258        let rules = &mut *self.rules.write().await;
259        rules.apply(rule_commands);
260
261        Ok(())
262    }
263
264    /// Create a custom conditional push rule.
265    ///
266    /// # Arguments
267    ///
268    /// * `rule_id` - The identifier of the push rule.
269    /// * `rule_kind` - The kind of the push rule.
270    /// * `actions` - The actions to set for the push rule.
271    /// * `conditions` - The conditions for the push rule.
272    ///
273    /// See more in the matrix spec: <https://spec.matrix.org/latest/client-server-api/#push-rules>
274    pub async fn create_custom_conditional_push_rule(
275        &self,
276        rule_id: String,
277        rule_kind: RuleKind,
278        actions: Vec<Action>,
279        conditions: Vec<ruma::push::PushCondition>,
280    ) -> Result<(), NotificationSettingsError> {
281        let new_conditional_rule =
282            ruma::push::NewConditionalPushRule::new(rule_id, conditions, actions);
283
284        let new_push_rule = match rule_kind {
285            RuleKind::Override => NewPushRule::Override(new_conditional_rule),
286            RuleKind::Underride => NewPushRule::Underride(new_conditional_rule),
287            _ => return Err(NotificationSettingsError::InvalidParameter("rule_kind".to_owned())),
288        };
289
290        let rules = self.rules.read().await.clone();
291        let mut rule_commands = RuleCommands::new(rules.clone().ruleset);
292        rule_commands.insert_custom_rule(new_push_rule)?;
293
294        self.run_server_commands(&rule_commands).await?;
295
296        let rules = &mut *self.rules.write().await;
297        rules.apply(rule_commands);
298
299        Ok(())
300    }
301
302    /// Set the notification mode for a room.
303    pub async fn set_room_notification_mode(
304        &self,
305        room_id: &RoomId,
306        mode: RoomNotificationMode,
307    ) -> Result<(), NotificationSettingsError> {
308        let rules = self.rules.read().await.clone();
309
310        // Check that the current mode is not already the target mode.
311        if rules.get_user_defined_room_notification_mode(room_id) == Some(mode) {
312            return Ok(());
313        }
314
315        // Build the command list to set the new mode
316        let (new_rule_kind, notify) = match mode {
317            RoomNotificationMode::AllMessages => {
318                // insert a `Room` rule which notifies
319                (RuleKind::Room, true)
320            }
321            RoomNotificationMode::MentionsAndKeywordsOnly => {
322                // insert a `Room` rule which doesn't notify
323                (RuleKind::Room, false)
324            }
325            RoomNotificationMode::Mute => {
326                // insert an `Override` rule which doesn't notify
327                (RuleKind::Override, false)
328            }
329        };
330
331        // Extract all the custom rules except the one we just created.
332        let new_rule_id = room_id.as_str();
333        let custom_rules: Vec<(RuleKind, String)> = rules
334            .get_custom_rules_for_room(room_id)
335            .into_iter()
336            .filter(|(kind, rule_id)| kind != &new_rule_kind || rule_id != new_rule_id)
337            .collect();
338
339        // Build the command list to delete all other custom rules, with the exception
340        // of the newly inserted rule.
341        let mut rule_commands = RuleCommands::new(rules.ruleset);
342        rule_commands.insert_rule(new_rule_kind.clone(), room_id, notify)?;
343        for (kind, rule_id) in custom_rules {
344            rule_commands.delete_rule(kind, rule_id)?;
345        }
346
347        self.run_server_commands(&rule_commands).await?;
348
349        let rules = &mut *self.rules.write().await;
350        rules.apply(rule_commands);
351
352        Ok(())
353    }
354
355    /// Delete all user defined rules for a room.
356    pub async fn delete_user_defined_room_rules(
357        &self,
358        room_id: &RoomId,
359    ) -> Result<(), NotificationSettingsError> {
360        let rules = self.rules.read().await.clone();
361
362        let custom_rules = rules.get_custom_rules_for_room(room_id);
363        if custom_rules.is_empty() {
364            return Ok(());
365        }
366
367        let mut rule_commands = RuleCommands::new(rules.ruleset);
368        for (kind, rule_id) in custom_rules {
369            rule_commands.delete_rule(kind, rule_id)?;
370        }
371
372        self.run_server_commands(&rule_commands).await?;
373
374        let rules = &mut *self.rules.write().await;
375        rules.apply(rule_commands);
376
377        Ok(())
378    }
379
380    /// Unmute a room.
381    pub async fn unmute_room(
382        &self,
383        room_id: &RoomId,
384        is_encrypted: IsEncrypted,
385        is_one_to_one: IsOneToOne,
386    ) -> Result<(), NotificationSettingsError> {
387        let rules = self.rules.read().await.clone();
388
389        // Check if there is a user defined mode
390        if let Some(room_mode) = rules.get_user_defined_room_notification_mode(room_id) {
391            if room_mode != RoomNotificationMode::Mute {
392                // Already unmuted
393                return Ok(());
394            }
395
396            // Get default mode for this room
397            let default_mode =
398                rules.get_default_room_notification_mode(is_encrypted, is_one_to_one);
399
400            // If the default mode is `Mute`, set it to `AllMessages`
401            if default_mode == RoomNotificationMode::Mute {
402                self.set_room_notification_mode(room_id, RoomNotificationMode::AllMessages).await
403            } else {
404                // Otherwise, delete user defined rules to use the default mode
405                self.delete_user_defined_room_rules(room_id).await
406            }
407        } else {
408            // This is the default mode, create a custom rule to unmute this room by setting
409            // the mode to `AllMessages`
410            self.set_room_notification_mode(room_id, RoomNotificationMode::AllMessages).await
411        }
412    }
413
414    /// Get the keywords which have enabled rules.
415    pub async fn enabled_keywords(&self) -> IndexSet<String> {
416        self.rules.read().await.enabled_keywords()
417    }
418
419    /// Add or enable a rule for the given keyword.
420    ///
421    /// # Arguments
422    ///
423    /// * `keyword` - The keyword to match.
424    pub async fn add_keyword(&self, keyword: String) -> Result<(), NotificationSettingsError> {
425        let rules = self.rules.read().await.clone();
426
427        let mut rule_commands = RuleCommands::new(rules.clone().ruleset);
428
429        let existing_rules = rules.keyword_rules(&keyword);
430
431        if existing_rules.is_empty() {
432            // Create a rule.
433            rule_commands.insert_keyword_rule(keyword)?;
434        } else {
435            if existing_rules.iter().any(|r| r.enabled) {
436                // Nothing to do.
437                return Ok(());
438            }
439
440            // Enable one of the rules.
441            rule_commands.set_rule_enabled(RuleKind::Content, &existing_rules[0].rule_id, true)?;
442        }
443
444        self.run_server_commands(&rule_commands).await?;
445
446        let rules = &mut *self.rules.write().await;
447        rules.apply(rule_commands);
448
449        Ok(())
450    }
451
452    /// Remove the rules for the given keyword.
453    ///
454    /// # Arguments
455    ///
456    /// * `keyword` - The keyword to unmatch.
457    pub async fn remove_keyword(&self, keyword: &str) -> Result<(), NotificationSettingsError> {
458        let rules = self.rules.read().await.clone();
459
460        let mut rule_commands = RuleCommands::new(rules.clone().ruleset);
461
462        let existing_rules = rules.keyword_rules(keyword);
463
464        if existing_rules.is_empty() {
465            return Ok(());
466        }
467
468        for rule in existing_rules {
469            rule_commands.delete_rule(RuleKind::Content, rule.rule_id.clone())?;
470        }
471
472        self.run_server_commands(&rule_commands).await?;
473
474        let rules = &mut *self.rules.write().await;
475        rules.apply(rule_commands);
476
477        Ok(())
478    }
479
480    /// Convert commands into requests to the server, and run them.
481    async fn run_server_commands(
482        &self,
483        rule_commands: &RuleCommands,
484    ) -> Result<(), NotificationSettingsError> {
485        let request_config = Some(RequestConfig::short_retry());
486        for command in &rule_commands.commands {
487            match command {
488                Command::DeletePushRule { kind, rule_id } => {
489                    let request = delete_pushrule::v3::Request::new(kind.clone(), rule_id.clone());
490                    self.client.send(request).with_request_config(request_config).await.map_err(
491                        |error| {
492                            error!("Unable to delete {kind} push rule `{rule_id}`: {error}");
493                            NotificationSettingsError::UnableToRemovePushRule
494                        },
495                    )?;
496                }
497                Command::SetRoomPushRule { room_id, notify: _ } => {
498                    let push_rule = command.to_push_rule()?;
499                    let request = set_pushrule::v3::Request::new(push_rule);
500                    self.client.send(request).with_request_config(request_config).await.map_err(
501                        |error| {
502                            error!("Unable to set room push rule `{room_id}`: {error}");
503                            NotificationSettingsError::UnableToAddPushRule
504                        },
505                    )?;
506                }
507                Command::SetOverridePushRule { rule_id, room_id: _, notify: _ } => {
508                    let push_rule = command.to_push_rule()?;
509                    let request = set_pushrule::v3::Request::new(push_rule);
510                    self.client.send(request).with_request_config(request_config).await.map_err(
511                        |error| {
512                            error!("Unable to set override push rule `{rule_id}`: {error}");
513                            NotificationSettingsError::UnableToAddPushRule
514                        },
515                    )?;
516                }
517                Command::SetKeywordPushRule { keyword: _ } => {
518                    let push_rule = command.to_push_rule()?;
519                    let request = set_pushrule::v3::Request::new(push_rule);
520                    self.client
521                        .send(request)
522                        .with_request_config(request_config)
523                        .await
524                        .map_err(|_| NotificationSettingsError::UnableToAddPushRule)?;
525                }
526                Command::SetPushRuleEnabled { kind, rule_id, enabled } => {
527                    let request = set_pushrule_enabled::v3::Request::new(
528                        kind.clone(),
529                        rule_id.clone(),
530                        *enabled,
531                    );
532                    self.client.send(request).with_request_config(request_config).await.map_err(
533                        |error| {
534                            error!("Unable to set {kind} push rule `{rule_id}` enabled: {error}");
535                            NotificationSettingsError::UnableToUpdatePushRule
536                        },
537                    )?;
538                }
539                Command::SetPushRuleActions { kind, rule_id, actions } => {
540                    let request = set_pushrule_actions::v3::Request::new(
541                        kind.clone(),
542                        rule_id.clone(),
543                        actions.clone(),
544                    );
545                    self.client.send(request).with_request_config(request_config).await.map_err(
546                        |error| {
547                            error!("Unable to set {kind} push rule `{rule_id}` actions: {error}");
548                            NotificationSettingsError::UnableToUpdatePushRule
549                        },
550                    )?;
551                }
552                Command::SetCustomPushRule { rule } => {
553                    let request = set_pushrule::v3::Request::new(rule.clone());
554
555                    self.client.send(request).with_request_config(request_config).await.map_err(
556                        |error| {
557                            error!("Unable to set custom push rule `{rule:#?}`: {error}");
558                            NotificationSettingsError::UnableToAddPushRule
559                        },
560                    )?;
561                }
562            }
563        }
564        Ok(())
565    }
566
567    /// Returns the inner ruleset currently known by this
568    /// [`NotificationSettings`] instance.
569    pub async fn ruleset(&self) -> Ruleset {
570        self.rules.read().await.ruleset.clone()
571    }
572
573    /// Returns the inner [`Rules`] object currently known by this instance.
574    pub(crate) async fn rules(&self) -> impl Deref<Target = Rules> + '_ {
575        self.rules.read().await
576    }
577}
578
579// The http mocking library is not supported for wasm32
580#[cfg(all(test, not(target_family = "wasm")))]
581mod tests {
582    use std::sync::{
583        Arc,
584        atomic::{AtomicBool, Ordering},
585    };
586
587    use assert_matches::assert_matches;
588    use matrix_sdk_test::{
589        TestResult, async_test,
590        event_factory::EventFactory,
591        notification_settings::{build_ruleset, get_server_default_ruleset},
592    };
593    use ruma::{
594        OwnedRoomId, RoomId, owned_room_id,
595        push::{
596            Action, AnyPushRuleRef, NewPatternedPushRule, NewPushRule, PredefinedContentRuleId,
597            PredefinedOverrideRuleId, PredefinedUnderrideRuleId, RuleKind, Ruleset,
598        },
599    };
600    use stream_assert::{assert_next_eq, assert_pending};
601    use tokio_stream::wrappers::BroadcastStream;
602    use wiremock::{
603        Mock, MockServer, ResponseTemplate,
604        matchers::{method, path, path_regex},
605    };
606
607    use crate::{
608        Client,
609        error::NotificationSettingsError,
610        notification_settings::{
611            IsEncrypted, IsOneToOne, NotificationSettings, RoomNotificationMode,
612        },
613        test_utils::{logged_in_client, mocks::MatrixMockServer},
614    };
615
616    fn get_test_room_id() -> OwnedRoomId {
617        owned_room_id!("!AAAaAAAAAaaAAaaaaa:matrix.org")
618    }
619
620    fn from_insert_rules(
621        client: &Client,
622        rules: Vec<(RuleKind, &RoomId, bool)>,
623    ) -> NotificationSettings {
624        let ruleset = build_ruleset(rules);
625        NotificationSettings::new(client.to_owned(), ruleset)
626    }
627
628    async fn get_custom_rules_for_room(
629        settings: &NotificationSettings,
630        room_id: &RoomId,
631    ) -> Vec<(RuleKind, String)> {
632        settings.rules.read().await.get_custom_rules_for_room(room_id)
633    }
634
635    #[async_test]
636    async fn test_subscribe_to_changes() -> TestResult {
637        let server = MatrixMockServer::new().await;
638        let client = server.client_builder().build().await;
639        let settings = client.notification_settings().await;
640
641        let subscriber = settings.subscribe_to_changes();
642        let mut stream = BroadcastStream::new(subscriber);
643
644        assert_pending!(stream);
645
646        server
647            .mock_sync()
648            .ok_and_run(&client, |sync_response_builder| {
649                let f = EventFactory::new();
650                sync_response_builder.add_global_account_data(
651                    f.push_rules(Ruleset::server_default(client.user_id().unwrap())),
652                );
653            })
654            .await;
655
656        assert_next_eq!(stream, Ok(()));
657        assert_pending!(stream);
658
659        Ok(())
660    }
661
662    #[async_test]
663    async fn test_get_custom_rules_for_room() {
664        let server = MockServer::start().await;
665        let client = logged_in_client(Some(server.uri())).await;
666        let room_id = get_test_room_id();
667
668        let settings = from_insert_rules(&client, vec![(RuleKind::Room, &room_id, true)]);
669
670        let custom_rules = get_custom_rules_for_room(&settings, &room_id).await;
671        assert_eq!(custom_rules.len(), 1);
672        assert_eq!(custom_rules[0], (RuleKind::Room, room_id.to_string()));
673
674        let settings = from_insert_rules(
675            &client,
676            vec![(RuleKind::Room, &room_id, true), (RuleKind::Override, &room_id, true)],
677        );
678        let custom_rules = get_custom_rules_for_room(&settings, &room_id).await;
679        assert_eq!(custom_rules.len(), 2);
680        assert_eq!(custom_rules[0], (RuleKind::Override, room_id.to_string()));
681        assert_eq!(custom_rules[1], (RuleKind::Room, room_id.to_string()));
682    }
683
684    #[async_test]
685    async fn test_get_user_defined_room_notification_mode_none() {
686        let server = MockServer::start().await;
687        let client = logged_in_client(Some(server.uri())).await;
688        let room_id = get_test_room_id();
689
690        let settings = client.notification_settings().await;
691        assert!(settings.get_user_defined_room_notification_mode(&room_id).await.is_none());
692    }
693
694    #[async_test]
695    async fn test_get_user_defined_room_notification_mode_all_messages() {
696        let server = MockServer::start().await;
697        let client = logged_in_client(Some(server.uri())).await;
698        let room_id = get_test_room_id();
699
700        // Initialize with a notifying `Room` rule to be in `AllMessages`
701        let settings = from_insert_rules(&client, vec![(RuleKind::Room, &room_id, true)]);
702
703        assert_eq!(
704            settings.get_user_defined_room_notification_mode(&room_id).await.unwrap(),
705            RoomNotificationMode::AllMessages
706        );
707    }
708
709    #[async_test]
710    async fn test_get_user_defined_room_notification_mode_mentions_and_keywords() {
711        let server = MockServer::start().await;
712        let client = logged_in_client(Some(server.uri())).await;
713        let room_id = get_test_room_id();
714
715        // Initialize with a muted `Room` rule to be in `MentionsAndKeywordsOnly`
716        let settings = from_insert_rules(&client, vec![(RuleKind::Room, &room_id, false)]);
717        assert_eq!(
718            settings.get_user_defined_room_notification_mode(&room_id).await.unwrap(),
719            RoomNotificationMode::MentionsAndKeywordsOnly
720        );
721    }
722
723    #[async_test]
724    async fn test_get_user_defined_room_notification_mode_mute() {
725        let server = MockServer::start().await;
726        let client = logged_in_client(Some(server.uri())).await;
727        let room_id = get_test_room_id();
728
729        // Initialize with a muted `Override` rule to be in `Mute`
730        let settings = from_insert_rules(&client, vec![(RuleKind::Override, &room_id, false)]);
731        assert_eq!(
732            settings.get_user_defined_room_notification_mode(&room_id).await.unwrap(),
733            RoomNotificationMode::Mute
734        );
735    }
736
737    #[async_test]
738    async fn test_get_default_room_notification_mode_all_messages() -> TestResult {
739        let server = MockServer::start().await;
740        let client = logged_in_client(Some(server.uri())).await;
741
742        let mut ruleset = get_server_default_ruleset();
743        ruleset.set_actions(
744            RuleKind::Underride,
745            PredefinedUnderrideRuleId::RoomOneToOne,
746            vec![Action::Notify],
747        )?;
748
749        let settings = NotificationSettings::new(client, ruleset);
750        assert_eq!(
751            settings.get_default_room_notification_mode(IsEncrypted::No, IsOneToOne::Yes).await,
752            RoomNotificationMode::AllMessages
753        );
754
755        Ok(())
756    }
757
758    #[async_test]
759    async fn test_get_default_room_notification_mode_mentions_and_keywords() -> TestResult {
760        let server = MockServer::start().await;
761        let client = logged_in_client(Some(server.uri())).await;
762
763        // The default mode must be `MentionsAndKeywords` if the corresponding Underride
764        // rule doesn't notify
765        let mut ruleset = get_server_default_ruleset();
766        ruleset.set_actions(
767            RuleKind::Underride,
768            PredefinedUnderrideRuleId::RoomOneToOne,
769            vec![],
770        )?;
771
772        let settings = NotificationSettings::new(client.to_owned(), ruleset.to_owned());
773        assert_eq!(
774            settings.get_default_room_notification_mode(IsEncrypted::No, IsOneToOne::Yes).await,
775            RoomNotificationMode::MentionsAndKeywordsOnly
776        );
777
778        // The default mode must be `MentionsAndKeywords` if the corresponding Underride
779        // rule is disabled
780        ruleset.set_enabled(RuleKind::Underride, PredefinedUnderrideRuleId::RoomOneToOne, false)?;
781
782        let settings = NotificationSettings::new(client, ruleset);
783        assert_eq!(
784            settings.get_default_room_notification_mode(IsEncrypted::No, IsOneToOne::Yes).await,
785            RoomNotificationMode::MentionsAndKeywordsOnly
786        );
787
788        Ok(())
789    }
790
791    #[async_test]
792    async fn test_contains_keyword_rules() -> TestResult {
793        let server = MockServer::start().await;
794        let client = logged_in_client(Some(server.uri())).await;
795
796        let mut ruleset = get_server_default_ruleset();
797        let settings = NotificationSettings::new(client.to_owned(), ruleset.to_owned());
798
799        // By default, no keywords rules should be present
800        let contains_keywords_rules = settings.contains_keyword_rules().await;
801        assert!(!contains_keywords_rules);
802
803        // Initialize with a keyword rule
804        let rule = NewPatternedPushRule::new(
805            "keyword_rule_id".into(),
806            "keyword".into(),
807            vec![Action::Notify],
808        );
809        ruleset.insert(NewPushRule::Content(rule), None, None)?;
810
811        let settings = NotificationSettings::new(client, ruleset);
812        let contains_keywords_rules = settings.contains_keyword_rules().await;
813        assert!(contains_keywords_rules);
814
815        Ok(())
816    }
817
818    #[async_test]
819    async fn test_is_push_rule_enabled() -> TestResult {
820        let server = MockServer::start().await;
821        let client = logged_in_client(Some(server.uri())).await;
822
823        // Initial state: Reaction disabled
824        let mut ruleset = get_server_default_ruleset();
825        ruleset.set_enabled(RuleKind::Override, PredefinedOverrideRuleId::Reaction, false)?;
826
827        let settings = NotificationSettings::new(client.clone(), ruleset);
828
829        let enabled = settings
830            .is_push_rule_enabled(RuleKind::Override, PredefinedOverrideRuleId::Reaction)
831            .await?;
832
833        assert!(!enabled);
834
835        // Initial state: Reaction enabled
836        let mut ruleset = get_server_default_ruleset();
837        ruleset.set_enabled(RuleKind::Override, PredefinedOverrideRuleId::Reaction, true)?;
838
839        let settings = NotificationSettings::new(client, ruleset);
840
841        let enabled = settings
842            .is_push_rule_enabled(RuleKind::Override, PredefinedOverrideRuleId::Reaction)
843            .await?;
844
845        assert!(enabled);
846        Ok(())
847    }
848
849    #[async_test]
850    async fn test_set_push_rule_enabled() -> TestResult {
851        let server = MockServer::start().await;
852        let client = logged_in_client(Some(server.uri())).await;
853        let mut ruleset = client.account().push_rules().await?;
854        // Initial state
855        ruleset.set_enabled(RuleKind::Override, PredefinedOverrideRuleId::Reaction, false)?;
856
857        let settings = NotificationSettings::new(client, ruleset);
858
859        Mock::given(method("PUT"))
860            .and(path("/_matrix/client/r0/pushrules/global/override/.m.rule.reaction/enabled"))
861            .respond_with(ResponseTemplate::new(200))
862            .expect(1)
863            .mount(&server)
864            .await;
865
866        settings
867            .set_push_rule_enabled(RuleKind::Override, PredefinedOverrideRuleId::Reaction, true)
868            .await?;
869
870        // The ruleset must have been updated
871        let rules = settings.rules.read().await;
872        let rule =
873            rules.ruleset.get(RuleKind::Override, PredefinedOverrideRuleId::Reaction).unwrap();
874        assert!(rule.enabled());
875
876        server.verify().await;
877        Ok(())
878    }
879
880    #[async_test]
881    async fn test_set_push_rule_enabled_api_error() -> TestResult {
882        let server = MockServer::start().await;
883        let client = logged_in_client(Some(server.uri())).await;
884        let mut ruleset = client.account().push_rules().await?;
885        // Initial state
886        ruleset.set_enabled(RuleKind::Override, PredefinedOverrideRuleId::IsUserMention, false)?;
887
888        let settings = NotificationSettings::new(client, ruleset);
889
890        // If the server returns an error
891        Mock::given(method("PUT")).respond_with(ResponseTemplate::new(500)).mount(&server).await;
892
893        // When enabling the push rule
894        assert_eq!(
895            settings
896                .set_push_rule_enabled(
897                    RuleKind::Override,
898                    PredefinedOverrideRuleId::IsUserMention,
899                    true,
900                )
901                .await,
902            Err(NotificationSettingsError::UnableToUpdatePushRule)
903        );
904
905        // The ruleset must not have been updated
906        let rules = settings.rules.read().await;
907        let rule =
908            rules.ruleset.get(RuleKind::Override, PredefinedOverrideRuleId::IsUserMention).unwrap();
909        assert!(!rule.enabled());
910
911        Ok(())
912    }
913
914    #[async_test]
915    async fn test_set_room_notification_mode() -> TestResult {
916        let server = MockServer::start().await;
917        let client = logged_in_client(Some(server.uri())).await;
918
919        Mock::given(method("PUT")).respond_with(ResponseTemplate::new(200)).mount(&server).await;
920        Mock::given(method("DELETE")).respond_with(ResponseTemplate::new(200)).mount(&server).await;
921
922        let settings = client.notification_settings().await;
923        let room_id = get_test_room_id();
924
925        let mode = settings.get_user_defined_room_notification_mode(&room_id).await;
926        assert!(mode.is_none());
927
928        let new_modes = [
929            RoomNotificationMode::AllMessages,
930            RoomNotificationMode::MentionsAndKeywordsOnly,
931            RoomNotificationMode::Mute,
932        ];
933        for new_mode in new_modes {
934            settings.set_room_notification_mode(&room_id, new_mode).await?;
935            assert_eq!(
936                new_mode,
937                settings.get_user_defined_room_notification_mode(&room_id).await.unwrap()
938            );
939        }
940
941        Ok(())
942    }
943
944    #[async_test]
945    async fn test_set_room_notification_mode_requests_order() -> TestResult {
946        let server = MockServer::start().await;
947        let client = logged_in_client(Some(server.uri())).await;
948
949        let put_was_called = Arc::new(AtomicBool::default());
950
951        Mock::given(method("PUT"))
952            .and(path_regex(r"_matrix/client/r0/pushrules/global/override/.*"))
953            .and({
954                let put_was_called = put_was_called.clone();
955                move |_: &wiremock::Request| {
956                    put_was_called.store(true, Ordering::SeqCst);
957
958                    true
959                }
960            })
961            .respond_with(ResponseTemplate::new(200))
962            .expect(1)
963            .mount(&server)
964            .await;
965
966        Mock::given(method("DELETE"))
967            .and(path_regex(r"_matrix/client/r0/pushrules/global/room/.*"))
968            .and(move |_: &wiremock::Request| {
969                // Make sure that the PUT is executed before the DELETE, so that the following
970                // sync results will give the following transitions:
971                // `AllMessages` -> `AllMessages` -> `Mute` by sending the
972                // DELETE before the PUT, we would have `AllMessages` ->
973                // `Default` -> `Mute`
974
975                let put_was_called = put_was_called.load(Ordering::SeqCst);
976                assert!(
977                    put_was_called,
978                    "The PUT /pushrules/global/override/ method should have been called before the \
979                     DELETE method"
980                );
981
982                true
983            })
984            .respond_with(ResponseTemplate::new(200))
985            .expect(1)
986            .mount(&server)
987            .await;
988
989        let room_id = get_test_room_id();
990
991        // Set the initial state to `AllMessages` by setting a `Room` rule that notifies
992        let settings = from_insert_rules(&client, vec![(RuleKind::Room, &room_id, true)]);
993
994        // Set the new mode to `Mute`, this will add a new `Override` rule without
995        // action and remove the `Room` rule.
996        settings.set_room_notification_mode(&room_id, RoomNotificationMode::Mute).await?;
997
998        assert_eq!(
999            RoomNotificationMode::Mute,
1000            settings.get_user_defined_room_notification_mode(&room_id).await.unwrap()
1001        );
1002
1003        server.verify().await;
1004        Ok(())
1005    }
1006
1007    #[async_test]
1008    async fn test_set_room_notification_mode_put_api_error() {
1009        let server = MockServer::start().await;
1010        let client = logged_in_client(Some(server.uri())).await;
1011
1012        // If the server returns an error
1013        Mock::given(method("PUT")).respond_with(ResponseTemplate::new(500)).mount(&server).await;
1014        Mock::given(method("DELETE")).respond_with(ResponseTemplate::new(200)).mount(&server).await;
1015
1016        let room_id = get_test_room_id();
1017
1018        // Set the initial state to `AllMessages` by setting a `Room` rule that notifies
1019        let settings = from_insert_rules(&client, vec![(RuleKind::Room, &room_id, true)]);
1020
1021        assert_eq!(
1022            settings.get_user_defined_room_notification_mode(&room_id).await.unwrap(),
1023            RoomNotificationMode::AllMessages
1024        );
1025
1026        // Setting the new mode should fail
1027        assert_eq!(
1028            settings.set_room_notification_mode(&room_id, RoomNotificationMode::Mute).await,
1029            Err(NotificationSettingsError::UnableToAddPushRule)
1030        );
1031
1032        // The ruleset must not have been updated
1033        assert_eq!(
1034            settings.get_user_defined_room_notification_mode(&room_id).await.unwrap(),
1035            RoomNotificationMode::AllMessages
1036        );
1037    }
1038
1039    #[async_test]
1040    async fn test_set_room_notification_mode_delete_api_error() {
1041        let server = MockServer::start().await;
1042        let client = logged_in_client(Some(server.uri())).await;
1043
1044        // If the server returns an error
1045        Mock::given(method("PUT")).respond_with(ResponseTemplate::new(200)).mount(&server).await;
1046        Mock::given(method("DELETE")).respond_with(ResponseTemplate::new(500)).mount(&server).await;
1047
1048        let room_id = get_test_room_id();
1049
1050        // Set the initial state to `AllMessages` by setting a `Room` rule that notifies
1051        let settings = from_insert_rules(&client, vec![(RuleKind::Room, &room_id, true)]);
1052
1053        assert_eq!(
1054            settings.get_user_defined_room_notification_mode(&room_id).await.unwrap(),
1055            RoomNotificationMode::AllMessages
1056        );
1057
1058        // Setting the new mode should fail
1059        assert_eq!(
1060            settings.set_room_notification_mode(&room_id, RoomNotificationMode::Mute).await,
1061            Err(NotificationSettingsError::UnableToRemovePushRule)
1062        );
1063
1064        // The ruleset must not have been updated
1065        assert_eq!(
1066            settings.get_user_defined_room_notification_mode(&room_id).await.unwrap(),
1067            RoomNotificationMode::AllMessages
1068        );
1069    }
1070
1071    #[async_test]
1072    async fn test_delete_user_defined_room_rules() -> TestResult {
1073        let server = MockServer::start().await;
1074        let client = logged_in_client(Some(server.uri())).await;
1075        let room_id_a = owned_room_id!("!AAAaAAAAAaaAAaaaaa:matrix.org");
1076        let room_id_b = owned_room_id!("!BBBbBBBBBbbBBbbbbb:matrix.org");
1077
1078        Mock::given(method("DELETE")).respond_with(ResponseTemplate::new(200)).mount(&server).await;
1079
1080        // Initialize with some of custom rules
1081        let settings = from_insert_rules(
1082            &client,
1083            vec![
1084                (RuleKind::Room, &room_id_a, true),
1085                (RuleKind::Room, &room_id_b, true),
1086                (RuleKind::Override, &room_id_b, true),
1087            ],
1088        );
1089
1090        // Delete all user defined rules for room_id_a
1091        settings.delete_user_defined_room_rules(&room_id_a).await?;
1092
1093        // Only the rules for room_id_b should remain
1094        let updated_rules = settings.rules.read().await;
1095        assert_eq!(updated_rules.get_custom_rules_for_room(&room_id_b).len(), 2);
1096        assert!(updated_rules.get_custom_rules_for_room(&room_id_a).is_empty());
1097        Ok(())
1098    }
1099
1100    #[async_test]
1101    async fn test_unmute_room_not_muted() -> TestResult {
1102        let server = MockServer::start().await;
1103        let client = logged_in_client(Some(server.uri())).await;
1104        let room_id = get_test_room_id();
1105
1106        // Initialize with a `MentionsAndKeywordsOnly` mode
1107        let settings = from_insert_rules(&client, vec![(RuleKind::Room, &room_id, false)]);
1108        assert_eq!(
1109            settings.get_user_defined_room_notification_mode(&room_id).await.unwrap(),
1110            RoomNotificationMode::MentionsAndKeywordsOnly
1111        );
1112
1113        // Unmute the room
1114        settings.unmute_room(&room_id, IsEncrypted::Yes, IsOneToOne::Yes).await?;
1115
1116        // The ruleset must not be modified
1117        assert_eq!(
1118            settings.get_user_defined_room_notification_mode(&room_id).await.unwrap(),
1119            RoomNotificationMode::MentionsAndKeywordsOnly
1120        );
1121
1122        let room_rules = get_custom_rules_for_room(&settings, &room_id).await;
1123        assert_eq!(room_rules.len(), 1);
1124        assert_matches!(settings.rules.read().await.ruleset.get(RuleKind::Room, &room_id),
1125            Some(AnyPushRuleRef::Room(rule)) => {
1126                assert_eq!(rule.rule_id, room_id);
1127                assert!(rule.actions.is_empty());
1128            }
1129        );
1130
1131        Ok(())
1132    }
1133
1134    #[async_test]
1135    async fn test_unmute_room() -> TestResult {
1136        let server = MockServer::start().await;
1137        Mock::given(method("PUT")).respond_with(ResponseTemplate::new(200)).mount(&server).await;
1138        Mock::given(method("DELETE")).respond_with(ResponseTemplate::new(200)).mount(&server).await;
1139        let client = logged_in_client(Some(server.uri())).await;
1140        let room_id = get_test_room_id();
1141
1142        // Start with the room muted
1143        let settings = from_insert_rules(&client, vec![(RuleKind::Override, &room_id, false)]);
1144        assert_eq!(
1145            settings.get_user_defined_room_notification_mode(&room_id).await,
1146            Some(RoomNotificationMode::Mute)
1147        );
1148
1149        // Unmute the room
1150        settings.unmute_room(&room_id, IsEncrypted::No, IsOneToOne::Yes).await?;
1151
1152        // The user defined mode must have been removed
1153        assert!(settings.get_user_defined_room_notification_mode(&room_id).await.is_none());
1154
1155        Ok(())
1156    }
1157
1158    #[async_test]
1159    async fn test_unmute_room_default_mode() -> TestResult {
1160        let server = MockServer::start().await;
1161        Mock::given(method("PUT")).respond_with(ResponseTemplate::new(200)).mount(&server).await;
1162        let client = logged_in_client(Some(server.uri())).await;
1163        let room_id = get_test_room_id();
1164        let settings = client.notification_settings().await;
1165
1166        // Unmute the room
1167        settings.unmute_room(&room_id, IsEncrypted::No, IsOneToOne::Yes).await?;
1168
1169        // The new mode must be `AllMessages`
1170        assert_eq!(
1171            Some(RoomNotificationMode::AllMessages),
1172            settings.get_user_defined_room_notification_mode(&room_id).await
1173        );
1174
1175        let room_rules = get_custom_rules_for_room(&settings, &room_id).await;
1176        assert_eq!(room_rules.len(), 1);
1177        assert_matches!(settings.rules.read().await.ruleset.get(RuleKind::Room, &room_id),
1178            Some(AnyPushRuleRef::Room(rule)) => {
1179                assert_eq!(rule.rule_id, room_id);
1180                assert!(!rule.actions.is_empty());
1181            }
1182        );
1183
1184        Ok(())
1185    }
1186
1187    #[async_test]
1188    async fn test_set_default_room_notification_mode() -> TestResult {
1189        let server = MockServer::start().await;
1190        Mock::given(method("PUT")).respond_with(ResponseTemplate::new(200)).mount(&server).await;
1191        let client = logged_in_client(Some(server.uri())).await;
1192
1193        // If the initial mode is `AllMessages`
1194        let mut ruleset = get_server_default_ruleset();
1195        ruleset.set_actions(
1196            RuleKind::Underride,
1197            PredefinedUnderrideRuleId::Message,
1198            vec![Action::Notify],
1199        )?;
1200
1201        ruleset.set_actions(
1202            RuleKind::Underride,
1203            PredefinedUnderrideRuleId::PollStart,
1204            vec![Action::Notify],
1205        )?;
1206
1207        let settings = NotificationSettings::new(client, ruleset);
1208        assert_eq!(
1209            settings.get_default_room_notification_mode(IsEncrypted::No, IsOneToOne::No).await,
1210            RoomNotificationMode::AllMessages
1211        );
1212
1213        // After setting the default mode to `MentionsAndKeywordsOnly`
1214        settings
1215            .set_default_room_notification_mode(
1216                IsEncrypted::No,
1217                IsOneToOne::No,
1218                RoomNotificationMode::MentionsAndKeywordsOnly,
1219            )
1220            .await?;
1221
1222        // The list of actions for this rule must be empty
1223        assert_matches!(settings.rules.read().await.ruleset.get(RuleKind::Underride, PredefinedUnderrideRuleId::Message),
1224            Some(AnyPushRuleRef::Underride(rule)) => {
1225                assert!(rule.actions.is_empty());
1226            }
1227        );
1228
1229        assert_matches!(settings.rules.read().await.ruleset.get(RuleKind::Underride, PredefinedUnderrideRuleId::PollStart),
1230            Some(AnyPushRuleRef::Underride(rule)) => {
1231                assert!(rule.actions.is_empty());
1232            }
1233        );
1234
1235        // and the new mode returned by `get_default_room_notification_mode()` should
1236        // reflect the change.
1237        assert_matches!(
1238            settings.get_default_room_notification_mode(IsEncrypted::No, IsOneToOne::No).await,
1239            RoomNotificationMode::MentionsAndKeywordsOnly
1240        );
1241
1242        Ok(())
1243    }
1244
1245    #[async_test]
1246    async fn test_set_default_room_notification_mode_one_to_one() -> TestResult {
1247        let server = MockServer::start().await;
1248        Mock::given(method("PUT")).respond_with(ResponseTemplate::new(200)).mount(&server).await;
1249        let client = logged_in_client(Some(server.uri())).await;
1250
1251        // If the initial mode is `AllMessages`
1252        let mut ruleset = get_server_default_ruleset();
1253        ruleset.set_actions(
1254            RuleKind::Underride,
1255            PredefinedUnderrideRuleId::RoomOneToOne,
1256            vec![Action::Notify],
1257        )?;
1258
1259        ruleset.set_actions(
1260            RuleKind::Underride,
1261            PredefinedUnderrideRuleId::PollStartOneToOne,
1262            vec![Action::Notify],
1263        )?;
1264
1265        let settings = NotificationSettings::new(client, ruleset);
1266        assert_eq!(
1267            settings.get_default_room_notification_mode(IsEncrypted::No, IsOneToOne::Yes).await,
1268            RoomNotificationMode::AllMessages
1269        );
1270
1271        // After setting the default mode to `MentionsAndKeywordsOnly`
1272        settings
1273            .set_default_room_notification_mode(
1274                IsEncrypted::No,
1275                IsOneToOne::Yes,
1276                RoomNotificationMode::MentionsAndKeywordsOnly,
1277            )
1278            .await?;
1279
1280        // The list of actions for this rule must be empty
1281        assert_matches!(settings.rules.read().await.ruleset.get(RuleKind::Underride, PredefinedUnderrideRuleId::RoomOneToOne),
1282            Some(AnyPushRuleRef::Underride(rule)) => {
1283                assert!(rule.actions.is_empty());
1284            }
1285        );
1286
1287        assert_matches!(settings.rules.read().await.ruleset.get(RuleKind::Underride, PredefinedUnderrideRuleId::PollStartOneToOne),
1288            Some(AnyPushRuleRef::Underride(rule)) => {
1289                assert!(rule.actions.is_empty());
1290            }
1291        );
1292
1293        // and the new mode returned by `get_default_room_notification_mode()` should
1294        // reflect the change.
1295        assert_matches!(
1296            settings.get_default_room_notification_mode(IsEncrypted::No, IsOneToOne::Yes).await,
1297            RoomNotificationMode::MentionsAndKeywordsOnly
1298        );
1299
1300        Ok(())
1301    }
1302
1303    #[async_test]
1304    async fn test_set_default_room_notification_mode_enables_rules() -> TestResult {
1305        let server = MockServer::start().await;
1306        Mock::given(method("PUT")).respond_with(ResponseTemplate::new(200)).mount(&server).await;
1307        let client = logged_in_client(Some(server.uri())).await;
1308
1309        // If the initial mode is `MentionsAndKeywordsOnly`
1310        let mut ruleset = get_server_default_ruleset();
1311        ruleset.set_actions(
1312            RuleKind::Underride,
1313            PredefinedUnderrideRuleId::RoomOneToOne,
1314            vec![],
1315        )?;
1316
1317        ruleset.set_actions(
1318            RuleKind::Underride,
1319            PredefinedUnderrideRuleId::PollStartOneToOne,
1320            vec![],
1321        )?;
1322
1323        // Disable one of the rules that will be updated
1324        ruleset.set_enabled(RuleKind::Underride, PredefinedUnderrideRuleId::RoomOneToOne, false)?;
1325
1326        let settings = NotificationSettings::new(client, ruleset);
1327
1328        // After setting the default mode to `AllMessages`
1329        settings
1330            .set_default_room_notification_mode(
1331                IsEncrypted::No,
1332                IsOneToOne::Yes,
1333                RoomNotificationMode::AllMessages,
1334            )
1335            .await?;
1336
1337        // The new mode returned should be `AllMessages` which means that the disabled
1338        // rule (`RoomOneToOne`) has been enabled.
1339        assert_matches!(
1340            settings.get_default_room_notification_mode(IsEncrypted::No, IsOneToOne::Yes).await,
1341            RoomNotificationMode::AllMessages
1342        );
1343
1344        Ok(())
1345    }
1346
1347    #[async_test]
1348    async fn test_list_keywords() -> TestResult {
1349        let server = MockServer::start().await;
1350        let client = logged_in_client(Some(server.uri())).await;
1351
1352        // Initial state: No keywords
1353        let ruleset = get_server_default_ruleset();
1354        let settings = NotificationSettings::new(client.clone(), ruleset);
1355
1356        let keywords = settings.enabled_keywords().await;
1357
1358        assert!(keywords.is_empty());
1359
1360        // Initial state: 3 rules, 2 keywords
1361        let mut ruleset = get_server_default_ruleset();
1362        ruleset.insert(
1363            NewPushRule::Content(NewPatternedPushRule::new("a".to_owned(), "a".to_owned(), vec![])),
1364            None,
1365            None,
1366        )?;
1367        // Test deduplication.
1368        ruleset.insert(
1369            NewPushRule::Content(NewPatternedPushRule::new(
1370                "a_bis".to_owned(),
1371                "a".to_owned(),
1372                vec![],
1373            )),
1374            None,
1375            None,
1376        )?;
1377        ruleset.insert(
1378            NewPushRule::Content(NewPatternedPushRule::new("b".to_owned(), "b".to_owned(), vec![])),
1379            None,
1380            None,
1381        )?;
1382
1383        let settings = NotificationSettings::new(client, ruleset);
1384
1385        let keywords = settings.enabled_keywords().await;
1386        assert_eq!(keywords.len(), 2);
1387        assert!(keywords.get("a").is_some());
1388        assert!(keywords.get("b").is_some());
1389
1390        Ok(())
1391    }
1392
1393    #[async_test]
1394    async fn test_add_keyword_missing() -> TestResult {
1395        let server = MockServer::start().await;
1396        let client = logged_in_client(Some(server.uri())).await;
1397        let settings = client.notification_settings().await;
1398
1399        Mock::given(method("PUT"))
1400            .and(path("/_matrix/client/r0/pushrules/global/content/banana"))
1401            .respond_with(ResponseTemplate::new(200))
1402            .expect(1)
1403            .mount(&server)
1404            .await;
1405
1406        settings.add_keyword("banana".to_owned()).await?;
1407
1408        // The ruleset must have been updated.
1409        let keywords = settings.enabled_keywords().await;
1410        assert_eq!(keywords.len(), 1);
1411        assert!(keywords.get("banana").is_some());
1412
1413        // Rule exists.
1414        let rule_enabled = settings.is_push_rule_enabled(RuleKind::Content, "banana").await?;
1415        assert!(rule_enabled);
1416
1417        Ok(())
1418    }
1419
1420    #[async_test]
1421    async fn test_add_keyword_disabled() -> TestResult {
1422        let server = MockServer::start().await;
1423        let client = logged_in_client(Some(server.uri())).await;
1424
1425        let mut ruleset = get_server_default_ruleset();
1426        ruleset.insert(
1427            NewPushRule::Content(NewPatternedPushRule::new(
1428                "banana_two".to_owned(),
1429                "banana".to_owned(),
1430                vec![],
1431            )),
1432            None,
1433            None,
1434        )?;
1435        ruleset.set_enabled(RuleKind::Content, "banana_two", false)?;
1436        ruleset.insert(
1437            NewPushRule::Content(NewPatternedPushRule::new(
1438                "banana_one".to_owned(),
1439                "banana".to_owned(),
1440                vec![],
1441            )),
1442            None,
1443            None,
1444        )?;
1445        ruleset.set_enabled(RuleKind::Content, "banana_one", false)?;
1446
1447        let settings = NotificationSettings::new(client, ruleset);
1448        Mock::given(method("PUT"))
1449            .and(path("/_matrix/client/r0/pushrules/global/content/banana_one/enabled"))
1450            .respond_with(ResponseTemplate::new(200))
1451            .expect(1)
1452            .mount(&server)
1453            .await;
1454
1455        settings.add_keyword("banana".to_owned()).await?;
1456
1457        // The ruleset must have been updated.
1458        let keywords = settings.enabled_keywords().await;
1459
1460        assert_eq!(keywords.len(), 1);
1461        assert!(keywords.get("banana").is_some());
1462
1463        // The first rule was enabled.
1464        let first_rule_enabled =
1465            settings.is_push_rule_enabled(RuleKind::Content, "banana_one").await?;
1466        assert!(first_rule_enabled);
1467        let second_rule_enabled =
1468            settings.is_push_rule_enabled(RuleKind::Content, "banana_two").await?;
1469        assert!(!second_rule_enabled);
1470
1471        Ok(())
1472    }
1473
1474    #[async_test]
1475    async fn test_add_keyword_noop() -> TestResult {
1476        let server = MockServer::start().await;
1477        let client = logged_in_client(Some(server.uri())).await;
1478
1479        let mut ruleset = get_server_default_ruleset();
1480        ruleset.insert(
1481            NewPushRule::Content(NewPatternedPushRule::new(
1482                "banana_two".to_owned(),
1483                "banana".to_owned(),
1484                vec![],
1485            )),
1486            None,
1487            None,
1488        )?;
1489        ruleset.insert(
1490            NewPushRule::Content(NewPatternedPushRule::new(
1491                "banana_one".to_owned(),
1492                "banana".to_owned(),
1493                vec![],
1494            )),
1495            None,
1496            None,
1497        )?;
1498        ruleset.set_enabled(RuleKind::Content, "banana_one", false)?;
1499
1500        let settings = NotificationSettings::new(client, ruleset);
1501        settings.add_keyword("banana".to_owned()).await?;
1502
1503        // Nothing changed.
1504        let keywords = settings.enabled_keywords().await;
1505
1506        assert_eq!(keywords.len(), 1);
1507        assert!(keywords.get("banana").is_some());
1508
1509        let first_rule_enabled =
1510            settings.is_push_rule_enabled(RuleKind::Content, "banana_one").await?;
1511        assert!(!first_rule_enabled);
1512        let second_rule_enabled =
1513            settings.is_push_rule_enabled(RuleKind::Content, "banana_two").await?;
1514        assert!(second_rule_enabled);
1515
1516        Ok(())
1517    }
1518
1519    #[async_test]
1520    async fn test_remove_keyword_all() -> TestResult {
1521        let server = MockServer::start().await;
1522        let client = logged_in_client(Some(server.uri())).await;
1523
1524        let mut ruleset = get_server_default_ruleset();
1525        ruleset.insert(
1526            NewPushRule::Content(NewPatternedPushRule::new(
1527                "banana_two".to_owned(),
1528                "banana".to_owned(),
1529                vec![],
1530            )),
1531            None,
1532            None,
1533        )?;
1534        ruleset.insert(
1535            NewPushRule::Content(NewPatternedPushRule::new(
1536                "banana_one".to_owned(),
1537                "banana".to_owned(),
1538                vec![],
1539            )),
1540            None,
1541            None,
1542        )?;
1543        ruleset.set_enabled(RuleKind::Content, "banana_one", false)?;
1544
1545        let settings = NotificationSettings::new(client, ruleset);
1546
1547        Mock::given(method("DELETE"))
1548            .and(path("/_matrix/client/r0/pushrules/global/content/banana_one"))
1549            .respond_with(ResponseTemplate::new(200))
1550            .expect(1)
1551            .mount(&server)
1552            .await;
1553        Mock::given(method("DELETE"))
1554            .and(path("/_matrix/client/r0/pushrules/global/content/banana_two"))
1555            .respond_with(ResponseTemplate::new(200))
1556            .expect(1)
1557            .mount(&server)
1558            .await;
1559
1560        settings.remove_keyword("banana").await?;
1561
1562        // The ruleset must have been updated.
1563        let keywords = settings.enabled_keywords().await;
1564        assert!(keywords.is_empty());
1565
1566        // Rules we removed.
1567        let first_rule_error =
1568            settings.is_push_rule_enabled(RuleKind::Content, "banana_one").await.unwrap_err();
1569        assert_matches!(first_rule_error, NotificationSettingsError::RuleNotFound(_));
1570        let second_rule_error =
1571            settings.is_push_rule_enabled(RuleKind::Content, "banana_two").await.unwrap_err();
1572        assert_matches!(second_rule_error, NotificationSettingsError::RuleNotFound(_));
1573
1574        Ok(())
1575    }
1576
1577    #[async_test]
1578    async fn test_remove_keyword_noop() -> TestResult {
1579        let server = MockServer::start().await;
1580        let client = logged_in_client(Some(server.uri())).await;
1581        let settings = client.notification_settings().await;
1582
1583        settings.remove_keyword("banana").await?;
1584        Ok(())
1585    }
1586
1587    #[async_test]
1588    async fn test_set_default_room_notification_mode_missing_poll_start() -> TestResult {
1589        let server = MockServer::start().await;
1590        Mock::given(method("PUT")).respond_with(ResponseTemplate::new(200)).mount(&server).await;
1591        let client = logged_in_client(Some(server.uri())).await;
1592
1593        // If the initial mode is `AllMessages`
1594        let mut ruleset = get_server_default_ruleset();
1595        ruleset.underride.swap_remove(PredefinedUnderrideRuleId::PollStart.as_str());
1596
1597        let settings = NotificationSettings::new(client, ruleset);
1598        assert_eq!(
1599            settings.get_default_room_notification_mode(IsEncrypted::No, IsOneToOne::No).await,
1600            RoomNotificationMode::AllMessages
1601        );
1602
1603        // After setting the default mode to `MentionsAndKeywordsOnly`
1604        settings
1605            .set_default_room_notification_mode(
1606                IsEncrypted::No,
1607                IsOneToOne::No,
1608                RoomNotificationMode::MentionsAndKeywordsOnly,
1609            )
1610            .await?;
1611
1612        // the new mode returned by `get_default_room_notification_mode()` should
1613        // reflect the change.
1614        assert_matches!(
1615            settings.get_default_room_notification_mode(IsEncrypted::No, IsOneToOne::No).await,
1616            RoomNotificationMode::MentionsAndKeywordsOnly
1617        );
1618        Ok(())
1619    }
1620
1621    #[async_test]
1622    async fn test_create_custom_conditional_push_rule() -> TestResult {
1623        let server = MockServer::start().await;
1624        let client = logged_in_client(Some(server.uri())).await;
1625        let settings = client.notification_settings().await;
1626
1627        Mock::given(method("PUT"))
1628            .and(path("/_matrix/client/r0/pushrules/global/override/custom_rule"))
1629            .respond_with(ResponseTemplate::new(200))
1630            .expect(1)
1631            .mount(&server)
1632            .await;
1633
1634        let actions = vec![Action::Notify];
1635        let conditions = vec![ruma::push::PushCondition::EventMatch {
1636            key: "content.body".to_owned(),
1637            pattern: "hello".to_owned(),
1638        }];
1639
1640        settings
1641            .create_custom_conditional_push_rule(
1642                "custom_rule".to_owned(),
1643                RuleKind::Override,
1644                actions.clone(),
1645                conditions.clone(),
1646            )
1647            .await?;
1648
1649        let rules = settings.rules.read().await;
1650        let rule = rules.ruleset.get(RuleKind::Override, "custom_rule").unwrap();
1651
1652        assert_eq!(rule.rule_id(), "custom_rule");
1653        assert!(rule.enabled());
1654
1655        Ok(())
1656    }
1657
1658    #[async_test]
1659    async fn test_create_custom_conditional_push_rule_invalid_kind() {
1660        let server = MockServer::start().await;
1661        let client = logged_in_client(Some(server.uri())).await;
1662        let settings = client.notification_settings().await;
1663
1664        let actions = vec![Action::Notify];
1665        let conditions = vec![ruma::push::PushCondition::EventMatch {
1666            key: "content.body".to_owned(),
1667            pattern: "hello".to_owned(),
1668        }];
1669
1670        let result = settings
1671            .create_custom_conditional_push_rule(
1672                "custom_rule".to_owned(),
1673                RuleKind::Room,
1674                actions,
1675                conditions,
1676            )
1677            .await;
1678
1679        assert_matches!(result, Err(NotificationSettingsError::InvalidParameter(_)));
1680    }
1681
1682    #[async_test]
1683    #[allow(deprecated)]
1684    async fn test_enable_mention_ignore_missing_legacy_push_rules() -> TestResult {
1685        let server = MatrixMockServer::new().await;
1686        let client = server.client_builder().build().await;
1687        let mut ruleset = get_server_default_ruleset();
1688
1689        // Make sure that the legacy mention push rules are missing.
1690        if let Some(idx) = ruleset
1691            .override_
1692            .iter()
1693            .position(|rule| rule.rule_id == PredefinedOverrideRuleId::ContainsDisplayName.as_ref())
1694        {
1695            ruleset.override_.shift_remove_index(idx);
1696        }
1697
1698        if let Some(idx) = ruleset
1699            .override_
1700            .iter()
1701            .position(|rule| rule.rule_id == PredefinedOverrideRuleId::RoomNotif.as_ref())
1702        {
1703            ruleset.override_.shift_remove_index(idx);
1704        }
1705
1706        if let Some(idx) = ruleset
1707            .content
1708            .iter()
1709            .position(|rule| rule.rule_id == PredefinedContentRuleId::ContainsUserName.as_ref())
1710        {
1711            ruleset.content.shift_remove_index(idx);
1712        }
1713
1714        assert_matches!(
1715            ruleset.iter().find(|rule| {
1716                rule.rule_id() == PredefinedOverrideRuleId::ContainsDisplayName.as_ref()
1717                    || rule.rule_id() == PredefinedOverrideRuleId::RoomNotif.as_ref()
1718                    || rule.rule_id() == PredefinedContentRuleId::ContainsUserName.as_ref()
1719            }),
1720            None,
1721            "ruleset must not have legacy mention push rules"
1722        );
1723
1724        let settings = NotificationSettings::new(client, ruleset);
1725
1726        server
1727            .mock_enable_push_rule(RuleKind::Override, PredefinedOverrideRuleId::IsUserMention)
1728            .ok()
1729            .mock_once()
1730            .named("is_user_mention")
1731            .mount()
1732            .await;
1733        settings
1734            .set_push_rule_enabled(
1735                RuleKind::Override,
1736                PredefinedOverrideRuleId::IsUserMention,
1737                false,
1738            )
1739            .await?;
1740
1741        server
1742            .mock_enable_push_rule(RuleKind::Override, PredefinedOverrideRuleId::IsRoomMention)
1743            .ok()
1744            .mock_once()
1745            .named("is_room_mention")
1746            .mount()
1747            .await;
1748        settings
1749            .set_push_rule_enabled(
1750                RuleKind::Override,
1751                PredefinedOverrideRuleId::IsRoomMention,
1752                false,
1753            )
1754            .await?;
1755
1756        Ok(())
1757    }
1758}