Skip to main content

matrix_sdk/notification_settings/
mod.rs

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