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_family = "wasm")))]
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, TestResult,
585    };
586    use ruma::{
587        owned_room_id,
588        push::{
589            Action, AnyPushRuleRef, NewPatternedPushRule, NewPushRule, PredefinedContentRuleId,
590            PredefinedOverrideRuleId, PredefinedUnderrideRuleId, RuleKind,
591        },
592        OwnedRoomId, RoomId,
593    };
594    use serde_json::json;
595    use stream_assert::{assert_next_eq, assert_pending};
596    use tokio_stream::wrappers::BroadcastStream;
597    use wiremock::{
598        matchers::{header, method, path, path_regex},
599        Mock, MockServer, ResponseTemplate,
600    };
601
602    use crate::{
603        config::SyncSettings,
604        error::NotificationSettingsError,
605        notification_settings::{
606            IsEncrypted, IsOneToOne, NotificationSettings, RoomNotificationMode,
607        },
608        test_utils::{logged_in_client, mocks::MatrixMockServer},
609        Client,
610    };
611
612    fn get_test_room_id() -> OwnedRoomId {
613        owned_room_id!("!AAAaAAAAAaaAAaaaaa:matrix.org")
614    }
615
616    fn from_insert_rules(
617        client: &Client,
618        rules: Vec<(RuleKind, &RoomId, bool)>,
619    ) -> NotificationSettings {
620        let ruleset = build_ruleset(rules);
621        NotificationSettings::new(client.to_owned(), ruleset)
622    }
623
624    async fn get_custom_rules_for_room(
625        settings: &NotificationSettings,
626        room_id: &RoomId,
627    ) -> Vec<(RuleKind, String)> {
628        settings.rules.read().await.get_custom_rules_for_room(room_id)
629    }
630
631    #[async_test]
632    async fn test_subscribe_to_changes() -> TestResult {
633        let server = MockServer::start().await;
634        let client = logged_in_client(Some(server.uri())).await;
635        let settings = client.notification_settings().await;
636
637        Mock::given(method("GET"))
638            .and(path("/_matrix/client/r0/sync"))
639            .and(header("authorization", "Bearer 1234"))
640            .respond_with(ResponseTemplate::new(200).set_body_json(json!({
641                "next_batch": "1234",
642                "account_data": {
643                    "events": [*test_json::PUSH_RULES]
644                }
645            })))
646            .expect(1)
647            .mount(&server)
648            .await;
649
650        let subscriber = settings.subscribe_to_changes();
651        let mut stream = BroadcastStream::new(subscriber);
652
653        assert_pending!(stream);
654
655        client.sync_once(SyncSettings::default()).await?;
656
657        assert_next_eq!(stream, Ok(()));
658        assert_pending!(stream);
659
660        Ok(())
661    }
662
663    #[async_test]
664    async fn test_get_custom_rules_for_room() {
665        let server = MockServer::start().await;
666        let client = logged_in_client(Some(server.uri())).await;
667        let room_id = get_test_room_id();
668
669        let settings = from_insert_rules(&client, vec![(RuleKind::Room, &room_id, true)]);
670
671        let custom_rules = get_custom_rules_for_room(&settings, &room_id).await;
672        assert_eq!(custom_rules.len(), 1);
673        assert_eq!(custom_rules[0], (RuleKind::Room, room_id.to_string()));
674
675        let settings = from_insert_rules(
676            &client,
677            vec![(RuleKind::Room, &room_id, true), (RuleKind::Override, &room_id, true)],
678        );
679        let custom_rules = get_custom_rules_for_room(&settings, &room_id).await;
680        assert_eq!(custom_rules.len(), 2);
681        assert_eq!(custom_rules[0], (RuleKind::Override, room_id.to_string()));
682        assert_eq!(custom_rules[1], (RuleKind::Room, room_id.to_string()));
683    }
684
685    #[async_test]
686    async fn test_get_user_defined_room_notification_mode_none() {
687        let server = MockServer::start().await;
688        let client = logged_in_client(Some(server.uri())).await;
689        let room_id = get_test_room_id();
690
691        let settings = client.notification_settings().await;
692        assert!(settings.get_user_defined_room_notification_mode(&room_id).await.is_none());
693    }
694
695    #[async_test]
696    async fn test_get_user_defined_room_notification_mode_all_messages() {
697        let server = MockServer::start().await;
698        let client = logged_in_client(Some(server.uri())).await;
699        let room_id = get_test_room_id();
700
701        // Initialize with a notifying `Room` rule to be in `AllMessages`
702        let settings = from_insert_rules(&client, vec![(RuleKind::Room, &room_id, true)]);
703
704        assert_eq!(
705            settings.get_user_defined_room_notification_mode(&room_id).await.unwrap(),
706            RoomNotificationMode::AllMessages
707        );
708    }
709
710    #[async_test]
711    async fn test_get_user_defined_room_notification_mode_mentions_and_keywords() {
712        let server = MockServer::start().await;
713        let client = logged_in_client(Some(server.uri())).await;
714        let room_id = get_test_room_id();
715
716        // Initialize with a muted `Room` rule to be in `MentionsAndKeywordsOnly`
717        let settings = from_insert_rules(&client, vec![(RuleKind::Room, &room_id, false)]);
718        assert_eq!(
719            settings.get_user_defined_room_notification_mode(&room_id).await.unwrap(),
720            RoomNotificationMode::MentionsAndKeywordsOnly
721        );
722    }
723
724    #[async_test]
725    async fn test_get_user_defined_room_notification_mode_mute() {
726        let server = MockServer::start().await;
727        let client = logged_in_client(Some(server.uri())).await;
728        let room_id = get_test_room_id();
729
730        // Initialize with a muted `Override` rule to be in `Mute`
731        let settings = from_insert_rules(&client, vec![(RuleKind::Override, &room_id, false)]);
732        assert_eq!(
733            settings.get_user_defined_room_notification_mode(&room_id).await.unwrap(),
734            RoomNotificationMode::Mute
735        );
736    }
737
738    #[async_test]
739    async fn test_get_default_room_notification_mode_all_messages() -> TestResult {
740        let server = MockServer::start().await;
741        let client = logged_in_client(Some(server.uri())).await;
742
743        let mut ruleset = get_server_default_ruleset();
744        ruleset.set_actions(
745            RuleKind::Underride,
746            PredefinedUnderrideRuleId::RoomOneToOne,
747            vec![Action::Notify],
748        )?;
749
750        let settings = NotificationSettings::new(client, ruleset);
751        assert_eq!(
752            settings.get_default_room_notification_mode(IsEncrypted::No, IsOneToOne::Yes).await,
753            RoomNotificationMode::AllMessages
754        );
755
756        Ok(())
757    }
758
759    #[async_test]
760    async fn test_get_default_room_notification_mode_mentions_and_keywords() -> TestResult {
761        let server = MockServer::start().await;
762        let client = logged_in_client(Some(server.uri())).await;
763
764        // The default mode must be `MentionsAndKeywords` if the corresponding Underride
765        // rule doesn't notify
766        let mut ruleset = get_server_default_ruleset();
767        ruleset.set_actions(
768            RuleKind::Underride,
769            PredefinedUnderrideRuleId::RoomOneToOne,
770            vec![],
771        )?;
772
773        let settings = NotificationSettings::new(client.to_owned(), ruleset.to_owned());
774        assert_eq!(
775            settings.get_default_room_notification_mode(IsEncrypted::No, IsOneToOne::Yes).await,
776            RoomNotificationMode::MentionsAndKeywordsOnly
777        );
778
779        // The default mode must be `MentionsAndKeywords` if the corresponding Underride
780        // rule is disabled
781        ruleset.set_enabled(RuleKind::Underride, PredefinedUnderrideRuleId::RoomOneToOne, false)?;
782
783        let settings = NotificationSettings::new(client, ruleset);
784        assert_eq!(
785            settings.get_default_room_notification_mode(IsEncrypted::No, IsOneToOne::Yes).await,
786            RoomNotificationMode::MentionsAndKeywordsOnly
787        );
788
789        Ok(())
790    }
791
792    #[async_test]
793    async fn test_contains_keyword_rules() -> TestResult {
794        let server = MockServer::start().await;
795        let client = logged_in_client(Some(server.uri())).await;
796
797        let mut ruleset = get_server_default_ruleset();
798        let settings = NotificationSettings::new(client.to_owned(), ruleset.to_owned());
799
800        // By default, no keywords rules should be present
801        let contains_keywords_rules = settings.contains_keyword_rules().await;
802        assert!(!contains_keywords_rules);
803
804        // Initialize with a keyword rule
805        let rule = NewPatternedPushRule::new(
806            "keyword_rule_id".into(),
807            "keyword".into(),
808            vec![Action::Notify],
809        );
810        ruleset.insert(NewPushRule::Content(rule), None, None)?;
811
812        let settings = NotificationSettings::new(client, ruleset);
813        let contains_keywords_rules = settings.contains_keyword_rules().await;
814        assert!(contains_keywords_rules);
815
816        Ok(())
817    }
818
819    #[async_test]
820    async fn test_is_push_rule_enabled() -> TestResult {
821        let server = MockServer::start().await;
822        let client = logged_in_client(Some(server.uri())).await;
823
824        // Initial state: Reaction disabled
825        let mut ruleset = get_server_default_ruleset();
826        ruleset.set_enabled(RuleKind::Override, PredefinedOverrideRuleId::Reaction, false)?;
827
828        let settings = NotificationSettings::new(client.clone(), ruleset);
829
830        let enabled = settings
831            .is_push_rule_enabled(RuleKind::Override, PredefinedOverrideRuleId::Reaction)
832            .await?;
833
834        assert!(!enabled);
835
836        // Initial state: Reaction enabled
837        let mut ruleset = get_server_default_ruleset();
838        ruleset.set_enabled(RuleKind::Override, PredefinedOverrideRuleId::Reaction, true)?;
839
840        let settings = NotificationSettings::new(client, ruleset);
841
842        let enabled = settings
843            .is_push_rule_enabled(RuleKind::Override, PredefinedOverrideRuleId::Reaction)
844            .await?;
845
846        assert!(enabled);
847        Ok(())
848    }
849
850    #[async_test]
851    async fn test_set_push_rule_enabled() -> TestResult {
852        let server = MockServer::start().await;
853        let client = logged_in_client(Some(server.uri())).await;
854        let mut ruleset = client.account().push_rules().await?;
855        // Initial state
856        ruleset.set_enabled(RuleKind::Override, PredefinedOverrideRuleId::Reaction, false)?;
857
858        let settings = NotificationSettings::new(client, ruleset);
859
860        Mock::given(method("PUT"))
861            .and(path("/_matrix/client/r0/pushrules/global/override/.m.rule.reaction/enabled"))
862            .respond_with(ResponseTemplate::new(200))
863            .expect(1)
864            .mount(&server)
865            .await;
866
867        settings
868            .set_push_rule_enabled(RuleKind::Override, PredefinedOverrideRuleId::Reaction, true)
869            .await?;
870
871        // The ruleset must have been updated
872        let rules = settings.rules.read().await;
873        let rule =
874            rules.ruleset.get(RuleKind::Override, PredefinedOverrideRuleId::Reaction).unwrap();
875        assert!(rule.enabled());
876
877        server.verify().await;
878        Ok(())
879    }
880
881    #[async_test]
882    async fn test_set_push_rule_enabled_api_error() -> TestResult {
883        let server = MockServer::start().await;
884        let client = logged_in_client(Some(server.uri())).await;
885        let mut ruleset = client.account().push_rules().await?;
886        // Initial state
887        ruleset.set_enabled(RuleKind::Override, PredefinedOverrideRuleId::IsUserMention, false)?;
888
889        let settings = NotificationSettings::new(client, ruleset);
890
891        // If the server returns an error
892        Mock::given(method("PUT")).respond_with(ResponseTemplate::new(500)).mount(&server).await;
893
894        // When enabling the push rule
895        assert_eq!(
896            settings
897                .set_push_rule_enabled(
898                    RuleKind::Override,
899                    PredefinedOverrideRuleId::IsUserMention,
900                    true,
901                )
902                .await,
903            Err(NotificationSettingsError::UnableToUpdatePushRule)
904        );
905
906        // The ruleset must not have been updated
907        let rules = settings.rules.read().await;
908        let rule =
909            rules.ruleset.get(RuleKind::Override, PredefinedOverrideRuleId::IsUserMention).unwrap();
910        assert!(!rule.enabled());
911
912        Ok(())
913    }
914
915    #[async_test]
916    async fn test_set_room_notification_mode() -> TestResult {
917        let server = MockServer::start().await;
918        let client = logged_in_client(Some(server.uri())).await;
919
920        Mock::given(method("PUT")).respond_with(ResponseTemplate::new(200)).mount(&server).await;
921        Mock::given(method("DELETE")).respond_with(ResponseTemplate::new(200)).mount(&server).await;
922
923        let settings = client.notification_settings().await;
924        let room_id = get_test_room_id();
925
926        let mode = settings.get_user_defined_room_notification_mode(&room_id).await;
927        assert!(mode.is_none());
928
929        let new_modes = [
930            RoomNotificationMode::AllMessages,
931            RoomNotificationMode::MentionsAndKeywordsOnly,
932            RoomNotificationMode::Mute,
933        ];
934        for new_mode in new_modes {
935            settings.set_room_notification_mode(&room_id, new_mode).await?;
936            assert_eq!(
937                new_mode,
938                settings.get_user_defined_room_notification_mode(&room_id).await.unwrap()
939            );
940        }
941
942        Ok(())
943    }
944
945    #[async_test]
946    async fn test_set_room_notification_mode_requests_order() -> TestResult {
947        let server = MockServer::start().await;
948        let client = logged_in_client(Some(server.uri())).await;
949
950        let put_was_called = Arc::new(AtomicBool::default());
951
952        Mock::given(method("PUT"))
953            .and(path_regex(r"_matrix/client/r0/pushrules/global/override/.*"))
954            .and({
955                let put_was_called = put_was_called.clone();
956                move |_: &wiremock::Request| {
957                    put_was_called.store(true, Ordering::SeqCst);
958
959                    true
960                }
961            })
962            .respond_with(ResponseTemplate::new(200))
963            .expect(1)
964            .mount(&server)
965            .await;
966
967        Mock::given(method("DELETE"))
968            .and(path_regex(r"_matrix/client/r0/pushrules/global/room/.*"))
969            .and(move |_: &wiremock::Request| {
970                // Make sure that the PUT is executed before the DELETE, so that the following
971                // sync results will give the following transitions:
972                // `AllMessages` -> `AllMessages` -> `Mute` by sending the
973                // DELETE before the PUT, we would have `AllMessages` ->
974                // `Default` -> `Mute`
975
976                let put_was_called = put_was_called.load(Ordering::SeqCst);
977                assert!(
978                    put_was_called,
979                    "The PUT /pushrules/global/override/ method should have been called before the \
980                     DELETE method"
981                );
982
983                true
984            })
985            .respond_with(ResponseTemplate::new(200))
986            .expect(1)
987            .mount(&server)
988            .await;
989
990        let room_id = get_test_room_id();
991
992        // Set the initial state to `AllMessages` by setting a `Room` rule that notifies
993        let settings = from_insert_rules(&client, vec![(RuleKind::Room, &room_id, true)]);
994
995        // Set the new mode to `Mute`, this will add a new `Override` rule without
996        // action and remove the `Room` rule.
997        settings.set_room_notification_mode(&room_id, RoomNotificationMode::Mute).await?;
998
999        assert_eq!(
1000            RoomNotificationMode::Mute,
1001            settings.get_user_defined_room_notification_mode(&room_id).await.unwrap()
1002        );
1003
1004        server.verify().await;
1005        Ok(())
1006    }
1007
1008    #[async_test]
1009    async fn test_set_room_notification_mode_put_api_error() {
1010        let server = MockServer::start().await;
1011        let client = logged_in_client(Some(server.uri())).await;
1012
1013        // If the server returns an error
1014        Mock::given(method("PUT")).respond_with(ResponseTemplate::new(500)).mount(&server).await;
1015        Mock::given(method("DELETE")).respond_with(ResponseTemplate::new(200)).mount(&server).await;
1016
1017        let room_id = get_test_room_id();
1018
1019        // Set the initial state to `AllMessages` by setting a `Room` rule that notifies
1020        let settings = from_insert_rules(&client, vec![(RuleKind::Room, &room_id, true)]);
1021
1022        assert_eq!(
1023            settings.get_user_defined_room_notification_mode(&room_id).await.unwrap(),
1024            RoomNotificationMode::AllMessages
1025        );
1026
1027        // Setting the new mode should fail
1028        assert_eq!(
1029            settings.set_room_notification_mode(&room_id, RoomNotificationMode::Mute).await,
1030            Err(NotificationSettingsError::UnableToAddPushRule)
1031        );
1032
1033        // The ruleset must not have been updated
1034        assert_eq!(
1035            settings.get_user_defined_room_notification_mode(&room_id).await.unwrap(),
1036            RoomNotificationMode::AllMessages
1037        );
1038    }
1039
1040    #[async_test]
1041    async fn test_set_room_notification_mode_delete_api_error() {
1042        let server = MockServer::start().await;
1043        let client = logged_in_client(Some(server.uri())).await;
1044
1045        // If the server returns an error
1046        Mock::given(method("PUT")).respond_with(ResponseTemplate::new(200)).mount(&server).await;
1047        Mock::given(method("DELETE")).respond_with(ResponseTemplate::new(500)).mount(&server).await;
1048
1049        let room_id = get_test_room_id();
1050
1051        // Set the initial state to `AllMessages` by setting a `Room` rule that notifies
1052        let settings = from_insert_rules(&client, vec![(RuleKind::Room, &room_id, true)]);
1053
1054        assert_eq!(
1055            settings.get_user_defined_room_notification_mode(&room_id).await.unwrap(),
1056            RoomNotificationMode::AllMessages
1057        );
1058
1059        // Setting the new mode should fail
1060        assert_eq!(
1061            settings.set_room_notification_mode(&room_id, RoomNotificationMode::Mute).await,
1062            Err(NotificationSettingsError::UnableToRemovePushRule)
1063        );
1064
1065        // The ruleset must not have been updated
1066        assert_eq!(
1067            settings.get_user_defined_room_notification_mode(&room_id).await.unwrap(),
1068            RoomNotificationMode::AllMessages
1069        );
1070    }
1071
1072    #[async_test]
1073    async fn test_delete_user_defined_room_rules() -> TestResult {
1074        let server = MockServer::start().await;
1075        let client = logged_in_client(Some(server.uri())).await;
1076        let room_id_a = owned_room_id!("!AAAaAAAAAaaAAaaaaa:matrix.org");
1077        let room_id_b = owned_room_id!("!BBBbBBBBBbbBBbbbbb:matrix.org");
1078
1079        Mock::given(method("DELETE")).respond_with(ResponseTemplate::new(200)).mount(&server).await;
1080
1081        // Initialize with some of custom rules
1082        let settings = from_insert_rules(
1083            &client,
1084            vec![
1085                (RuleKind::Room, &room_id_a, true),
1086                (RuleKind::Room, &room_id_b, true),
1087                (RuleKind::Override, &room_id_b, true),
1088            ],
1089        );
1090
1091        // Delete all user defined rules for room_id_a
1092        settings.delete_user_defined_room_rules(&room_id_a).await?;
1093
1094        // Only the rules for room_id_b should remain
1095        let updated_rules = settings.rules.read().await;
1096        assert_eq!(updated_rules.get_custom_rules_for_room(&room_id_b).len(), 2);
1097        assert!(updated_rules.get_custom_rules_for_room(&room_id_a).is_empty());
1098        Ok(())
1099    }
1100
1101    #[async_test]
1102    async fn test_unmute_room_not_muted() -> TestResult {
1103        let server = MockServer::start().await;
1104        let client = logged_in_client(Some(server.uri())).await;
1105        let room_id = get_test_room_id();
1106
1107        // Initialize with a `MentionsAndKeywordsOnly` mode
1108        let settings = from_insert_rules(&client, vec![(RuleKind::Room, &room_id, false)]);
1109        assert_eq!(
1110            settings.get_user_defined_room_notification_mode(&room_id).await.unwrap(),
1111            RoomNotificationMode::MentionsAndKeywordsOnly
1112        );
1113
1114        // Unmute the room
1115        settings.unmute_room(&room_id, IsEncrypted::Yes, IsOneToOne::Yes).await?;
1116
1117        // The ruleset must not be modified
1118        assert_eq!(
1119            settings.get_user_defined_room_notification_mode(&room_id).await.unwrap(),
1120            RoomNotificationMode::MentionsAndKeywordsOnly
1121        );
1122
1123        let room_rules = get_custom_rules_for_room(&settings, &room_id).await;
1124        assert_eq!(room_rules.len(), 1);
1125        assert_matches!(settings.rules.read().await.ruleset.get(RuleKind::Room, &room_id),
1126            Some(AnyPushRuleRef::Room(rule)) => {
1127                assert_eq!(rule.rule_id, room_id);
1128                assert!(rule.actions.is_empty());
1129            }
1130        );
1131
1132        Ok(())
1133    }
1134
1135    #[async_test]
1136    async fn test_unmute_room() -> TestResult {
1137        let server = MockServer::start().await;
1138        Mock::given(method("PUT")).respond_with(ResponseTemplate::new(200)).mount(&server).await;
1139        Mock::given(method("DELETE")).respond_with(ResponseTemplate::new(200)).mount(&server).await;
1140        let client = logged_in_client(Some(server.uri())).await;
1141        let room_id = get_test_room_id();
1142
1143        // Start with the room muted
1144        let settings = from_insert_rules(&client, vec![(RuleKind::Override, &room_id, false)]);
1145        assert_eq!(
1146            settings.get_user_defined_room_notification_mode(&room_id).await,
1147            Some(RoomNotificationMode::Mute)
1148        );
1149
1150        // Unmute the room
1151        settings.unmute_room(&room_id, IsEncrypted::No, IsOneToOne::Yes).await?;
1152
1153        // The user defined mode must have been removed
1154        assert!(settings.get_user_defined_room_notification_mode(&room_id).await.is_none());
1155
1156        Ok(())
1157    }
1158
1159    #[async_test]
1160    async fn test_unmute_room_default_mode() -> TestResult {
1161        let server = MockServer::start().await;
1162        Mock::given(method("PUT")).respond_with(ResponseTemplate::new(200)).mount(&server).await;
1163        let client = logged_in_client(Some(server.uri())).await;
1164        let room_id = get_test_room_id();
1165        let settings = client.notification_settings().await;
1166
1167        // Unmute the room
1168        settings.unmute_room(&room_id, IsEncrypted::No, IsOneToOne::Yes).await?;
1169
1170        // The new mode must be `AllMessages`
1171        assert_eq!(
1172            Some(RoomNotificationMode::AllMessages),
1173            settings.get_user_defined_room_notification_mode(&room_id).await
1174        );
1175
1176        let room_rules = get_custom_rules_for_room(&settings, &room_id).await;
1177        assert_eq!(room_rules.len(), 1);
1178        assert_matches!(settings.rules.read().await.ruleset.get(RuleKind::Room, &room_id),
1179            Some(AnyPushRuleRef::Room(rule)) => {
1180                assert_eq!(rule.rule_id, room_id);
1181                assert!(!rule.actions.is_empty());
1182            }
1183        );
1184
1185        Ok(())
1186    }
1187
1188    #[async_test]
1189    async fn test_set_default_room_notification_mode() -> TestResult {
1190        let server = MockServer::start().await;
1191        Mock::given(method("PUT")).respond_with(ResponseTemplate::new(200)).mount(&server).await;
1192        let client = logged_in_client(Some(server.uri())).await;
1193
1194        // If the initial mode is `AllMessages`
1195        let mut ruleset = get_server_default_ruleset();
1196        ruleset.set_actions(
1197            RuleKind::Underride,
1198            PredefinedUnderrideRuleId::Message,
1199            vec![Action::Notify],
1200        )?;
1201
1202        ruleset.set_actions(
1203            RuleKind::Underride,
1204            PredefinedUnderrideRuleId::PollStart,
1205            vec![Action::Notify],
1206        )?;
1207
1208        let settings = NotificationSettings::new(client, ruleset);
1209        assert_eq!(
1210            settings.get_default_room_notification_mode(IsEncrypted::No, IsOneToOne::No).await,
1211            RoomNotificationMode::AllMessages
1212        );
1213
1214        // After setting the default mode to `MentionsAndKeywordsOnly`
1215        settings
1216            .set_default_room_notification_mode(
1217                IsEncrypted::No,
1218                IsOneToOne::No,
1219                RoomNotificationMode::MentionsAndKeywordsOnly,
1220            )
1221            .await?;
1222
1223        // The list of actions for this rule must be empty
1224        assert_matches!(settings.rules.read().await.ruleset.get(RuleKind::Underride, PredefinedUnderrideRuleId::Message),
1225            Some(AnyPushRuleRef::Underride(rule)) => {
1226                assert!(rule.actions.is_empty());
1227            }
1228        );
1229
1230        assert_matches!(settings.rules.read().await.ruleset.get(RuleKind::Underride, PredefinedUnderrideRuleId::PollStart),
1231            Some(AnyPushRuleRef::Underride(rule)) => {
1232                assert!(rule.actions.is_empty());
1233            }
1234        );
1235
1236        // and the new mode returned by `get_default_room_notification_mode()` should
1237        // reflect the change.
1238        assert_matches!(
1239            settings.get_default_room_notification_mode(IsEncrypted::No, IsOneToOne::No).await,
1240            RoomNotificationMode::MentionsAndKeywordsOnly
1241        );
1242
1243        Ok(())
1244    }
1245
1246    #[async_test]
1247    async fn test_set_default_room_notification_mode_one_to_one() -> TestResult {
1248        let server = MockServer::start().await;
1249        Mock::given(method("PUT")).respond_with(ResponseTemplate::new(200)).mount(&server).await;
1250        let client = logged_in_client(Some(server.uri())).await;
1251
1252        // If the initial mode is `AllMessages`
1253        let mut ruleset = get_server_default_ruleset();
1254        ruleset.set_actions(
1255            RuleKind::Underride,
1256            PredefinedUnderrideRuleId::RoomOneToOne,
1257            vec![Action::Notify],
1258        )?;
1259
1260        ruleset.set_actions(
1261            RuleKind::Underride,
1262            PredefinedUnderrideRuleId::PollStartOneToOne,
1263            vec![Action::Notify],
1264        )?;
1265
1266        let settings = NotificationSettings::new(client, ruleset);
1267        assert_eq!(
1268            settings.get_default_room_notification_mode(IsEncrypted::No, IsOneToOne::Yes).await,
1269            RoomNotificationMode::AllMessages
1270        );
1271
1272        // After setting the default mode to `MentionsAndKeywordsOnly`
1273        settings
1274            .set_default_room_notification_mode(
1275                IsEncrypted::No,
1276                IsOneToOne::Yes,
1277                RoomNotificationMode::MentionsAndKeywordsOnly,
1278            )
1279            .await?;
1280
1281        // The list of actions for this rule must be empty
1282        assert_matches!(settings.rules.read().await.ruleset.get(RuleKind::Underride, PredefinedUnderrideRuleId::RoomOneToOne),
1283            Some(AnyPushRuleRef::Underride(rule)) => {
1284                assert!(rule.actions.is_empty());
1285            }
1286        );
1287
1288        assert_matches!(settings.rules.read().await.ruleset.get(RuleKind::Underride, PredefinedUnderrideRuleId::PollStartOneToOne),
1289            Some(AnyPushRuleRef::Underride(rule)) => {
1290                assert!(rule.actions.is_empty());
1291            }
1292        );
1293
1294        // and the new mode returned by `get_default_room_notification_mode()` should
1295        // reflect the change.
1296        assert_matches!(
1297            settings.get_default_room_notification_mode(IsEncrypted::No, IsOneToOne::Yes).await,
1298            RoomNotificationMode::MentionsAndKeywordsOnly
1299        );
1300
1301        Ok(())
1302    }
1303
1304    #[async_test]
1305    async fn test_set_default_room_notification_mode_enables_rules() -> TestResult {
1306        let server = MockServer::start().await;
1307        Mock::given(method("PUT")).respond_with(ResponseTemplate::new(200)).mount(&server).await;
1308        let client = logged_in_client(Some(server.uri())).await;
1309
1310        // If the initial mode is `MentionsAndKeywordsOnly`
1311        let mut ruleset = get_server_default_ruleset();
1312        ruleset.set_actions(
1313            RuleKind::Underride,
1314            PredefinedUnderrideRuleId::RoomOneToOne,
1315            vec![],
1316        )?;
1317
1318        ruleset.set_actions(
1319            RuleKind::Underride,
1320            PredefinedUnderrideRuleId::PollStartOneToOne,
1321            vec![],
1322        )?;
1323
1324        // Disable one of the rules that will be updated
1325        ruleset.set_enabled(RuleKind::Underride, PredefinedUnderrideRuleId::RoomOneToOne, false)?;
1326
1327        let settings = NotificationSettings::new(client, ruleset);
1328
1329        // After setting the default mode to `AllMessages`
1330        settings
1331            .set_default_room_notification_mode(
1332                IsEncrypted::No,
1333                IsOneToOne::Yes,
1334                RoomNotificationMode::AllMessages,
1335            )
1336            .await?;
1337
1338        // The new mode returned should be `AllMessages` which means that the disabled
1339        // rule (`RoomOneToOne`) has been enabled.
1340        assert_matches!(
1341            settings.get_default_room_notification_mode(IsEncrypted::No, IsOneToOne::Yes).await,
1342            RoomNotificationMode::AllMessages
1343        );
1344
1345        Ok(())
1346    }
1347
1348    #[async_test]
1349    async fn test_list_keywords() -> TestResult {
1350        let server = MockServer::start().await;
1351        let client = logged_in_client(Some(server.uri())).await;
1352
1353        // Initial state: No keywords
1354        let ruleset = get_server_default_ruleset();
1355        let settings = NotificationSettings::new(client.clone(), ruleset);
1356
1357        let keywords = settings.enabled_keywords().await;
1358
1359        assert!(keywords.is_empty());
1360
1361        // Initial state: 3 rules, 2 keywords
1362        let mut ruleset = get_server_default_ruleset();
1363        ruleset.insert(
1364            NewPushRule::Content(NewPatternedPushRule::new("a".to_owned(), "a".to_owned(), vec![])),
1365            None,
1366            None,
1367        )?;
1368        // Test deduplication.
1369        ruleset.insert(
1370            NewPushRule::Content(NewPatternedPushRule::new(
1371                "a_bis".to_owned(),
1372                "a".to_owned(),
1373                vec![],
1374            )),
1375            None,
1376            None,
1377        )?;
1378        ruleset.insert(
1379            NewPushRule::Content(NewPatternedPushRule::new("b".to_owned(), "b".to_owned(), vec![])),
1380            None,
1381            None,
1382        )?;
1383
1384        let settings = NotificationSettings::new(client, ruleset);
1385
1386        let keywords = settings.enabled_keywords().await;
1387        assert_eq!(keywords.len(), 2);
1388        assert!(keywords.get("a").is_some());
1389        assert!(keywords.get("b").is_some());
1390
1391        Ok(())
1392    }
1393
1394    #[async_test]
1395    async fn test_add_keyword_missing() -> TestResult {
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?;
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 = settings.is_push_rule_enabled(RuleKind::Content, "banana").await?;
1416        assert!(rule_enabled);
1417
1418        Ok(())
1419    }
1420
1421    #[async_test]
1422    async fn test_add_keyword_disabled() -> TestResult {
1423        let server = MockServer::start().await;
1424        let client = logged_in_client(Some(server.uri())).await;
1425
1426        let mut ruleset = get_server_default_ruleset();
1427        ruleset.insert(
1428            NewPushRule::Content(NewPatternedPushRule::new(
1429                "banana_two".to_owned(),
1430                "banana".to_owned(),
1431                vec![],
1432            )),
1433            None,
1434            None,
1435        )?;
1436        ruleset.set_enabled(RuleKind::Content, "banana_two", false)?;
1437        ruleset.insert(
1438            NewPushRule::Content(NewPatternedPushRule::new(
1439                "banana_one".to_owned(),
1440                "banana".to_owned(),
1441                vec![],
1442            )),
1443            None,
1444            None,
1445        )?;
1446        ruleset.set_enabled(RuleKind::Content, "banana_one", false)?;
1447
1448        let settings = NotificationSettings::new(client, ruleset);
1449        Mock::given(method("PUT"))
1450            .and(path("/_matrix/client/r0/pushrules/global/content/banana_one/enabled"))
1451            .respond_with(ResponseTemplate::new(200))
1452            .expect(1)
1453            .mount(&server)
1454            .await;
1455
1456        settings.add_keyword("banana".to_owned()).await?;
1457
1458        // The ruleset must have been updated.
1459        let keywords = settings.enabled_keywords().await;
1460
1461        assert_eq!(keywords.len(), 1);
1462        assert!(keywords.get("banana").is_some());
1463
1464        // The first rule was enabled.
1465        let first_rule_enabled =
1466            settings.is_push_rule_enabled(RuleKind::Content, "banana_one").await?;
1467        assert!(first_rule_enabled);
1468        let second_rule_enabled =
1469            settings.is_push_rule_enabled(RuleKind::Content, "banana_two").await?;
1470        assert!(!second_rule_enabled);
1471
1472        Ok(())
1473    }
1474
1475    #[async_test]
1476    async fn test_add_keyword_noop() -> TestResult {
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.insert(
1482            NewPushRule::Content(NewPatternedPushRule::new(
1483                "banana_two".to_owned(),
1484                "banana".to_owned(),
1485                vec![],
1486            )),
1487            None,
1488            None,
1489        )?;
1490        ruleset.insert(
1491            NewPushRule::Content(NewPatternedPushRule::new(
1492                "banana_one".to_owned(),
1493                "banana".to_owned(),
1494                vec![],
1495            )),
1496            None,
1497            None,
1498        )?;
1499        ruleset.set_enabled(RuleKind::Content, "banana_one", false)?;
1500
1501        let settings = NotificationSettings::new(client, ruleset);
1502        settings.add_keyword("banana".to_owned()).await?;
1503
1504        // Nothing changed.
1505        let keywords = settings.enabled_keywords().await;
1506
1507        assert_eq!(keywords.len(), 1);
1508        assert!(keywords.get("banana").is_some());
1509
1510        let first_rule_enabled =
1511            settings.is_push_rule_enabled(RuleKind::Content, "banana_one").await?;
1512        assert!(!first_rule_enabled);
1513        let second_rule_enabled =
1514            settings.is_push_rule_enabled(RuleKind::Content, "banana_two").await?;
1515        assert!(second_rule_enabled);
1516
1517        Ok(())
1518    }
1519
1520    #[async_test]
1521    async fn test_remove_keyword_all() -> TestResult {
1522        let server = MockServer::start().await;
1523        let client = logged_in_client(Some(server.uri())).await;
1524
1525        let mut ruleset = get_server_default_ruleset();
1526        ruleset.insert(
1527            NewPushRule::Content(NewPatternedPushRule::new(
1528                "banana_two".to_owned(),
1529                "banana".to_owned(),
1530                vec![],
1531            )),
1532            None,
1533            None,
1534        )?;
1535        ruleset.insert(
1536            NewPushRule::Content(NewPatternedPushRule::new(
1537                "banana_one".to_owned(),
1538                "banana".to_owned(),
1539                vec![],
1540            )),
1541            None,
1542            None,
1543        )?;
1544        ruleset.set_enabled(RuleKind::Content, "banana_one", false)?;
1545
1546        let settings = NotificationSettings::new(client, ruleset);
1547
1548        Mock::given(method("DELETE"))
1549            .and(path("/_matrix/client/r0/pushrules/global/content/banana_one"))
1550            .respond_with(ResponseTemplate::new(200))
1551            .expect(1)
1552            .mount(&server)
1553            .await;
1554        Mock::given(method("DELETE"))
1555            .and(path("/_matrix/client/r0/pushrules/global/content/banana_two"))
1556            .respond_with(ResponseTemplate::new(200))
1557            .expect(1)
1558            .mount(&server)
1559            .await;
1560
1561        settings.remove_keyword("banana").await?;
1562
1563        // The ruleset must have been updated.
1564        let keywords = settings.enabled_keywords().await;
1565        assert!(keywords.is_empty());
1566
1567        // Rules we removed.
1568        let first_rule_error =
1569            settings.is_push_rule_enabled(RuleKind::Content, "banana_one").await.unwrap_err();
1570        assert_matches!(first_rule_error, NotificationSettingsError::RuleNotFound(_));
1571        let second_rule_error =
1572            settings.is_push_rule_enabled(RuleKind::Content, "banana_two").await.unwrap_err();
1573        assert_matches!(second_rule_error, NotificationSettingsError::RuleNotFound(_));
1574
1575        Ok(())
1576    }
1577
1578    #[async_test]
1579    async fn test_remove_keyword_noop() -> TestResult {
1580        let server = MockServer::start().await;
1581        let client = logged_in_client(Some(server.uri())).await;
1582        let settings = client.notification_settings().await;
1583
1584        settings.remove_keyword("banana").await?;
1585        Ok(())
1586    }
1587
1588    #[async_test]
1589    async fn test_set_default_room_notification_mode_missing_poll_start() -> TestResult {
1590        let server = MockServer::start().await;
1591        Mock::given(method("PUT")).respond_with(ResponseTemplate::new(200)).mount(&server).await;
1592        let client = logged_in_client(Some(server.uri())).await;
1593
1594        // If the initial mode is `AllMessages`
1595        let mut ruleset = get_server_default_ruleset();
1596        ruleset.underride.swap_remove(PredefinedUnderrideRuleId::PollStart.as_str());
1597
1598        let settings = NotificationSettings::new(client, ruleset);
1599        assert_eq!(
1600            settings.get_default_room_notification_mode(IsEncrypted::No, IsOneToOne::No).await,
1601            RoomNotificationMode::AllMessages
1602        );
1603
1604        // After setting the default mode to `MentionsAndKeywordsOnly`
1605        settings
1606            .set_default_room_notification_mode(
1607                IsEncrypted::No,
1608                IsOneToOne::No,
1609                RoomNotificationMode::MentionsAndKeywordsOnly,
1610            )
1611            .await?;
1612
1613        // the new mode returned by `get_default_room_notification_mode()` should
1614        // reflect the change.
1615        assert_matches!(
1616            settings.get_default_room_notification_mode(IsEncrypted::No, IsOneToOne::No).await,
1617            RoomNotificationMode::MentionsAndKeywordsOnly
1618        );
1619        Ok(())
1620    }
1621
1622    #[async_test]
1623    async fn test_create_custom_conditional_push_rule() -> TestResult {
1624        let server = MockServer::start().await;
1625        let client = logged_in_client(Some(server.uri())).await;
1626        let settings = client.notification_settings().await;
1627
1628        Mock::given(method("PUT"))
1629            .and(path("/_matrix/client/r0/pushrules/global/override/custom_rule"))
1630            .respond_with(ResponseTemplate::new(200))
1631            .expect(1)
1632            .mount(&server)
1633            .await;
1634
1635        let actions = vec![Action::Notify];
1636        let conditions = vec![ruma::push::PushCondition::EventMatch {
1637            key: "content.body".to_owned(),
1638            pattern: "hello".to_owned(),
1639        }];
1640
1641        settings
1642            .create_custom_conditional_push_rule(
1643                "custom_rule".to_owned(),
1644                RuleKind::Override,
1645                actions.clone(),
1646                conditions.clone(),
1647            )
1648            .await?;
1649
1650        let rules = settings.rules.read().await;
1651        let rule = rules.ruleset.get(RuleKind::Override, "custom_rule").unwrap();
1652
1653        assert_eq!(rule.rule_id(), "custom_rule");
1654        assert!(rule.enabled());
1655
1656        Ok(())
1657    }
1658
1659    #[async_test]
1660    async fn test_create_custom_conditional_push_rule_invalid_kind() {
1661        let server = MockServer::start().await;
1662        let client = logged_in_client(Some(server.uri())).await;
1663        let settings = client.notification_settings().await;
1664
1665        let actions = vec![Action::Notify];
1666        let conditions = vec![ruma::push::PushCondition::EventMatch {
1667            key: "content.body".to_owned(),
1668            pattern: "hello".to_owned(),
1669        }];
1670
1671        let result = settings
1672            .create_custom_conditional_push_rule(
1673                "custom_rule".to_owned(),
1674                RuleKind::Room,
1675                actions,
1676                conditions,
1677            )
1678            .await;
1679
1680        assert_matches!(result, Err(NotificationSettingsError::InvalidParameter(_)));
1681    }
1682
1683    #[async_test]
1684    #[allow(deprecated)]
1685    async fn test_enable_mention_ignore_missing_legacy_push_rules() -> TestResult {
1686        let server = MatrixMockServer::new().await;
1687        let client = server.client_builder().build().await;
1688        let mut ruleset = get_server_default_ruleset();
1689
1690        // Make sure that the legacy mention push rules are missing.
1691        if let Some(idx) = ruleset
1692            .override_
1693            .iter()
1694            .position(|rule| rule.rule_id == PredefinedOverrideRuleId::ContainsDisplayName.as_ref())
1695        {
1696            ruleset.override_.shift_remove_index(idx);
1697        }
1698
1699        if let Some(idx) = ruleset
1700            .override_
1701            .iter()
1702            .position(|rule| rule.rule_id == PredefinedOverrideRuleId::RoomNotif.as_ref())
1703        {
1704            ruleset.override_.shift_remove_index(idx);
1705        }
1706
1707        if let Some(idx) = ruleset
1708            .content
1709            .iter()
1710            .position(|rule| rule.rule_id == PredefinedContentRuleId::ContainsUserName.as_ref())
1711        {
1712            ruleset.content.shift_remove_index(idx);
1713        }
1714
1715        assert_matches!(
1716            ruleset.iter().find(|rule| {
1717                rule.rule_id() == PredefinedOverrideRuleId::ContainsDisplayName.as_ref()
1718                    || rule.rule_id() == PredefinedOverrideRuleId::RoomNotif.as_ref()
1719                    || rule.rule_id() == PredefinedContentRuleId::ContainsUserName.as_ref()
1720            }),
1721            None,
1722            "ruleset must not have legacy mention push rules"
1723        );
1724
1725        let settings = NotificationSettings::new(client, ruleset);
1726
1727        server
1728            .mock_enable_push_rule(RuleKind::Override, PredefinedOverrideRuleId::IsUserMention)
1729            .ok()
1730            .mock_once()
1731            .named("is_user_mention")
1732            .mount()
1733            .await;
1734        settings
1735            .set_push_rule_enabled(
1736                RuleKind::Override,
1737                PredefinedOverrideRuleId::IsUserMention,
1738                false,
1739            )
1740            .await?;
1741
1742        server
1743            .mock_enable_push_rule(RuleKind::Override, PredefinedOverrideRuleId::IsRoomMention)
1744            .ok()
1745            .mock_once()
1746            .named("is_room_mention")
1747            .mount()
1748            .await;
1749        settings
1750            .set_push_rule_enabled(
1751                RuleKind::Override,
1752                PredefinedOverrideRuleId::IsRoomMention,
1753                false,
1754            )
1755            .await?;
1756
1757        Ok(())
1758    }
1759}