Skip to main content

slim_session/
session_builder.rs

1// Copyright AGNTCY Contributors (https://github.com/agntcy)
2// SPDX-License-Identifier: Apache-2.0
3
4use std::marker::PhantomData;
5
6use slim_auth::traits::{TokenProvider, Verifier};
7use slim_datapath::messages::Name;
8
9use crate::{
10    Direction,
11    common::SessionMessage,
12    errors::SessionError,
13    session_config::SessionConfig,
14    session_controller::SessionController,
15    session_moderator::SessionModerator,
16    session_participant::SessionParticipant,
17    session_settings::SessionSettings,
18    subscription_manager::{SubscriptionManager, SubscriptionOps},
19    traits::MessageHandler,
20    transmitter::SessionTransmitter,
21};
22
23// Marker types for builder states
24pub struct NotReady;
25pub struct Ready;
26
27// Marker types for target types
28pub struct ForController;
29pub struct ForParticipant;
30pub struct ForModerator;
31
32/// Unified generic builder for constructing session-related types.
33///
34/// This builder eliminates the need for multiple constructors with many parameters
35/// by providing a fluent, type-safe API for building session components.
36///
37/// # Type Safety
38///
39/// The builder uses compile-time type states to ensure:
40/// 1. All required fields are set before building (enforced by `ready()`)
41/// 2. The correct build method is available for each target type
42/// 3. No runtime panics due to missing fields
43///
44/// # Supported Types
45///
46/// - **`SessionController`** - High-level controller that wraps participant/moderator
47///   - Use `SessionBuilder::for_controller()` or `SessionController::builder()`
48///   - Automatically creates moderator or participant based on config
49///
50/// - **`SessionParticipant`** - Direct participant construction (advanced)
51///   - Use `SessionBuilder::for_participant()` or `SessionParticipant::builder()`
52///
53/// - **`SessionModerator`** - Direct moderator construction (advanced)
54///   - Use `SessionBuilder::for_moderator()` or `SessionModerator::builder()`
55///
56/// # Examples
57///
58/// ## Building a SessionController (Recommended)
59///
60/// ```ignore
61/// use agntcy_slim_session::{SessionBuilder, SessionController};
62///
63/// let controller = SessionController::builder()
64///     .with_id(1)
65///     .with_source(source_name)
66///     .with_destination(dest_name)
67///     .with_config(session_config)
68///     .with_identity_provider(provider)
69///     .with_identity_verifier(verifier)
70///     .with_storage_path(storage_path)
71///     .with_tx(transmitter)
72///     .with_tx_to_session_layer(tx_channel)
73///     .ready()?  // Validates all fields are set
74///     .build()   // Constructs the SessionController
75///     .await?;
76/// ```
77///
78/// ## Building a SessionParticipant (Advanced)
79///
80/// ```ignore
81/// let participant = SessionBuilder::for_participant()
82///     .with_id(1)
83///     .with_source(name)
84///     .with_destination(dest)
85///     .with_config(config)
86///     .with_identity_provider(provider)
87///     .with_identity_verifier(verifier)
88///     .with_storage_path(path)
89///     .with_tx(tx)
90///     .with_tx_to_session_layer(tx_channel)
91///     .ready()?
92///     .build()
93///     .await;
94/// ```
95///
96/// ## Building a SessionModerator (Advanced)
97///
98/// ```ignore
99/// let moderator = SessionModerator::builder()
100///     .with_id(session_id)
101///     .with_source(moderator_name)
102///     .with_destination(group_name)
103///     .with_config(moderator_config)
104///     .with_identity_provider(provider)
105///     .with_identity_verifier(verifier)
106///     .with_storage_path(storage_path)
107///     .with_tx(tx)
108///     .with_tx_to_session_layer(tx_channel)
109///     .ready()?
110///     .build()
111///     .await;
112/// ```
113pub struct SessionBuilder<P, V, Target, State = NotReady, M = SubscriptionManager>
114where
115    P: TokenProvider + Send + Sync + Clone + 'static,
116    V: Verifier + Send + Sync + Clone + 'static,
117    M: SubscriptionOps,
118{
119    id: Option<u32>,
120    source: Option<Name>,
121    destination: Option<Name>,
122    config: Option<SessionConfig>,
123    identity_provider: Option<P>,
124    identity_verifier: Option<V>,
125    tx: Option<SessionTransmitter>,
126    tx_to_session_layer: Option<tokio::sync::mpsc::Sender<Result<SessionMessage, SessionError>>>,
127    graceful_shutdown_timeout: Option<std::time::Duration>,
128    direction: Direction,
129    subscription_manager: Option<M>,
130    service_id: Option<String>,
131    _target: PhantomData<Target>,
132    _state: PhantomData<State>,
133}
134
135// Common builder methods (available in NotReady state for all target types)
136impl<P, V, Target, M> SessionBuilder<P, V, Target, NotReady, M>
137where
138    P: TokenProvider + Send + Sync + Clone + 'static,
139    V: Verifier + Send + Sync + Clone + 'static,
140    M: SubscriptionOps,
141{
142    fn new() -> Self {
143        Self {
144            id: None,
145            source: None,
146            destination: None,
147            config: None,
148            identity_provider: None,
149            identity_verifier: None,
150            tx: None,
151            tx_to_session_layer: None,
152            graceful_shutdown_timeout: None,
153            direction: Direction::Bidirectional,
154            subscription_manager: None,
155            service_id: None,
156            _target: PhantomData,
157            _state: PhantomData,
158        }
159    }
160
161    pub fn with_id(mut self, id: u32) -> Self {
162        self.id = Some(id);
163        self
164    }
165
166    pub fn with_source(mut self, source: Name) -> Self {
167        self.source = Some(source);
168        self
169    }
170
171    pub fn with_destination(mut self, destination: Name) -> Self {
172        self.destination = Some(destination);
173        self
174    }
175
176    pub fn with_config(mut self, config: SessionConfig) -> Self {
177        self.config = Some(config);
178        self
179    }
180
181    pub fn with_identity_provider(mut self, identity_provider: P) -> Self {
182        self.identity_provider = Some(identity_provider);
183        self
184    }
185
186    pub fn with_identity_verifier(mut self, identity_verifier: V) -> Self {
187        self.identity_verifier = Some(identity_verifier);
188        self
189    }
190
191    pub fn with_tx(mut self, tx: SessionTransmitter) -> Self {
192        self.tx = Some(tx);
193        self
194    }
195
196    pub fn with_tx_to_session_layer(
197        mut self,
198        tx_to_session_layer: tokio::sync::mpsc::Sender<Result<SessionMessage, SessionError>>,
199    ) -> Self {
200        self.tx_to_session_layer = Some(tx_to_session_layer);
201        self
202    }
203
204    pub fn with_graceful_shutdown_timeout(mut self, timeout: std::time::Duration) -> Self {
205        self.graceful_shutdown_timeout = Some(timeout);
206        self
207    }
208
209    pub fn with_direction(mut self, direction: Direction) -> Self {
210        self.direction = direction;
211        self
212    }
213
214    /// Set a custom subscription manager.  This method changes the manager
215    /// type `M`, so it returns a new builder type `SessionBuilder<P, V,
216    /// Target, NotReady, N>`.  Call this before `ready()`.
217    pub fn with_subscription_manager<N: SubscriptionOps>(
218        self,
219        manager: N,
220    ) -> SessionBuilder<P, V, Target, NotReady, N> {
221        SessionBuilder {
222            id: self.id,
223            source: self.source,
224            destination: self.destination,
225            config: self.config,
226            identity_provider: self.identity_provider,
227            identity_verifier: self.identity_verifier,
228            tx: self.tx,
229            tx_to_session_layer: self.tx_to_session_layer,
230            graceful_shutdown_timeout: self.graceful_shutdown_timeout,
231            direction: self.direction,
232            subscription_manager: Some(manager),
233            service_id: self.service_id,
234            _target: PhantomData,
235            _state: PhantomData,
236        }
237    }
238
239    pub fn with_service_id(mut self, service_id: String) -> Self {
240        self.service_id = Some(service_id);
241        self
242    }
243
244    pub fn ready(self) -> Result<SessionBuilder<P, V, Target, Ready, M>, SessionError> {
245        // Verify all required fields are set
246        if self.id.is_none()
247            || self.source.is_none()
248            || self.destination.is_none()
249            || self.config.is_none()
250            || self.identity_provider.is_none()
251            || self.identity_verifier.is_none()
252            || self.tx.is_none()
253            || self.tx_to_session_layer.is_none()
254        {
255            return Err(SessionError::SessionBuilderIncomplete);
256        }
257
258        Ok(SessionBuilder {
259            id: self.id,
260            source: self.source,
261            destination: self.destination,
262            config: self.config,
263            identity_provider: self.identity_provider,
264            identity_verifier: self.identity_verifier,
265            tx: self.tx,
266            tx_to_session_layer: self.tx_to_session_layer,
267            graceful_shutdown_timeout: self.graceful_shutdown_timeout,
268            direction: self.direction,
269            subscription_manager: self.subscription_manager,
270            service_id: self.service_id,
271            _target: PhantomData,
272            _state: PhantomData,
273        })
274    }
275}
276
277// Convenience constructors for different target types
278impl<P, V, M> SessionBuilder<P, V, ForController, NotReady, M>
279where
280    P: TokenProvider + Send + Sync + Clone + 'static,
281    V: Verifier + Send + Sync + Clone + 'static,
282    M: SubscriptionOps,
283{
284    /// Create a new builder for constructing a SessionController
285    pub fn for_controller() -> Self {
286        Self::new()
287    }
288}
289
290impl<P, V, M> SessionBuilder<P, V, ForParticipant, NotReady, M>
291where
292    P: TokenProvider + Send + Sync + Clone + 'static,
293    V: Verifier + Send + Sync + Clone + 'static,
294    M: SubscriptionOps,
295{
296    /// Create a new builder for constructing a SessionParticipant
297    pub fn for_participant() -> Self {
298        Self::new()
299    }
300}
301
302impl<P, V, M> SessionBuilder<P, V, ForModerator, NotReady, M>
303where
304    P: TokenProvider + Send + Sync + Clone + 'static,
305    V: Verifier + Send + Sync + Clone + 'static,
306    M: SubscriptionOps,
307{
308    /// Create a new builder for constructing a SessionModerator
309    pub fn for_moderator() -> Self {
310        Self::new()
311    }
312}
313
314// Build methods for SessionController
315impl<P, V, M> SessionBuilder<P, V, ForController, Ready, M>
316where
317    P: TokenProvider + Send + Sync + Clone + 'static,
318    V: Verifier + Send + Sync + Clone + 'static,
319    M: SubscriptionOps,
320{
321    /// Build a SessionController
322    ///
323    /// Automatically determines whether to create a moderator or participant
324    /// internally based on the session configuration's `initiator` flag.
325    pub fn build(self) -> Result<SessionController, SessionError> {
326        let id = self.id.unwrap();
327        let source = self.source.clone().unwrap();
328        let destination = self.destination.clone().unwrap();
329        let config = self.config.clone().unwrap();
330
331        let role = if config.initiator {
332            "Moderator"
333        } else {
334            "Participant"
335        };
336        tracing::debug!(%role, "Building SessionController");
337
338        let session_controller = if config.initiator {
339            let (inner, tx, rx, settings) = self.build_session_stack(SessionModerator::new)?;
340            SessionController::from_parts(
341                id,
342                source,
343                destination,
344                config.clone(),
345                settings,
346                tx,
347                rx,
348                inner,
349            )
350        } else {
351            let (inner, tx, rx, settings) = self.build_session_stack(SessionParticipant::new)?;
352            SessionController::from_parts(id, source, destination, config, settings, tx, rx, inner)
353        };
354
355        Ok(session_controller)
356    }
357
358    /// Generic helper function to build session stacks, eliminating code duplication
359    /// between moderator and participant stack building.
360    fn build_session_stack<W>(
361        self,
362        wrapper_constructor: impl FnOnce(crate::session::Session, SessionSettings<P, V, M>) -> W,
363    ) -> Result<
364        (
365            W,
366            tokio::sync::mpsc::Sender<SessionMessage>,
367            tokio::sync::mpsc::Receiver<SessionMessage>,
368            SessionSettings<P, V, M>,
369        ),
370        SessionError,
371    >
372    where
373        W: MessageHandler,
374    {
375        let (tx_session, rx_session) = tokio::sync::mpsc::channel(256);
376
377        // Create the base Session layer
378        let inner = crate::session::Session::new(
379            self.id.unwrap(),
380            self.config.clone().unwrap(),
381            &self.source.clone().unwrap(),
382            self.tx.clone().unwrap(),
383            tx_session.clone(),
384            self.direction,
385        );
386
387        let tx = self.tx.unwrap();
388        let subscription_manager = self
389            .subscription_manager
390            .or_else(|| M::from_slim_tx(&tx.slim_tx))
391            .expect("subscription_manager must be provided or M must implement from_slim_tx");
392        let settings = SessionSettings {
393            id: self.id.unwrap(),
394            source: self.source.unwrap(),
395            destination: self.destination.unwrap(),
396            config: self.config.unwrap(),
397            tx,
398            tx_session: tx_session.clone(),
399            tx_to_session_layer: self.tx_to_session_layer.unwrap(),
400            identity_provider: self.identity_provider.unwrap(),
401            identity_verifier: self.identity_verifier.unwrap(),
402            graceful_shutdown_timeout: self.graceful_shutdown_timeout,
403            subscription_manager,
404            service_id: self.service_id.unwrap_or_default(),
405        };
406
407        let wrapper = wrapper_constructor(inner, settings.clone());
408
409        Ok((wrapper, tx_session, rx_session, settings))
410    }
411}
412
413#[cfg(test)]
414mod tests {
415    use super::*;
416    use crate::{
417        SessionError,
418        test_utils::{MockTokenProvider, MockVerifier},
419        transmitter::SessionTransmitter,
420    };
421    use slim_datapath::{api::ProtoSessionType, messages::Name};
422    use std::collections::HashMap;
423    use tokio::sync::mpsc;
424
425    fn create_test_config(initiator: bool) -> SessionConfig {
426        SessionConfig {
427            session_type: ProtoSessionType::PointToPoint,
428            max_retries: Some(3),
429            interval: Some(std::time::Duration::from_secs(1)),
430            mls_enabled: false,
431            initiator,
432            metadata: HashMap::new(),
433        }
434    }
435
436    fn create_test_name(prefix: &str) -> Name {
437        Name::from_strings([prefix, "test", "name"]).with_id(1)
438    }
439
440    fn create_test_transmitter() -> SessionTransmitter {
441        let (slim_tx, _) = mpsc::channel(10);
442        let (app_tx, _) = mpsc::unbounded_channel();
443        SessionTransmitter::new(slim_tx, app_tx)
444    }
445
446    #[test]
447    fn test_builder_for_controller_creation() {
448        let builder =
449            SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller();
450        assert!(builder.id.is_none());
451        assert!(builder.source.is_none());
452        assert!(builder.destination.is_none());
453    }
454
455    #[test]
456    fn test_builder_for_participant_creation() {
457        let builder =
458            SessionBuilder::<MockTokenProvider, MockVerifier, ForParticipant, NotReady>::for_participant();
459        assert!(builder.id.is_none());
460        assert!(builder.source.is_none());
461        assert!(builder.destination.is_none());
462    }
463
464    #[test]
465    fn test_builder_for_moderator_creation() {
466        let builder =
467            SessionBuilder::<MockTokenProvider, MockVerifier, ForModerator, NotReady>::for_moderator();
468        assert!(builder.id.is_none());
469        assert!(builder.source.is_none());
470        assert!(builder.destination.is_none());
471    }
472
473    #[test]
474    fn test_builder_with_id() {
475        let builder =
476            SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
477                .with_id(42);
478        assert_eq!(builder.id, Some(42));
479    }
480
481    #[test]
482    fn test_builder_with_source() {
483        let source = create_test_name("source");
484        let builder =
485            SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
486                .with_source(source.clone());
487        assert_eq!(builder.source, Some(source));
488    }
489
490    #[test]
491    fn test_builder_with_destination() {
492        let destination = create_test_name("dest");
493        let builder =
494            SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
495                .with_destination(destination.clone());
496        assert_eq!(builder.destination, Some(destination));
497    }
498
499    #[test]
500    fn test_builder_with_config() {
501        let config = create_test_config(true);
502        let builder =
503            SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
504                .with_config(config.clone());
505        assert!(builder.config.is_some());
506        assert!(builder.config.unwrap().initiator);
507    }
508
509    #[test]
510    fn test_builder_with_identity_provider() {
511        let provider = MockTokenProvider;
512        let builder =
513            SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
514                .with_identity_provider(provider);
515        assert!(builder.identity_provider.is_some());
516    }
517
518    #[test]
519    fn test_builder_with_identity_verifier() {
520        let verifier = MockVerifier;
521        let builder =
522            SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
523                .with_identity_verifier(verifier);
524        assert!(builder.identity_verifier.is_some());
525    }
526
527    #[test]
528    fn test_builder_with_tx() {
529        let tx = create_test_transmitter();
530        let builder =
531            SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
532                .with_tx(tx);
533        assert!(builder.tx.is_some());
534    }
535
536    #[test]
537    fn test_builder_with_tx_to_session_layer() {
538        let (tx, _) = mpsc::channel(10);
539        let builder =
540            SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
541                .with_tx_to_session_layer(tx);
542        assert!(builder.tx_to_session_layer.is_some());
543    }
544
545    #[test]
546    fn test_builder_ready_with_all_fields() {
547        let (tx_to_session, _) = mpsc::channel(10);
548        let builder =
549            SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
550                .with_id(1)
551                .with_source(create_test_name("source"))
552                .with_destination(create_test_name("dest"))
553                .with_config(create_test_config(true))
554                .with_identity_provider(MockTokenProvider)
555                .with_identity_verifier(MockVerifier)
556                .with_tx(create_test_transmitter())
557                .with_tx_to_session_layer(tx_to_session);
558
559        let ready_result = builder.ready();
560        assert!(ready_result.is_ok());
561    }
562
563    #[test]
564    fn test_builder_ready_missing_id() {
565        let (tx_to_session, _) = mpsc::channel(10);
566        let builder =
567            SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
568                .with_source(create_test_name("source"))
569                .with_destination(create_test_name("dest"))
570                .with_config(create_test_config(true))
571                .with_identity_provider(MockTokenProvider)
572                .with_identity_verifier(MockVerifier)
573                .with_tx(create_test_transmitter())
574                .with_tx_to_session_layer(tx_to_session);
575
576        let ready_result = builder.ready();
577        assert!(ready_result.is_err_and(|e| matches!(e, SessionError::SessionBuilderIncomplete)));
578    }
579
580    #[test]
581    fn test_builder_ready_missing_source() {
582        let (tx_to_session, _) = mpsc::channel(10);
583        let builder =
584            SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
585                .with_id(1)
586                .with_destination(create_test_name("dest"))
587                .with_config(create_test_config(true))
588                .with_identity_provider(MockTokenProvider)
589                .with_identity_verifier(MockVerifier)
590                .with_tx(create_test_transmitter())
591                .with_tx_to_session_layer(tx_to_session);
592        let ready_result = builder.ready();
593        assert!(ready_result.is_err());
594    }
595
596    #[test]
597    fn test_builder_ready_missing_destination() {
598        let (tx_to_session, _) = mpsc::channel(10);
599        let builder =
600            SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
601                .with_id(1)
602                .with_source(create_test_name("source"))
603                .with_config(create_test_config(true))
604                .with_identity_provider(MockTokenProvider)
605                .with_identity_verifier(MockVerifier)
606                .with_tx(create_test_transmitter())
607                .with_tx_to_session_layer(tx_to_session);
608
609        let ready_result = builder.ready();
610        assert!(ready_result.is_err());
611    }
612
613    #[test]
614    fn test_builder_ready_missing_config() {
615        let (tx_to_session, _) = mpsc::channel(10);
616        let builder =
617            SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
618                .with_id(1)
619                .with_source(create_test_name("source"))
620                .with_destination(create_test_name("dest"))
621                .with_identity_provider(MockTokenProvider)
622                .with_identity_verifier(MockVerifier)
623                .with_tx(create_test_transmitter())
624                .with_tx_to_session_layer(tx_to_session);
625
626        let ready_result = builder.ready();
627        assert!(ready_result.is_err());
628    }
629
630    #[test]
631    fn test_builder_ready_missing_identity_provider() {
632        let (tx_to_session, _) = mpsc::channel(10);
633        let builder =
634            SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
635                .with_id(1)
636                .with_source(create_test_name("source"))
637                .with_destination(create_test_name("dest"))
638                .with_config(create_test_config(true))
639                .with_identity_verifier(MockVerifier)
640                .with_tx(create_test_transmitter())
641                .with_tx_to_session_layer(tx_to_session);
642
643        let ready_result = builder.ready();
644        assert!(ready_result.is_err());
645    }
646
647    #[test]
648    fn test_builder_ready_missing_identity_verifier() {
649        let (tx_to_session, _) = mpsc::channel(10);
650        let builder =
651            SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
652                .with_id(1)
653                .with_source(create_test_name("source"))
654                .with_destination(create_test_name("dest"))
655                .with_config(create_test_config(true))
656                .with_identity_provider(MockTokenProvider)
657                .with_tx(create_test_transmitter())
658                .with_tx_to_session_layer(tx_to_session);
659
660        let ready_result = builder.ready();
661        assert!(ready_result.is_err());
662    }
663
664    #[test]
665    fn test_builder_ready_missing_tx() {
666        let (tx_to_session, _) = mpsc::channel(10);
667        let builder =
668            SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
669                .with_id(1)
670                .with_source(create_test_name("source"))
671                .with_destination(create_test_name("dest"))
672                .with_config(create_test_config(true))
673                .with_identity_provider(MockTokenProvider)
674                .with_identity_verifier(MockVerifier)
675                .with_tx_to_session_layer(tx_to_session);
676
677        let ready_result = builder.ready();
678        assert!(ready_result.is_err());
679    }
680
681    #[test]
682    fn test_builder_ready_missing_tx_to_session_layer() {
683        let builder =
684            SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
685                .with_id(1)
686                .with_source(create_test_name("source"))
687                .with_destination(create_test_name("dest"))
688                .with_config(create_test_config(true))
689                .with_identity_provider(MockTokenProvider)
690                .with_identity_verifier(MockVerifier)
691                .with_tx(create_test_transmitter());
692
693        let ready_result = builder.ready();
694        assert!(ready_result.is_err());
695    }
696
697    #[test]
698    fn test_builder_chaining() {
699        let (tx_to_session, _) = mpsc::channel(10);
700        let builder =
701            SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
702                .with_id(123)
703                .with_source(create_test_name("src"))
704                .with_destination(create_test_name("dst"))
705                .with_config(create_test_config(false))
706                .with_identity_provider(MockTokenProvider)
707                .with_identity_verifier(MockVerifier)
708                .with_tx(create_test_transmitter())
709                .with_tx_to_session_layer(tx_to_session);
710
711        assert_eq!(builder.id, Some(123));
712        assert!(builder.source.is_some());
713        assert!(builder.destination.is_some());
714        assert!(builder.config.is_some());
715        assert!(builder.identity_provider.is_some());
716        assert!(builder.identity_verifier.is_some());
717        assert!(builder.tx.is_some());
718        assert!(builder.tx_to_session_layer.is_some());
719    }
720
721    #[test]
722    fn test_builder_ready_state_transition() {
723        let (tx_to_session, _) = mpsc::channel(10);
724        let builder =
725            SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
726                .with_id(1)
727                .with_source(create_test_name("source"))
728                .with_destination(create_test_name("dest"))
729                .with_config(create_test_config(true))
730                .with_identity_provider(MockTokenProvider)
731                .with_identity_verifier(MockVerifier)
732                .with_tx(create_test_transmitter())
733                .with_tx_to_session_layer(tx_to_session);
734
735        let ready_builder = builder.ready().unwrap();
736
737        // Verify all fields are still present after transition
738        assert_eq!(ready_builder.id, Some(1));
739        assert!(ready_builder.source.is_some());
740        assert!(ready_builder.destination.is_some());
741        assert!(ready_builder.config.is_some());
742        assert!(ready_builder.identity_provider.is_some());
743        assert!(ready_builder.identity_verifier.is_some());
744        assert!(ready_builder.tx.is_some());
745        assert!(ready_builder.tx_to_session_layer.is_some());
746    }
747
748    #[test]
749    fn test_builder_different_target_types() {
750        // Test that different target types can be created independently
751        let _controller_builder = SessionBuilder::<
752            MockTokenProvider,
753            MockVerifier,
754            ForController,
755            NotReady,
756        >::for_controller();
757        let _participant_builder = SessionBuilder::<
758            MockTokenProvider,
759            MockVerifier,
760            ForParticipant,
761            NotReady,
762        >::for_participant();
763        let _moderator_builder = SessionBuilder::<
764            MockTokenProvider,
765            MockVerifier,
766            ForModerator,
767            NotReady,
768        >::for_moderator();
769    }
770
771    #[test]
772    fn test_builder_with_different_config_types() {
773        let config_initiator = create_test_config(true);
774        let config_participant = create_test_config(false);
775
776        let builder1 =
777            SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
778                .with_config(config_initiator);
779        assert!(builder1.config.unwrap().initiator);
780
781        let builder2 =
782            SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
783                .with_config(config_participant);
784        assert!(!builder2.config.unwrap().initiator);
785    }
786
787    #[test]
788    fn test_builder_with_multicast_session_config() {
789        let mut config = create_test_config(true);
790        config.session_type = ProtoSessionType::Multicast;
791
792        let builder =
793            SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
794                .with_config(config);
795
796        assert_eq!(
797            builder.config.unwrap().session_type,
798            ProtoSessionType::Multicast
799        );
800    }
801
802    #[test]
803    fn test_builder_with_mls_enabled() {
804        let mut config = create_test_config(true);
805        config.mls_enabled = true;
806
807        let builder =
808            SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
809                .with_config(config);
810
811        assert!(builder.config.unwrap().mls_enabled);
812    }
813
814    #[test]
815    fn test_builder_with_custom_retry_settings() {
816        let mut config = create_test_config(true);
817        config.max_retries = Some(10);
818        config.interval = Some(std::time::Duration::from_secs(5));
819
820        let builder =
821            SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
822                .with_config(config.clone());
823
824        let stored_config = builder.config.unwrap();
825        assert_eq!(stored_config.max_retries, Some(10));
826        assert_eq!(
827            stored_config.interval,
828            Some(std::time::Duration::from_secs(5))
829        );
830    }
831
832    #[test]
833    fn test_builder_with_metadata() {
834        let mut config = create_test_config(true);
835        let mut metadata = HashMap::new();
836        metadata.insert("key1".to_string(), "value1".to_string());
837        metadata.insert("key2".to_string(), "value2".to_string());
838        config.metadata = metadata.clone();
839
840        let builder =
841            SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
842                .with_config(config);
843
844        let stored_config = builder.config.unwrap();
845        assert_eq!(
846            stored_config.metadata.get("key1"),
847            Some(&"value1".to_string())
848        );
849        assert_eq!(
850            stored_config.metadata.get("key2"),
851            Some(&"value2".to_string())
852        );
853    }
854
855    #[test]
856    fn test_builder_overwrites_previous_values() {
857        // Test that calling builder methods multiple times overwrites previous values
858        let builder =
859            SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
860                .with_id(1)
861                .with_id(2)
862                .with_id(3);
863
864        assert_eq!(builder.id, Some(3));
865
866        let source1 = create_test_name("first");
867        let source2 = create_test_name("second");
868        let builder = SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
869            .with_source(source1)
870            .with_source(source2.clone());
871
872        assert_eq!(builder.source, Some(source2));
873    }
874
875    #[test]
876    fn test_builder_partial_configuration() {
877        // Test that builder can be created with partial configuration
878        // and then completed later
879        let builder =
880            SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
881                .with_id(42);
882
883        assert_eq!(builder.id, Some(42));
884        assert!(builder.source.is_none());
885
886        // Continue building
887        let (tx_to_session, _) = mpsc::channel(10);
888        let builder = builder
889            .with_source(create_test_name("source"))
890            .with_destination(create_test_name("dest"))
891            .with_config(create_test_config(true))
892            .with_identity_provider(MockTokenProvider)
893            .with_identity_verifier(MockVerifier)
894            .with_tx(create_test_transmitter())
895            .with_tx_to_session_layer(tx_to_session);
896
897        assert!(builder.ready().is_ok());
898    }
899
900    #[test]
901    fn test_builder_ready_validation_comprehensive() {
902        // Test that ready() properly validates each field individually
903        let (tx_to_session, _) = mpsc::channel(10);
904
905        // Create a fully configured builder
906        let mut builder =
907            SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller();
908
909        builder.id = Some(1);
910        builder.source = Some(create_test_name("source"));
911        builder.destination = Some(create_test_name("dest"));
912        builder.config = Some(create_test_config(true));
913        builder.identity_provider = Some(MockTokenProvider);
914        builder.identity_verifier = Some(MockVerifier);
915        builder.tx = Some(create_test_transmitter());
916        builder.tx_to_session_layer = Some(tx_to_session);
917
918        // This should succeed
919        assert!(builder.ready().is_ok());
920    }
921
922    #[test]
923    fn test_builder_with_empty_metadata() {
924        let config = create_test_config(true);
925        assert!(config.metadata.is_empty());
926
927        let builder =
928            SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
929                .with_config(config);
930
931        assert!(builder.config.unwrap().metadata.is_empty());
932    }
933
934    #[test]
935    fn test_builder_with_none_retries() {
936        let mut config = create_test_config(true);
937        config.max_retries = None;
938        config.interval = None;
939
940        let builder =
941            SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
942                .with_config(config);
943
944        let stored = builder.config.unwrap();
945        assert_eq!(stored.max_retries, None);
946        assert_eq!(stored.interval, None);
947    }
948
949    #[test]
950    fn test_builder_with_zero_duration() {
951        let mut config = create_test_config(true);
952        config.interval = Some(std::time::Duration::from_secs(0));
953
954        let builder =
955            SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
956                .with_config(config);
957
958        assert_eq!(
959            builder.config.unwrap().interval,
960            Some(std::time::Duration::from_secs(0))
961        );
962    }
963
964    #[test]
965    fn test_builder_with_large_id() {
966        let builder =
967            SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
968                .with_id(u32::MAX);
969
970        assert_eq!(builder.id, Some(u32::MAX));
971    }
972
973    #[test]
974    fn test_builder_type_states() {
975        // Verify that NotReady state has the methods we expect
976        let not_ready =
977            SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller();
978
979        // Can call builder methods
980        let not_ready = not_ready.with_id(1);
981        assert_eq!(not_ready.id, Some(1));
982
983        // PhantomData doesn't affect size
984        assert_eq!(
985            std::mem::size_of::<
986                SessionBuilder<MockTokenProvider, MockVerifier, ForController, NotReady>,
987            >(),
988            std::mem::size_of::<
989                SessionBuilder<MockTokenProvider, MockVerifier, ForController, Ready>,
990            >()
991        );
992    }
993
994    #[test]
995    fn test_builder_clone_safe_types() {
996        // Test that clone-safe types work correctly
997        let provider1 = MockTokenProvider;
998        let provider2 = provider1.clone();
999
1000        let builder1 =
1001            SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
1002                .with_identity_provider(provider1);
1003
1004        let builder2 =
1005            SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
1006                .with_identity_provider(provider2);
1007
1008        assert!(builder1.identity_provider.is_some());
1009        assert!(builder2.identity_provider.is_some());
1010    }
1011
1012    #[test]
1013    fn test_builder_error_message_content() {
1014        let builder =
1015            SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
1016                .with_id(1);
1017
1018        let res = builder.ready();
1019        assert!(res.is_err_and(|e| matches!(e, SessionError::SessionBuilderIncomplete)));
1020    }
1021
1022    #[test]
1023    fn test_builder_with_all_session_types() {
1024        // Test with PointToPoint
1025        let config_p2p = SessionConfig {
1026            session_type: ProtoSessionType::PointToPoint,
1027            max_retries: None,
1028            interval: None,
1029            mls_enabled: false,
1030            initiator: true,
1031            metadata: HashMap::new(),
1032        };
1033
1034        let builder_p2p =
1035            SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
1036                .with_config(config_p2p);
1037        assert_eq!(
1038            builder_p2p.config.unwrap().session_type,
1039            ProtoSessionType::PointToPoint
1040        );
1041
1042        // Test with Multicast
1043        let config_multicast = SessionConfig {
1044            session_type: ProtoSessionType::Multicast,
1045            max_retries: None,
1046            interval: None,
1047            mls_enabled: false,
1048            initiator: true,
1049            metadata: HashMap::new(),
1050        };
1051
1052        let builder_multicast = SessionBuilder::<
1053            MockTokenProvider,
1054            MockVerifier,
1055            ForController,
1056            NotReady,
1057        >::for_controller()
1058        .with_config(config_multicast);
1059        assert_eq!(
1060            builder_multicast.config.unwrap().session_type,
1061            ProtoSessionType::Multicast
1062        );
1063
1064        // Test with Unspecified
1065        let config_unspecified = SessionConfig {
1066            session_type: ProtoSessionType::Unspecified,
1067            max_retries: None,
1068            interval: None,
1069            mls_enabled: false,
1070            initiator: false,
1071            metadata: HashMap::new(),
1072        };
1073
1074        let builder_unspecified = SessionBuilder::<
1075            MockTokenProvider,
1076            MockVerifier,
1077            ForController,
1078            NotReady,
1079        >::for_controller()
1080        .with_config(config_unspecified);
1081        assert_eq!(
1082            builder_unspecified.config.unwrap().session_type,
1083            ProtoSessionType::Unspecified
1084        );
1085    }
1086
1087    // ========== ASYNC TESTS ==========
1088
1089    #[tokio::test]
1090    async fn test_builder_build_as_participant() {
1091        let (tx_to_session, _rx_from_session) = mpsc::channel(10);
1092        let config = create_test_config(false); // participant (not initiator)
1093
1094        let builder =
1095            SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
1096                .with_id(1)
1097                .with_source(create_test_name("participant"))
1098                .with_destination(create_test_name("moderator"))
1099                .with_config(config)
1100                .with_identity_provider(MockTokenProvider)
1101                .with_identity_verifier(MockVerifier)
1102                .with_tx(create_test_transmitter())
1103                .with_tx_to_session_layer(tx_to_session);
1104
1105        let controller = builder.ready().unwrap().build();
1106
1107        // Should succeed for participant
1108        assert!(controller.is_ok());
1109        let controller = controller.unwrap();
1110        assert_eq!(controller.id(), 1);
1111        assert!(!controller.is_initiator());
1112    }
1113
1114    #[tokio::test]
1115    async fn test_builder_build_as_moderator_p2p() {
1116        let (tx_to_session, _rx_from_session) = mpsc::channel(10);
1117        let (slim_tx, mut slim_rx) = mpsc::channel(10);
1118        let (app_tx, _app_rx) = mpsc::unbounded_channel();
1119        let tx = SessionTransmitter::new(slim_tx, app_tx);
1120
1121        let mut config = create_test_config(true); // moderator (initiator)
1122        config.session_type = ProtoSessionType::PointToPoint;
1123
1124        let builder =
1125            SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
1126                .with_id(2)
1127                .with_source(create_test_name("moderator"))
1128                .with_destination(create_test_name("participant"))
1129                .with_config(config)
1130                .with_identity_provider(MockTokenProvider)
1131                .with_identity_verifier(MockVerifier)
1132                .with_tx(tx)
1133                .with_tx_to_session_layer(tx_to_session);
1134
1135        let controller = builder.ready().unwrap().build();
1136
1137        assert!(controller.is_ok());
1138        let controller = controller.unwrap();
1139        assert_eq!(controller.id(), 2);
1140        assert!(controller.is_initiator());
1141        assert_eq!(controller.session_type(), ProtoSessionType::PointToPoint);
1142
1143        // For P2P initiator, should send discovery request
1144        // Check that a message was sent to slim
1145        tokio::time::timeout(std::time::Duration::from_millis(100), slim_rx.recv())
1146            .await
1147            .ok();
1148    }
1149
1150    #[tokio::test]
1151    async fn test_builder_build_as_moderator_multicast() {
1152        let (tx_to_session, _rx_from_session) = mpsc::channel(10);
1153        let (slim_tx, mut slim_rx) = mpsc::channel(10);
1154        let (app_tx, _app_rx) = mpsc::unbounded_channel();
1155        let tx = SessionTransmitter::new(slim_tx, app_tx);
1156
1157        let mut config = create_test_config(true); // moderator (initiator)
1158        config.session_type = ProtoSessionType::Multicast;
1159
1160        let builder =
1161            SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
1162                .with_id(3)
1163                .with_source(create_test_name("moderator"))
1164                .with_destination(create_test_name("group"))
1165                .with_config(config)
1166                .with_identity_provider(MockTokenProvider)
1167                .with_identity_verifier(MockVerifier)
1168                .with_tx(tx)
1169                .with_tx_to_session_layer(tx_to_session);
1170
1171        let controller = builder.ready().unwrap().build();
1172
1173        assert!(controller.is_ok());
1174        let controller = controller.unwrap();
1175        assert_eq!(controller.id(), 3);
1176        assert!(controller.is_initiator());
1177        assert_eq!(controller.session_type(), ProtoSessionType::Multicast);
1178
1179        // For Multicast, should NOT send discovery request automatically
1180        // Try to receive with timeout - should timeout (no message)
1181        let result =
1182            tokio::time::timeout(std::time::Duration::from_millis(50), slim_rx.recv()).await;
1183        // Either timeout or channel is empty
1184        assert!(result.is_err() || result.unwrap().is_none());
1185    }
1186
1187    #[tokio::test]
1188    async fn test_builder_build_with_mls_disabled() {
1189        let (tx_to_session, _) = mpsc::channel(10);
1190        let mut config = create_test_config(false);
1191        config.mls_enabled = false;
1192
1193        let builder =
1194            SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
1195                .with_id(4)
1196                .with_source(create_test_name("source"))
1197                .with_destination(create_test_name("dest"))
1198                .with_config(config)
1199                .with_identity_provider(MockTokenProvider)
1200                .with_identity_verifier(MockVerifier)
1201                .with_tx(create_test_transmitter())
1202                .with_tx_to_session_layer(tx_to_session);
1203
1204        let controller = builder.ready().unwrap().build();
1205        assert!(controller.is_ok());
1206    }
1207
1208    #[tokio::test]
1209    async fn test_builder_build_with_custom_retry_settings() {
1210        let (tx_to_session, _) = mpsc::channel(10);
1211        let mut config = create_test_config(true);
1212        config.max_retries = Some(5);
1213        config.interval = Some(std::time::Duration::from_millis(500));
1214
1215        let builder =
1216            SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
1217                .with_id(5)
1218                .with_source(create_test_name("source"))
1219                .with_destination(create_test_name("dest"))
1220                .with_config(config.clone())
1221                .with_identity_provider(MockTokenProvider)
1222                .with_identity_verifier(MockVerifier)
1223                .with_tx(create_test_transmitter())
1224                .with_tx_to_session_layer(tx_to_session);
1225
1226        let controller = builder.ready().unwrap().build();
1227        assert!(controller.is_ok());
1228
1229        let controller = controller.unwrap();
1230        let retrieved_config = controller.session_config();
1231        assert_eq!(retrieved_config.max_retries, Some(5));
1232        assert_eq!(
1233            retrieved_config.interval,
1234            Some(std::time::Duration::from_millis(500))
1235        );
1236    }
1237
1238    #[tokio::test]
1239    async fn test_builder_build_with_metadata() {
1240        let (tx_to_session, _) = mpsc::channel(10);
1241        let mut config = create_test_config(false);
1242        let mut metadata = HashMap::new();
1243        metadata.insert("app_name".to_string(), "test_app".to_string());
1244        metadata.insert("version".to_string(), "1.0".to_string());
1245        config.metadata = metadata.clone();
1246
1247        let builder =
1248            SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
1249                .with_id(6)
1250                .with_source(create_test_name("source"))
1251                .with_destination(create_test_name("dest"))
1252                .with_config(config)
1253                .with_identity_provider(MockTokenProvider)
1254                .with_identity_verifier(MockVerifier)
1255                .with_tx(create_test_transmitter())
1256                .with_tx_to_session_layer(tx_to_session);
1257
1258        let controller = builder.ready().unwrap().build();
1259        assert!(controller.is_ok());
1260
1261        let controller = controller.unwrap();
1262        let retrieved_metadata = controller.metadata();
1263        assert_eq!(
1264            retrieved_metadata.get("app_name"),
1265            Some(&"test_app".to_string())
1266        );
1267        assert_eq!(retrieved_metadata.get("version"), Some(&"1.0".to_string()));
1268    }
1269
1270    #[tokio::test]
1271    async fn test_builder_build_verifies_session_source_and_destination() {
1272        let (tx_to_session, _) = mpsc::channel(10);
1273        let source = create_test_name("my_source");
1274        let destination = create_test_name("my_dest");
1275
1276        let builder =
1277            SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
1278                .with_id(7)
1279                .with_source(source.clone())
1280                .with_destination(destination.clone())
1281                .with_config(create_test_config(false))
1282                .with_identity_provider(MockTokenProvider)
1283                .with_identity_verifier(MockVerifier)
1284                .with_tx(create_test_transmitter())
1285                .with_tx_to_session_layer(tx_to_session);
1286
1287        let controller = builder.ready().unwrap().build();
1288        assert!(controller.is_ok());
1289
1290        let controller = controller.unwrap();
1291        assert_eq!(controller.source(), &source);
1292        assert_eq!(controller.dst(), &destination);
1293    }
1294
1295    #[tokio::test]
1296    async fn test_builder_build_multiple_sessions_different_ids() {
1297        let (tx_to_session1, _) = mpsc::channel(10);
1298        let (tx_to_session2, _) = mpsc::channel(10);
1299
1300        let builder1 =
1301            SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
1302                .with_id(100)
1303                .with_source(create_test_name("source1"))
1304                .with_destination(create_test_name("dest1"))
1305                .with_config(create_test_config(false))
1306                .with_identity_provider(MockTokenProvider)
1307                .with_identity_verifier(MockVerifier)
1308                .with_tx(create_test_transmitter())
1309                .with_tx_to_session_layer(tx_to_session1);
1310
1311        let builder2 =
1312            SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
1313                .with_id(200)
1314                .with_source(create_test_name("source2"))
1315                .with_destination(create_test_name("dest2"))
1316                .with_config(create_test_config(true))
1317                .with_identity_provider(MockTokenProvider)
1318                .with_identity_verifier(MockVerifier)
1319                .with_tx(create_test_transmitter())
1320                .with_tx_to_session_layer(tx_to_session2);
1321
1322        let controller1 = builder1.ready().unwrap().build();
1323        let controller2 = builder2.ready().unwrap().build();
1324
1325        assert!(controller1.is_ok());
1326        assert!(controller2.is_ok());
1327
1328        let controller1 = controller1.unwrap();
1329        let controller2 = controller2.unwrap();
1330
1331        assert_eq!(controller1.id(), 100);
1332        assert_eq!(controller2.id(), 200);
1333        assert_ne!(controller1.id(), controller2.id());
1334    }
1335
1336    #[tokio::test]
1337    async fn test_builder_build_with_unspecified_session_type() {
1338        let (tx_to_session, _) = mpsc::channel(10);
1339        let mut config = create_test_config(false);
1340        config.session_type = ProtoSessionType::Unspecified;
1341
1342        let builder =
1343            SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
1344                .with_id(8)
1345                .with_source(create_test_name("source"))
1346                .with_destination(create_test_name("dest"))
1347                .with_config(config)
1348                .with_identity_provider(MockTokenProvider)
1349                .with_identity_verifier(MockVerifier)
1350               .with_tx(create_test_transmitter())
1351                .with_tx_to_session_layer(tx_to_session);
1352
1353        let controller = builder.ready().unwrap().build();
1354        assert!(controller.is_ok());
1355
1356        let controller = controller.unwrap();
1357        assert_eq!(controller.session_type(), ProtoSessionType::Unspecified);
1358    }
1359}