Skip to main content

slim_bindings/
service.rs

1// Copyright AGNTCY Contributors (https://github.com/agntcy)
2// SPDX-License-Identifier: Apache-2.0
3
4use std::sync::Arc;
5
6use crate::client_config::ClientConfig;
7use crate::errors::SlimError;
8use crate::get_runtime;
9use crate::identity_config::{IdentityProviderConfig, IdentityVerifierConfig};
10use crate::server_config::ServerConfig;
11use slim_auth::auth_provider::{AuthProvider, AuthVerifier};
12use slim_auth::traits::{TokenProvider, Verifier};
13use slim_config::auth::identity::{
14    IdentityProviderConfig as CoreIdentityProviderConfig,
15    IdentityVerifierConfig as CoreIdentityVerifierConfig,
16};
17use slim_config::component::Component;
18use slim_config::component::id::{ID, Kind};
19use slim_config::grpc::client::ClientConfig as CoreClientConfig;
20use slim_config::grpc::server::ServerConfig as CoreServerConfig;
21use slim_controller::config::Config as CoreControllerConfig;
22use slim_datapath::messages::Name as SlimName;
23use slim_service::{
24    KIND, Service as SlimService, ServiceConfiguration as SlimServiceConfiguration,
25};
26use tokio::task::JoinHandle;
27
28use crate::name::Name;
29
30/// DataPlane configuration wrapper for uniffi bindings
31#[derive(Clone, Default, uniffi::Record)]
32pub struct DataplaneConfig {
33    /// DataPlane GRPC server settings
34    pub servers: Vec<ServerConfig>,
35    /// DataPlane client configs
36    pub clients: Vec<ClientConfig>,
37}
38
39impl From<DataplaneConfig> for CoreControllerConfig {
40    fn from(config: DataplaneConfig) -> Self {
41        let mut core_config = CoreControllerConfig::new();
42        core_config.servers = config
43            .servers
44            .into_iter()
45            .map(|s| {
46                let core: CoreServerConfig = s.into();
47                core
48            })
49            .collect();
50        core_config.clients = config
51            .clients
52            .into_iter()
53            .map(|c| {
54                let core: CoreClientConfig = c.into();
55                core
56            })
57            .collect();
58        core_config.token_provider = CoreIdentityProviderConfig::None;
59        core_config.token_verifier = CoreIdentityVerifierConfig::None;
60        core_config
61    }
62}
63
64impl From<CoreControllerConfig> for DataplaneConfig {
65    fn from(config: CoreControllerConfig) -> Self {
66        Self {
67            servers: config.servers.into_iter().map(|s| s.into()).collect(),
68            clients: config.clients.into_iter().map(|c| c.into()).collect(),
69        }
70    }
71}
72
73/// Service configuration wrapper for uniffi bindings
74#[derive(Clone, Default, uniffi::Record)]
75pub struct ServiceConfig {
76    /// Optional node ID for the service
77    pub node_id: Option<String>,
78
79    /// Optional group name for the service
80    pub group_name: Option<String>,
81
82    /// DataPlane configuration (servers and clients)
83    pub dataplane: DataplaneConfig,
84}
85
86impl ServiceConfig {
87    pub fn new() -> Self {
88        Self {
89            node_id: None,
90            group_name: None,
91            dataplane: DataplaneConfig::default(),
92        }
93    }
94}
95
96impl From<ServiceConfig> for SlimServiceConfiguration {
97    fn from(config: ServiceConfig) -> Self {
98        let mut core_config = SlimServiceConfiguration::new();
99        core_config.node_id = config.node_id;
100        core_config.group_name = config.group_name;
101        core_config.dataplane = config.dataplane.into();
102        core_config
103    }
104}
105
106impl From<SlimServiceConfiguration> for ServiceConfig {
107    fn from(config: SlimServiceConfiguration) -> Self {
108        Self {
109            node_id: config.node_id,
110            group_name: config.group_name,
111            dataplane: config.dataplane.into(),
112        }
113    }
114}
115
116impl From<&SlimServiceConfiguration> for ServiceConfig {
117    fn from(config: &SlimServiceConfiguration) -> Self {
118        Self {
119            node_id: config.node_id.clone(),
120            group_name: config.group_name.clone(),
121            dataplane: config.dataplane.clone().into(),
122        }
123    }
124}
125
126/// Service wrapper for uniffi bindings
127#[derive(uniffi::Object)]
128pub struct Service {
129    pub(crate) inner: Arc<SlimService>,
130}
131
132impl Service {
133    /// Get a clone of the inner service Arc for advanced use cases
134    pub fn inner(&self) -> Arc<SlimService> {
135        self.inner.clone()
136    }
137}
138
139/// Conversion traits
140impl From<SlimService> for Service {
141    fn from(service: SlimService) -> Self {
142        Service {
143            inner: Arc::new(service),
144        }
145    }
146}
147
148#[uniffi::export]
149impl Service {
150    /// Create a new Service with the given name
151    #[uniffi::constructor]
152    pub fn new(name: String) -> Self {
153        let kind = Kind::new(KIND).expect("Invalid service kind");
154        let id = ID::new_with_name(kind, &name).expect("Invalid service name");
155        let service = SlimService::new(id);
156        Service {
157            inner: Arc::new(service),
158        }
159    }
160
161    /// Create a new Service with configuration
162    #[uniffi::constructor]
163    pub fn new_with_config(name: String, config: ServiceConfig) -> Self {
164        let kind = Kind::new(KIND).expect("Invalid service kind");
165        let id = ID::new_with_name(kind, &name).expect("Invalid service name");
166        let core_config: SlimServiceConfiguration = config.into();
167        let service = SlimService::new_with_config(id, core_config);
168        Service {
169            inner: Arc::new(service),
170        }
171    }
172
173    /// Get the service configuration
174    pub fn config(&self) -> ServiceConfig {
175        self.inner.config().clone().into()
176    }
177
178    /// Get the service identifier/name
179    pub fn get_name(&self) -> String {
180        self.inner.identifier().to_string()
181    }
182
183    /// Run the service (starts all configured servers and clients)
184    pub async fn run_async(&self) -> Result<(), SlimError> {
185        self.inner.run().await?;
186        Ok(())
187    }
188
189    /// Run the service (starts all configured servers and clients) - blocking version
190    pub fn run(&self) -> Result<(), SlimError> {
191        crate::config::get_runtime().block_on(self.run_async())
192    }
193
194    /// Shutdown the service gracefully
195    pub async fn shutdown_async(&self) -> Result<(), SlimError> {
196        self.inner.shutdown().await?;
197        Ok(())
198    }
199
200    /// Shutdown the service gracefully - blocking version
201    pub fn shutdown(&self) -> Result<(), SlimError> {
202        crate::config::get_runtime().block_on(self.shutdown_async())
203    }
204
205    /// Start a server with the given configuration
206    pub async fn run_server_async(&self, config: ServerConfig) -> Result<(), SlimError> {
207        // Use the runtime handle to spawn the task, ensuring proper tokio context
208        let runtime = get_runtime();
209        let core_config: slim_config::grpc::server::ServerConfig = config.into();
210        let inner = self.inner.clone();
211
212        // Spawn on the runtime's handle to ensure tokio context is available
213        let handle: JoinHandle<Result<(), SlimError>> = runtime.handle().spawn(async move {
214            inner.run_server(&core_config).await?;
215            Ok(())
216        });
217
218        handle.await.map_err(|e| SlimError::ServiceError {
219            message: format!("Failed to join server task: {}", e),
220        })?
221    }
222
223    /// Start a server with the given configuration - blocking version
224    pub fn run_server(&self, config: ServerConfig) -> Result<(), SlimError> {
225        crate::config::get_runtime().block_on(self.run_server_async(config))
226    }
227
228    /// Stop a server by endpoint - blocking version
229    pub fn stop_server(&self, endpoint: String) -> Result<(), SlimError> {
230        self.inner.stop_server(&endpoint)?;
231        Ok(())
232    }
233
234    /// Connect to a remote endpoint as a client
235    pub async fn connect_async(&self, config: ClientConfig) -> Result<u64, SlimError> {
236        let core_config: slim_config::grpc::client::ClientConfig = config.into();
237        let inner = self.inner.clone();
238        let runtime = get_runtime();
239
240        // Spawn in tokio runtime since connect internally uses tokio::spawn
241        let handle = runtime.spawn(async move { inner.connect(&core_config).await });
242
243        let result = handle.await.map_err(|e| SlimError::InternalError {
244            message: format!("Failed to join connect task: {}", e),
245        })?;
246
247        Ok(result?)
248    }
249
250    /// Connect to a remote endpoint as a client - blocking version
251    pub fn connect(&self, config: ClientConfig) -> Result<u64, SlimError> {
252        crate::config::get_runtime().block_on(self.connect_async(config))
253    }
254
255    /// Disconnect a client connection by connection ID - blocking version
256    pub fn disconnect(&self, conn_id: u64) -> Result<(), SlimError> {
257        self.inner.disconnect(conn_id)?;
258        Ok(())
259    }
260
261    /// Get the connection ID for a given endpoint
262    pub fn get_connection_id(&self, endpoint: String) -> Option<u64> {
263        self.inner.get_connection_id(&endpoint)
264    }
265
266    /// Create a new App with authentication configuration (async version)
267    ///
268    /// This method initializes authentication providers/verifiers and creates a App
269    /// on this service instance.
270    ///
271    /// # Arguments
272    /// * `base_name` - The base name for the app (without ID)
273    /// * `identity_provider_config` - Configuration for proving identity to others
274    /// * `identity_verifier_config` - Configuration for verifying identity of others
275    ///
276    /// # Returns
277    /// * `Ok(Arc<App>)` - Successfully created adapter
278    /// * `Err(SlimError)` - If adapter creation fails
279    pub async fn create_app_async(
280        &self,
281        base_name: Arc<Name>,
282        identity_provider_config: IdentityProviderConfig,
283        identity_verifier_config: IdentityVerifierConfig,
284    ) -> Result<Arc<crate::app::App>, SlimError> {
285        let slim_name: SlimName = base_name.as_ref().into();
286        create_app_async_internal(
287            slim_name,
288            identity_provider_config,
289            identity_verifier_config,
290            self.inner.clone(),
291            crate::app::Direction::Bidirectional,
292        )
293        .await
294        .map(Arc::new)
295    }
296
297    /// Create a new App with authentication configuration and traffic direction (async version)
298    ///
299    /// This method initializes authentication providers/verifiers and creates an App
300    /// on this service instance. The direction parameter controls whether the app
301    /// can send messages, receive messages, both, or neither.
302    ///
303    /// # Arguments
304    /// * `base_name` - The base name for the app (without ID)
305    /// * `identity_provider_config` - Configuration for proving identity to others
306    /// * `identity_verifier_config` - Configuration for verifying identity of others
307    /// * `direction` - Traffic direction: Send, Recv, Bidirectional, or None
308    ///
309    /// # Returns
310    /// * `Ok(Arc<App>)` - Successfully created adapter
311    /// * `Err(SlimError)` - If adapter creation fails
312    pub async fn create_app_with_direction_async(
313        &self,
314        name: Arc<Name>,
315        identity_provider_config: IdentityProviderConfig,
316        identity_verifier_config: IdentityVerifierConfig,
317        direction: crate::app::Direction,
318    ) -> Result<Arc<crate::app::App>, SlimError> {
319        let slim_name: SlimName = name.as_ref().into();
320        create_app_async_internal(
321            slim_name,
322            identity_provider_config,
323            identity_verifier_config,
324            self.inner.clone(),
325            direction,
326        )
327        .await
328        .map(Arc::new)
329    }
330
331    /// Create a new App with SharedSecret authentication (async version)
332    ///
333    /// This is a convenience function for creating a SLIM application using SharedSecret authentication
334    /// on this service instance. This is the async version.
335    ///
336    /// # Arguments
337    /// * `name` - The base name for the app (without ID)
338    /// * `secret` - The shared secret string for authentication
339    ///
340    /// # Returns
341    /// * `Ok(Arc<App>)` - Successfully created app
342    /// * `Err(SlimError)` - If app creation fails
343    pub async fn create_app_with_secret_async(
344        &self,
345        name: Arc<Name>,
346        secret: String,
347    ) -> Result<Arc<crate::app::App>, SlimError> {
348        let identity_provider_config = IdentityProviderConfig::SharedSecret {
349            id: name.to_string(),
350            data: secret.clone(),
351        };
352        let identity_verifier_config = IdentityVerifierConfig::SharedSecret {
353            id: name.to_string(),
354            data: secret,
355        };
356
357        self.create_app_async(name, identity_provider_config, identity_verifier_config)
358            .await
359    }
360
361    /// Create a new App with authentication configuration (blocking version)
362    ///
363    /// This method initializes authentication providers/verifiers and creates a App
364    /// on this service instance. This is a blocking wrapper around create_app_async.
365    ///
366    /// # Arguments
367    /// * `base_name` - The base name for the app (without ID)
368    /// * `identity_provider_config` - Configuration for proving identity to others
369    /// * `identity_verifier_config` - Configuration for verifying identity of others
370    ///
371    /// # Returns
372    /// * `Ok(Arc<App>)` - Successfully created adapter
373    /// * `Err(SlimError)` - If adapter creation fails
374    #[uniffi::method]
375    pub fn create_app(
376        &self,
377        base_name: Arc<Name>,
378        identity_provider_config: IdentityProviderConfig,
379        identity_verifier_config: IdentityVerifierConfig,
380    ) -> Result<Arc<crate::app::App>, SlimError> {
381        get_runtime().block_on(self.create_app_async(
382            base_name,
383            identity_provider_config,
384            identity_verifier_config,
385        ))
386    }
387
388    /// Create a new App with authentication configuration and traffic direction (blocking version)
389    ///
390    /// This method initializes authentication providers/verifiers and creates an App
391    /// on this service instance. The direction parameter controls whether the app
392    /// can send messages, receive messages, both, or neither.
393    ///
394    /// # Arguments
395    /// * `base_name` - The base name for the app (without ID)
396    /// * `identity_provider_config` - Configuration for proving identity to others
397    /// * `identity_verifier_config` - Configuration for verifying identity of others
398    /// * `direction` - Traffic direction: Send, Recv, Bidirectional, or None
399    ///
400    /// # Returns
401    /// * `Ok(Arc<App>)` - Successfully created adapter
402    /// * `Err(SlimError)` - If adapter creation fails
403    pub fn create_app_with_direction(
404        &self,
405        base_name: Arc<Name>,
406        identity_provider_config: IdentityProviderConfig,
407        identity_verifier_config: IdentityVerifierConfig,
408        direction: crate::app::Direction,
409    ) -> Result<Arc<crate::app::App>, SlimError> {
410        get_runtime().block_on(self.create_app_with_direction_async(
411            base_name,
412            identity_provider_config,
413            identity_verifier_config,
414            direction,
415        ))
416    }
417
418    /// Create a new App with SharedSecret authentication (helper function)
419    ///
420    /// This is a convenience function for creating a SLIM application using SharedSecret authentication
421    /// on this service instance.
422    ///
423    /// # Arguments
424    /// * `name` - The base name for the app (without ID)
425    /// * `secret` - The shared secret string for authentication
426    ///
427    /// # Returns
428    /// * `Ok(Arc<App>)` - Successfully created app
429    /// * `Err(SlimError)` - If app creation fails
430    #[uniffi::method]
431    pub fn create_app_with_secret(
432        &self,
433        name: Arc<Name>,
434        secret: String,
435    ) -> Result<Arc<crate::app::App>, SlimError> {
436        get_runtime().block_on(self.create_app_with_secret_async(name, secret))
437    }
438}
439
440/// Internal async app creation logic (used by both service and app constructors)
441///
442/// This is the core implementation shared by Service::create_app_async and App::new_async_with_service.
443///
444/// # Arguments
445/// * `base_name` - The base name for the app (without ID)
446/// * `identity_provider_config` - Configuration for proving identity to others
447/// * `identity_verifier_config` - Configuration for verifying identity of others
448/// * `service` - The service instance to use for creating the app
449///
450/// # Returns
451/// * `Ok(App)` - Successfully created app
452/// * `Err(SlimError)` - If app creation fails
453pub(crate) async fn create_app_async_internal(
454    base_name: SlimName,
455    identity_provider_config: IdentityProviderConfig,
456    identity_verifier_config: IdentityVerifierConfig,
457    service: Arc<SlimService>,
458    direction: crate::app::Direction,
459) -> Result<crate::app::App, SlimError> {
460    // Convert configurations to actual providers/verifiers
461    let mut identity_provider: AuthProvider = identity_provider_config.try_into()?;
462    let mut identity_verifier: AuthVerifier = identity_verifier_config.try_into()?;
463
464    // Initialize the identity provider
465    identity_provider.initialize().await?;
466
467    // Initialize the identity verifier
468    identity_verifier.initialize().await?;
469
470    // Validate token
471    let _identity_token = identity_provider.get_token()?;
472
473    // Create the app using the provided service
474    let (app, rx) = service.create_app_with_direction(
475        &base_name,
476        identity_provider,
477        identity_verifier,
478        direction.into(),
479    )?;
480
481    Ok(crate::app::App::from_parts(
482        Arc::new(app),
483        Arc::new(tokio::sync::RwLock::new(rx)),
484        service,
485    ))
486}
487
488/// Create a new ServiceConfiguration
489#[uniffi::export]
490pub fn new_service_configuration() -> ServiceConfig {
491    ServiceConfig::new()
492}
493
494/// Create a new DataplaneConfig
495#[uniffi::export]
496pub fn new_dataplane_config() -> DataplaneConfig {
497    DataplaneConfig::default()
498}
499
500/// Create a new Service with builder pattern
501#[uniffi::export]
502pub fn create_service(name: String) -> Result<Arc<Service>, SlimError> {
503    Ok(Arc::new(Service::new(name)))
504}
505
506/// Create a new Service with configuration
507#[uniffi::export]
508pub fn create_service_with_config(
509    name: String,
510    config: ServiceConfig,
511) -> Result<Arc<Service>, SlimError> {
512    Ok(Arc::new(Service::new_with_config(name, config)))
513}
514
515#[cfg(test)]
516mod tests {
517    use super::*;
518    use slim_datapath::messages::Name as SlimName;
519    use slim_testing::utils::TEST_VALID_SECRET;
520
521    use crate::app::App;
522    use crate::config::get_global_service;
523    use crate::identity_config::{IdentityProviderConfig, IdentityVerifierConfig};
524    use crate::name::Name;
525
526    /// Create test authentication configurations
527    fn create_test_auth() -> (IdentityProviderConfig, IdentityVerifierConfig) {
528        let provider = IdentityProviderConfig::SharedSecret {
529            id: "test-service".to_string(),
530            data: TEST_VALID_SECRET.to_string(),
531        };
532        let verifier = IdentityVerifierConfig::SharedSecret {
533            id: "test-service".to_string(),
534            data: TEST_VALID_SECRET.to_string(),
535        };
536        (provider, verifier)
537    }
538
539    /// Create test app name
540    fn create_test_name() -> SlimName {
541        SlimName::from_strings(["org", "namespace", "test-app"])
542    }
543
544    // ========================================================================
545    // Configuration Tests
546    // ========================================================================
547
548    #[test]
549    fn test_dataplane_config_default() {
550        let config = DataplaneConfig::default();
551        assert!(config.servers.is_empty());
552        assert!(config.clients.is_empty());
553    }
554
555    #[test]
556    fn test_dataplane_config_to_core_conversion() {
557        let server_config = ServerConfig::default();
558        let client_config = ClientConfig::default();
559
560        let config = DataplaneConfig {
561            servers: vec![server_config],
562            clients: vec![client_config],
563        };
564
565        let core_config: CoreControllerConfig = config.clone().into();
566        assert_eq!(core_config.servers.len(), 1);
567        assert_eq!(core_config.clients.len(), 1);
568
569        // Verify token provider/verifier are set to None
570        assert!(matches!(
571            core_config.token_provider,
572            CoreIdentityProviderConfig::None
573        ));
574        assert!(matches!(
575            core_config.token_verifier,
576            CoreIdentityVerifierConfig::None
577        ));
578    }
579
580    #[test]
581    fn test_dataplane_config_from_core_conversion() {
582        let mut core_config = CoreControllerConfig::new();
583        core_config.servers = vec![CoreServerConfig::default()];
584        core_config.clients = vec![CoreClientConfig::default()];
585
586        let config: DataplaneConfig = core_config.into();
587        assert_eq!(config.servers.len(), 1);
588        assert_eq!(config.clients.len(), 1);
589    }
590
591    #[test]
592    fn test_dataplane_config_roundtrip() {
593        let original = DataplaneConfig {
594            servers: vec![ServerConfig::default()],
595            clients: vec![ClientConfig::default()],
596        };
597
598        let core: CoreControllerConfig = original.clone().into();
599        let roundtrip: DataplaneConfig = core.into();
600
601        assert_eq!(original.servers.len(), roundtrip.servers.len());
602        assert_eq!(original.clients.len(), roundtrip.clients.len());
603    }
604
605    #[test]
606    fn test_service_configuration_new() {
607        let config = ServiceConfig::new();
608        assert!(config.node_id.is_none());
609        assert!(config.group_name.is_none());
610        assert!(config.dataplane.servers.is_empty());
611        assert!(config.dataplane.clients.is_empty());
612    }
613
614    #[test]
615    fn test_service_configuration_default() {
616        let config = ServiceConfig::default();
617        assert!(config.node_id.is_none());
618        assert!(config.group_name.is_none());
619    }
620
621    #[test]
622    fn test_service_configuration_with_values() {
623        let config = ServiceConfig {
624            node_id: Some("node-123".to_string()),
625            group_name: Some("test-group".to_string()),
626            dataplane: DataplaneConfig::default(),
627        };
628
629        assert_eq!(config.node_id.as_deref(), Some("node-123"));
630        assert_eq!(config.group_name.as_deref(), Some("test-group"));
631    }
632
633    #[test]
634    fn test_service_configuration_to_core_conversion() {
635        let config = ServiceConfig {
636            node_id: Some("node-456".to_string()),
637            group_name: Some("group-abc".to_string()),
638            dataplane: DataplaneConfig::default(),
639        };
640
641        let core_config: SlimServiceConfiguration = config.clone().into();
642        assert_eq!(core_config.node_id.as_deref(), Some("node-456"));
643        assert_eq!(core_config.group_name.as_deref(), Some("group-abc"));
644    }
645
646    #[test]
647    fn test_service_configuration_from_core_conversion() {
648        let mut core_config = SlimServiceConfiguration::new();
649        core_config.node_id = Some("core-node".to_string());
650        core_config.group_name = Some("core-group".to_string());
651
652        let config: ServiceConfig = core_config.into();
653        assert_eq!(config.node_id.as_deref(), Some("core-node"));
654        assert_eq!(config.group_name.as_deref(), Some("core-group"));
655    }
656
657    #[test]
658    fn test_service_configuration_roundtrip() {
659        let original = ServiceConfig {
660            node_id: Some("roundtrip-node".to_string()),
661            group_name: Some("roundtrip-group".to_string()),
662            dataplane: DataplaneConfig::default(),
663        };
664
665        let core: SlimServiceConfiguration = original.clone().into();
666        let roundtrip: ServiceConfig = core.into();
667
668        assert_eq!(original.node_id, roundtrip.node_id);
669        assert_eq!(original.group_name, roundtrip.group_name);
670    }
671
672    // ========================================================================
673    // Service Creation Tests
674    // ========================================================================
675
676    #[test]
677    fn test_service_new() {
678        let service = Service::new("test-service".to_string());
679        let name = service.get_name();
680        assert!(name.contains("test-service"));
681    }
682
683    #[test]
684    fn test_service_new_with_config() {
685        let config = ServiceConfig {
686            node_id: Some("test-node".to_string()),
687            group_name: Some("test-group".to_string()),
688            dataplane: DataplaneConfig::default(),
689        };
690
691        let service = Service::new_with_config("configured-service".to_string(), config.clone());
692        let retrieved_config = service.config();
693
694        assert_eq!(retrieved_config.node_id, config.node_id);
695        assert_eq!(retrieved_config.group_name, config.group_name);
696    }
697
698    #[test]
699    fn test_service_inner_clone() {
700        let service = Service::new("inner-test".to_string());
701        let inner1 = service.inner();
702        let inner2 = service.inner();
703
704        // Both should point to the same Arc
705        assert!(Arc::ptr_eq(&inner1, &inner2));
706    }
707
708    #[test]
709    fn test_service_from_slim_service() {
710        let kind = Kind::new(KIND).expect("Invalid service kind");
711        let id = ID::new_with_name(kind, "from-test").expect("Invalid service name");
712        let slim_service = SlimService::new(id);
713
714        let service: Service = slim_service.into();
715        // Just verify it doesn't panic
716        assert!(Arc::strong_count(&service.inner) >= 1);
717    }
718
719    // ========================================================================
720    // Global Service Tests
721    // ========================================================================
722
723    #[test]
724    fn test_global_service_singleton() {
725        let service1 = get_global_service();
726        let service2 = get_global_service();
727
728        // They should be the same instance (same memory address)
729        assert!(Arc::ptr_eq(&service1, &service2));
730
731        // Also check that the inner Arc is the same
732        assert!(Arc::ptr_eq(&service1.inner, &service2.inner));
733    }
734
735    #[test]
736    fn test_get_global_service_function() {
737        let service1 = get_global_service();
738        let service2 = get_global_service();
739
740        assert!(Arc::ptr_eq(&service1, &service2));
741    }
742
743    #[tokio::test]
744    async fn test_service_arc_sharing() {
745        let base_name = create_test_name();
746        let (provider, verifier) = create_test_auth();
747
748        // Get global service instance
749        let global_service = get_global_service();
750        let ptr1 = Arc::as_ptr(&global_service.inner) as usize;
751
752        // Test global service - just ensure it creates without error
753        let _global_adapter1 =
754            App::new_async(base_name.clone(), provider.clone(), verifier.clone())
755                .await
756                .unwrap();
757
758        // Test global service again - ensure it uses the same Arc
759        let _global_adapter2 = App::new_async(base_name, provider, verifier).await.unwrap();
760
761        let global_service2 = get_global_service();
762        let ptr2 = Arc::as_ptr(&global_service2.inner) as usize;
763
764        // They should point to the same inner Arc
765        assert_eq!(ptr1, ptr2);
766    }
767
768    #[test]
769    fn test_global_service_name() {
770        let name = get_global_service().get_name();
771        assert!(!name.is_empty());
772        assert!(name.contains("global-bindings-service"));
773    }
774
775    // ========================================================================
776    // Service Lifecycle Tests
777    // ========================================================================
778
779    #[tokio::test]
780    async fn test_service_shutdown_without_run() {
781        let service = Service::new("shutdown-test".to_string());
782        // Should not error even if service wasn't run
783        let result = service.shutdown_async().await;
784        // Shutdown might succeed or fail gracefully
785        assert!(result.is_ok() || result.is_err());
786    }
787
788    // ========================================================================
789    // Client/Server Configuration Tests
790    // ========================================================================
791
792    #[test]
793    fn test_stop_nonexistent_server() {
794        let service = Service::new("stop-test".to_string());
795        let result = service.stop_server("127.0.0.1:99999".to_string());
796        // Should fail because server doesn't exist
797        assert!(result.is_err());
798    }
799
800    #[test]
801    fn test_disconnect_invalid_connection() {
802        let service = Service::new("disconnect-test".to_string());
803        let result = service.disconnect(999999);
804        // Should fail because connection doesn't exist
805        assert!(result.is_err());
806    }
807
808    #[tokio::test]
809    async fn test_get_connection_id_nonexistent() {
810        let service = Service::new("conn-id-test".to_string());
811        let conn_id = service.get_connection_id("nonexistent-endpoint".to_string());
812        assert!(conn_id.is_none());
813    }
814
815    // ========================================================================
816    // Adapter Creation Tests
817    // ========================================================================
818
819    #[tokio::test]
820    async fn test_create_adapter_async() {
821        let service = Service::new("adapter-test".to_string());
822        let base_name = Arc::new(Name::new(
823            "org".to_string(),
824            "namespace".to_string(),
825            "adapter-app".to_string(),
826        ));
827        let (provider, verifier) = create_test_auth();
828
829        let result = service
830            .create_app_async(base_name, provider, verifier)
831            .await;
832
833        assert!(result.is_ok(), "Should create adapter successfully");
834        let adapter = result.unwrap();
835        assert!(adapter.id() > 0, "Adapter should have non-zero ID");
836    }
837
838    #[tokio::test]
839    async fn test_create_adapter_with_different_names() {
840        let service = Service::new("multi-adapter-test".to_string());
841        let (provider, verifier) = create_test_auth();
842
843        let names = vec![
844            ("org1", "ns1", "app1"),
845            ("org2", "ns2", "app2"),
846            ("org3", "ns3", "app3"),
847        ];
848
849        for (org, ns, app) in names {
850            let base_name = Arc::new(Name::new(org.to_string(), ns.to_string(), app.to_string()));
851
852            let result = service
853                .create_app_async(base_name, provider.clone(), verifier.clone())
854                .await;
855
856            assert!(
857                result.is_ok(),
858                "Should create adapter for {}/{}/{}",
859                org,
860                ns,
861                app
862            );
863        }
864    }
865
866    #[tokio::test]
867    async fn test_create_adapter_unique_ids() {
868        // Each adapter gets a unique ID due to token generation
869        let service = Service::new("unique-ids-test".to_string());
870        let base_name = Arc::new(Name::new(
871            "org".to_string(),
872            "namespace".to_string(),
873            "unique-app".to_string(),
874        ));
875        let (provider, verifier) = create_test_auth();
876
877        let adapter1 = service
878            .create_app_async(base_name.clone(), provider.clone(), verifier.clone())
879            .await
880            .expect("Failed to create first adapter");
881
882        let adapter2 = service
883            .create_app_async(base_name, provider, verifier)
884            .await
885            .expect("Failed to create second adapter");
886
887        // IDs should be different since each adapter gets a fresh token
888        // (tokens include timestamps, so they're unique per creation)
889        assert_ne!(adapter1.id(), adapter2.id());
890
891        // Both IDs should be non-zero
892        assert!(adapter1.id() > 0);
893        assert!(adapter2.id() > 0);
894    }
895
896    #[tokio::test]
897    async fn test_create_app_with_secret_async() {
898        let service = Service::new("secret-app-test".to_string());
899        let base_name = Arc::new(Name::new(
900            "org".to_string(),
901            "namespace".to_string(),
902            "secret-app".to_string(),
903        ));
904        let secret = TEST_VALID_SECRET.to_string();
905
906        let result = service
907            .create_app_with_secret_async(base_name, secret)
908            .await;
909
910        assert!(result.is_ok(), "Should create app with secret successfully");
911        let app = result.unwrap();
912        assert!(app.id() > 0, "App should have non-zero ID");
913    }
914
915    #[tokio::test]
916    async fn test_create_app_with_secret_multiple() {
917        let service = Service::new("multi-secret-app-test".to_string());
918        let secret = TEST_VALID_SECRET.to_string();
919
920        let names = vec![
921            ("org1", "ns1", "secret-app1"),
922            ("org2", "ns2", "secret-app2"),
923            ("org3", "ns3", "secret-app3"),
924        ];
925
926        for (org, ns, app) in names {
927            let base_name = Arc::new(Name::new(org.to_string(), ns.to_string(), app.to_string()));
928
929            let result = service
930                .create_app_with_secret_async(base_name, secret.clone())
931                .await;
932
933            assert!(
934                result.is_ok(),
935                "Should create secret app for {}/{}/{}",
936                org,
937                ns,
938                app
939            );
940        }
941    }
942
943    #[test]
944    fn test_create_app_blocking() {
945        let service = Service::new("blocking-app-test".to_string());
946        let base_name = Arc::new(Name::new(
947            "org".to_string(),
948            "namespace".to_string(),
949            "blocking-app".to_string(),
950        ));
951        let (provider, verifier) = create_test_auth();
952
953        let result = service.create_app(base_name, provider, verifier);
954
955        assert!(result.is_ok(), "Should create app in blocking mode");
956        let app = result.unwrap();
957        assert!(app.id() > 0, "App should have non-zero ID");
958    }
959
960    #[test]
961    fn test_create_app_with_secret_blocking() {
962        let service = Service::new("blocking-secret-app-test".to_string());
963        let base_name = Arc::new(Name::new(
964            "org".to_string(),
965            "namespace".to_string(),
966            "blocking-secret-app".to_string(),
967        ));
968        let secret = TEST_VALID_SECRET.to_string();
969
970        let result = service.create_app_with_secret(base_name, secret);
971
972        assert!(result.is_ok(), "Should create secret app in blocking mode");
973        let app = result.unwrap();
974        assert!(app.id() > 0, "App should have non-zero ID");
975    }
976
977    #[tokio::test]
978    async fn test_create_app_async_internal_directly() {
979        let service = Service::new("internal-test".to_string());
980        let base_name = create_test_name();
981        let (provider, verifier) = create_test_auth();
982
983        let result = create_app_async_internal(
984            base_name,
985            provider,
986            verifier,
987            service.inner.clone(),
988            crate::app::Direction::Bidirectional,
989        )
990        .await;
991
992        assert!(result.is_ok(), "Should create app via internal function");
993        let app = result.unwrap();
994        assert!(app.id() > 0, "App should have non-zero ID");
995    }
996
997    #[tokio::test]
998    async fn test_create_app_with_same_secret_different_ids() {
999        // Even with the same secret, different apps should get unique IDs
1000        let service = Service::new("same-secret-test".to_string());
1001        let secret = TEST_VALID_SECRET.to_string();
1002        let base_name = Arc::new(Name::new(
1003            "org".to_string(),
1004            "namespace".to_string(),
1005            "same-secret-app".to_string(),
1006        ));
1007
1008        let app1 = service
1009            .create_app_with_secret_async(base_name.clone(), secret.clone())
1010            .await
1011            .expect("Failed to create first app");
1012
1013        let app2 = service
1014            .create_app_with_secret_async(base_name, secret)
1015            .await
1016            .expect("Failed to create second app");
1017
1018        // IDs should be different due to timestamp in token generation
1019        assert_ne!(app1.id(), app2.id());
1020        assert!(app1.id() > 0);
1021        assert!(app2.id() > 0);
1022    }
1023
1024    // ========================================================================
1025    // Factory Function Tests
1026    // ========================================================================
1027
1028    #[test]
1029    fn test_new_service_configuration() {
1030        let config = new_service_configuration();
1031        assert!(config.node_id.is_none());
1032        assert!(config.group_name.is_none());
1033    }
1034
1035    #[test]
1036    fn test_new_dataplane_config() {
1037        let config = new_dataplane_config();
1038        assert!(config.servers.is_empty());
1039        assert!(config.clients.is_empty());
1040    }
1041
1042    #[test]
1043    fn test_create_service() {
1044        let result = create_service("factory-test".to_string());
1045        assert!(result.is_ok());
1046    }
1047
1048    #[test]
1049    fn test_create_service_with_config() {
1050        let config = ServiceConfig {
1051            node_id: Some("factory-node".to_string()),
1052            group_name: Some("factory-group".to_string()),
1053            dataplane: DataplaneConfig::default(),
1054        };
1055
1056        let result = create_service_with_config("factory-configured-test".to_string(), config);
1057        assert!(result.is_ok());
1058    }
1059
1060    // ========================================================================
1061    // Global Service Convenience Functions Tests
1062    // ========================================================================
1063
1064    #[test]
1065    fn test_global_stop_server_convenience() {
1066        let result = get_global_service().stop_server("127.0.0.1:88888".to_string());
1067        // Should error since server doesn't exist
1068        assert!(result.is_err());
1069    }
1070
1071    #[test]
1072    fn test_global_disconnect_convenience() {
1073        let result = get_global_service().disconnect(888888);
1074        // Should error since connection doesn't exist
1075        assert!(result.is_err());
1076    }
1077
1078    #[test]
1079    fn test_global_get_connection_id_convenience() {
1080        let conn_id = get_global_service().get_connection_id("nonexistent-global".to_string());
1081        assert!(conn_id.is_none());
1082    }
1083
1084    // ========================================================================
1085    // Error Handling Tests
1086    // ========================================================================
1087
1088    #[tokio::test]
1089    async fn test_service_operations_on_uninitialized_service() {
1090        // These operations should handle gracefully even on a fresh service
1091        let service = Service::new("uninitialized-test".to_string());
1092
1093        // Stop server that doesn't exist
1094        let result = service.stop_server("127.0.0.1:11111".to_string());
1095        assert!(result.is_err());
1096
1097        // Disconnect non-existent connection
1098        let result = service.disconnect(11111);
1099        assert!(result.is_err());
1100
1101        // Get non-existent connection ID
1102        let conn_id = service.get_connection_id("fake-endpoint".to_string());
1103        assert!(conn_id.is_none());
1104    }
1105
1106    // ========================================================================
1107    // Integration Tests
1108    // ========================================================================
1109
1110    #[test]
1111    fn test_service_with_multiple_configs() {
1112        let server_config = ServerConfig::default();
1113        let client_config = ClientConfig::default();
1114
1115        let dataplane = DataplaneConfig {
1116            servers: vec![server_config.clone(), server_config],
1117            clients: vec![client_config.clone(), client_config],
1118        };
1119
1120        let service_config = ServiceConfig {
1121            node_id: Some("multi-config-node".to_string()),
1122            group_name: Some("multi-config-group".to_string()),
1123            dataplane,
1124        };
1125
1126        let service = Service::new_with_config("multi-config-service".to_string(), service_config);
1127        let retrieved = service.config();
1128
1129        assert_eq!(retrieved.dataplane.servers.len(), 2);
1130        assert_eq!(retrieved.dataplane.clients.len(), 2);
1131    }
1132
1133    #[test]
1134    fn test_service_config_mutation_isolation() {
1135        let config = ServiceConfig {
1136            node_id: Some("original-node".to_string()),
1137            group_name: Some("original-group".to_string()),
1138            dataplane: DataplaneConfig::default(),
1139        };
1140
1141        let service = Service::new_with_config("isolation-test".to_string(), config.clone());
1142
1143        // Get config and verify it matches
1144        let retrieved = service.config();
1145        assert_eq!(retrieved.node_id, config.node_id);
1146
1147        // Original config should be independent
1148        let mut modified_config = config;
1149        modified_config.node_id = Some("modified-node".to_string());
1150
1151        // Service config should not have changed
1152        let retrieved2 = service.config();
1153        assert_eq!(retrieved2.node_id.as_deref(), Some("original-node"));
1154    }
1155
1156    // ========================================================================
1157    // Runtime Spawning Tests
1158    // ========================================================================
1159
1160    #[tokio::test]
1161    async fn test_run_server_async_runtime_context() {
1162        let service = Service::new("run-server-runtime-test".to_string());
1163        let config = ServerConfig::default();
1164
1165        // This should not panic even though it's spawning on runtime
1166        let result = service.run_server_async(config).await;
1167        // May fail due to address already in use or other reasons, but shouldn't panic
1168        let _ = result; // We just want to ensure it doesn't panic
1169    }
1170
1171    #[tokio::test]
1172    async fn test_connect_async_runtime_context() {
1173        let service = Service::new("connect-runtime-test".to_string());
1174        let config = ClientConfig::default();
1175
1176        // This should not panic even though it's spawning on runtime
1177        let result = service.connect_async(config).await;
1178        // May fail to connect, but shouldn't panic from join error
1179        let _ = result; // We just want to ensure proper error handling
1180    }
1181
1182    #[test]
1183    fn test_run_server_blocking() {
1184        let service = Service::new("run-server-blocking-test".to_string());
1185        let config = ServerConfig::default();
1186
1187        // Test blocking version
1188        let result = service.run_server(config);
1189        // May fail but should not panic
1190        let _ = result;
1191    }
1192
1193    #[test]
1194    fn test_connect_blocking() {
1195        let service = Service::new("connect-blocking-test".to_string());
1196        let config = ClientConfig::default();
1197
1198        // Test blocking version
1199        let result = service.connect(config);
1200        // May fail but should not panic
1201        let _ = result;
1202    }
1203
1204    // ========================================================================
1205    // Additional Edge Case Tests
1206    // ========================================================================
1207
1208    #[tokio::test]
1209    async fn test_multiple_apps_on_same_service() {
1210        let service = Service::new("multi-app-service".to_string());
1211        let secret = TEST_VALID_SECRET.to_string();
1212
1213        let name1 = Arc::new(Name::new(
1214            "org".to_string(),
1215            "ns".to_string(),
1216            "app1".to_string(),
1217        ));
1218        let name2 = Arc::new(Name::new(
1219            "org".to_string(),
1220            "ns".to_string(),
1221            "app2".to_string(),
1222        ));
1223        let name3 = Arc::new(Name::new(
1224            "org".to_string(),
1225            "ns".to_string(),
1226            "app3".to_string(),
1227        ));
1228
1229        let app1 = service
1230            .create_app_with_secret_async(name1, secret.clone())
1231            .await
1232            .expect("Failed to create app1");
1233
1234        let app2 = service
1235            .create_app_with_secret_async(name2, secret.clone())
1236            .await
1237            .expect("Failed to create app2");
1238
1239        let app3 = service
1240            .create_app_with_secret_async(name3, secret)
1241            .await
1242            .expect("Failed to create app3");
1243
1244        // All apps should have unique IDs
1245        assert_ne!(app1.id(), app2.id());
1246        assert_ne!(app1.id(), app3.id());
1247        assert_ne!(app2.id(), app3.id());
1248
1249        // All IDs should be non-zero
1250        assert!(app1.id() > 0);
1251        assert!(app2.id() > 0);
1252        assert!(app3.id() > 0);
1253    }
1254
1255    #[test]
1256    fn test_service_run_blocking() {
1257        let service = Service::new("run-blocking-test".to_string());
1258        // Test that run() doesn't panic
1259        let result = service.run();
1260        // May succeed or fail, we just ensure no panic
1261        let _ = result;
1262    }
1263
1264    #[test]
1265    fn test_service_shutdown_blocking() {
1266        let service = Service::new("shutdown-blocking-test".to_string());
1267        // Test that shutdown() doesn't panic
1268        let result = service.shutdown();
1269        // May succeed or fail, we just ensure no panic
1270        let _ = result;
1271    }
1272
1273    #[tokio::test]
1274    async fn test_run_async() {
1275        let service = Service::new("run-async-test".to_string());
1276        // run_async starts all configured servers/clients
1277        let result = service.run_async().await;
1278        // May fail if nothing configured, but shouldn't panic
1279        let _ = result;
1280    }
1281}