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