Skip to main content

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