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