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