zoe_app_primitives/group/states.rs
1use serde::{Deserialize, Serialize};
2use std::collections::{BTreeMap, HashSet};
3use zoe_wire_protocol::{MessageId, VerifyingKey};
4
5use super::events::roles::GroupRole;
6use super::events::{GroupActivityEvent, GroupSettings};
7use crate::{IdentityInfo, IdentityRef, IdentityType, Metadata, Permission};
8
9#[cfg(feature = "frb-api")]
10use flutter_rust_bridge::frb;
11
12/// Advanced identity and membership management for distributed groups.
13///
14/// `GroupMembership` handles the complex identity scenarios that arise in distributed,
15/// encrypted group communication. It separates cryptographic identity (via
16/// [`zoe_wire_protocol::VerifyingKey`]) from display identity (names, aliases).
17///
18/// ## 🎭 Identity Architecture
19///
20/// The system operates on a two-layer identity model:
21///
22/// ### Layer 1: Cryptographic Identity (VerifyingKeys)
23/// - Each participant has one or more [`zoe_wire_protocol::VerifyingKey`]s
24/// - These keys are used for message signing and verification
25/// - Keys are the fundamental unit of authentication and authorization
26/// - A key represents a device, account, or cryptographic identity
27///
28/// ### Layer 2: Display Identity (Aliases and Names)
29/// - Each key can declare multiple [`crate::IdentityType`] variants:
30/// - **Main Identity**: The primary identity for a key (often a real name)
31/// - **Aliases**: Secondary identities for role-playing, privacy, or context
32/// - Each identity can have associated [`crate::IdentityInfo`] with display names
33/// - Identities are what users see and interact with in the UI
34///
35/// ## 🔄 Use Cases and Benefits
36///
37/// ### Privacy and Pseudonymity
38/// ```text
39/// VerifyingKey(Alice_Device_1) ──┬─→ Main: "Alice Johnson"
40/// ├─→ Alias: "ProjectLead"
41/// └─→ Alias: "AnonymousReviewer"
42/// ```
43///
44/// Alice can participate in the same group with different personas:
45/// - Official communications as "Alice Johnson"
46/// - Project management as "ProjectLead"
47/// - Anonymous feedback as "AnonymousReviewer"
48///
49/// ### Multi-Device Identity
50/// ```text
51/// Real Person: Bob ──┬─→ VerifyingKey(Bob_Phone) ─→ Main: "Bob Smith"
52/// └─→ VerifyingKey(Bob_Laptop) ─→ Main: "Bob Smith"
53/// ```
54///
55/// Bob can use multiple devices with the same display identity.
56///
57/// ### Role-Based Communication
58/// ```text
59/// VerifyingKey(Company_Bot) ──┬─→ Alias: "HR Bot"
60/// ├─→ Alias: "Security Alert System"
61/// └─→ Alias: "Meeting Scheduler"
62/// ```
63///
64/// Automated systems can present different faces for different functions.
65///
66/// ## 🔒 Security and Authorization
67///
68/// ### Key-Based Authorization
69/// - All permissions and role assignments are tied to [`IdentityRef`] variants
70/// - An [`IdentityRef::Key`] directly authorizes the key holder
71/// - An [`IdentityRef::Alias`] authorizes only if the key controls that alias
72/// - Use [`GroupMembership::is_authorized`] to check if a key can act as an identity
73///
74/// ### Self-Sovereign Identity Declaration
75/// - Only a key can declare identities for itself
76/// - Other participants cannot assign aliases to someone else's key
77/// - Identity information is cryptographically signed by the declaring key
78/// - Malicious identity claims are prevented by signature verification
79///
80/// ## 📊 Data Structure
81///
82/// ### Identity Storage
83/// - [`GroupMembership::identity_info`]: Maps `(VerifyingKey, IdentityType) → IdentityInfo`
84/// - Stores display names and metadata for each declared identity
85/// - Multiple identities per key are fully supported
86///
87/// ### Role Assignments
88/// - [`GroupMembership::identity_roles`]: Maps `IdentityRef → GroupRole`
89/// - Roles can be assigned to specific identities, not just keys
90/// - Enables fine-grained permission control per identity
91///
92/// ## 🔧 Core Operations
93///
94/// ### Identity Discovery
95/// - [`GroupMembership::get_available_identities`]: Find all identities a key can use
96/// - [`GroupMembership::get_display_name`]: Get human-readable name for an identity
97/// - [`GroupMembership::has_identity_info`]: Check if identity has been declared
98///
99/// ### Role Management
100/// - [`GroupMembership::get_role`]: Get the role assigned to a specific identity
101/// - [`GroupMembership::get_effective_role`]: Get role when key acts as an alias
102/// - Roles default to [`super::events::roles::GroupRole::Member`] if not explicitly set
103///
104/// ## 💡 Usage Examples
105///
106/// ### Setting Up Multiple Identities
107/// ```rust
108/// use zoe_app_primitives::{GroupMembership, IdentityType, IdentityRef, IdentityInfo};
109/// use zoe_wire_protocol::KeyPair;
110/// use std::collections::HashMap;
111///
112/// let mut membership = GroupMembership::new();
113/// let alice_key = KeyPair::generate(&mut rand::rngs::OsRng).public_key();
114///
115/// // Alice declares her main identity
116/// let main_identity = IdentityInfo {
117/// display_name: "Alice Johnson".to_string(),
118/// metadata: vec![],
119/// };
120///
121/// // Alice declares an alias for anonymous feedback
122/// let anon_identity = IdentityInfo {
123/// display_name: "Anonymous Reviewer".to_string(),
124/// metadata: vec![],
125/// };
126///
127/// // In practice, these would be set via GroupManagementEvent::SetIdentity
128/// // Here we simulate the result of processing those events
129/// membership.identity_info.insert(
130/// IdentityRef::Key(alice_key.clone()),
131/// main_identity,
132/// );
133/// membership.identity_info.insert(
134/// IdentityRef::Alias { key: alice_key, alias: "anon".to_string() },
135/// anon_identity,
136/// );
137/// ```
138///
139/// ### Checking Authorization
140/// ```rust
141/// # use zoe_app_primitives::{GroupMembership, IdentityRef};
142/// # use zoe_wire_protocol::KeyPair;
143/// # let membership = GroupMembership::new();
144/// # let alice_key = KeyPair::generate(&mut rand::rngs::OsRng).public_key();
145///
146/// // Check if Alice can act as her main identity (always true)
147/// let main_ref = IdentityRef::Key(alice_key.clone());
148/// assert!(membership.is_authorized(&alice_key, &main_ref));
149///
150/// // Check if Alice can act as her anonymous alias
151/// let alias_ref = IdentityRef::Alias {
152/// key: alice_key.clone(),
153/// alias: "anon".to_string(),
154/// };
155/// assert!(membership.is_authorized(&alice_key, &alias_ref));
156///
157/// // Check if Alice can act as someone else's alias (false)
158/// let other_key = KeyPair::generate(&mut rand::rngs::OsRng).public_key();
159/// let other_alias = IdentityRef::Alias {
160/// key: other_key,
161/// alias: "not_alice".to_string(),
162/// };
163/// assert!(!membership.is_authorized(&alice_key, &other_alias));
164/// ```
165///
166/// ### Role-Based Access with Identities
167/// ```rust
168/// # use zoe_app_primitives::{GroupMembership, IdentityRef};
169/// # use zoe_app_primitives::events::roles::GroupRole;
170/// # use zoe_wire_protocol::KeyPair;
171/// # let mut membership = GroupMembership::new();
172/// # let alice_key = KeyPair::generate(&mut rand::rngs::OsRng).public_key();
173///
174/// // Assign admin role to Alice's main identity
175/// let main_ref = IdentityRef::Key(alice_key.clone());
176/// membership.identity_roles.insert(main_ref.clone(), GroupRole::Admin);
177///
178/// // Assign member role to Alice's anonymous alias
179/// let alias_ref = IdentityRef::Alias {
180/// key: alice_key,
181/// alias: "anon".to_string(),
182/// };
183/// membership.identity_roles.insert(alias_ref.clone(), GroupRole::Member);
184///
185/// // Check effective roles
186/// assert_eq!(
187/// membership.get_role(&main_ref),
188/// Some(GroupRole::Admin)
189/// );
190/// assert_eq!(
191/// membership.get_role(&alias_ref),
192/// Some(GroupRole::Member)
193/// );
194/// ```
195///
196/// ## 🌐 Integration with Group Events
197///
198/// Identity management integrates with the event system through:
199/// - [`super::events::GroupActivityEvent::SetIdentity`]: Declares new identities
200/// - [`super::events::GroupActivityEvent::AssignRole`]: Assigns roles to identities
201/// - Event processing updates the membership state automatically
202/// - All identity changes are part of the signed, encrypted event history
203///
204/// This ensures that identity management is:
205/// - **Auditable**: Full history of identity changes
206/// - **Consistent**: Same view across all group members
207/// - **Secure**: Cryptographically signed and verified
208#[derive(Debug, Clone, Serialize, Deserialize)]
209pub struct GroupMembership {
210 /// Identity information for keys and their aliases: (key_bytes, identity_type) -> identity_info
211 /// Keys are ML-DSA verifying keys encoded as bytes for serialization compatibility
212 pub identity_info: BTreeMap<IdentityRef, IdentityInfo>,
213 /// Role assignments for identities (both keys and aliases)
214 pub identity_roles: BTreeMap<IdentityRef, GroupRole>,
215}
216
217impl GroupMembership {
218 /// Create a new empty membership state
219 pub fn new() -> Self {
220 Self {
221 identity_info: BTreeMap::new(),
222 identity_roles: BTreeMap::new(),
223 }
224 }
225
226 /// Check if a verifying key is authorized to act as a specific identity
227 pub fn is_authorized(&self, key: &VerifyingKey, identity_ref: &IdentityRef) -> bool {
228 // Check if this key controls the identity
229 // For now, we'll need to convert to bytes for comparison since IdentityRef expects Ed25519 keys
230 // This is a temporary compatibility layer
231 identity_ref.is_controlled_by(key)
232 }
233
234 /// Get all identities that a verifying key can act as
235 pub fn get_available_identities(&self, _key: &VerifyingKey) -> HashSet<IdentityRef> {
236 // For now, ML-DSA keys cannot act as Ed25519-based identities
237 // This will need to be updated when we fully transition to ML-DSA
238 // Return empty set as a temporary compatibility measure
239 HashSet::new()
240 }
241
242 /// Get the role for a specific identity
243 pub fn get_role(&self, identity_ref: &IdentityRef) -> Option<GroupRole> {
244 // Check for explicit role assignment first
245 if let Some(role) = self.identity_roles.get(identity_ref) {
246 return Some(role.clone());
247 }
248
249 // Fall back to default member role for any valid identity
250 Some(GroupRole::Member)
251 }
252
253 /// Get effective role when a key acts as a specific identity
254 pub fn get_effective_role(
255 &self,
256 _key: &VerifyingKey,
257 _acting_as_alias: &Option<String>,
258 ) -> Option<GroupRole> {
259 // For now, ML-DSA keys cannot act as Ed25519-based identities
260 // This will need to be updated when we fully transition to ML-DSA
261 // Return default member role as a temporary compatibility measure
262 Some(GroupRole::Member)
263 }
264
265 /// Get display name for an identity
266 pub fn get_display_name(&self, key: &VerifyingKey, identity_type: &IdentityType) -> String {
267 // For now, ML-DSA keys don't have identity info in the Ed25519-based system
268 // This will need to be updated when we fully transition to ML-DSA
269 // Fall back to default display
270 match identity_type {
271 IdentityType::Main => format!("ML-DSA Key:{key:?}"),
272 IdentityType::Alias { alias_id } => alias_id.clone(),
273 }
274 }
275
276 /// Check if an identity has been declared by a key
277 pub fn has_identity_info(&self, _key: &VerifyingKey, _identity_type: &IdentityType) -> bool {
278 // For now, ML-DSA keys don't have identity info in the Ed25519-based system
279 // This will need to be updated when we fully transition to ML-DSA
280 false
281 }
282}
283
284impl Default for GroupMembership {
285 fn default() -> Self {
286 Self::new()
287 }
288}
289
290/// Error types for group state operations
291#[derive(Debug, thiserror::Error)]
292pub enum GroupStateError {
293 #[error("Permission denied: {0}")]
294 PermissionDenied(String),
295
296 #[error("Member not found: {member} in group {group}")]
297 MemberNotFound { member: String, group: String },
298
299 #[error("State transition error: {0}")]
300 StateTransition(String),
301
302 #[error("Invalid operation: {0}")]
303 InvalidOperation(String),
304}
305
306/// Result type for group state operations
307pub type GroupStateResult<T> = Result<T, GroupStateError>;
308
309/// Runtime information about an active group member.
310///
311/// `GroupMember` tracks the runtime state of a participant in a group. This includes
312/// their role, activity timestamps, and member-specific metadata. It's distinct from
313/// the cryptographic identity and display identity managed by [`GroupMembership`].
314///
315/// ## 📊 Member State vs Identity State
316///
317/// - **GroupMember**: Runtime participation state (roles, activity, metadata)
318/// - **GroupMembership**: Identity management (aliases, display names, authorization)
319/// - **VerifyingKey**: Cryptographic identity (authentication, message signing)
320///
321/// These three layers work together to provide comprehensive member management:
322/// ```text
323/// VerifyingKey → GroupMember (runtime state) + GroupMembership (identity state)
324/// ```
325///
326/// ## 🔄 Lifecycle and State Transitions
327///
328/// 1. **Initial Creation**: When a key first participates, a `GroupMember` is created
329/// 2. **Activity Updates**: [`GroupMember::last_active`] updated with each message
330/// 3. **Role Changes**: [`GroupMember::role`] updated via role assignment events
331/// 4. **Metadata Updates**: [`GroupMember::metadata`] can store custom key-value data
332/// 5. **Departure**: `GroupMember` removed when user leaves (but could rejoin later)
333///
334/// ## 💡 Usage Examples
335///
336/// ### Tracking Member Activity
337/// ```rust
338/// use zoe_app_primitives::{GroupMember, IdentityRef, events::roles::GroupRole};
339/// use zoe_wire_protocol::KeyPair;
340/// use std::collections::BTreeMap;
341///
342/// let member_key = KeyPair::generate(&mut rand::rngs::OsRng).public_key();
343/// let join_time = 1234567890;
344///
345/// let mut member = GroupMember {
346/// key: IdentityRef::Key(member_key),
347/// role: GroupRole::Member,
348/// joined_at: join_time,
349/// last_active: join_time,
350/// metadata: vec![],
351/// };
352///
353/// // Update activity when they send a message
354/// member.last_active = join_time + 3600; // 1 hour later
355///
356/// // Check how long they've been inactive
357/// let current_time = join_time + 7200; // 2 hours later
358/// let inactive_duration = current_time - member.last_active;
359/// assert_eq!(inactive_duration, 3600); // 1 hour inactive
360/// ```
361///
362/// ### Role-Based Member Management
363/// ```rust
364/// # use zoe_app_primitives::{GroupMember, IdentityRef, events::roles::GroupRole};
365/// # use zoe_wire_protocol::KeyPair;
366/// # use std::collections::BTreeMap;
367/// # let member_key = KeyPair::generate(&mut rand::rngs::OsRng).public_key();
368/// # let mut member = GroupMember {
369/// # key: IdentityRef::Key(member_key), role: GroupRole::Member, joined_at: 0, last_active: 0,
370/// # metadata: vec![],
371/// # };
372///
373/// // Promote member to moderator
374/// member.role = GroupRole::Moderator;
375///
376/// // Check permissions
377/// use zoe_app_primitives::Permission;
378/// assert!(member.role.has_permission(&Permission::AllMembers));
379/// assert!(member.role.has_permission(&Permission::ModeratorOrAbove));
380/// ```
381///
382/// ### Custom Member Metadata
383/// ```rust
384/// # use zoe_app_primitives::{GroupMember, IdentityRef, Metadata};
385/// # use zoe_wire_protocol::KeyPair;
386/// # let mut member = GroupMember {
387/// # key: IdentityRef::Key(KeyPair::generate(&mut rand::rngs::OsRng).public_key()),
388/// # role: zoe_app_primitives::events::roles::GroupRole::Member,
389/// # joined_at: 0, last_active: 0, metadata: vec![],
390/// # };
391///
392/// // Store custom metadata about the member using structured types
393/// member.metadata.push(Metadata::Generic { key: "department".to_string(), value: "engineering".to_string() });
394/// member.metadata.push(Metadata::Generic { key: "team".to_string(), value: "backend".to_string() });
395/// member.metadata.push(Metadata::Generic { key: "timezone".to_string(), value: "UTC-8".to_string() });
396/// member.metadata.push(Metadata::Email("member@company.com".to_string()));
397///
398/// // Query metadata
399/// for meta in &member.metadata {
400/// match meta {
401/// Metadata::Generic { key, value } if key == "department" => {
402/// println!("Member is in {} department", value);
403/// }
404/// Metadata::Email(email) => {
405/// println!("Member email: {}", email);
406/// }
407/// _ => {}
408/// }
409/// }
410/// ```
411#[derive(Debug, Clone, Serialize, Deserialize)]
412pub struct GroupMember {
413 /// Member's public key encoded as bytes for serialization compatibility
414 pub key: IdentityRef,
415 /// Member's role in the group
416 pub role: GroupRole,
417 /// When they joined the group
418 pub joined_at: u64,
419 /// When they were last active
420 pub last_active: u64,
421 /// Member-specific metadata using structured types
422 pub metadata: Vec<Metadata>,
423}
424
425/// The complete runtime state of a distributed encrypted group.
426///
427/// `GroupState` represents the unified, authoritative state of a group at any point in time.
428/// It combines immutable group information (from [`super::events::GroupInfo`]) with runtime
429/// state such as active members, event history, and identity management.
430///
431/// ## 🏗️ Design Philosophy
432///
433/// This type unifies what were previously separate concerns:
434/// - **Static Group Information**: Name, settings, and structured metadata
435/// - **Dynamic Member State**: Active participants, roles, and activity tracking
436/// - **Event History**: Audit trail and conflict resolution capability
437/// - **Identity Management**: Complex alias and display name handling
438///
439/// ## 🔄 Event-Sourced Architecture
440///
441/// Groups maintain state through event sourcing:
442/// ```text
443/// CreateGroup Event → Initial GroupState
444/// ↓
445/// Member Activity → Updated GroupState (new member added)
446/// ↓
447/// Role Assignment → Updated GroupState (permissions changed)
448/// ↓
449/// Group Update → Updated GroupState (metadata modified)
450/// ```
451///
452/// Each event is applied via [`GroupState::apply_event`], ensuring consistency
453/// and providing an audit trail through [`GroupState::event_history`].
454///
455/// ## 🔐 Security and Access Control
456///
457/// ### Encryption-Based Membership
458/// - Anyone with the group's encryption key can participate
459/// - [`GroupState::members`] tracks known active participants, not access control
460/// - True access control is enforced by possession of the encryption key
461///
462/// ### Role-Based Permissions
463/// - Each member has a [`super::events::roles::GroupRole`] defining their capabilities
464/// - Permissions are checked via [`GroupState::check_permission`]
465/// - Role assignments are cryptographically signed and part of the event history
466///
467/// ### Identity Privacy
468/// - Members can use aliases within groups via [`GroupMembership`]
469/// - Display names can be set independently of cryptographic identities
470/// - Multiple aliases per [`zoe_wire_protocol::VerifyingKey`] are supported
471///
472/// ## 📊 Member Lifecycle
473///
474/// 1. **Discovery**: A user obtains the group encryption key through some secure channel
475/// 2. **Announcement**: User sends any [`super::events::GroupActivityEvent`] to announce participation
476/// 3. **Recognition**: Internal handling adds them to active member list
477/// 4. **Activity**: Member's [`GroupMember::last_active`] is updated with each message
478/// 5. **Departure**: [`super::events::GroupActivityEvent::LeaveGroup`] removes from active list
479///
480/// Note: Departure only removes from the active member tracking - the user still
481/// possesses the encryption key and could rejoin at any time.
482///
483/// ## 🏷️ Structured Metadata System
484///
485/// Metadata is stored as [`crate::Metadata`] variants rather than simple key-value pairs:
486/// - [`crate::Metadata::Description`]: Human-readable group description
487/// - [`crate::Metadata::Generic`]: Key-value pairs for backward compatibility
488/// - Future variants can add typed metadata (images, files, etc.)
489///
490/// Use [`GroupState::description()`] and [`GroupState::generic_metadata()`] for
491/// convenient access to common metadata patterns.
492///
493/// ## 🔗 Relationship to GroupInfo
494///
495/// [`super::events::GroupInfo`] is used for events (creation, updates) while
496/// `GroupState` represents the current runtime state:
497///
498/// ```text
499/// GroupInfo (in events) → GroupState (runtime) → GroupInfo (for updates)
500/// ```
501///
502/// Use [`GroupState::from_group_info`] and [`GroupState::to_group_info`] to
503/// convert between representations.
504///
505/// ## 💡 Usage Examples
506///
507/// ### Creating a Group State
508/// ```rust
509/// use zoe_app_primitives::{GroupState, GroupSettings, Metadata};
510/// use zoe_wire_protocol::KeyPair;
511/// use blake3::Hash;
512///
513/// let creator_key = KeyPair::generate(&mut rand::rngs::OsRng);
514/// let group_id = Hash::from([1u8; 32]);
515///
516/// let metadata = vec![
517/// Metadata::Description("Development team coordination".to_string()),
518/// Metadata::Generic { key: "department".to_string(), value: "engineering".to_string() },
519/// ];
520///
521/// let group_state = GroupState::new(
522/// group_id,
523/// "Dev Team".to_string(),
524/// GroupSettings::default(),
525/// metadata,
526/// creator_key.public_key(),
527/// 1234567890,
528/// );
529///
530/// // Creator is automatically added as Owner
531/// assert_eq!(group_state.members.len(), 1);
532/// assert!(group_state.is_member(&creator_key.public_key()));
533/// ```
534///
535/// ### Processing Member Activity
536/// ```rust
537/// # use zoe_app_primitives::*;
538/// # use zoe_wire_protocol::KeyPair;
539/// # use blake3::Hash;
540/// # let mut group_state = GroupState::new(
541/// # Hash::from([1u8; 32]), "Test".to_string(), GroupSettings::default(),
542/// # vec![], KeyPair::generate(&mut rand::rngs::OsRng).public_key(), 1234567890
543/// # );
544///
545/// let new_member = KeyPair::generate(&mut rand::rngs::OsRng);
546/// let activity_event = GroupActivityEvent::Activity(());
547///
548/// // New member announces participation
549/// group_state.apply_event(
550/// &activity_event,
551/// Hash::from([2u8; 32]),
552/// new_member.public_key(),
553/// 1234567891,
554/// ).unwrap();
555///
556/// // They're now tracked as an active member
557/// assert!(group_state.is_member(&new_member.public_key()));
558/// ```
559///
560/// ### Working with Metadata
561/// ```rust
562/// # use zoe_app_primitives::*;
563/// # use zoe_wire_protocol::KeyPair;
564/// # use blake3::Hash;
565/// # let group_state = GroupState::new(
566/// # Hash::from([1u8; 32]), "Test".to_string(), GroupSettings::default(),
567/// # vec![Metadata::Description("Test group".to_string())],
568/// # KeyPair::generate(&mut rand::rngs::OsRng).public_key(), 1234567890
569/// # );
570///
571/// // Extract specific metadata types
572/// assert_eq!(group_state.description(), Some("Test group".to_string()));
573///
574/// // Get all generic metadata as a map
575/// let generic_meta = group_state.generic_metadata();
576/// ```
577#[derive(Debug, Clone, Serialize, Deserialize)]
578#[cfg_attr(feature = "frb-api", frb(opaque))]
579pub struct GroupState {
580 /// The group identifier - this is the Blake3 hash of the CreateGroup message
581 /// Also serves as the root event ID (used as channel tag)
582 pub group_id: MessageId,
583
584 /// Current group name
585 pub name: String,
586
587 /// Current group settings
588 pub settings: GroupSettings,
589
590 /// Group metadata as structured types
591 pub metadata: Vec<Metadata>,
592
593 /// Runtime member state with roles and activity tracking
594 /// Keys are ML-DSA verifying keys encoded as bytes for serialization compatibility
595 pub members: BTreeMap<IdentityRef, GroupMember>,
596
597 /// Advanced identity management for aliases and display names
598 pub membership: GroupMembership,
599
600 /// Event history for this group (event ID -> event details)
601 pub event_history: Vec<MessageId>,
602
603 /// Last processed event timestamp (for ordering)
604 pub last_event_timestamp: u64,
605
606 /// State version (incremented on each event)
607 pub version: u64,
608}
609
610impl GroupState {
611 /// Create a new group state from a group creation event.
612 ///
613 /// This constructor sets up the initial state for a newly created group, including:
614 /// - Setting the creator as the first member with [`GroupRole::Owner`] role
615 /// - Initializing empty membership state for identity management
616 /// - Recording the group creation as the first event in history
617 /// - Setting initial timestamps and version number
618 ///
619 /// # Arguments
620 ///
621 /// * `group_id` - Blake3 hash of the group creation message (also serves as root event ID)
622 /// * `name` - Human-readable group name
623 /// * `settings` - Group configuration and permissions
624 /// * `metadata` - Structured metadata using [`crate::Metadata`] types
625 /// * `creator` - Public key of the group creator (becomes first Owner)
626 /// * `timestamp` - Unix timestamp of group creation
627 ///
628 /// # Returns
629 ///
630 /// A new `GroupState` with the creator as the sole member and owner.
631 ///
632 /// # Examples
633 ///
634 /// ```rust
635 /// use zoe_app_primitives::{GroupState, GroupSettings, Metadata, events::roles::GroupRole};
636 /// use zoe_wire_protocol::KeyPair;
637 /// use blake3::Hash;
638 ///
639 /// let creator_key = KeyPair::generate(&mut rand::rngs::OsRng);
640 /// let group_id = Hash::from([42u8; 32]);
641 ///
642 /// let metadata = vec![
643 /// Metadata::Description("Team coordination space".to_string()),
644 /// Metadata::Generic { key: "project".to_string(), value: "zoe-chat".to_string() },
645 /// ];
646 ///
647 /// let group_state = GroupState::new(
648 /// group_id,
649 /// "Engineering Team".to_string(),
650 /// GroupSettings::default(),
651 /// metadata,
652 /// creator_key.public_key(),
653 /// 1640995200, // 2022-01-01 00:00:00 UTC
654 /// );
655 ///
656 /// // Verify initial state
657 /// assert_eq!(group_state.name, "Engineering Team");
658 /// assert_eq!(group_state.members.len(), 1);
659 /// assert_eq!(group_state.version, 1);
660 /// assert!(group_state.is_member(&creator_key.public_key()));
661 /// assert_eq!(
662 /// group_state.member_role(&creator_key.public_key()),
663 /// Some(&GroupRole::Owner)
664 /// );
665 /// ```
666 pub fn new(
667 group_id: MessageId,
668 name: String,
669 settings: GroupSettings,
670 metadata: Vec<Metadata>,
671 creator: VerifyingKey,
672 timestamp: u64,
673 ) -> Self {
674 let creator_ref = IdentityRef::Key(creator.clone());
675 let mut members = BTreeMap::new();
676 members.insert(
677 creator_ref.clone(),
678 GroupMember {
679 key: creator_ref.clone(),
680 role: GroupRole::Owner,
681 joined_at: timestamp,
682 last_active: timestamp,
683 metadata: vec![],
684 },
685 );
686
687 Self {
688 group_id,
689 name,
690 settings,
691 metadata,
692 members,
693 membership: GroupMembership::new(),
694 event_history: vec![group_id], // First event is the group creation
695 last_event_timestamp: timestamp,
696 version: 1,
697 }
698 }
699
700 /// Create a GroupState from existing GroupInfo (for compatibility)
701 pub fn from_group_info(
702 group_id: MessageId,
703 group_info: &super::events::GroupInfo,
704 creator: VerifyingKey,
705 timestamp: u64,
706 ) -> Self {
707 Self::new(
708 group_id,
709 group_info.name.clone(),
710 group_info.settings.clone(),
711 group_info.metadata.clone(),
712 creator,
713 timestamp,
714 )
715 }
716
717 /// Convert to GroupInfo for events (extracts the core group information)
718 pub fn to_group_info(&self, key_info: super::events::GroupKeyInfo) -> super::events::GroupInfo {
719 super::events::GroupInfo {
720 name: self.name.clone(),
721 settings: self.settings.clone(),
722 key_info,
723 metadata: self.metadata.clone(),
724 }
725 }
726
727 /// Apply an event to this group state, updating it according to event-sourced principles.
728 ///
729 /// This is the core method for updating group state. All state changes must go through
730 /// this method to ensure consistency, proper ordering, and audit trail maintenance.
731 /// Events are applied in chronological order to maintain deterministic state.
732 ///
733 /// # Event Processing
734 ///
735 /// The method handles several types of events:
736 /// - **Member Activity**: Any activity announces participation and updates last_active
737 /// - **Role Changes**: Updates member roles and permissions
738 /// - **Group Updates**: Modifies name, settings, and metadata
739 /// - **Member Departure**: Removes members from active tracking
740 /// - **Identity Management**: Processes identity declarations and updates
741 ///
742 /// # Ordering and Consistency
743 ///
744 /// Events must be applied in timestamp order. The method will reject events with
745 /// timestamps older than the last processed event to maintain consistency across
746 /// all group participants.
747 ///
748 /// # Arguments
749 ///
750 /// * `event` - The group activity event to process
751 /// * `event_id` - Blake3 hash of the event message (for audit trail)
752 /// * `sender` - Public key of the event sender (for authorization)
753 /// * `timestamp` - Unix timestamp of the event (for ordering)
754 ///
755 /// # Returns
756 ///
757 /// `Ok(())` if the event was successfully applied, or [`GroupStateError`] if:
758 /// - Event timestamp is out of order
759 /// - Sender lacks required permissions
760 /// - Member is not found for role operations
761 /// - Other validation failures
762 ///
763 /// # Examples
764 ///
765 /// ```rust
766 /// use zoe_app_primitives::{GroupState, GroupActivityEvent, GroupSettings, Metadata};
767 /// use zoe_wire_protocol::KeyPair;
768 /// use blake3::Hash;
769 ///
770 /// let creator_key = KeyPair::generate(&mut rand::rngs::OsRng);
771 /// let new_member_key = KeyPair::generate(&mut rand::rngs::OsRng);
772 ///
773 /// let mut group_state = GroupState::new(
774 /// Hash::from([1u8; 32]),
775 /// "Test Group".to_string(),
776 /// GroupSettings::default(),
777 /// vec![],
778 /// creator_key.public_key(),
779 /// 1000,
780 /// );
781 ///
782 /// // New member announces participation via activity
783 /// let activity_event = GroupActivityEvent::Activity(());
784 /// let event_id = Hash::from([2u8; 32]);
785 ///
786 /// group_state.apply_event(
787 /// &activity_event,
788 /// event_id,
789 /// new_member_key.public_key(),
790 /// 1001, // Must be after creation timestamp
791 /// ).unwrap();
792 ///
793 /// // Member is now tracked in the group
794 /// assert!(group_state.is_member(&new_member_key.public_key()));
795 /// assert_eq!(group_state.members.len(), 2); // Creator + new member
796 /// assert_eq!(group_state.version, 2); // Version incremented
797 /// assert_eq!(group_state.event_history.len(), 2); // Event recorded
798 /// ```
799 ///
800 /// # State Transitions
801 ///
802 /// After each successful event application:
803 /// - [`GroupState::version`] is incremented
804 /// - [`GroupState::last_event_timestamp`] is updated
805 /// - [`GroupState::event_history`] includes the new event ID
806 /// - Specific state changes depend on the event type
807 pub fn apply_event<T>(
808 &mut self,
809 event: &GroupActivityEvent<T>,
810 event_id: MessageId,
811 sender: VerifyingKey,
812 timestamp: u64,
813 ) -> GroupStateResult<()> {
814 // Verify timestamp ordering (events should be processed in order)
815 if timestamp < self.last_event_timestamp {
816 return Err(GroupStateError::StateTransition(format!(
817 "Event timestamp {} is older than last processed timestamp {}",
818 timestamp, self.last_event_timestamp
819 )));
820 }
821
822 // Apply the specific event
823 match event {
824 GroupActivityEvent::LeaveGroup { message } => {
825 self.handle_leave_group(sender, message.clone(), timestamp)?;
826 }
827
828 GroupActivityEvent::UpdateGroup(group_info) => {
829 // Handle group updates
830 self.name = group_info.name.clone();
831 self.settings = group_info.settings.clone();
832 self.metadata = group_info.metadata.clone();
833 }
834
835 GroupActivityEvent::AssignRole { target, role } => {
836 self.handle_role_assignment(sender, target, role, timestamp)?;
837 }
838
839 GroupActivityEvent::SetIdentity(_) => {
840 // Handle identity setting - for now just ensure sender is a member
841 self.handle_member_announcement(sender, timestamp)?;
842 }
843
844 GroupActivityEvent::RemoveFromGroup { target: _ } => {
845 // For now, skip member removal for Ed25519-based identities when sender is ML-DSA
846 // This is a temporary compatibility limitation during the transition
847 // TODO: Implement proper key type conversion or dual-key support
848 // Note: Skipping member removal due to key type mismatch during ML-DSA transition
849 }
850
851 GroupActivityEvent::Unknown { discriminant, .. } => {
852 // Unknown management event - ignore for forward compatibility
853 // Future implementations could log this with: discriminant value {discriminant}
854 let _ = discriminant; // Acknowledge the discriminant without warning
855 }
856
857 GroupActivityEvent::Activity(_activity_data) => {
858 // Handle custom activity
859 self.handle_member_announcement(sender, timestamp)?;
860 }
861 }
862
863 // Update state metadata
864 self.event_history.push(event_id);
865 self.last_event_timestamp = timestamp;
866 self.version += 1;
867
868 Ok(())
869 }
870
871 /// Check if a member has permission to perform an action
872 pub fn check_permission(
873 &self,
874 member: &VerifyingKey,
875 required_permission: &Permission,
876 ) -> GroupStateResult<()> {
877 let member_ref = IdentityRef::Key(member.clone());
878 match self.members.get(&member_ref) {
879 Some(member_info) => {
880 if member_info.role.has_permission(required_permission) {
881 Ok(())
882 } else {
883 Err(GroupStateError::PermissionDenied(format!(
884 "Member {:?} with role {:?} does not have required permission {:?}",
885 member, member_info.role, required_permission
886 )))
887 }
888 }
889 None => Err(GroupStateError::MemberNotFound {
890 member: format!("{member:?}"),
891 group: format!("{:?}", self.group_id),
892 }),
893 }
894 }
895
896 /// Handle a member announcing their participation in the group
897 /// In encrypted groups, anyone with the key can participate
898 fn handle_member_announcement(
899 &mut self,
900 sender: VerifyingKey,
901 timestamp: u64,
902 ) -> GroupStateResult<()> {
903 let sender_ref = IdentityRef::Key(sender.clone());
904 // Add or update member
905 if let Some(existing_member) = self.members.get_mut(&sender_ref) {
906 existing_member.last_active = timestamp;
907 } else {
908 // New member - anyone with the key can participate
909 self.members.insert(
910 sender_ref.clone(),
911 GroupMember {
912 key: sender_ref.clone(),
913 role: GroupRole::Member, // Default role for new key holders
914 joined_at: timestamp,
915 last_active: timestamp,
916 metadata: vec![],
917 },
918 );
919 }
920
921 Ok(())
922 }
923
924 fn handle_leave_group(
925 &mut self,
926 sender: VerifyingKey,
927 _message: Option<String>,
928 _timestamp: u64,
929 ) -> GroupStateResult<()> {
930 let sender_ref = IdentityRef::Key(sender.clone());
931 // In encrypted groups, leaving is just an announcement - they still have the key
932 // This removes them from the active member list but doesn't revoke access
933 if !self.members.contains_key(&sender_ref) {
934 return Err(GroupStateError::MemberNotFound {
935 member: format!("{sender:?}"),
936 group: format!("{:?}", self.group_id),
937 });
938 }
939
940 // Remove from active members list
941 self.members.remove(&sender_ref);
942 Ok(())
943 }
944
945 /// Handle role assignment using IdentityRef
946 fn handle_role_assignment(
947 &mut self,
948 sender: VerifyingKey,
949 target: &IdentityRef,
950 role: &GroupRole,
951 _timestamp: u64,
952 ) -> GroupStateResult<()> {
953 // Check permission - sender must have permission to assign roles
954 self.check_permission(&sender, &self.settings.permissions.assign_roles)?;
955
956 // Check if target member exists
957 let member_info =
958 self.members
959 .get_mut(target)
960 .ok_or_else(|| GroupStateError::MemberNotFound {
961 member: format!("{target:?}"),
962 group: format!("{:?}", self.group_id),
963 })?;
964
965 // Update role
966 member_info.role = role.clone();
967 Ok(())
968 }
969
970 #[allow(dead_code)]
971 fn handle_update_member_role(
972 &mut self,
973 sender: VerifyingKey,
974 member: VerifyingKey,
975 role: GroupRole,
976 ) -> GroupStateResult<()> {
977 // Check permission
978 self.check_permission(&sender, &self.settings.permissions.assign_roles)?;
979
980 let member_ref = IdentityRef::Key(member.clone());
981 // Check if target member exists
982 let member_info =
983 self.members
984 .get_mut(&member_ref)
985 .ok_or_else(|| GroupStateError::MemberNotFound {
986 member: format!("{member:?}"),
987 group: format!("{:?}", self.group_id),
988 })?;
989
990 // Update role
991 member_info.role = role;
992 Ok(())
993 }
994
995 /// Get all active members
996 pub fn get_members(&self) -> &BTreeMap<IdentityRef, GroupMember> {
997 &self.members
998 }
999
1000 /// Check if a user is a member of this group
1001 pub fn is_member(&self, user: &VerifyingKey) -> bool {
1002 let user_ref = IdentityRef::Key(user.clone());
1003 self.members.contains_key(&user_ref)
1004 }
1005
1006 /// Get a member's role
1007 pub fn member_role(&self, user: &VerifyingKey) -> Option<&GroupRole> {
1008 let user_ref = IdentityRef::Key(user.clone());
1009 self.members.get(&user_ref).map(|m| &m.role)
1010 }
1011
1012 /// Extract the group description from structured metadata.
1013 ///
1014 /// This method searches through the structured [`crate::Metadata`] collection
1015 /// to find a [`crate::Metadata::Description`] variant and returns its value.
1016 /// This provides a convenient way to access the primary descriptive text
1017 /// for the group.
1018 ///
1019 /// # Returns
1020 ///
1021 /// `Some(description)` if a description metadata entry exists, `None` otherwise.
1022 ///
1023 /// # Examples
1024 ///
1025 /// ```rust
1026 /// use zoe_app_primitives::{GroupState, GroupSettings, Metadata};
1027 /// use zoe_wire_protocol::KeyPair;
1028 /// use blake3::Hash;
1029 ///
1030 /// let creator_key = KeyPair::generate(&mut rand::rngs::OsRng);
1031 ///
1032 /// // Group with description
1033 /// let metadata_with_desc = vec![
1034 /// Metadata::Description("A team coordination space".to_string()),
1035 /// Metadata::Generic { key: "category".to_string(), value: "work".to_string() },
1036 /// ];
1037 ///
1038 /// let group_state = GroupState::new(
1039 /// Hash::from([1u8; 32]),
1040 /// "Team Chat".to_string(),
1041 /// GroupSettings::default(),
1042 /// metadata_with_desc,
1043 /// creator_key.public_key(),
1044 /// 1000,
1045 /// );
1046 ///
1047 /// assert_eq!(
1048 /// group_state.description(),
1049 /// Some("A team coordination space".to_string())
1050 /// );
1051 ///
1052 /// // Group without description
1053 /// let metadata_no_desc = vec![
1054 /// Metadata::Generic { key: "category".to_string(), value: "work".to_string() },
1055 /// ];
1056 ///
1057 /// let group_state_no_desc = GroupState::new(
1058 /// Hash::from([2u8; 32]),
1059 /// "Another Group".to_string(),
1060 /// GroupSettings::default(),
1061 /// metadata_no_desc,
1062 /// creator_key.public_key(),
1063 /// 1000,
1064 /// );
1065 ///
1066 /// assert_eq!(group_state_no_desc.description(), None);
1067 /// ```
1068 pub fn description(&self) -> Option<String> {
1069 self.metadata.iter().find_map(|m| match m {
1070 Metadata::Description(desc) => Some(desc.clone()),
1071 _ => None,
1072 })
1073 }
1074
1075 /// Extract generic key-value metadata as a BTreeMap for backward compatibility.
1076 ///
1077 /// This method filters the structured [`crate::Metadata`] collection to extract
1078 /// only the [`crate::Metadata::Generic`] variants and returns them as a
1079 /// [`std::collections::BTreeMap`]. This provides compatibility with code that
1080 /// expects simple key-value metadata storage.
1081 ///
1082 /// # Structured vs Generic Metadata
1083 ///
1084 /// The group system supports both structured metadata (typed variants like
1085 /// [`crate::Metadata::Description`]) and generic key-value pairs. This method
1086 /// extracts only the generic pairs, ignoring other metadata types.
1087 ///
1088 /// # Returns
1089 ///
1090 /// A [`std::collections::BTreeMap`] containing all generic metadata key-value pairs.
1091 /// The map will be empty if no generic metadata exists.
1092 ///
1093 /// # Examples
1094 ///
1095 /// ```rust
1096 /// use zoe_app_primitives::{GroupState, GroupSettings, Metadata};
1097 /// use zoe_wire_protocol::KeyPair;
1098 /// use blake3::Hash;
1099 ///
1100 /// let creator_key = KeyPair::generate(&mut rand::rngs::OsRng);
1101 ///
1102 /// let metadata = vec![
1103 /// Metadata::Description("Team workspace".to_string()), // Not included in generic
1104 /// Metadata::Generic { key: "department".to_string(), value: "engineering".to_string() },
1105 /// Metadata::Generic { key: "project".to_string(), value: "zoe-chat".to_string() },
1106 /// Metadata::Generic { key: "visibility".to_string(), value: "internal".to_string() },
1107 /// ];
1108 ///
1109 /// let group_state = GroupState::new(
1110 /// Hash::from([1u8; 32]),
1111 /// "Engineering Team".to_string(),
1112 /// GroupSettings::default(),
1113 /// metadata,
1114 /// creator_key.public_key(),
1115 /// 1000,
1116 /// );
1117 ///
1118 /// let generic_meta = group_state.generic_metadata();
1119 ///
1120 /// // Only generic metadata is included (3 items, description excluded)
1121 /// assert_eq!(generic_meta.len(), 3);
1122 /// assert_eq!(generic_meta.get("department"), Some(&"engineering".to_string()));
1123 /// assert_eq!(generic_meta.get("project"), Some(&"zoe-chat".to_string()));
1124 /// assert_eq!(generic_meta.get("visibility"), Some(&"internal".to_string()));
1125 ///
1126 /// // Description is not in generic metadata
1127 /// assert!(!generic_meta.contains_key("description"));
1128 ///
1129 /// // But it's still accessible via the description() method
1130 /// assert_eq!(
1131 /// group_state.description(),
1132 /// Some("Team workspace".to_string())
1133 /// );
1134 /// ```
1135 ///
1136 /// # Use Cases
1137 ///
1138 /// This method is particularly useful for:
1139 /// - **Legacy Code Integration**: Existing code expecting simple key-value metadata
1140 /// - **Generic Queries**: Searching through all key-value pairs programmatically
1141 /// - **Serialization**: Converting to formats that don't support structured metadata
1142 /// - **Configuration**: Accessing arbitrary configuration key-value pairs
1143 pub fn generic_metadata(&self) -> BTreeMap<String, String> {
1144 let mut map = BTreeMap::new();
1145 for meta in &self.metadata {
1146 if let Metadata::Generic { key, value } = meta {
1147 map.insert(key.clone(), value.clone());
1148 }
1149 }
1150 map
1151 }
1152}
1153
1154#[cfg(test)]
1155mod tests {
1156 use super::*;
1157 use crate::group::events::roles::GroupRole;
1158 use crate::group::events::{GroupActivityEvent, GroupInfo, GroupKeyInfo, GroupSettings};
1159 use crate::{IdentityInfo, IdentityType, Metadata, Permission};
1160
1161 use rand::rngs::OsRng;
1162 use zoe_wire_protocol::{KeyPair, VerifyingKey};
1163
1164 // Helper functions for creating test data
1165 fn create_test_verifying_key() -> VerifyingKey {
1166 let keypair = KeyPair::generate(&mut OsRng);
1167 keypair.public_key()
1168 }
1169
1170 fn create_test_message_id(seed: u8) -> MessageId {
1171 MessageId::from_bytes([seed; 32])
1172 }
1173
1174 fn create_test_group_key_info() -> GroupKeyInfo {
1175 GroupKeyInfo::new_chacha20_poly1305(
1176 vec![1, 2, 3, 4],
1177 zoe_wire_protocol::crypto::KeyDerivationInfo {
1178 method: zoe_wire_protocol::crypto::KeyDerivationMethod::Bip39Argon2,
1179 salt: vec![1, 2, 3, 4, 5, 6, 7, 8],
1180 argon2_params: zoe_wire_protocol::crypto::Argon2Params::default(),
1181 context: "test-group-key".to_string(),
1182 },
1183 )
1184 }
1185
1186 fn create_test_group_info() -> GroupInfo {
1187 GroupInfo {
1188 name: "Test Group".to_string(),
1189 settings: GroupSettings::default(),
1190 key_info: create_test_group_key_info(),
1191 metadata: vec![
1192 Metadata::Description("Test group description".to_string()),
1193 Metadata::Generic {
1194 key: "category".to_string(),
1195 value: "test".to_string(),
1196 },
1197 ],
1198 }
1199 }
1200
1201 // GroupMembership Tests
1202 #[test]
1203 fn test_group_membership_new() {
1204 let membership = GroupMembership::new();
1205 assert!(membership.identity_info.is_empty());
1206 assert!(membership.identity_roles.is_empty());
1207 }
1208
1209 #[test]
1210 fn test_group_membership_default() {
1211 let membership = GroupMembership::default();
1212 assert!(membership.identity_info.is_empty());
1213 assert!(membership.identity_roles.is_empty());
1214 }
1215
1216 #[test]
1217 fn test_group_membership_is_authorized() {
1218 let membership = GroupMembership::new();
1219 let key = create_test_verifying_key();
1220
1221 // Test authorization for key identity
1222 let key_identity = IdentityRef::Key(key.clone());
1223 assert!(membership.is_authorized(&key, &key_identity));
1224
1225 // Test authorization for alias identity
1226 let alias_identity = IdentityRef::Alias {
1227 key: key.clone(),
1228 alias: "test_alias".to_string(),
1229 };
1230 assert!(membership.is_authorized(&key, &alias_identity));
1231
1232 // Test authorization failure for different key
1233 let other_key = create_test_verifying_key();
1234 let other_identity = IdentityRef::Key(other_key);
1235 assert!(!membership.is_authorized(&key, &other_identity));
1236 }
1237
1238 #[test]
1239 fn test_group_membership_get_role() {
1240 let mut membership = GroupMembership::new();
1241 let key = create_test_verifying_key();
1242 let identity = IdentityRef::Key(key);
1243
1244 // Test default role
1245 assert_eq!(membership.get_role(&identity), Some(GroupRole::Member));
1246
1247 // Test explicit role assignment
1248 membership
1249 .identity_roles
1250 .insert(identity.clone(), GroupRole::Admin);
1251 assert_eq!(membership.get_role(&identity), Some(GroupRole::Admin));
1252 }
1253
1254 #[test]
1255 fn test_group_membership_get_available_identities() {
1256 let membership = GroupMembership::new();
1257 let key = create_test_verifying_key();
1258
1259 // Currently returns empty set due to ML-DSA transition
1260 let identities = membership.get_available_identities(&key);
1261 assert!(identities.is_empty());
1262 }
1263
1264 #[test]
1265 fn test_group_membership_get_effective_role() {
1266 let membership = GroupMembership::new();
1267 let key = create_test_verifying_key();
1268
1269 // Currently returns default member role due to ML-DSA transition
1270 let role = membership.get_effective_role(&key, &None);
1271 assert_eq!(role, Some(GroupRole::Member));
1272
1273 let role_with_alias = membership.get_effective_role(&key, &Some("alias".to_string()));
1274 assert_eq!(role_with_alias, Some(GroupRole::Member));
1275 }
1276
1277 #[test]
1278 fn test_group_membership_get_display_name() {
1279 let membership = GroupMembership::new();
1280 let key = create_test_verifying_key();
1281
1282 // Test main identity display name
1283 let main_type = IdentityType::Main;
1284 let display_name = membership.get_display_name(&key, &main_type);
1285 assert!(display_name.starts_with("ML-DSA Key:"));
1286
1287 // Test alias identity display name
1288 let alias_type = IdentityType::Alias {
1289 alias_id: "test_alias".to_string(),
1290 };
1291 let alias_display_name = membership.get_display_name(&key, &alias_type);
1292 assert_eq!(alias_display_name, "test_alias");
1293 }
1294
1295 #[test]
1296 fn test_group_membership_has_identity_info() {
1297 let membership = GroupMembership::new();
1298 let key = create_test_verifying_key();
1299 let identity_type = IdentityType::Main;
1300
1301 // Currently returns false due to ML-DSA transition
1302 assert!(!membership.has_identity_info(&key, &identity_type));
1303 }
1304
1305 // GroupMember Tests
1306 #[test]
1307 fn test_group_member_creation() {
1308 let key = create_test_verifying_key();
1309 let identity_ref = IdentityRef::Key(key);
1310 let timestamp = 1234567890;
1311
1312 let member = GroupMember {
1313 key: identity_ref.clone(),
1314 role: GroupRole::Member,
1315 joined_at: timestamp,
1316 last_active: timestamp,
1317 metadata: vec![],
1318 };
1319
1320 assert_eq!(member.key, identity_ref);
1321 assert_eq!(member.role, GroupRole::Member);
1322 assert_eq!(member.joined_at, timestamp);
1323 assert_eq!(member.last_active, timestamp);
1324 assert!(member.metadata.is_empty());
1325 }
1326
1327 #[test]
1328 fn test_group_member_with_metadata() {
1329 let key = create_test_verifying_key();
1330 let identity_ref = IdentityRef::Key(key);
1331
1332 let metadata = vec![
1333 Metadata::Generic {
1334 key: "department".to_string(),
1335 value: "engineering".to_string(),
1336 },
1337 Metadata::Email("user@example.com".to_string()),
1338 ];
1339
1340 let member = GroupMember {
1341 key: identity_ref,
1342 role: GroupRole::Admin,
1343 joined_at: 1000,
1344 last_active: 2000,
1345 metadata: metadata.clone(),
1346 };
1347
1348 assert_eq!(member.metadata, metadata);
1349 assert_eq!(member.role, GroupRole::Admin);
1350 }
1351
1352 // GroupState Tests
1353 #[test]
1354 fn test_group_state_new() {
1355 let creator_key = create_test_verifying_key();
1356 let group_id = create_test_message_id(1);
1357 let timestamp = 1234567890;
1358
1359 let metadata = vec![
1360 Metadata::Description("Test group".to_string()),
1361 Metadata::Generic {
1362 key: "category".to_string(),
1363 value: "test".to_string(),
1364 },
1365 ];
1366
1367 let group_state = GroupState::new(
1368 group_id,
1369 "Test Group".to_string(),
1370 GroupSettings::default(),
1371 metadata.clone(),
1372 creator_key.clone(),
1373 timestamp,
1374 );
1375
1376 assert_eq!(group_state.group_id, group_id);
1377 assert_eq!(group_state.name, "Test Group");
1378 assert_eq!(group_state.metadata, metadata);
1379 assert_eq!(group_state.members.len(), 1);
1380 assert_eq!(group_state.version, 1);
1381 assert_eq!(group_state.last_event_timestamp, timestamp);
1382 assert_eq!(group_state.event_history.len(), 1);
1383 assert_eq!(group_state.event_history[0], group_id);
1384
1385 // Verify creator is added as owner
1386 assert!(group_state.is_member(&creator_key));
1387 assert_eq!(
1388 group_state.member_role(&creator_key),
1389 Some(&GroupRole::Owner)
1390 );
1391 }
1392
1393 #[test]
1394 fn test_group_state_from_group_info() {
1395 let creator_key = create_test_verifying_key();
1396 let group_id = create_test_message_id(2);
1397 let group_info = create_test_group_info();
1398 let timestamp = 1234567890;
1399
1400 let group_state =
1401 GroupState::from_group_info(group_id, &group_info, creator_key.clone(), timestamp);
1402
1403 assert_eq!(group_state.group_id, group_id);
1404 assert_eq!(group_state.name, group_info.name);
1405 assert_eq!(group_state.settings, group_info.settings);
1406 assert_eq!(group_state.metadata, group_info.metadata);
1407 assert!(group_state.is_member(&creator_key));
1408 }
1409
1410 #[test]
1411 fn test_group_state_to_group_info() {
1412 let creator_key = create_test_verifying_key();
1413 let group_id = create_test_message_id(3);
1414 let group_state = GroupState::new(
1415 group_id,
1416 "Test Group".to_string(),
1417 GroupSettings::default(),
1418 vec![],
1419 creator_key,
1420 1000,
1421 );
1422
1423 let key_info = create_test_group_key_info();
1424 let group_info = group_state.to_group_info(key_info.clone());
1425
1426 assert_eq!(group_info.name, group_state.name);
1427 assert_eq!(group_info.settings, group_state.settings);
1428 assert_eq!(group_info.key_info, key_info);
1429 assert_eq!(group_info.metadata, group_state.metadata);
1430 }
1431
1432 #[test]
1433 fn test_group_state_is_member() {
1434 let creator_key = create_test_verifying_key();
1435 let other_key = create_test_verifying_key();
1436 let group_state = GroupState::new(
1437 create_test_message_id(4),
1438 "Test".to_string(),
1439 GroupSettings::default(),
1440 vec![],
1441 creator_key.clone(),
1442 1000,
1443 );
1444
1445 assert!(group_state.is_member(&creator_key));
1446 assert!(!group_state.is_member(&other_key));
1447 }
1448
1449 #[test]
1450 fn test_group_state_member_role() {
1451 let creator_key = create_test_verifying_key();
1452 let group_state = GroupState::new(
1453 create_test_message_id(5),
1454 "Test".to_string(),
1455 GroupSettings::default(),
1456 vec![],
1457 creator_key.clone(),
1458 1000,
1459 );
1460
1461 assert_eq!(
1462 group_state.member_role(&creator_key),
1463 Some(&GroupRole::Owner)
1464 );
1465
1466 let non_member_key = create_test_verifying_key();
1467 assert_eq!(group_state.member_role(&non_member_key), None);
1468 }
1469
1470 #[test]
1471 fn test_group_state_get_members() {
1472 let creator_key = create_test_verifying_key();
1473 let group_state = GroupState::new(
1474 create_test_message_id(6),
1475 "Test".to_string(),
1476 GroupSettings::default(),
1477 vec![],
1478 creator_key.clone(),
1479 1000,
1480 );
1481
1482 let members = group_state.get_members();
1483 assert_eq!(members.len(), 1);
1484
1485 let creator_ref = IdentityRef::Key(creator_key);
1486 assert!(members.contains_key(&creator_ref));
1487 }
1488
1489 #[test]
1490 fn test_group_state_description() {
1491 let creator_key = create_test_verifying_key();
1492
1493 // Test with description
1494 let metadata_with_desc = vec![
1495 Metadata::Description("Test description".to_string()),
1496 Metadata::Generic {
1497 key: "other".to_string(),
1498 value: "value".to_string(),
1499 },
1500 ];
1501
1502 let group_state_with_desc = GroupState::new(
1503 create_test_message_id(7),
1504 "Test".to_string(),
1505 GroupSettings::default(),
1506 metadata_with_desc,
1507 creator_key.clone(),
1508 1000,
1509 );
1510
1511 assert_eq!(
1512 group_state_with_desc.description(),
1513 Some("Test description".to_string())
1514 );
1515
1516 // Test without description
1517 let metadata_no_desc = vec![Metadata::Generic {
1518 key: "other".to_string(),
1519 value: "value".to_string(),
1520 }];
1521
1522 let group_state_no_desc = GroupState::new(
1523 create_test_message_id(8),
1524 "Test".to_string(),
1525 GroupSettings::default(),
1526 metadata_no_desc,
1527 creator_key,
1528 1000,
1529 );
1530
1531 assert_eq!(group_state_no_desc.description(), None);
1532 }
1533
1534 #[test]
1535 fn test_group_state_generic_metadata() {
1536 let creator_key = create_test_verifying_key();
1537
1538 let metadata = vec![
1539 Metadata::Description("Not included".to_string()),
1540 Metadata::Generic {
1541 key: "category".to_string(),
1542 value: "test".to_string(),
1543 },
1544 Metadata::Generic {
1545 key: "priority".to_string(),
1546 value: "high".to_string(),
1547 },
1548 ];
1549
1550 let group_state = GroupState::new(
1551 create_test_message_id(9),
1552 "Test".to_string(),
1553 GroupSettings::default(),
1554 metadata,
1555 creator_key,
1556 1000,
1557 );
1558
1559 let generic_meta = group_state.generic_metadata();
1560 assert_eq!(generic_meta.len(), 2);
1561 assert_eq!(generic_meta.get("category"), Some(&"test".to_string()));
1562 assert_eq!(generic_meta.get("priority"), Some(&"high".to_string()));
1563 assert!(!generic_meta.contains_key("description"));
1564 }
1565
1566 // Event Processing Tests
1567 #[test]
1568 fn test_group_state_apply_activity_event() {
1569 let creator_key = create_test_verifying_key();
1570 let new_member_key = create_test_verifying_key();
1571 let mut group_state = GroupState::new(
1572 create_test_message_id(10),
1573 "Test".to_string(),
1574 GroupSettings::default(),
1575 vec![],
1576 creator_key,
1577 1000,
1578 );
1579
1580 let activity_event = GroupActivityEvent::Activity(());
1581 let event_id = create_test_message_id(11);
1582
1583 // New member announces participation
1584 let result =
1585 group_state.apply_event(&activity_event, event_id, new_member_key.clone(), 1001);
1586
1587 assert!(result.is_ok());
1588 assert!(group_state.is_member(&new_member_key));
1589 assert_eq!(group_state.members.len(), 2);
1590 assert_eq!(group_state.version, 2);
1591 assert_eq!(group_state.last_event_timestamp, 1001);
1592 assert_eq!(group_state.event_history.len(), 2);
1593 assert_eq!(group_state.event_history[1], event_id);
1594
1595 // Verify new member has default role
1596 assert_eq!(
1597 group_state.member_role(&new_member_key),
1598 Some(&GroupRole::Member)
1599 );
1600 }
1601
1602 #[test]
1603 fn test_group_state_apply_update_group_event() {
1604 let creator_key = create_test_verifying_key();
1605 let mut group_state = GroupState::new(
1606 create_test_message_id(12),
1607 "Original Name".to_string(),
1608 GroupSettings::default(),
1609 vec![],
1610 creator_key.clone(),
1611 1000,
1612 );
1613
1614 let new_group_info = GroupInfo {
1615 name: "Updated Name".to_string(),
1616 settings: GroupSettings::default(),
1617 key_info: create_test_group_key_info(),
1618 metadata: vec![Metadata::Description("Updated description".to_string())],
1619 };
1620
1621 let update_event: GroupActivityEvent<()> =
1622 GroupActivityEvent::UpdateGroup(new_group_info.clone());
1623 let event_id = create_test_message_id(13);
1624
1625 let result = group_state.apply_event(&update_event, event_id, creator_key.clone(), 1001);
1626
1627 assert!(result.is_ok());
1628 assert_eq!(group_state.name, "Updated Name");
1629 assert_eq!(group_state.settings, new_group_info.settings);
1630 assert_eq!(group_state.metadata, new_group_info.metadata);
1631 assert_eq!(group_state.version, 2);
1632 }
1633
1634 #[test]
1635 fn test_group_state_apply_leave_group_event() {
1636 let creator_key = create_test_verifying_key();
1637 let member_key = create_test_verifying_key();
1638 let mut group_state = GroupState::new(
1639 create_test_message_id(14),
1640 "Test".to_string(),
1641 GroupSettings::default(),
1642 vec![],
1643 creator_key,
1644 1000,
1645 );
1646
1647 // Add member first
1648 let activity_event = GroupActivityEvent::Activity(());
1649 group_state
1650 .apply_event(
1651 &activity_event,
1652 create_test_message_id(15),
1653 member_key.clone(),
1654 1001,
1655 )
1656 .unwrap();
1657
1658 assert!(group_state.is_member(&member_key));
1659 assert_eq!(group_state.members.len(), 2);
1660
1661 // Member leaves
1662 let leave_event: GroupActivityEvent<()> = GroupActivityEvent::LeaveGroup {
1663 message: Some("Goodbye".to_string()),
1664 };
1665 let result = group_state.apply_event(
1666 &leave_event,
1667 create_test_message_id(16),
1668 member_key.clone(),
1669 1002,
1670 );
1671
1672 assert!(result.is_ok());
1673 assert!(!group_state.is_member(&member_key));
1674 assert_eq!(group_state.members.len(), 1);
1675 assert_eq!(group_state.version, 3);
1676 }
1677
1678 #[test]
1679 fn test_group_state_apply_assign_role_event() {
1680 let creator_key = create_test_verifying_key();
1681 let member_key = create_test_verifying_key();
1682 let mut group_state = GroupState::new(
1683 create_test_message_id(17),
1684 "Test".to_string(),
1685 GroupSettings::default(),
1686 vec![],
1687 creator_key.clone(),
1688 1000,
1689 );
1690
1691 // Add member first
1692 let activity_event = GroupActivityEvent::Activity(());
1693 group_state
1694 .apply_event(
1695 &activity_event,
1696 create_test_message_id(18),
1697 member_key.clone(),
1698 1001,
1699 )
1700 .unwrap();
1701
1702 // Assign admin role
1703 let target_identity = IdentityRef::Key(member_key.clone());
1704 let assign_role_event: GroupActivityEvent<()> = GroupActivityEvent::AssignRole {
1705 target: target_identity,
1706 role: GroupRole::Admin,
1707 };
1708
1709 let result = group_state.apply_event(
1710 &assign_role_event,
1711 create_test_message_id(19),
1712 creator_key,
1713 1002,
1714 );
1715
1716 assert!(result.is_ok());
1717 assert_eq!(
1718 group_state.member_role(&member_key),
1719 Some(&GroupRole::Admin)
1720 );
1721 assert_eq!(group_state.version, 3);
1722 }
1723
1724 #[test]
1725 fn test_group_state_apply_event_timestamp_ordering() {
1726 let creator_key = create_test_verifying_key();
1727 let mut group_state = GroupState::new(
1728 create_test_message_id(20),
1729 "Test".to_string(),
1730 GroupSettings::default(),
1731 vec![],
1732 creator_key.clone(),
1733 1000,
1734 );
1735
1736 let activity_event = GroupActivityEvent::Activity(());
1737
1738 // Try to apply event with older timestamp
1739 let result = group_state.apply_event(
1740 &activity_event,
1741 create_test_message_id(21),
1742 creator_key,
1743 999, // Older than creation timestamp
1744 );
1745
1746 assert!(result.is_err());
1747 match result.unwrap_err() {
1748 GroupStateError::StateTransition(msg) => {
1749 assert!(msg.contains("older than last processed timestamp"));
1750 }
1751 _ => panic!("Expected StateTransition error"),
1752 }
1753 }
1754
1755 // Permission Tests
1756 #[test]
1757 fn test_group_state_check_permission_success() {
1758 let creator_key = create_test_verifying_key();
1759 let group_state = GroupState::new(
1760 create_test_message_id(22),
1761 "Test".to_string(),
1762 GroupSettings::default(),
1763 vec![],
1764 creator_key.clone(),
1765 1000,
1766 );
1767
1768 // Owner should have all permissions
1769 let result = group_state.check_permission(&creator_key, &Permission::OwnerOnly);
1770 assert!(result.is_ok());
1771
1772 let result = group_state.check_permission(&creator_key, &Permission::AdminOrAbove);
1773 assert!(result.is_ok());
1774
1775 let result = group_state.check_permission(&creator_key, &Permission::AllMembers);
1776 assert!(result.is_ok());
1777 }
1778
1779 #[test]
1780 fn test_group_state_check_permission_denied() {
1781 let creator_key = create_test_verifying_key();
1782 let member_key = create_test_verifying_key();
1783 let mut group_state = GroupState::new(
1784 create_test_message_id(23),
1785 "Test".to_string(),
1786 GroupSettings::default(),
1787 vec![],
1788 creator_key,
1789 1000,
1790 );
1791
1792 // Add member
1793 let activity_event = GroupActivityEvent::Activity(());
1794 group_state
1795 .apply_event(
1796 &activity_event,
1797 create_test_message_id(24),
1798 member_key.clone(),
1799 1001,
1800 )
1801 .unwrap();
1802
1803 // Member should not have owner-only permissions
1804 let result = group_state.check_permission(&member_key, &Permission::OwnerOnly);
1805 assert!(result.is_err());
1806 match result.unwrap_err() {
1807 GroupStateError::PermissionDenied(msg) => {
1808 assert!(msg.contains("does not have required permission"));
1809 }
1810 _ => panic!("Expected PermissionDenied error"),
1811 }
1812
1813 // But should have all-members permissions
1814 let result = group_state.check_permission(&member_key, &Permission::AllMembers);
1815 assert!(result.is_ok());
1816 }
1817
1818 #[test]
1819 fn test_group_state_check_permission_member_not_found() {
1820 let creator_key = create_test_verifying_key();
1821 let non_member_key = create_test_verifying_key();
1822 let group_state = GroupState::new(
1823 create_test_message_id(25),
1824 "Test".to_string(),
1825 GroupSettings::default(),
1826 vec![],
1827 creator_key,
1828 1000,
1829 );
1830
1831 let result = group_state.check_permission(&non_member_key, &Permission::AllMembers);
1832 assert!(result.is_err());
1833 match result.unwrap_err() {
1834 GroupStateError::MemberNotFound { .. } => {}
1835 _ => panic!("Expected MemberNotFound error"),
1836 }
1837 }
1838
1839 // Error Handling Tests
1840 #[test]
1841 fn test_group_state_error_display() {
1842 let error = GroupStateError::PermissionDenied("Test permission denied".to_string());
1843 assert_eq!(
1844 error.to_string(),
1845 "Permission denied: Test permission denied"
1846 );
1847
1848 let error = GroupStateError::MemberNotFound {
1849 member: "test_member".to_string(),
1850 group: "test_group".to_string(),
1851 };
1852 assert_eq!(
1853 error.to_string(),
1854 "Member not found: test_member in group test_group"
1855 );
1856
1857 let error = GroupStateError::StateTransition("Test transition error".to_string());
1858 assert_eq!(
1859 error.to_string(),
1860 "State transition error: Test transition error"
1861 );
1862
1863 let error = GroupStateError::InvalidOperation("Test invalid operation".to_string());
1864 assert_eq!(
1865 error.to_string(),
1866 "Invalid operation: Test invalid operation"
1867 );
1868 }
1869
1870 #[test]
1871 fn test_group_state_handle_leave_group_member_not_found() {
1872 let creator_key = create_test_verifying_key();
1873 let non_member_key = create_test_verifying_key();
1874 let mut group_state = GroupState::new(
1875 create_test_message_id(26),
1876 "Test".to_string(),
1877 GroupSettings::default(),
1878 vec![],
1879 creator_key,
1880 1000,
1881 );
1882
1883 let leave_event: GroupActivityEvent<()> = GroupActivityEvent::LeaveGroup { message: None };
1884 let result = group_state.apply_event(
1885 &leave_event,
1886 create_test_message_id(27),
1887 non_member_key,
1888 1001,
1889 );
1890
1891 assert!(result.is_err());
1892 match result.unwrap_err() {
1893 GroupStateError::MemberNotFound { .. } => {}
1894 _ => panic!("Expected MemberNotFound error"),
1895 }
1896 }
1897
1898 #[test]
1899 fn test_group_state_handle_role_assignment_member_not_found() {
1900 let creator_key = create_test_verifying_key();
1901 let non_member_key = create_test_verifying_key();
1902 let mut group_state = GroupState::new(
1903 create_test_message_id(28),
1904 "Test".to_string(),
1905 GroupSettings::default(),
1906 vec![],
1907 creator_key.clone(),
1908 1000,
1909 );
1910
1911 let target_identity = IdentityRef::Key(non_member_key);
1912 let assign_role_event: GroupActivityEvent<()> = GroupActivityEvent::AssignRole {
1913 target: target_identity,
1914 role: GroupRole::Admin,
1915 };
1916
1917 let result = group_state.apply_event(
1918 &assign_role_event,
1919 create_test_message_id(29),
1920 creator_key,
1921 1001,
1922 );
1923
1924 assert!(result.is_err());
1925 match result.unwrap_err() {
1926 GroupStateError::MemberNotFound { .. } => {}
1927 _ => panic!("Expected MemberNotFound error"),
1928 }
1929 }
1930
1931 #[test]
1932 fn test_group_state_handle_role_assignment_permission_denied() {
1933 let creator_key = create_test_verifying_key();
1934 let member_key = create_test_verifying_key();
1935 let target_key = create_test_verifying_key();
1936 let mut group_state = GroupState::new(
1937 create_test_message_id(30),
1938 "Test".to_string(),
1939 GroupSettings::default(),
1940 vec![],
1941 creator_key,
1942 1000,
1943 );
1944
1945 // Add both members
1946 let activity_event = GroupActivityEvent::Activity(());
1947 group_state
1948 .apply_event(
1949 &activity_event,
1950 create_test_message_id(31),
1951 member_key.clone(),
1952 1001,
1953 )
1954 .unwrap();
1955 group_state
1956 .apply_event(
1957 &activity_event,
1958 create_test_message_id(32),
1959 target_key.clone(),
1960 1002,
1961 )
1962 .unwrap();
1963
1964 // Regular member tries to assign role (should fail)
1965 let target_identity = IdentityRef::Key(target_key);
1966 let assign_role_event: GroupActivityEvent<()> = GroupActivityEvent::AssignRole {
1967 target: target_identity,
1968 role: GroupRole::Admin,
1969 };
1970
1971 let result = group_state.apply_event(
1972 &assign_role_event,
1973 create_test_message_id(33),
1974 member_key, // Regular member, not owner
1975 1003,
1976 );
1977
1978 assert!(result.is_err());
1979 match result.unwrap_err() {
1980 GroupStateError::PermissionDenied(_) => {}
1981 _ => panic!("Expected PermissionDenied error"),
1982 }
1983 }
1984
1985 // Serialization Tests
1986 #[test]
1987 fn test_postcard_serialization_group_membership() {
1988 let mut membership = GroupMembership::new();
1989 let key = create_test_verifying_key();
1990 let identity = IdentityRef::Key(key);
1991
1992 membership
1993 .identity_roles
1994 .insert(identity.clone(), GroupRole::Admin);
1995 membership.identity_info.insert(
1996 identity,
1997 IdentityInfo {
1998 display_name: "Test User".to_string(),
1999 metadata: vec![Metadata::Email("test@example.com".to_string())],
2000 },
2001 );
2002
2003 let serialized = postcard::to_stdvec(&membership).expect("Failed to serialize");
2004 let deserialized: GroupMembership =
2005 postcard::from_bytes(&serialized).expect("Failed to deserialize");
2006
2007 assert_eq!(
2008 membership.identity_roles.len(),
2009 deserialized.identity_roles.len()
2010 );
2011 assert_eq!(
2012 membership.identity_info.len(),
2013 deserialized.identity_info.len()
2014 );
2015 }
2016
2017 #[test]
2018 fn test_postcard_serialization_group_member() {
2019 let key = create_test_verifying_key();
2020 let identity_ref = IdentityRef::Key(key);
2021
2022 let member = GroupMember {
2023 key: identity_ref,
2024 role: GroupRole::Moderator,
2025 joined_at: 1234567890,
2026 last_active: 1234567900,
2027 metadata: vec![Metadata::Generic {
2028 key: "department".to_string(),
2029 value: "engineering".to_string(),
2030 }],
2031 };
2032
2033 let serialized = postcard::to_stdvec(&member).expect("Failed to serialize");
2034 let deserialized: GroupMember =
2035 postcard::from_bytes(&serialized).expect("Failed to deserialize");
2036
2037 assert_eq!(member.role, deserialized.role);
2038 assert_eq!(member.joined_at, deserialized.joined_at);
2039 assert_eq!(member.last_active, deserialized.last_active);
2040 assert_eq!(member.metadata, deserialized.metadata);
2041 }
2042
2043 #[test]
2044 fn test_postcard_serialization_group_state() {
2045 let creator_key = create_test_verifying_key();
2046 let group_state = GroupState::new(
2047 create_test_message_id(34),
2048 "Serialization Test Group".to_string(),
2049 GroupSettings::default(),
2050 vec![
2051 Metadata::Description("Test serialization".to_string()),
2052 Metadata::Generic {
2053 key: "test".to_string(),
2054 value: "value".to_string(),
2055 },
2056 ],
2057 creator_key,
2058 1234567890,
2059 );
2060
2061 let serialized = postcard::to_stdvec(&group_state).expect("Failed to serialize");
2062 let deserialized: GroupState =
2063 postcard::from_bytes(&serialized).expect("Failed to deserialize");
2064
2065 assert_eq!(group_state.group_id, deserialized.group_id);
2066 assert_eq!(group_state.name, deserialized.name);
2067 assert_eq!(group_state.settings, deserialized.settings);
2068 assert_eq!(group_state.metadata, deserialized.metadata);
2069 assert_eq!(group_state.members.len(), deserialized.members.len());
2070 assert_eq!(group_state.version, deserialized.version);
2071 assert_eq!(
2072 group_state.last_event_timestamp,
2073 deserialized.last_event_timestamp
2074 );
2075 assert_eq!(group_state.event_history, deserialized.event_history);
2076 }
2077
2078 // Integration Tests
2079 #[test]
2080 fn test_group_state_full_lifecycle() {
2081 let creator_key = create_test_verifying_key();
2082 let member1_key = create_test_verifying_key();
2083 let member2_key = create_test_verifying_key();
2084
2085 // Create group
2086 let mut group_state = GroupState::new(
2087 create_test_message_id(35),
2088 "Lifecycle Test Group".to_string(),
2089 GroupSettings::default(),
2090 vec![Metadata::Description("Full lifecycle test".to_string())],
2091 creator_key.clone(),
2092 1000,
2093 );
2094
2095 assert_eq!(group_state.members.len(), 1);
2096 assert_eq!(group_state.version, 1);
2097
2098 // Member 1 joins
2099 let activity_event = GroupActivityEvent::Activity(());
2100 group_state
2101 .apply_event(
2102 &activity_event,
2103 create_test_message_id(36),
2104 member1_key.clone(),
2105 1001,
2106 )
2107 .unwrap();
2108
2109 assert_eq!(group_state.members.len(), 2);
2110 assert_eq!(group_state.version, 2);
2111 assert!(group_state.is_member(&member1_key));
2112
2113 // Member 2 joins
2114 group_state
2115 .apply_event(
2116 &activity_event,
2117 create_test_message_id(37),
2118 member2_key.clone(),
2119 1002,
2120 )
2121 .unwrap();
2122
2123 assert_eq!(group_state.members.len(), 3);
2124 assert_eq!(group_state.version, 3);
2125
2126 // Creator promotes member1 to admin
2127 let promote_event: GroupActivityEvent<()> = GroupActivityEvent::AssignRole {
2128 target: IdentityRef::Key(member1_key.clone()),
2129 role: GroupRole::Admin,
2130 };
2131 group_state
2132 .apply_event(
2133 &promote_event,
2134 create_test_message_id(38),
2135 creator_key.clone(),
2136 1003,
2137 )
2138 .unwrap();
2139
2140 assert_eq!(
2141 group_state.member_role(&member1_key),
2142 Some(&GroupRole::Admin)
2143 );
2144 assert_eq!(group_state.version, 4);
2145
2146 // Creator (owner) promotes member2 to moderator (only owners can assign roles by default)
2147 let promote_event2: GroupActivityEvent<()> = GroupActivityEvent::AssignRole {
2148 target: IdentityRef::Key(member2_key.clone()),
2149 role: GroupRole::Moderator,
2150 };
2151 group_state
2152 .apply_event(
2153 &promote_event2,
2154 create_test_message_id(39),
2155 creator_key.clone(),
2156 1004,
2157 )
2158 .unwrap();
2159
2160 assert_eq!(
2161 group_state.member_role(&member2_key),
2162 Some(&GroupRole::Moderator)
2163 );
2164 assert_eq!(group_state.version, 5);
2165
2166 // Update group info
2167 let new_group_info = GroupInfo {
2168 name: "Updated Lifecycle Test Group".to_string(),
2169 settings: GroupSettings::default(),
2170 key_info: create_test_group_key_info(),
2171 metadata: vec![
2172 Metadata::Description("Updated description".to_string()),
2173 Metadata::Generic {
2174 key: "status".to_string(),
2175 value: "active".to_string(),
2176 },
2177 ],
2178 };
2179
2180 let update_event: GroupActivityEvent<()> =
2181 GroupActivityEvent::UpdateGroup(new_group_info.clone());
2182 group_state
2183 .apply_event(
2184 &update_event,
2185 create_test_message_id(40),
2186 creator_key.clone(),
2187 1005,
2188 )
2189 .unwrap();
2190
2191 assert_eq!(group_state.name, "Updated Lifecycle Test Group");
2192 assert_eq!(group_state.metadata, new_group_info.metadata);
2193 assert_eq!(group_state.version, 6);
2194
2195 // Member2 leaves
2196 let leave_event: GroupActivityEvent<()> = GroupActivityEvent::LeaveGroup {
2197 message: Some("Goodbye!".to_string()),
2198 };
2199 group_state
2200 .apply_event(
2201 &leave_event,
2202 create_test_message_id(41),
2203 member2_key.clone(),
2204 1006,
2205 )
2206 .unwrap();
2207
2208 assert!(!group_state.is_member(&member2_key));
2209 assert_eq!(group_state.members.len(), 2);
2210 assert_eq!(group_state.version, 7);
2211
2212 // Verify final state
2213 assert_eq!(group_state.event_history.len(), 7);
2214 assert_eq!(group_state.last_event_timestamp, 1006);
2215 assert!(group_state.is_member(&creator_key));
2216 assert!(group_state.is_member(&member1_key));
2217 assert!(!group_state.is_member(&member2_key));
2218 }
2219
2220 #[test]
2221 fn test_group_state_concurrent_member_activity() {
2222 let creator_key = create_test_verifying_key();
2223 let member_key = create_test_verifying_key();
2224 let mut group_state = GroupState::new(
2225 create_test_message_id(42),
2226 "Concurrent Test".to_string(),
2227 GroupSettings::default(),
2228 vec![],
2229 creator_key,
2230 1000,
2231 );
2232
2233 // Member joins
2234 let activity_event = GroupActivityEvent::Activity(());
2235 group_state
2236 .apply_event(
2237 &activity_event,
2238 create_test_message_id(43),
2239 member_key.clone(),
2240 1001,
2241 )
2242 .unwrap();
2243
2244 let initial_last_active = group_state
2245 .members
2246 .get(&IdentityRef::Key(member_key.clone()))
2247 .unwrap()
2248 .last_active;
2249 assert_eq!(initial_last_active, 1001);
2250
2251 // Member is active again (should update last_active)
2252 group_state
2253 .apply_event(
2254 &activity_event,
2255 create_test_message_id(44),
2256 member_key.clone(),
2257 1010,
2258 )
2259 .unwrap();
2260
2261 let updated_last_active = group_state
2262 .members
2263 .get(&IdentityRef::Key(member_key))
2264 .unwrap()
2265 .last_active;
2266 assert_eq!(updated_last_active, 1010);
2267 assert_eq!(group_state.members.len(), 2); // Should still be 2 members
2268 }
2269}