1use imbl::HashSet;
4use indexmap::IndexSet;
5use ruma::{
6 RoomId,
7 push::{
8 AnyPushRuleRef, PatternedPushRule, PredefinedContentRuleId, PredefinedOverrideRuleId,
9 PredefinedUnderrideRuleId, PushCondition, RuleKind, Ruleset,
10 },
11};
12
13use super::{RoomNotificationMode, command::Command, rule_commands::RuleCommands};
14use crate::{
15 error::NotificationSettingsError,
16 notification_settings::{IsEncrypted, IsOneToOne},
17};
18
19#[derive(Clone, Debug)]
20pub(crate) struct Rules {
21 pub ruleset: Ruleset,
22}
23
24impl Rules {
25 pub(crate) fn new(ruleset: Ruleset) -> Self {
26 Rules { ruleset }
27 }
28
29 pub(crate) fn get_custom_rules_for_room(&self, room_id: &RoomId) -> Vec<(RuleKind, String)> {
31 let mut custom_rules = vec![];
32
33 for rule in &self.ruleset.override_ {
35 if &rule.rule_id == room_id || rule.conditions.iter().any(|x| matches!(
37 x,
38 PushCondition::EventMatch(data) if data.key == "room_id" && data.pattern == *room_id
39 )) {
40 custom_rules.push((RuleKind::Override, rule.rule_id.clone()));
42 }
43 }
44
45 if let Some(rule) = self.ruleset.get(RuleKind::Room, room_id) {
47 custom_rules.push((RuleKind::Room, rule.rule_id().to_owned()));
48 }
49
50 for rule in &self.ruleset.underride {
52 if &rule.rule_id == room_id || rule.conditions.iter().any(|x| matches!(
54 x,
55 PushCondition::EventMatch(data) if data.key == "room_id" && data.pattern == *room_id
56 )) {
57 custom_rules.push((RuleKind::Underride, rule.rule_id.clone()));
59 }
60 }
61
62 custom_rules
63 }
64
65 pub(crate) fn get_user_defined_room_notification_mode(
67 &self,
68 room_id: &RoomId,
69 ) -> Option<RoomNotificationMode> {
70 if self.ruleset.override_.iter().any(|x| {
72 x.enabled &&
74 x.conditions.iter().any(|x| matches!(
77 x,
78 PushCondition::EventMatch(data) if data.key == "room_id" && data.pattern == *room_id
79 )) &&
80 !x.actions.iter().any(|x| x.should_notify())
82 }) {
83 return Some(RoomNotificationMode::Mute);
84 }
85
86 if let Some(rule) = self.ruleset.get(RuleKind::Room, room_id) {
88 if rule.triggers_notification() {
90 return Some(RoomNotificationMode::AllMessages);
91 }
92 return Some(RoomNotificationMode::MentionsAndKeywordsOnly);
93 }
94
95 None
97 }
98
99 pub(crate) fn get_default_room_notification_mode(
107 &self,
108 is_encrypted: IsEncrypted,
109 is_one_to_one: IsOneToOne,
110 ) -> RoomNotificationMode {
111 let predefined_rule_id = get_predefined_underride_room_rule_id(is_encrypted, is_one_to_one);
113 let rule_id = predefined_rule_id.as_str();
114
115 if self
118 .ruleset
119 .get(RuleKind::Underride, rule_id)
120 .is_some_and(|r| r.enabled() && r.triggers_notification())
121 {
122 RoomNotificationMode::AllMessages
123 } else {
124 RoomNotificationMode::MentionsAndKeywordsOnly
126 }
127 }
128
129 pub(crate) fn get_rooms_with_user_defined_rules(&self, enabled: Option<bool>) -> Vec<String> {
131 let test_if_enabled = enabled.is_some();
132 let must_be_enabled = enabled.unwrap_or(false);
133
134 let mut room_ids = HashSet::new();
135 for rule in &self.ruleset {
136 if rule.is_server_default() {
137 continue;
138 }
139 if test_if_enabled && rule.enabled() != must_be_enabled {
140 continue;
141 }
142 match rule {
143 AnyPushRuleRef::Override(r) | AnyPushRuleRef::Underride(r) => {
144 for condition in &r.conditions {
145 if let PushCondition::EventMatch(data) = condition
146 && data.key == "room_id"
147 {
148 room_ids.insert(data.pattern.clone());
149 break;
150 }
151 }
152 }
153 AnyPushRuleRef::Room(r) => {
154 room_ids.insert(r.rule_id.to_string());
155 }
156 _ => {}
157 }
158 }
159 Vec::from_iter(room_ids)
160 }
161
162 fn is_user_mention_enabled(&self) -> bool {
164 if let Some(rule) =
167 self.ruleset.get(RuleKind::Override, PredefinedOverrideRuleId::IsUserMention)
168 {
169 return rule.enabled();
170 }
171
172 #[allow(deprecated)]
174 if let Some(rule) =
175 self.ruleset.get(RuleKind::Override, PredefinedOverrideRuleId::ContainsDisplayName)
176 && rule.enabled()
177 && rule.triggers_notification()
178 {
179 return true;
180 }
181
182 #[allow(deprecated)]
183 if let Some(rule) =
184 self.ruleset.get(RuleKind::Content, PredefinedContentRuleId::ContainsUserName)
185 && rule.enabled()
186 && rule.triggers_notification()
187 {
188 return true;
189 }
190
191 false
192 }
193
194 fn is_room_mention_enabled(&self) -> bool {
196 if let Some(rule) =
199 self.ruleset.get(RuleKind::Override, PredefinedOverrideRuleId::IsRoomMention)
200 {
201 return rule.enabled();
202 }
203
204 #[allow(deprecated)]
206 self.ruleset
207 .get(RuleKind::Override, PredefinedOverrideRuleId::RoomNotif)
208 .is_some_and(|r| r.enabled() && r.triggers_notification())
209 }
210
211 pub(crate) fn contains_keyword_rules(&self) -> bool {
213 self.ruleset.content.iter().any(|r| !r.default && r.enabled)
215 }
216
217 pub(crate) fn enabled_keywords(&self) -> IndexSet<String> {
219 self.ruleset
220 .content
221 .iter()
222 .filter(|r| !r.default && r.enabled)
223 .map(|r| r.pattern.clone())
224 .collect()
225 }
226
227 pub(crate) fn keyword_rules(&self, keyword: &str) -> Vec<&PatternedPushRule> {
229 self.ruleset.content.iter().filter(|r| !r.default && r.pattern == keyword).collect()
230 }
231
232 pub(crate) fn is_enabled(
234 &self,
235 kind: RuleKind,
236 rule_id: &str,
237 ) -> Result<bool, NotificationSettingsError> {
238 if rule_id == PredefinedOverrideRuleId::IsRoomMention.as_str() {
239 Ok(self.is_room_mention_enabled())
240 } else if rule_id == PredefinedOverrideRuleId::IsUserMention.as_str() {
241 Ok(self.is_user_mention_enabled())
242 } else if let Some(rule) = self.ruleset.get(kind, rule_id) {
243 Ok(rule.enabled())
244 } else {
245 Err(NotificationSettingsError::RuleNotFound(rule_id.to_owned()))
246 }
247 }
248
249 pub(crate) fn apply(&mut self, commands: RuleCommands) {
254 for command in commands.commands {
255 match command {
256 Command::DeletePushRule { kind, rule_id } => {
257 _ = self.ruleset.remove(kind, rule_id);
258 }
259 Command::SetRoomPushRule { .. }
260 | Command::SetOverridePushRule { .. }
261 | Command::SetKeywordPushRule { .. } => {
262 if let Ok(push_rule) = command.to_push_rule() {
263 _ = self.ruleset.insert(push_rule, None, None);
264 }
265 }
266 Command::SetPushRuleEnabled { kind, rule_id, enabled } => {
267 _ = self.ruleset.set_enabled(kind, rule_id, enabled);
268 }
269 Command::SetPushRuleActions { kind, rule_id, actions } => {
270 _ = self.ruleset.set_actions(kind, rule_id, actions);
271 }
272 Command::SetCustomPushRule { rule } => {
273 _ = self.ruleset.insert(rule, None, None);
274 }
275 }
276 }
277 }
278}
279
280pub(crate) fn get_predefined_underride_room_rule_id(
288 is_encrypted: IsEncrypted,
289 is_one_to_one: IsOneToOne,
290) -> PredefinedUnderrideRuleId {
291 match (is_encrypted, is_one_to_one) {
292 (IsEncrypted::Yes, IsOneToOne::Yes) => PredefinedUnderrideRuleId::EncryptedRoomOneToOne,
293 (IsEncrypted::No, IsOneToOne::Yes) => PredefinedUnderrideRuleId::RoomOneToOne,
294 (IsEncrypted::Yes, IsOneToOne::No) => PredefinedUnderrideRuleId::Encrypted,
295 (IsEncrypted::No, IsOneToOne::No) => PredefinedUnderrideRuleId::Message,
296 }
297}
298
299pub(crate) fn get_predefined_underride_poll_start_rule_id(
306 is_one_to_one: IsOneToOne,
307) -> PredefinedUnderrideRuleId {
308 match is_one_to_one {
309 IsOneToOne::Yes => PredefinedUnderrideRuleId::PollStartOneToOne,
310 IsOneToOne::No => PredefinedUnderrideRuleId::PollStart,
311 }
312}
313
314#[cfg(test)]
315pub(crate) mod tests {
316 use imbl::HashSet;
317 use matrix_sdk_test::{
318 async_test,
319 notification_settings::{
320 build_ruleset, get_server_default_ruleset, server_default_ruleset_with_legacy_mentions,
321 },
322 };
323 use ruma::{
324 OwnedRoomId, RoomId,
325 push::{
326 Action, EventMatchConditionData, NewConditionalPushRule, NewPushRule,
327 PredefinedContentRuleId, PredefinedOverrideRuleId, PredefinedUnderrideRuleId,
328 PushCondition, RuleKind,
329 },
330 };
331
332 use super::RuleCommands;
333 use crate::{
334 error::NotificationSettingsError,
335 notification_settings::{
336 IsEncrypted, IsOneToOne, RoomNotificationMode,
337 rules::{self, Rules},
338 },
339 };
340
341 fn get_test_room_id() -> OwnedRoomId {
342 RoomId::parse("!AAAaAAAAAaaAAaaaaa:matrix.org").unwrap()
343 }
344
345 #[async_test]
346 async fn test_get_custom_rules_for_room() {
347 let room_id = get_test_room_id();
348
349 let rules = Rules::new(get_server_default_ruleset());
350 assert_eq!(rules.get_custom_rules_for_room(&room_id).len(), 0);
351
352 let ruleset = build_ruleset(vec![(RuleKind::Override, &room_id, false)]);
354 let rules = Rules::new(ruleset);
355 assert_eq!(rules.get_custom_rules_for_room(&room_id).len(), 1);
356
357 let ruleset = build_ruleset(vec![
359 (RuleKind::Override, &room_id, false),
360 (RuleKind::Room, &room_id, false),
361 ]);
362 let rules = Rules::new(ruleset);
363 assert_eq!(rules.get_custom_rules_for_room(&room_id).len(), 2);
364 }
365
366 #[async_test]
367 async fn test_get_custom_rules_for_room_special_override_rule() {
368 let room_id = get_test_room_id();
369 let mut ruleset = get_server_default_ruleset();
370
371 let new_rule = NewConditionalPushRule::new(
374 "custom_rule_id".to_owned(),
375 vec![PushCondition::EventMatch(EventMatchConditionData::new(
376 "room_id".into(),
377 room_id.to_string(),
378 ))],
379 vec![Action::Notify],
380 );
381 ruleset.insert(NewPushRule::Override(new_rule), None, None).unwrap();
382
383 let rules = Rules::new(ruleset);
384 assert_eq!(rules.get_custom_rules_for_room(&room_id).len(), 1);
385 }
386
387 #[async_test]
388 async fn test_get_user_defined_room_notification_mode() {
389 let room_id = get_test_room_id();
390 let rules = Rules::new(get_server_default_ruleset());
391 assert_eq!(rules.get_user_defined_room_notification_mode(&room_id), None);
392
393 let ruleset = build_ruleset(vec![(RuleKind::Override, &room_id, false)]);
395 let rules = Rules::new(ruleset);
396 assert_eq!(
397 rules.get_user_defined_room_notification_mode(&room_id),
398 Some(RoomNotificationMode::Mute)
399 );
400
401 let ruleset = build_ruleset(vec![(RuleKind::Room, &room_id, false)]);
403 let rules = Rules::new(ruleset);
404 assert_eq!(
405 rules.get_user_defined_room_notification_mode(&room_id),
406 Some(RoomNotificationMode::MentionsAndKeywordsOnly)
407 );
408
409 let ruleset = build_ruleset(vec![(RuleKind::Room, &room_id, true)]);
411 let rules = Rules::new(ruleset);
412 assert_eq!(
413 rules.get_user_defined_room_notification_mode(&room_id),
414 Some(RoomNotificationMode::AllMessages)
415 );
416
417 let room_id_a = RoomId::parse("!AAAaAAAAAaaAAaaaaa:matrix.org").unwrap();
418 let room_id_b = RoomId::parse("!BBBbBBBBBbbBBbbbbb:matrix.org").unwrap();
419 let ruleset = build_ruleset(vec![
420 (RuleKind::Override, &room_id_a, false),
422 (RuleKind::Override, &room_id_b, true),
424 ]);
425 let rules = Rules::new(ruleset);
426 let mode = rules.get_user_defined_room_notification_mode(&room_id_a);
427
428 assert_eq!(mode, Some(RoomNotificationMode::Mute));
431 }
432
433 #[async_test]
434 async fn test_get_predefined_underride_room_rule_id() {
435 assert_eq!(
436 rules::get_predefined_underride_room_rule_id(IsEncrypted::No, IsOneToOne::No),
437 PredefinedUnderrideRuleId::Message
438 );
439 assert_eq!(
440 rules::get_predefined_underride_room_rule_id(IsEncrypted::No, IsOneToOne::Yes),
441 PredefinedUnderrideRuleId::RoomOneToOne
442 );
443 assert_eq!(
444 rules::get_predefined_underride_room_rule_id(IsEncrypted::Yes, IsOneToOne::No),
445 PredefinedUnderrideRuleId::Encrypted
446 );
447 assert_eq!(
448 rules::get_predefined_underride_room_rule_id(IsEncrypted::Yes, IsOneToOne::Yes),
449 PredefinedUnderrideRuleId::EncryptedRoomOneToOne
450 );
451 }
452
453 #[async_test]
454 async fn test_get_predefined_underride_poll_start_rule_id() {
455 assert_eq!(
456 rules::get_predefined_underride_poll_start_rule_id(IsOneToOne::No),
457 PredefinedUnderrideRuleId::PollStart
458 );
459 assert_eq!(
460 rules::get_predefined_underride_poll_start_rule_id(IsOneToOne::Yes),
461 PredefinedUnderrideRuleId::PollStartOneToOne
462 );
463 }
464
465 #[async_test]
466 async fn test_get_default_room_notification_mode_mentions_and_keywords() {
467 let mut ruleset = get_server_default_ruleset();
468 ruleset
470 .set_enabled(RuleKind::Underride, PredefinedUnderrideRuleId::RoomOneToOne, false)
471 .unwrap();
472
473 let rules = Rules::new(ruleset);
474 let mode = rules.get_default_room_notification_mode(IsEncrypted::No, IsOneToOne::Yes);
475 assert_eq!(mode, RoomNotificationMode::MentionsAndKeywordsOnly);
477 }
478
479 #[async_test]
480 async fn test_get_default_room_notification_mode_all_messages() {
481 let mut ruleset = get_server_default_ruleset();
482 ruleset
484 .set_enabled(RuleKind::Underride, PredefinedUnderrideRuleId::RoomOneToOne, true)
485 .unwrap();
486
487 let rules = Rules::new(ruleset);
488 let mode = rules.get_default_room_notification_mode(IsEncrypted::No, IsOneToOne::Yes);
489 assert_eq!(mode, RoomNotificationMode::AllMessages);
491 }
492
493 #[async_test]
494 async fn test_is_user_mention_enabled() {
495 let mut ruleset = server_default_ruleset_with_legacy_mentions();
498 ruleset
499 .set_enabled(RuleKind::Override, PredefinedOverrideRuleId::IsUserMention, true)
500 .unwrap();
501 #[allow(deprecated)]
502 ruleset
503 .set_enabled(RuleKind::Override, PredefinedOverrideRuleId::ContainsDisplayName, false)
504 .unwrap();
505 #[allow(deprecated)]
506 ruleset
507 .set_enabled(RuleKind::Content, PredefinedContentRuleId::ContainsUserName, false)
508 .unwrap();
509
510 let rules = Rules::new(ruleset);
511 assert!(rules.is_user_mention_enabled());
512 assert!(
515 rules
516 .is_enabled(RuleKind::Override, PredefinedOverrideRuleId::IsUserMention.as_str())
517 .unwrap()
518 );
519
520 let mut ruleset = server_default_ruleset_with_legacy_mentions();
523 ruleset
524 .set_enabled(RuleKind::Override, PredefinedOverrideRuleId::IsUserMention, false)
525 .unwrap();
526 #[allow(deprecated)]
527 ruleset
528 .set_actions(
529 RuleKind::Override,
530 PredefinedOverrideRuleId::ContainsDisplayName,
531 vec![Action::Notify],
532 )
533 .unwrap();
534 #[allow(deprecated)]
535 ruleset
536 .set_enabled(RuleKind::Override, PredefinedOverrideRuleId::ContainsDisplayName, true)
537 .unwrap();
538 #[allow(deprecated)]
539 ruleset
540 .set_enabled(RuleKind::Content, PredefinedContentRuleId::ContainsUserName, true)
541 .unwrap();
542
543 let rules = Rules::new(ruleset);
544 assert!(!rules.is_user_mention_enabled());
545 assert!(
548 !rules
549 .is_enabled(RuleKind::Override, PredefinedOverrideRuleId::IsUserMention.as_str())
550 .unwrap()
551 );
552 }
553
554 #[async_test]
555 async fn test_is_room_mention_enabled() {
556 let mut ruleset = server_default_ruleset_with_legacy_mentions();
559 ruleset
560 .set_enabled(RuleKind::Override, PredefinedOverrideRuleId::IsRoomMention, true)
561 .unwrap();
562 #[allow(deprecated)]
563 ruleset
564 .set_enabled(RuleKind::Override, PredefinedOverrideRuleId::RoomNotif, false)
565 .unwrap();
566
567 let rules = Rules::new(ruleset);
568 assert!(rules.is_room_mention_enabled());
569 assert!(
572 rules
573 .is_enabled(RuleKind::Override, PredefinedOverrideRuleId::IsRoomMention.as_str())
574 .unwrap()
575 );
576
577 let mut ruleset = server_default_ruleset_with_legacy_mentions();
580 ruleset
581 .set_enabled(RuleKind::Override, PredefinedOverrideRuleId::IsRoomMention, false)
582 .unwrap();
583 #[allow(deprecated)]
584 ruleset.set_enabled(RuleKind::Override, PredefinedOverrideRuleId::RoomNotif, true).unwrap();
585
586 let rules = Rules::new(ruleset);
587 assert!(!rules.is_room_mention_enabled());
588 assert!(
591 !rules
592 .is_enabled(RuleKind::Override, PredefinedOverrideRuleId::IsRoomMention.as_str())
593 .unwrap()
594 );
595 }
596
597 #[async_test]
598 async fn test_is_enabled_rule_not_found() {
599 let rules = Rules::new(get_server_default_ruleset());
600
601 assert_eq!(
602 rules.is_enabled(RuleKind::Override, "unknown_rule_id"),
603 Err(NotificationSettingsError::RuleNotFound("unknown_rule_id".to_owned()))
604 );
605 }
606
607 #[async_test]
608 async fn test_apply_delete_command() {
609 let room_id = get_test_room_id();
610 let ruleset = build_ruleset(vec![(RuleKind::Override, &room_id, false)]);
612 let mut rules = Rules::new(ruleset);
613
614 let mut rules_commands = RuleCommands::new(rules.ruleset.clone());
616 rules_commands.delete_rule(RuleKind::Override, room_id.to_string()).unwrap();
617
618 rules.apply(rules_commands);
619
620 assert!(rules.get_custom_rules_for_room(&room_id).is_empty());
622 }
623
624 #[async_test]
625 async fn test_apply_set_command() {
626 let room_id = get_test_room_id();
627 let mut rules = Rules::new(get_server_default_ruleset());
628
629 let mut rules_commands = RuleCommands::new(rules.ruleset.clone());
631 rules_commands.insert_rule(RuleKind::Override, &room_id, false).unwrap();
632
633 rules.apply(rules_commands);
634
635 assert_eq!(rules.get_custom_rules_for_room(&room_id).len(), 1);
637 }
638
639 #[async_test]
640 async fn test_apply_set_enabled_command() {
641 let mut rules = Rules::new(get_server_default_ruleset());
642
643 rules
644 .ruleset
645 .set_enabled(RuleKind::Override, PredefinedOverrideRuleId::Reaction, true)
646 .unwrap();
647
648 let mut rules_commands = RuleCommands::new(rules.ruleset.clone());
650 rules_commands
651 .set_rule_enabled(
652 RuleKind::Override,
653 PredefinedOverrideRuleId::Reaction.as_str(),
654 false,
655 )
656 .unwrap();
657
658 rules.apply(rules_commands);
659
660 assert!(
662 !rules
663 .is_enabled(RuleKind::Override, PredefinedOverrideRuleId::Reaction.as_str())
664 .unwrap()
665 );
666 }
667
668 #[async_test]
669 async fn test_get_rooms_with_user_defined_rules() {
670 let rules = Rules::new(get_server_default_ruleset());
672 let room_ids = rules.get_rooms_with_user_defined_rules(None);
673 assert!(room_ids.is_empty());
674
675 let room_id = RoomId::parse("!room_a:matrix.org").unwrap();
677 let ruleset = build_ruleset(vec![(RuleKind::Override, &room_id, false)]);
678 let rules = Rules::new(ruleset);
679
680 let room_ids = rules.get_rooms_with_user_defined_rules(None);
681 assert_eq!(room_ids.len(), 1);
682
683 let ruleset = build_ruleset(vec![
685 (RuleKind::Override, &room_id, false),
686 (RuleKind::Underride, &room_id, false),
687 (RuleKind::Room, &room_id, false),
688 ]);
689 let rules = Rules::new(ruleset);
690
691 let room_ids = rules.get_rooms_with_user_defined_rules(None);
692 assert_eq!(room_ids.len(), 1);
693 assert_eq!(room_ids[0], room_id.to_string());
694
695 let ruleset = build_ruleset(vec![
697 (RuleKind::Room, &RoomId::parse("!room_a:matrix.org").unwrap(), false),
698 (RuleKind::Room, &RoomId::parse("!room_b:matrix.org").unwrap(), false),
699 (RuleKind::Room, &RoomId::parse("!room_c:matrix.org").unwrap(), false),
700 (RuleKind::Override, &RoomId::parse("!room_d:matrix.org").unwrap(), false),
701 (RuleKind::Underride, &RoomId::parse("!room_e:matrix.org").unwrap(), false),
702 ]);
703 let rules = Rules::new(ruleset);
704
705 let room_ids = rules.get_rooms_with_user_defined_rules(None);
706 assert_eq!(room_ids.len(), 5);
707 let expected_set: HashSet<String> = vec![
708 "!room_a:matrix.org",
709 "!room_b:matrix.org",
710 "!room_c:matrix.org",
711 "!room_d:matrix.org",
712 "!room_e:matrix.org",
713 ]
714 .into_iter()
715 .collect();
716 assert!(expected_set.symmetric_difference(HashSet::from(room_ids)).is_empty());
717
718 let room_ids = rules.get_rooms_with_user_defined_rules(Some(false));
720 assert_eq!(room_ids.len(), 0);
721
722 let room_ids = rules.get_rooms_with_user_defined_rules(Some(true));
724 assert_eq!(room_ids.len(), 5);
725
726 let mut ruleset = build_ruleset(vec![
727 (RuleKind::Room, &RoomId::parse("!room_a:matrix.org").unwrap(), false),
728 (RuleKind::Room, &RoomId::parse("!room_b:matrix.org").unwrap(), false),
729 (RuleKind::Override, &RoomId::parse("!room_c:matrix.org").unwrap(), false),
730 (RuleKind::Underride, &RoomId::parse("!room_d:matrix.org").unwrap(), false),
731 ]);
732 ruleset.set_enabled(RuleKind::Room, "!room_b:matrix.org", false).unwrap();
733 ruleset.set_enabled(RuleKind::Override, "!room_c:matrix.org", false).unwrap();
734 let rules = Rules::new(ruleset);
735 let room_ids = rules.get_rooms_with_user_defined_rules(Some(true));
737 assert_eq!(room_ids.len(), 2);
738 let expected_set: HashSet<String> =
739 vec!["!room_a:matrix.org", "!room_d:matrix.org"].into_iter().collect();
740 assert!(expected_set.symmetric_difference(HashSet::from(room_ids)).is_empty());
741 }
742}