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