1pub mod ban;
2pub mod configuration;
3pub mod content;
4pub mod direct_messages;
5pub mod identity;
6pub mod member;
7pub mod member_info;
8pub mod message;
9pub mod privacy;
10pub mod secret;
11pub mod upgrade;
12pub mod version;
13
14use crate::room_state::ban::BansV1;
15use crate::room_state::configuration::AuthorizedConfigurationV1;
16use crate::room_state::direct_messages::DirectMessagesV1;
17use crate::room_state::member::{MemberId, MembersV1};
18use crate::room_state::member_info::MemberInfoV1;
19use crate::room_state::message::MessagesV1;
20use crate::room_state::secret::RoomSecretsV1;
21use crate::room_state::upgrade::OptionalUpgradeV1;
22use crate::room_state::version::StateVersion;
23use ed25519_dalek::VerifyingKey;
24use freenet_scaffold_macro::composable;
25use serde::{Deserialize, Serialize};
26use std::collections::HashSet;
27
28#[composable(post_apply_delta = "post_apply_cleanup")]
29#[derive(Serialize, Deserialize, Clone, Default, PartialEq, Debug)]
30pub struct ChatRoomStateV1 {
31 pub configuration: AuthorizedConfigurationV1,
38
39 pub bans: BansV1,
42
43 pub members: MembersV1,
45
46 pub member_info: MemberInfoV1,
48
49 pub secrets: RoomSecretsV1,
52
53 pub recent_messages: MessagesV1,
55
56 #[serde(default)]
60 pub direct_messages: DirectMessagesV1,
61
62 pub upgrade: OptionalUpgradeV1,
65
66 #[serde(default)]
69 pub version: StateVersion,
70}
71
72impl ChatRoomStateV1 {
73 pub fn post_apply_cleanup(&mut self, parameters: &ChatRoomParametersV1) -> Result<(), String> {
93 let owner_id = MemberId::from(¶meters.owner);
94
95 let message_authors: HashSet<MemberId> = self
97 .recent_messages
98 .messages
99 .iter()
100 .map(|m| m.message.author)
101 .collect();
102 let dm_participants: HashSet<MemberId> = self.direct_messages.active_participants();
103
104 let required_ids = {
106 let members_by_id = self.members.members_by_member_id();
107 let mut required_ids: HashSet<MemberId> = HashSet::new();
108
109 for author_id in &message_authors {
110 if *author_id != owner_id && members_by_id.contains_key(author_id) {
111 required_ids.insert(*author_id);
112 }
113 }
114
115 for participant_id in &dm_participants {
116 if *participant_id != owner_id && members_by_id.contains_key(participant_id) {
117 required_ids.insert(*participant_id);
118 }
119 }
120
121 let mut to_process: Vec<MemberId> = required_ids.iter().cloned().collect();
123 while let Some(member_id) = to_process.pop() {
124 if let Some(member) = members_by_id.get(&member_id) {
125 let inviter_id = member.member.invited_by;
126 if inviter_id != owner_id && !required_ids.contains(&inviter_id) {
127 required_ids.insert(inviter_id);
128 to_process.push(inviter_id);
129 }
130 }
131 }
132
133 required_ids
134 };
135
136 self.members
138 .members
139 .retain(|m| required_ids.contains(&m.member.id()));
140
141 self.member_info.member_info.retain(|info| {
143 info.member_info.member_id == owner_id
144 || required_ids.contains(&info.member_info.member_id)
145 });
146
147 let banned_user_ids: HashSet<MemberId> =
153 self.bans.0.iter().map(|b| b.ban.banned_user).collect();
154 let current_member_ids: HashSet<MemberId> =
155 self.members.members.iter().map(|m| m.member.id()).collect();
156
157 self.bans.0.retain(|ban| {
158 ban.banned_by == owner_id
161 || current_member_ids.contains(&ban.banned_by)
162 || !banned_user_ids.contains(&ban.banned_by)
163 });
164
165 let banned_user_ids_for_sweep: HashSet<MemberId> =
170 self.bans.0.iter().map(|b| b.ban.banned_user).collect();
171 let active_member_ids_for_sweep: HashSet<MemberId> =
172 self.members.members.iter().map(|m| m.member.id()).collect();
173 self.direct_messages.sweep_after_membership_change(
174 owner_id,
175 &active_member_ids_for_sweep,
176 &banned_user_ids_for_sweep,
177 );
178
179 self.members.members.sort_by_key(|m| m.member.id());
181 self.member_info
182 .member_info
183 .sort_by_key(|info| info.member_info.member_id);
184
185 Ok(())
186 }
187}
188
189#[derive(Serialize, Deserialize, Clone, Default, PartialEq, Debug)]
190pub struct ChatRoomParametersV1 {
191 pub owner: VerifyingKey,
192}
193
194impl ChatRoomParametersV1 {
195 pub fn owner_id(&self) -> MemberId {
196 self.owner.into()
197 }
198}
199
200#[cfg(test)]
201mod tests {
202 use super::*;
203 use crate::room_state::ban::{AuthorizedUserBan, UserBan};
204 use crate::room_state::configuration::Configuration;
205 use crate::room_state::member::{AuthorizedMember, Member};
206 use crate::room_state::member_info::{AuthorizedMemberInfo, MemberInfo};
207 use crate::room_state::message::{AuthorizedMessageV1, MessageV1, RoomMessageBody};
208 use ed25519_dalek::SigningKey;
209 use std::fmt::Debug;
210 use std::time::SystemTime;
211
212 #[test]
213 fn test_state() {
214 let (state, parameters, owner_signing_key) = create_empty_chat_room_state();
215
216 assert!(
217 state.verify(&state, ¶meters).is_ok(),
218 "Empty state should verify"
219 );
220
221 let mut new_cfg = state.configuration.configuration.clone();
223 new_cfg.configuration_version += 1;
224 new_cfg.max_recent_messages = 10; let new_cfg = AuthorizedConfigurationV1::new(new_cfg, &owner_signing_key);
226
227 let mut cfg_modified_state = state.clone();
228 cfg_modified_state.configuration = new_cfg;
229 test_apply_delta(state.clone(), cfg_modified_state, ¶meters);
230 }
231
232 fn test_apply_delta<CS>(orig_state: CS, modified_state: CS, parameters: &CS::Parameters)
233 where
234 CS: ComposableState<ParentState = CS> + Clone + PartialEq + Debug,
235 {
236 let orig_verify_result = orig_state.verify(&orig_state, parameters);
237 assert!(
238 orig_verify_result.is_ok(),
239 "Original state verification failed: {:?}",
240 orig_verify_result.err()
241 );
242
243 let modified_verify_result = modified_state.verify(&modified_state, parameters);
244 assert!(
245 modified_verify_result.is_ok(),
246 "Modified state verification failed: {:?}",
247 modified_verify_result.err()
248 );
249
250 let delta = modified_state.delta(
251 &orig_state,
252 parameters,
253 &orig_state.summarize(&orig_state, parameters),
254 );
255
256 println!("Delta: {:?}", delta);
257
258 let mut new_state = orig_state.clone();
259 let apply_delta_result = new_state.apply_delta(&orig_state, parameters, &delta);
260 assert!(
261 apply_delta_result.is_ok(),
262 "Applying delta failed: {:?}",
263 apply_delta_result.err()
264 );
265
266 assert_eq!(new_state, modified_state);
267 }
268 fn create_empty_chat_room_state() -> (ChatRoomStateV1, ChatRoomParametersV1, SigningKey) {
269 let rng = &mut rand::thread_rng();
272 let owner_signing_key = SigningKey::generate(rng);
273 let owner_verifying_key = owner_signing_key.verifying_key();
274
275 let config = AuthorizedConfigurationV1::new(Configuration::default(), &owner_signing_key);
276
277 (
278 ChatRoomStateV1 {
279 configuration: config,
280 bans: BansV1::default(),
281 members: MembersV1::default(),
282 member_info: MemberInfoV1::default(),
283 secrets: RoomSecretsV1::default(),
284 recent_messages: MessagesV1::default(),
285 upgrade: OptionalUpgradeV1(None),
286 ..Default::default()
287 },
288 ChatRoomParametersV1 {
289 owner: owner_verifying_key,
290 },
291 owner_signing_key,
292 )
293 }
294
295 #[test]
300 fn test_orphaned_ban_cleanup_after_cascade_removal() {
301 let rng = &mut rand::thread_rng();
302
303 let owner_sk = SigningKey::generate(rng);
305 let owner_vk = owner_sk.verifying_key();
306 let owner_id = MemberId::from(&owner_vk);
307 let params = ChatRoomParametersV1 { owner: owner_vk };
308
309 let config = Configuration {
311 max_user_bans: 10,
312 max_members: 10,
313 ..Default::default()
314 };
315 let auth_config = AuthorizedConfigurationV1::new(config, &owner_sk);
316
317 let a_sk = SigningKey::generate(rng);
319 let a_vk = a_sk.verifying_key();
320 let a_id = MemberId::from(&a_vk);
321
322 let b_sk = SigningKey::generate(rng);
323 let b_vk = b_sk.verifying_key();
324 let b_id = MemberId::from(&b_vk);
325
326 let member_a = AuthorizedMember::new(
327 Member {
328 owner_member_id: owner_id,
329 invited_by: owner_id,
330 member_vk: a_vk,
331 },
332 &owner_sk,
333 );
334
335 let ban_b_by_a = AuthorizedUserBan::new(
337 UserBan {
338 owner_member_id: owner_id,
339 banned_at: std::time::SystemTime::now(),
340 banned_user: b_id,
341 },
342 a_id,
343 &a_sk,
344 );
345
346 let initial_state = ChatRoomStateV1 {
348 configuration: auth_config.clone(),
349 bans: BansV1(vec![ban_b_by_a.clone()]),
350 members: MembersV1 {
351 members: vec![member_a.clone()],
352 },
353 ..Default::default()
354 };
355
356 assert!(
357 initial_state.verify(&initial_state, ¶ms).is_ok(),
358 "Initial state should verify: {:?}",
359 initial_state.verify(&initial_state, ¶ms)
360 );
361
362 let ban_a_by_owner = AuthorizedUserBan::new(
365 UserBan {
366 owner_member_id: owner_id,
367 banned_at: std::time::SystemTime::now() + std::time::Duration::from_secs(1),
368 banned_user: a_id,
369 },
370 owner_id,
371 &owner_sk,
372 );
373
374 let modified_for_delta = ChatRoomStateV1 {
376 configuration: auth_config,
377 bans: BansV1(vec![ban_b_by_a.clone(), ban_a_by_owner.clone()]),
378 members: MembersV1 {
379 members: vec![member_a.clone()],
380 },
381 ..Default::default()
382 };
383
384 let summary = initial_state.summarize(&initial_state, ¶ms);
386 let delta = modified_for_delta.delta(&initial_state, ¶ms, &summary);
387
388 let mut result_state = initial_state.clone();
389 let apply_result = result_state.apply_delta(&initial_state, ¶ms, &delta);
390 assert!(
391 apply_result.is_ok(),
392 "apply_delta should succeed: {:?}",
393 apply_result
394 );
395
396 assert!(
398 result_state.members.members.is_empty(),
399 "A should be removed from members: {:?}",
400 result_state.members.members
401 );
402
403 assert_eq!(
405 result_state.bans.0.len(),
406 1,
407 "Only owner's ban should remain, orphaned ban cleaned: {:?}",
408 result_state.bans.0
409 );
410 assert_eq!(
411 result_state.bans.0[0].banned_by, owner_id,
412 "Remaining ban should be by owner"
413 );
414
415 assert!(
417 result_state.verify(&result_state, ¶ms).is_ok(),
418 "Result state should verify after orphaned ban cleanup: {:?}",
419 result_state.verify(&result_state, ¶ms)
420 );
421 }
422
423 #[test]
424 fn test_member_pruned_when_no_messages() {
425 let rng = &mut rand::thread_rng();
426 let owner_sk = SigningKey::generate(rng);
427 let owner_vk = owner_sk.verifying_key();
428 let owner_id = MemberId::from(&owner_vk);
429 let params = ChatRoomParametersV1 { owner: owner_vk };
430
431 let a_sk = SigningKey::generate(rng);
432 let a_vk = a_sk.verifying_key();
433 let a_id = MemberId::from(&a_vk);
434
435 let b_sk = SigningKey::generate(rng);
436 let b_vk = b_sk.verifying_key();
437
438 let member_a = AuthorizedMember::new(
439 Member {
440 owner_member_id: owner_id,
441 invited_by: owner_id,
442 member_vk: a_vk,
443 },
444 &owner_sk,
445 );
446 let member_b = AuthorizedMember::new(
447 Member {
448 owner_member_id: owner_id,
449 invited_by: owner_id,
450 member_vk: b_vk,
451 },
452 &owner_sk,
453 );
454
455 let msg_a = AuthorizedMessageV1::new(
457 MessageV1 {
458 room_owner: owner_id,
459 author: a_id,
460 time: SystemTime::now(),
461 content: RoomMessageBody::public("Hello from A".to_string()),
462 },
463 &a_sk,
464 );
465
466 let config = Configuration {
467 max_members: 10,
468 max_recent_messages: 100,
469 ..Default::default()
470 };
471 let auth_config = AuthorizedConfigurationV1::new(config, &owner_sk);
472
473 let mut state = ChatRoomStateV1 {
474 configuration: auth_config,
475 members: MembersV1 {
476 members: vec![member_a, member_b],
477 },
478 recent_messages: MessagesV1 {
479 messages: vec![msg_a],
480 ..Default::default()
481 },
482 ..Default::default()
483 };
484
485 state.post_apply_cleanup(¶ms).unwrap();
486
487 assert_eq!(state.members.members.len(), 1, "Only A should remain");
488 assert_eq!(state.members.members[0].member.id(), a_id);
489 }
490
491 #[test]
492 fn test_member_with_join_event_not_pruned() {
493 let rng = &mut rand::thread_rng();
494 let owner_sk = SigningKey::generate(rng);
495 let owner_vk = owner_sk.verifying_key();
496 let owner_id = MemberId::from(&owner_vk);
497 let params = ChatRoomParametersV1 { owner: owner_vk };
498
499 let a_sk = SigningKey::generate(rng);
500 let a_vk = a_sk.verifying_key();
501 let a_id = MemberId::from(&a_vk);
502
503 let member_a = AuthorizedMember::new(
504 Member {
505 owner_member_id: owner_id,
506 invited_by: owner_id,
507 member_vk: a_vk,
508 },
509 &owner_sk,
510 );
511
512 let join_msg = AuthorizedMessageV1::new(
514 MessageV1 {
515 room_owner: owner_id,
516 author: a_id,
517 time: SystemTime::now(),
518 content: RoomMessageBody::join_event(),
519 },
520 &a_sk,
521 );
522
523 let config = Configuration {
524 max_members: 10,
525 max_recent_messages: 100,
526 ..Default::default()
527 };
528 let auth_config = AuthorizedConfigurationV1::new(config, &owner_sk);
529
530 let mut state = ChatRoomStateV1 {
531 configuration: auth_config,
532 members: MembersV1 {
533 members: vec![member_a],
534 },
535 recent_messages: MessagesV1 {
536 messages: vec![join_msg],
537 ..Default::default()
538 },
539 ..Default::default()
540 };
541
542 state.post_apply_cleanup(¶ms).unwrap();
543
544 assert_eq!(
545 state.members.members.len(),
546 1,
547 "Member with join event should not be pruned"
548 );
549 assert_eq!(state.members.members[0].member.id(), a_id);
550 }
551
552 #[test]
555 fn test_atomic_join_delta_applies_and_verifies() {
556 use crate::room_state::member::MembersDelta;
557 use crate::room_state::member_info::{AuthorizedMemberInfo, MemberInfo};
558 use crate::room_state::privacy::SealedBytes;
559
560 let rng = &mut rand::thread_rng();
561 let owner_sk = SigningKey::generate(rng);
562 let owner_vk = owner_sk.verifying_key();
563 let owner_id = MemberId::from(&owner_vk);
564 let params = ChatRoomParametersV1 { owner: owner_vk };
565
566 let config = Configuration {
568 owner_member_id: owner_id,
569 max_members: 10,
570 max_recent_messages: 100,
571 ..Default::default()
572 };
573 let auth_config = AuthorizedConfigurationV1::new(config, &owner_sk);
574 let mut state = ChatRoomStateV1 {
575 configuration: auth_config,
576 ..Default::default()
577 };
578
579 let joiner_sk = SigningKey::generate(rng);
581 let joiner_vk = joiner_sk.verifying_key();
582 let joiner_id = MemberId::from(&joiner_vk);
583
584 let authorized_member = AuthorizedMember::new(
585 Member {
586 owner_member_id: owner_id,
587 invited_by: owner_id,
588 member_vk: joiner_vk,
589 },
590 &owner_sk,
591 );
592
593 let member_info = MemberInfo {
594 member_id: joiner_id,
595 version: 0,
596 preferred_nickname: SealedBytes::public("NewUser".to_string().into_bytes()),
597 };
598 let authorized_info = AuthorizedMemberInfo::new_with_member_key(member_info, &joiner_sk);
599
600 let join_message = AuthorizedMessageV1::new(
601 MessageV1 {
602 room_owner: owner_id,
603 author: joiner_id,
604 content: RoomMessageBody::join_event(),
605 time: SystemTime::now(),
606 },
607 &joiner_sk,
608 );
609
610 let delta = ChatRoomStateV1Delta {
612 recent_messages: Some(vec![join_message]),
613 members: Some(MembersDelta::new(vec![authorized_member])),
614 member_info: Some(vec![authorized_info]),
615 ..Default::default()
616 };
617
618 let old_state = state.clone();
620 state
621 .apply_delta(&old_state, ¶ms, &Some(delta))
622 .expect("atomic join delta should apply cleanly");
623
624 state
626 .verify(&state, ¶ms)
627 .expect("state should verify after join delta");
628
629 assert!(
631 state
632 .members
633 .members
634 .iter()
635 .any(|m| m.member.id() == joiner_id),
636 "Joiner should be in members list"
637 );
638
639 assert!(
641 state
642 .member_info
643 .member_info
644 .iter()
645 .any(|i| i.member_info.member_id == joiner_id),
646 "Joiner should have member_info"
647 );
648
649 assert_eq!(state.recent_messages.messages.len(), 1);
651 assert!(state.recent_messages.messages[0].message.content.is_event());
652
653 state.post_apply_cleanup(¶ms).unwrap();
655 assert!(
656 state
657 .members
658 .members
659 .iter()
660 .any(|m| m.member.id() == joiner_id),
661 "Joiner should survive cleanup"
662 );
663 }
664
665 #[test]
666 fn test_invite_chain_preserved_for_active_member() {
667 let rng = &mut rand::thread_rng();
668 let owner_sk = SigningKey::generate(rng);
669 let owner_vk = owner_sk.verifying_key();
670 let owner_id = MemberId::from(&owner_vk);
671 let params = ChatRoomParametersV1 { owner: owner_vk };
672
673 let a_sk = SigningKey::generate(rng);
674 let a_vk = a_sk.verifying_key();
675 let a_id = MemberId::from(&a_vk);
676
677 let b_sk = SigningKey::generate(rng);
678 let b_vk = b_sk.verifying_key();
679 let b_id = MemberId::from(&b_vk);
680
681 let member_a = AuthorizedMember::new(
683 Member {
684 owner_member_id: owner_id,
685 invited_by: owner_id,
686 member_vk: a_vk,
687 },
688 &owner_sk,
689 );
690 let member_b = AuthorizedMember::new(
691 Member {
692 owner_member_id: owner_id,
693 invited_by: a_id,
694 member_vk: b_vk,
695 },
696 &a_sk,
697 );
698
699 let msg_b = AuthorizedMessageV1::new(
701 MessageV1 {
702 room_owner: owner_id,
703 author: b_id,
704 time: SystemTime::now(),
705 content: RoomMessageBody::public("Hello from B".to_string()),
706 },
707 &b_sk,
708 );
709
710 let config = Configuration {
711 max_members: 10,
712 max_recent_messages: 100,
713 ..Default::default()
714 };
715 let auth_config = AuthorizedConfigurationV1::new(config, &owner_sk);
716
717 let mut state = ChatRoomStateV1 {
718 configuration: auth_config,
719 members: MembersV1 {
720 members: vec![member_a, member_b],
721 },
722 recent_messages: MessagesV1 {
723 messages: vec![msg_b],
724 ..Default::default()
725 },
726 ..Default::default()
727 };
728
729 state.post_apply_cleanup(¶ms).unwrap();
730
731 assert_eq!(state.members.members.len(), 2);
733 let member_ids: HashSet<MemberId> = state
734 .members
735 .members
736 .iter()
737 .map(|m| m.member.id())
738 .collect();
739 assert!(
740 member_ids.contains(&a_id),
741 "A should be kept (in B's invite chain)"
742 );
743 assert!(
744 member_ids.contains(&b_id),
745 "B should be kept (has messages)"
746 );
747 }
748
749 #[test]
750 fn test_ban_persists_after_banner_pruned() {
751 let rng = &mut rand::thread_rng();
752 let owner_sk = SigningKey::generate(rng);
753 let owner_vk = owner_sk.verifying_key();
754 let owner_id = MemberId::from(&owner_vk);
755 let params = ChatRoomParametersV1 { owner: owner_vk };
756
757 let a_sk = SigningKey::generate(rng);
758 let a_vk = a_sk.verifying_key();
759 let a_id = MemberId::from(&a_vk);
760
761 let c_sk = SigningKey::generate(rng);
762 let c_vk = c_sk.verifying_key();
763 let c_id = MemberId::from(&c_vk);
764
765 let member_a = AuthorizedMember::new(
767 Member {
768 owner_member_id: owner_id,
769 invited_by: owner_id,
770 member_vk: a_vk,
771 },
772 &owner_sk,
773 );
774
775 let ban_c_by_a = AuthorizedUserBan::new(
777 UserBan {
778 owner_member_id: owner_id,
779 banned_at: SystemTime::now(),
780 banned_user: c_id,
781 },
782 a_id,
783 &a_sk,
784 );
785
786 let config = Configuration {
787 max_members: 10,
788 max_user_bans: 10,
789 ..Default::default()
790 };
791 let auth_config = AuthorizedConfigurationV1::new(config, &owner_sk);
792
793 let mut state = ChatRoomStateV1 {
795 configuration: auth_config,
796 members: MembersV1 {
797 members: vec![member_a],
798 },
799 bans: BansV1(vec![ban_c_by_a]),
800 ..Default::default()
801 };
802
803 state.post_apply_cleanup(¶ms).unwrap();
804
805 assert!(state.members.members.is_empty(), "A should be pruned");
807
808 assert_eq!(state.bans.0.len(), 1, "Ban should persist");
810 assert_eq!(state.bans.0[0].ban.banned_user, c_id);
811 assert_eq!(state.bans.0[0].banned_by, a_id);
812 }
813
814 #[test]
815 fn test_member_re_added_with_message() {
816 let rng = &mut rand::thread_rng();
817 let owner_sk = SigningKey::generate(rng);
818 let owner_vk = owner_sk.verifying_key();
819 let owner_id = MemberId::from(&owner_vk);
820 let params = ChatRoomParametersV1 { owner: owner_vk };
821
822 let a_sk = SigningKey::generate(rng);
823 let a_vk = a_sk.verifying_key();
824 let a_id = MemberId::from(&a_vk);
825
826 let member_a = AuthorizedMember::new(
827 Member {
828 owner_member_id: owner_id,
829 invited_by: owner_id,
830 member_vk: a_vk,
831 },
832 &owner_sk,
833 );
834
835 let config = Configuration {
836 max_members: 10,
837 max_recent_messages: 100,
838 ..Default::default()
839 };
840 let auth_config = AuthorizedConfigurationV1::new(config, &owner_sk);
841
842 let mut state = ChatRoomStateV1 {
844 configuration: auth_config,
845 members: MembersV1 {
846 members: vec![member_a.clone()],
847 },
848 ..Default::default()
849 };
850
851 state.post_apply_cleanup(¶ms).unwrap();
853 assert!(state.members.members.is_empty(), "A should be pruned");
854
855 state.members.members.push(member_a);
857 let msg = AuthorizedMessageV1::new(
858 MessageV1 {
859 room_owner: owner_id,
860 author: a_id,
861 time: SystemTime::now(),
862 content: RoomMessageBody::public("Hello again!".to_string()),
863 },
864 &a_sk,
865 );
866 state.recent_messages.messages.push(msg);
867
868 state.post_apply_cleanup(¶ms).unwrap();
870 assert_eq!(state.members.members.len(), 1, "A should be kept");
871 assert_eq!(state.members.members[0].member.id(), a_id);
872 }
873
874 #[test]
875 fn test_member_info_cleaned_after_pruning() {
876 let rng = &mut rand::thread_rng();
877 let owner_sk = SigningKey::generate(rng);
878 let owner_vk = owner_sk.verifying_key();
879 let owner_id = MemberId::from(&owner_vk);
880 let params = ChatRoomParametersV1 { owner: owner_vk };
881
882 let a_sk = SigningKey::generate(rng);
883 let a_vk = a_sk.verifying_key();
884 let a_id = MemberId::from(&a_vk);
885
886 let member_a = AuthorizedMember::new(
887 Member {
888 owner_member_id: owner_id,
889 invited_by: owner_id,
890 member_vk: a_vk,
891 },
892 &owner_sk,
893 );
894
895 let a_info = AuthorizedMemberInfo::new_with_member_key(
897 MemberInfo::new_public(a_id, 1, "Alice".to_string()),
898 &a_sk,
899 );
900 let owner_info = AuthorizedMemberInfo::new(
901 MemberInfo::new_public(owner_id, 1, "Owner".to_string()),
902 &owner_sk,
903 );
904
905 let config = Configuration {
906 max_members: 10,
907 ..Default::default()
908 };
909 let auth_config = AuthorizedConfigurationV1::new(config, &owner_sk);
910
911 let mut state = ChatRoomStateV1 {
912 configuration: auth_config,
913 members: MembersV1 {
914 members: vec![member_a],
915 },
916 member_info: MemberInfoV1 {
917 member_info: vec![owner_info, a_info],
918 },
919 ..Default::default()
920 };
921
922 state.post_apply_cleanup(¶ms).unwrap();
924
925 assert!(state.members.members.is_empty(), "A should be pruned");
926 assert_eq!(
927 state.member_info.member_info.len(),
928 1,
929 "Only owner's info should remain"
930 );
931 assert_eq!(
932 state.member_info.member_info[0].member_info.member_id, owner_id,
933 "Remaining info should be owner's"
934 );
935 }
936
937 #[test]
938 fn test_state_with_none_deltas() {
939 let (state, parameters, owner_signing_key) = create_empty_chat_room_state();
940
941 let modified_state = state.clone();
943
944 let summary = state.summarize(&state, ¶meters);
946 let delta = modified_state.delta(&state, ¶meters, &summary);
947
948 assert!(
949 delta.is_none(),
950 "Delta should be None when no changes are made"
951 );
952
953 let mut partially_modified_state = state.clone();
955 let new_config = Configuration {
956 configuration_version: 2,
957 ..partially_modified_state.configuration.configuration.clone()
958 };
959 partially_modified_state.configuration =
960 AuthorizedConfigurationV1::new(new_config, &owner_signing_key);
961
962 let summary = state.summarize(&state, ¶meters);
963 let delta = partially_modified_state
964 .delta(&state, ¶meters, &summary)
965 .unwrap();
966
967 assert!(
969 delta.configuration.is_some(),
970 "Configuration delta should be Some"
971 );
972 assert!(delta.bans.is_none(), "Bans delta should be None");
973 assert!(delta.members.is_none(), "Members delta should be None");
974 assert!(
975 delta.member_info.is_none(),
976 "Member info delta should be None"
977 );
978 assert!(
979 delta.recent_messages.is_none(),
980 "Recent messages delta should be None"
981 );
982 assert!(delta.upgrade.is_none(), "Upgrade delta should be None");
983
984 let mut new_state = state.clone();
986 new_state
987 .apply_delta(&state, ¶meters, &Some(delta))
988 .unwrap();
989
990 assert_eq!(
991 new_state, partially_modified_state,
992 "State should be partially modified"
993 );
994 }
995}