1use std::sync::Arc;
16
17use tokio::sync::{RwLock, mpsc};
18
19use crate::errors::SlimError;
20use crate::name::Name;
21use crate::{get_global_service, get_runtime};
22
23use crate::session::SessionConfig;
24
25use slim_auth::auth_provider::{AuthProvider, AuthVerifier};
26
27use crate::identity_config::{IdentityProviderConfig, IdentityVerifierConfig};
28
29use futures_timer::Delay;
30use slim_datapath::messages::Name as SlimName;
31use slim_service::Service as SlimService;
32use slim_service::app::App as SlimApp;
33use slim_session::Direction as CoreDirection;
34
35use slim_session::SessionConfig as SlimSessionConfig;
36use slim_session::session_controller::SessionController;
37use slim_session::{Notification, SessionError as SlimSessionError};
38
39#[derive(Clone, Copy, Debug, uniffi::Enum)]
42pub enum Direction {
43 Send, Recv, Bidirectional, None, }
48
49impl From<Direction> for CoreDirection {
50 fn from(direction: Direction) -> Self {
51 match direction {
52 Direction::Send => CoreDirection::Send,
53 Direction::Recv => CoreDirection::Recv,
54 Direction::Bidirectional => CoreDirection::Bidirectional,
55 Direction::None => CoreDirection::None,
56 }
57 }
58}
59
60impl From<CoreDirection> for Direction {
61 fn from(direction: CoreDirection) -> Self {
62 match direction {
63 CoreDirection::Send => Direction::Send,
64 CoreDirection::Recv => Direction::Recv,
65 CoreDirection::Bidirectional => Direction::Bidirectional,
66 CoreDirection::None => Direction::None,
67 }
68 }
69}
70
71#[derive(uniffi::Record)]
79pub struct SessionWithCompletion {
80 pub session: Arc<crate::Session>,
82 pub completion: Arc<crate::CompletionHandle>,
84}
85
86#[derive(uniffi::Object)]
93pub struct App {
94 app: Arc<SlimApp<AuthProvider, AuthVerifier>>,
96
97 notification_rx: Arc<RwLock<mpsc::Receiver<Result<Notification, SlimSessionError>>>>,
99
100 service: Arc<SlimService>,
102}
103
104impl App {
105 pub(crate) fn from_parts(
109 app: Arc<SlimApp<AuthProvider, AuthVerifier>>,
110 notification_rx: Arc<RwLock<mpsc::Receiver<Result<Notification, SlimSessionError>>>>,
111 service: Arc<SlimService>,
112 ) -> Self {
113 Self {
114 app,
115 notification_rx,
116 service,
117 }
118 }
119
120 pub fn core_app(&self) -> &Arc<SlimApp<AuthProvider, AuthVerifier>> {
129 &self.app
130 }
131
132 pub fn inner(&self) -> Arc<SlimApp<AuthProvider, AuthVerifier>> {
137 self.app.clone()
138 }
139
140 pub async fn new_async(
145 base_name: SlimName,
146 identity_provider_config: IdentityProviderConfig,
147 identity_verifier_config: IdentityVerifierConfig,
148 ) -> Result<Self, SlimError> {
149 let service_arc = get_global_service().inner.clone();
151
152 crate::service::create_app_async_internal(
154 base_name,
155 identity_provider_config,
156 identity_verifier_config,
157 service_arc,
158 Direction::Bidirectional,
159 )
160 .await
161 }
162
163 pub fn service(&self) -> &Arc<SlimService> {
171 &self.service
172 }
173
174 pub async fn new_with_direction_async(
190 name: Arc<Name>,
191 identity_provider_config: IdentityProviderConfig,
192 identity_verifier_config: IdentityVerifierConfig,
193 direction: Direction,
194 ) -> Result<Arc<App>, SlimError> {
195 get_global_service()
197 .create_app_with_direction_async(
198 name,
199 identity_provider_config,
200 identity_verifier_config,
201 direction,
202 )
203 .await
204 }
205
206 pub async fn new_with_secret_async(
219 name: Arc<Name>,
220 secret: String,
221 ) -> Result<Arc<App>, SlimError> {
222 get_global_service()
224 .create_app_with_secret_async(name, secret)
225 .await
226 }
227}
228
229#[uniffi::export]
230impl App {
231 #[uniffi::constructor]
249 pub fn new(
250 base_name: Arc<Name>,
251 identity_provider_config: IdentityProviderConfig,
252 identity_verifier_config: IdentityVerifierConfig,
253 ) -> Result<Arc<Self>, SlimError> {
254 crate::config::get_runtime().block_on(async {
255 Self::new_async(
256 base_name.as_ref().into(),
257 identity_provider_config,
258 identity_verifier_config,
259 )
260 .await
261 .map(Arc::new)
262 })
263 }
264
265 #[uniffi::constructor]
280 pub fn new_with_direction(
281 name: Arc<Name>,
282 identity_provider_config: IdentityProviderConfig,
283 identity_verifier_config: IdentityVerifierConfig,
284 direction: Direction,
285 ) -> Result<Arc<App>, SlimError> {
286 get_global_service().create_app_with_direction(
288 name,
289 identity_provider_config,
290 identity_verifier_config,
291 direction,
292 )
293 }
294
295 #[uniffi::constructor]
307 pub fn new_with_secret(name: Arc<Name>, secret: String) -> Result<Arc<App>, SlimError> {
308 get_global_service().create_app_with_secret(name, secret)
310 }
311
312 pub fn id(&self) -> u64 {
314 self.app.app_name().id()
315 }
316
317 pub fn name(&self) -> Arc<Name> {
319 Arc::new(self.app.app_name().into())
320 }
321
322 pub fn create_session(
327 &self,
328 config: SessionConfig,
329 destination: Arc<Name>,
330 ) -> Result<SessionWithCompletion, SlimError> {
331 crate::config::get_runtime()
332 .block_on(async { self.create_session_async(config, destination).await })
333 }
334
335 pub async fn create_session_async(
342 &self,
343 config: SessionConfig,
344 destination: Arc<Name>,
345 ) -> Result<SessionWithCompletion, SlimError> {
346 let slim_config: SlimSessionConfig = config.into();
347 let slim_dest: SlimName = destination.as_ref().into();
348 let app = self.app.clone();
349 let runtime = get_runtime();
350
351 let handle = runtime
353 .handle()
354 .spawn(async move { app.create_session(slim_config, slim_dest, None).await });
355
356 let (session_ctx, completion) = handle.await.map_err(|e| SlimError::SessionError {
357 message: format!("Failed to create session: {}", e),
358 })??;
359
360 let bindings_ctx = Arc::new(crate::Session::new(session_ctx));
362 let completion_handle = Arc::new(crate::CompletionHandle::from(completion));
363
364 Ok(SessionWithCompletion {
365 session: bindings_ctx,
366 completion: completion_handle,
367 })
368 }
369
370 pub fn create_session_and_wait(
375 &self,
376 config: SessionConfig,
377 destination: Arc<Name>,
378 ) -> Result<Arc<crate::Session>, SlimError> {
379 crate::config::get_runtime().block_on(async {
380 self.create_session_and_wait_async(config, destination)
381 .await
382 })
383 }
384
385 pub async fn create_session_and_wait_async(
390 &self,
391 config: SessionConfig,
392 destination: Arc<Name>,
393 ) -> Result<Arc<crate::Session>, SlimError> {
394 let session_with_completion = self.create_session_async(config, destination).await?;
395 session_with_completion.completion.wait_async().await?;
396 Ok(session_with_completion.session)
397 }
398
399 pub fn delete_session(
403 &self,
404 session: Arc<crate::Session>,
405 ) -> Result<Arc<crate::CompletionHandle>, SlimError> {
406 crate::config::get_runtime().block_on(async { self.delete_session_async(session).await })
407 }
408
409 pub async fn delete_session_async(
413 &self,
414 session: Arc<crate::Session>,
415 ) -> Result<Arc<crate::CompletionHandle>, SlimError> {
416 let session_ref = session
417 .session
418 .upgrade()
419 .ok_or_else(|| SlimError::SessionError {
420 message: "Session already closed or dropped".to_string(),
421 })?;
422
423 let completion = self.app.delete_session(&session_ref)?;
424
425 Ok(Arc::new(crate::CompletionHandle::from(completion)))
427 }
428
429 pub fn delete_session_and_wait(&self, session: Arc<crate::Session>) -> Result<(), SlimError> {
433 crate::config::get_runtime()
434 .block_on(async { self.delete_session_and_wait_async(session).await })
435 }
436
437 pub async fn delete_session_and_wait_async(
441 &self,
442 session: Arc<crate::Session>,
443 ) -> Result<(), SlimError> {
444 let completion_handle = self.delete_session_async(session).await?;
445 completion_handle.wait_async().await
446 }
447
448 pub fn subscribe(&self, name: Arc<Name>, connection_id: Option<u64>) -> Result<(), SlimError> {
450 crate::config::get_runtime()
451 .block_on(async { self.subscribe_async(name, connection_id).await })
452 }
453
454 pub async fn subscribe_async(
456 &self,
457 name: Arc<Name>,
458 connection_id: Option<u64>,
459 ) -> Result<(), SlimError> {
460 let slim_name: SlimName = name.as_ref().into();
461 self.app.subscribe(&slim_name, connection_id).await?;
462 Ok(())
463 }
464
465 pub fn unsubscribe(
467 &self,
468 name: Arc<Name>,
469 connection_id: Option<u64>,
470 ) -> Result<(), SlimError> {
471 crate::config::get_runtime()
472 .block_on(async { self.unsubscribe_async(name, connection_id).await })
473 }
474
475 pub async fn unsubscribe_async(
477 &self,
478 name: Arc<Name>,
479 connection_id: Option<u64>,
480 ) -> Result<(), SlimError> {
481 let slim_name: SlimName = name.as_ref().into();
482 self.app.unsubscribe(&slim_name, connection_id).await?;
483 Ok(())
484 }
485
486 pub fn set_route(&self, name: Arc<Name>, connection_id: u64) -> Result<(), SlimError> {
488 crate::config::get_runtime()
489 .block_on(async { self.set_route_async(name, connection_id).await })
490 }
491
492 pub async fn set_route_async(
494 &self,
495 name: Arc<Name>,
496 connection_id: u64,
497 ) -> Result<(), SlimError> {
498 let slim_name: SlimName = name.as_ref().into();
499 self.app.set_route(&slim_name, connection_id).await?;
500 Ok(())
501 }
502
503 pub fn remove_route(&self, name: Arc<Name>, connection_id: u64) -> Result<(), SlimError> {
505 crate::config::get_runtime()
506 .block_on(async { self.remove_route_async(name, connection_id).await })
507 }
508
509 pub async fn remove_route_async(
511 &self,
512 name: Arc<Name>,
513 connection_id: u64,
514 ) -> Result<(), SlimError> {
515 let slim_name: SlimName = name.as_ref().into();
516 self.app.remove_route(&slim_name, connection_id).await?;
517 Ok(())
518 }
519
520 pub fn listen_for_session(
522 &self,
523 timeout: Option<std::time::Duration>,
524 ) -> Result<Arc<crate::Session>, SlimError> {
525 crate::get_runtime().block_on(async { self.listen_for_session_async(timeout).await })
526 }
527
528 pub async fn listen_for_session_async(
530 &self,
531 timeout: Option<std::time::Duration>,
532 ) -> Result<Arc<crate::Session>, SlimError> {
533 let mut rx = self.notification_rx.write().await;
534
535 let recv_fut = rx.recv();
536 let notification_opt = if let Some(dur) = timeout {
537 futures::pin_mut!(recv_fut);
539 let delay = Delay::new(dur);
540 futures::pin_mut!(delay);
541
542 match futures::future::select(recv_fut, delay).await {
543 futures::future::Either::Left((result, _)) => result,
544 futures::future::Either::Right(_) => {
545 return Err(SlimError::ReceiveError {
546 message: "listen_for_session timed out".to_string(),
547 });
548 }
549 }
550 } else {
551 recv_fut.await
552 };
553
554 if notification_opt.is_none() {
555 return Err(SlimError::ReceiveError {
556 message: "application channel closed".to_string(),
557 });
558 }
559
560 match notification_opt.unwrap() {
561 Ok(Notification::NewSession(ctx)) => Ok(Arc::new(crate::Session::new(ctx))),
562 Ok(Notification::NewMessage(_)) => Err(SlimError::ReceiveError {
563 message: "received unexpected message notification while listening for session"
564 .to_string(),
565 }),
566 Err(e) => Err(SlimError::ReceiveError {
567 message: format!("failed to receive session notification: {}", e),
568 }),
569 }
570 }
571}
572
573impl App {
575 pub fn inner_app(&self) -> &Arc<SlimApp<AuthProvider, AuthVerifier>> {
577 &self.app
578 }
579
580 pub fn notification_receiver(
582 &self,
583 ) -> Arc<RwLock<mpsc::Receiver<Result<Notification, SlimSessionError>>>> {
584 self.notification_rx.clone()
585 }
586}
587
588impl App {
593 pub async fn create_session_internal(
598 &self,
599 config: SlimSessionConfig,
600 destination: SlimName,
601 ) -> Result<
602 (
603 slim_session::context::SessionContext,
604 slim_session::CompletionHandle,
605 ),
606 SlimError,
607 > {
608 let (session_ctx, completion) = self.app.create_session(config, destination, None).await?;
609
610 Ok((session_ctx, completion))
611 }
612
613 pub async fn listen_for_session_internal(
615 &self,
616 timeout: Option<std::time::Duration>,
617 ) -> Result<slim_session::context::SessionContext, SlimError> {
618 let mut rx = self.notification_rx.write().await;
619
620 let recv_fut = rx.recv();
621 let notification_opt = if let Some(dur) = timeout {
622 futures::pin_mut!(recv_fut);
624 let delay = Delay::new(dur);
625 futures::pin_mut!(delay);
626
627 match futures::future::select(recv_fut, delay).await {
628 futures::future::Either::Left((result, _)) => result,
629 futures::future::Either::Right(_) => {
630 return Err(SlimError::ReceiveError {
631 message: "listen_for_session timed out".to_string(),
632 });
633 }
634 }
635 } else {
636 recv_fut.await
637 };
638
639 if notification_opt.is_none() {
640 return Err(SlimError::ReceiveError {
641 message: "application channel closed".to_string(),
642 });
643 }
644
645 match notification_opt.unwrap() {
646 Ok(Notification::NewSession(ctx)) => Ok(ctx),
647 Ok(Notification::NewMessage(_)) => Err(SlimError::ReceiveError {
648 message: "received unexpected message notification while listening for session"
649 .to_string(),
650 }),
651 Err(e) => Err(SlimError::ReceiveError {
652 message: format!("failed to receive session notification: {}", e),
653 }),
654 }
655 }
656
657 pub fn delete_session_internal(
661 &self,
662 session: &SessionController,
663 ) -> Result<slim_session::CompletionHandle, SlimError> {
664 let ret = self.app.delete_session(session)?;
665
666 Ok(ret)
667 }
668}
669
670#[cfg(test)]
675mod tests {
676 use crate::SessionType;
677
678 use super::*;
679
680 use slim_config::component::ComponentBuilder;
681 use slim_datapath::messages::Name as SlimName;
682 use slim_testing::utils::TEST_VALID_SECRET;
683
684 fn create_test_configs(secret: &str) -> (IdentityProviderConfig, IdentityVerifierConfig) {
686 (
687 IdentityProviderConfig::SharedSecret {
688 id: "test-service".to_string(),
689 data: secret.to_string(),
690 },
691 IdentityVerifierConfig::SharedSecret {
692 id: "test-service".to_string(),
693 data: secret.to_string(),
694 },
695 )
696 }
697
698 #[tokio::test]
700 async fn test_adapter_creation() {
701 let base_name = SlimName::from_strings(["org", "namespace", "test-app"]);
702 let (provider_config, verifier_config) = create_test_configs(TEST_VALID_SECRET);
703
704 let result = App::new_async(base_name, provider_config, verifier_config).await;
705 assert!(result.is_ok());
706
707 let adapter = result.unwrap();
708 assert!(adapter.id() > 0);
709 }
710
711 #[tokio::test]
713 async fn test_deterministic_id_generation() {
714 let base_name = SlimName::from_strings(["org", "namespace", "test-app"]);
715 let (provider_config, verifier_config) = create_test_configs(TEST_VALID_SECRET);
716
717 let adapter = App::new_async(base_name, provider_config, verifier_config)
719 .await
720 .expect("Failed to create adapter");
721
722 let adapter_id = adapter.id();
724 assert!(adapter_id > 0, "Adapter ID should be non-zero");
725
726 let adapter_name = adapter.name();
728 assert_eq!(
729 adapter_name.id(),
730 adapter_id,
731 "Name ID should match adapter ID"
732 );
733 }
734
735 #[tokio::test]
737 async fn test_session_creation_auto_wait() {
738 let base_name = SlimName::from_strings(["org", "namespace", "create-test"]);
742 let (provider_config, verifier_config) = create_test_configs(TEST_VALID_SECRET);
743
744 let adapter = App::new_async(base_name, provider_config, verifier_config)
745 .await
746 .expect("Failed to create adapter");
747
748 let session_config = SessionConfig {
749 session_type: SessionType::PointToPoint,
750 enable_mls: false,
751 max_retries: Some(3),
752 interval: Some(std::time::Duration::from_millis(100)),
753 metadata: std::collections::HashMap::new(),
754 };
755
756 let destination = Arc::new(Name::new(
757 "org".to_string(),
758 "test".to_string(),
759 "dest".to_string(),
760 ));
761
762 let result = adapter
765 .create_session_async(session_config, destination)
766 .await;
767
768 match result {
771 Ok(_session) => {
772 }
774 Err(e) => {
775 println!("Expected error in test environment: {:?}", e);
778 }
779 }
780 }
781
782 #[tokio::test]
784 async fn test_publish_with_completion_returns_handle() {
785 let base_name = SlimName::from_strings(["org", "namespace", "publish-test"]);
789 let (provider_config, verifier_config) = create_test_configs(TEST_VALID_SECRET);
790
791 let adapter = App::new_async(base_name, provider_config, verifier_config)
792 .await
793 .expect("Failed to create adapter");
794
795 let session_config = SessionConfig {
796 session_type: SessionType::PointToPoint,
797 enable_mls: false,
798 max_retries: Some(3),
799 interval: Some(std::time::Duration::from_millis(100)),
800 metadata: std::collections::HashMap::new(),
801 };
802
803 let destination = Arc::new(Name::new(
804 "org".to_string(),
805 "test".to_string(),
806 "dest".to_string(),
807 ));
808
809 if let Ok(session) = adapter
811 .create_session_async(session_config, destination)
812 .await
813 {
814 let data = b"test message".to_vec();
815
816 let result = session.session.publish_async(data, None, None).await;
819
820 match result {
821 Ok(completion_handle) => {
822 assert!(Arc::strong_count(&completion_handle) > 0);
825 }
826 Err(e) => {
827 println!("Expected error without network: {:?}", e);
829 }
830 }
831 }
832 }
833
834 #[test]
840 fn test_tls_config_full() {
841 use crate::common_config::TlsClientConfig;
842
843 let config = TlsClientConfig {
844 insecure: false,
845 insecure_skip_verify: false,
846 source: crate::common_config::TlsSource::File {
847 cert: "test-cert.pem".to_string(),
848 key: "test-key.pem".to_string(),
849 },
850 ca_source: crate::common_config::CaSource::File {
851 path: "test-ca.pem".to_string(),
852 },
853 include_system_ca_certs_pool: true,
854 tls_version: "tls1.3".to_string(),
855 };
856
857 assert!(!config.insecure);
858 assert!(!config.insecure_skip_verify);
859 assert!(matches!(
860 config.source,
861 crate::common_config::TlsSource::File { .. }
862 ));
863 assert!(matches!(
864 config.ca_source,
865 crate::common_config::CaSource::File { .. }
866 ));
867 assert_eq!(config.tls_version, "tls1.3");
868 assert!(config.include_system_ca_certs_pool);
869 }
870
871 #[test]
873 fn test_tls_config_insecure() {
874 use crate::common_config::TlsClientConfig;
875
876 let config = TlsClientConfig {
877 insecure: true,
878 insecure_skip_verify: false,
879 source: crate::common_config::TlsSource::None,
880 ca_source: crate::common_config::CaSource::None,
881 include_system_ca_certs_pool: true,
882 tls_version: "tls1.3".to_string(),
883 };
884
885 assert!(config.insecure);
886 assert!(matches!(
887 config.source,
888 crate::common_config::TlsSource::None
889 ));
890 }
891
892 #[tokio::test]
898 async fn test_adapter_id_and_name() {
899 let base_name = SlimName::from_strings(["org", "namespace", "id-test"]);
900 let (provider_config, verifier_config) = create_test_configs(TEST_VALID_SECRET);
901
902 let adapter = App::new_async(base_name.clone(), provider_config, verifier_config)
903 .await
904 .expect("Failed to create adapter");
905
906 let id = adapter.id();
908 assert!(id > 0, "Adapter ID should be positive");
909
910 let name = adapter.name();
912 assert_eq!(name.components()[0], "org");
913 assert_eq!(name.components()[1], "namespace");
914 assert_eq!(name.components()[2], "id-test");
915 assert!(name.id() > 0);
916 }
917
918 #[tokio::test]
921 async fn test_adapter_with_global_service() {
922 let base_name = SlimName::from_strings(["org", "namespace", "global-test"]);
923 let (provider_config, verifier_config) = create_test_configs(TEST_VALID_SECRET);
924
925 let result = App::new_async(base_name, provider_config, verifier_config).await;
926 assert!(result.is_ok(), "Should create adapter with global service");
927 }
928
929 #[tokio::test]
931 async fn test_adapter_different_namespaces() {
932 let namespaces = [
934 ["org1", "namespace1", "app1"],
935 ["company", "team", "service"],
936 ["prod", "api", "gateway"],
937 ];
938
939 for ns in namespaces {
940 let base_name = SlimName::from_strings(ns);
941 let (provider_config, verifier_config) = create_test_configs(TEST_VALID_SECRET);
942
943 let result = App::new_async(base_name, provider_config, verifier_config).await;
944 assert!(
945 result.is_ok(),
946 "Should create adapter for namespace {:?}",
947 ns
948 );
949 }
950 }
951
952 #[tokio::test]
960 async fn test_bindings_adapter_new() {
961 let base_name = SlimName::from_strings(["org", "namespace", "ffi-app"]);
962
963 let provider_config = IdentityProviderConfig::SharedSecret {
964 id: "test-sync-service".to_string(),
965 data: TEST_VALID_SECRET.to_string(),
966 };
967 let verifier_config = IdentityVerifierConfig::SharedSecret {
968 id: "test-sync-service".to_string(),
969 data: TEST_VALID_SECRET.to_string(),
970 };
971
972 let result = App::new_async(base_name, provider_config, verifier_config).await;
973 assert!(result.is_ok(), "App::new_async should succeed");
974
975 let adapter = result.unwrap();
976 assert!(adapter.id() > 0);
977
978 let name = adapter.name();
979 assert_eq!(name.components()[0], "org");
980 assert_eq!(name.components()[1], "namespace");
981 assert_eq!(name.components()[2], "ffi-app");
982 }
983
984 #[tokio::test]
986 async fn test_bindings_adapter_new_minimal_name() {
987 let base_name = SlimName::from_strings(["org", "ns", "test-app"]);
988
989 let provider_config = IdentityProviderConfig::SharedSecret {
990 id: "test-minimal-service".to_string(),
991 data: TEST_VALID_SECRET.to_string(),
992 };
993 let verifier_config = IdentityVerifierConfig::SharedSecret {
994 id: "test-minimal-service".to_string(),
995 data: TEST_VALID_SECRET.to_string(),
996 };
997
998 let result = App::new_async(base_name, provider_config, verifier_config).await;
999 assert!(result.is_ok(), "Should create adapter with minimal name");
1001 }
1002
1003 #[tokio::test]
1009 async fn test_listen_for_session_timeout() {
1010 let base_name = SlimName::from_strings(["org", "namespace", "listen-test"]);
1011 let (provider_config, verifier_config) = create_test_configs(TEST_VALID_SECRET);
1012
1013 let adapter = App::new_async(base_name, provider_config, verifier_config)
1014 .await
1015 .expect("Failed to create adapter");
1016
1017 let result = adapter
1019 .listen_for_session_async(Some(std::time::Duration::from_millis(10)))
1020 .await;
1021
1022 match result {
1023 Err(SlimError::ReceiveError { message }) => {
1024 assert!(
1025 message.contains("timed out"),
1026 "Should contain timeout message"
1027 );
1028 }
1029 _ => {
1030 }
1032 }
1033 }
1034
1035 #[tokio::test]
1041 async fn test_subscribe_unsubscribe() {
1042 let base_name = SlimName::from_strings(["org", "namespace", "sub-test"]);
1043 let (provider_config, verifier_config) = create_test_configs(TEST_VALID_SECRET);
1044
1045 let adapter = App::new_async(base_name, provider_config, verifier_config)
1046 .await
1047 .expect("Failed to create adapter");
1048
1049 let target_name = Arc::new(Name::new(
1050 "org".to_string(),
1051 "ns".to_string(),
1052 "target".to_string(),
1053 ));
1054
1055 let sub_result = adapter.subscribe_async(target_name.clone(), None).await;
1057 let unsub_result = adapter.unsubscribe_async(target_name, None).await;
1061 let _ = (sub_result, unsub_result);
1064 }
1065
1066 #[tokio::test]
1072 async fn test_set_remove_route() {
1073 let base_name = SlimName::from_strings(["org", "namespace", "route-test"]);
1074 let (provider_config, verifier_config) = create_test_configs(TEST_VALID_SECRET);
1075
1076 let adapter = App::new_async(base_name, provider_config, verifier_config)
1077 .await
1078 .expect("Failed to create adapter");
1079
1080 let target_name = Arc::new(Name::new(
1081 "org".to_string(),
1082 "ns".to_string(),
1083 "route-target".to_string(),
1084 ));
1085
1086 let set_result = adapter.set_route_async(target_name.clone(), 12345).await;
1088 let remove_result = adapter.remove_route_async(target_name, 12345).await;
1090
1091 let _ = (set_result, remove_result);
1093 }
1094
1095 #[tokio::test]
1101 async fn test_stop_server_nonexistent() {
1102 let base_name = SlimName::from_strings(["org", "namespace", "stop-test"]);
1103 let (provider_config, verifier_config) = create_test_configs(TEST_VALID_SECRET);
1104
1105 let _adapter = App::new_async(base_name, provider_config, verifier_config)
1106 .await
1107 .expect("Failed to create adapter");
1108
1109 let service = get_global_service();
1111 let result = service.stop_server("127.0.0.1:99999".to_string());
1112 assert!(result.is_err());
1114 }
1115
1116 #[tokio::test]
1122 async fn test_disconnect_invalid_id() {
1123 let base_name = SlimName::from_strings(["org", "namespace", "disconnect-test"]);
1124 let (provider_config, verifier_config) = create_test_configs(TEST_VALID_SECRET);
1125
1126 let _adapter = App::new_async(base_name, provider_config, verifier_config)
1127 .await
1128 .expect("Failed to create adapter");
1129
1130 let service = get_global_service();
1132 let result = service.disconnect(999999);
1133 assert!(result.is_err());
1135 }
1136
1137 #[tokio::test]
1143 async fn test_delete_session_flow() {
1144 let base_name = SlimName::from_strings(["org", "namespace", "delete-test"]);
1145 let (provider_config, verifier_config) = create_test_configs(TEST_VALID_SECRET);
1146
1147 let adapter = App::new_async(base_name, provider_config, verifier_config)
1148 .await
1149 .expect("Failed to create adapter");
1150
1151 let session_config = SessionConfig {
1152 session_type: SessionType::PointToPoint,
1153 enable_mls: false,
1154 max_retries: Some(1),
1155 interval: Some(std::time::Duration::from_millis(50)),
1156 metadata: std::collections::HashMap::new(),
1157 };
1158
1159 let destination = Arc::new(Name::new(
1160 "org".to_string(),
1161 "test".to_string(),
1162 "delete-dest".to_string(),
1163 ));
1164
1165 if let Ok(session_with_completion) = adapter
1167 .create_session_async(session_config, destination)
1168 .await
1169 {
1170 let delete_result = adapter
1172 .delete_session_async(session_with_completion.session)
1173 .await;
1174 if let Ok(completion) = delete_result {
1176 let _ = completion;
1177 }
1178 }
1179 }
1180
1181 #[tokio::test]
1187 async fn test_listen_for_session_blocking_timeout() {
1188 let base_name = SlimName::from_strings(["org", "namespace", "blocking-listen"]);
1189 let (provider_config, verifier_config) = create_test_configs(TEST_VALID_SECRET);
1190
1191 let adapter = App::new_async(base_name, provider_config, verifier_config)
1192 .await
1193 .expect("Failed to create adapter");
1194
1195 let result = adapter
1197 .listen_for_session_async(Some(std::time::Duration::from_millis(10)))
1198 .await;
1199
1200 match result {
1202 Err(SlimError::ReceiveError { message }) => {
1203 assert!(message.contains("timed out") || message.contains("closed"));
1204 }
1205 _ => {
1206 }
1208 }
1209 }
1210
1211 #[tokio::test]
1213 async fn test_subscribe_blocking() {
1214 let base_name = SlimName::from_strings(["org", "namespace", "blocking-sub"]);
1215 let (provider_config, verifier_config) = create_test_configs(TEST_VALID_SECRET);
1216
1217 let adapter = App::new_async(base_name, provider_config, verifier_config)
1218 .await
1219 .expect("Failed to create adapter");
1220
1221 let target_name = Arc::new(Name::new(
1222 "org".to_string(),
1223 "ns".to_string(),
1224 "block-target".to_string(),
1225 ));
1226
1227 let _ = adapter.subscribe_async(target_name, None).await;
1229 }
1230
1231 #[tokio::test]
1233 async fn test_from_parts_constructor() {
1234 let base_name = SlimName::from_strings(["org", "namespace", "from-parts-test"]);
1235 let (provider_config, verifier_config) = create_test_configs(TEST_VALID_SECRET);
1236
1237 let _adapter1 = App::new_async(
1239 base_name.clone(),
1240 provider_config.clone(),
1241 verifier_config.clone(),
1242 )
1243 .await
1244 .expect("Failed to create adapter");
1245
1246 let service = get_global_service();
1249 let name = Arc::new(Name::new(
1250 "org".to_string(),
1251 "namespace".to_string(),
1252 "from-parts-test-2".to_string(),
1253 ));
1254
1255 let adapter2 = service
1256 .create_app_async(name, provider_config, verifier_config)
1257 .await
1258 .expect("Failed to create adapter via service");
1259
1260 assert!(adapter2.id() > 0);
1262 assert!(!adapter2.name().to_string().is_empty());
1263 }
1264
1265 #[tokio::test]
1267 async fn test_new_async_with_custom_service() {
1268 use slim_service::Service as SlimService;
1269
1270 let base_name = SlimName::from_strings(["org", "namespace", "custom-service-test"]);
1271 let (provider_config, verifier_config) = create_test_configs(TEST_VALID_SECRET);
1272
1273 let custom_service = SlimService::builder()
1275 .build("test-custom-service".to_string())
1276 .expect("Failed to create custom service");
1277 let service_wrapper = crate::Service {
1278 inner: Arc::new(custom_service),
1279 };
1280
1281 let base_name_arc = Arc::new(Name::from(base_name));
1283 let result = service_wrapper
1284 .create_app_async(base_name_arc, provider_config, verifier_config)
1285 .await;
1286
1287 assert!(result.is_ok(), "Should create adapter with custom service");
1288 let adapter = result.unwrap();
1289 assert!(adapter.id() > 0);
1290 assert!(!adapter.name().to_string().is_empty());
1291 }
1292
1293 #[tokio::test]
1295 async fn test_new_async_uses_global_service() {
1296 let base_name = SlimName::from_strings(["org", "namespace", "new-async-global"]);
1297 let (provider_config, verifier_config) = create_test_configs(TEST_VALID_SECRET);
1298
1299 let adapter1 = App::new_async(
1301 base_name.clone(),
1302 provider_config.clone(),
1303 verifier_config.clone(),
1304 )
1305 .await
1306 .expect("Failed to create first adapter");
1307
1308 let base_name2 = SlimName::from_strings(["org", "namespace", "new-async-global-2"]);
1309 let adapter2 = App::new_async(base_name2, provider_config, verifier_config)
1310 .await
1311 .expect("Failed to create second adapter");
1312
1313 assert!(adapter1.id() > 0);
1315 assert!(adapter2.id() > 0);
1316 assert_ne!(
1317 adapter1.id(),
1318 adapter2.id(),
1319 "Different adapters should have different IDs"
1320 );
1321 }
1322
1323 #[tokio::test]
1326 async fn test_different_services_create_isolated_adapters() {
1327 use slim_service::Service as SlimService;
1328
1329 let base_name1 = SlimName::from_strings(["org", "namespace", "isolated-1"]);
1330 let base_name2 = SlimName::from_strings(["org", "namespace", "isolated-2"]);
1331 let (provider_config, verifier_config) = create_test_configs(TEST_VALID_SECRET);
1332
1333 let service1 = SlimService::builder()
1335 .build("test-service-1".to_string())
1336 .expect("Failed to create service 1");
1337 let service1_wrapper = crate::Service {
1338 inner: Arc::new(service1),
1339 };
1340
1341 let service2 = SlimService::builder()
1342 .build("test-service-2".to_string())
1343 .expect("Failed to create service 2");
1344 let service2_wrapper = crate::Service {
1345 inner: Arc::new(service2),
1346 };
1347
1348 let base_name1_arc = Arc::new(Name::from(base_name1));
1350 let adapter1 = service1_wrapper
1351 .create_app_async(
1352 base_name1_arc,
1353 provider_config.clone(),
1354 verifier_config.clone(),
1355 )
1356 .await
1357 .expect("Failed to create adapter 1");
1358
1359 let base_name2_arc = Arc::new(Name::from(base_name2));
1360 let adapter2 = service2_wrapper
1361 .create_app_async(base_name2_arc, provider_config, verifier_config)
1362 .await
1363 .expect("Failed to create adapter 2");
1364
1365 assert_ne!(adapter1.id(), adapter2.id());
1367 }
1368
1369 #[tokio::test]
1371 async fn test_new_with_secret_async() {
1372 let name = Arc::new(Name::new(
1373 "org".to_string(),
1374 "namespace".to_string(),
1375 "secret-test".to_string(),
1376 ));
1377 let secret = TEST_VALID_SECRET.to_string();
1378
1379 let result = App::new_with_secret_async(name, secret).await;
1380
1381 assert!(result.is_ok(), "Should create app with secret");
1382 let app = result.unwrap();
1383 assert!(app.id() > 0, "App should have non-zero ID");
1384 assert!(
1385 !app.name().to_string().is_empty(),
1386 "App should have valid name"
1387 );
1388 }
1389
1390 #[test]
1392 fn test_new_with_secret_blocking() {
1393 let name = Arc::new(Name::new(
1394 "org".to_string(),
1395 "namespace".to_string(),
1396 "secret-blocking-test".to_string(),
1397 ));
1398 let secret = TEST_VALID_SECRET.to_string();
1399
1400 let result = App::new_with_secret(name, secret);
1402
1403 assert!(
1404 result.is_ok(),
1405 "Should create app with secret in blocking mode"
1406 );
1407 let app = result.unwrap();
1408 assert!(app.id() > 0, "App should have non-zero ID");
1409 }
1410
1411 #[tokio::test]
1413 async fn test_new_with_secret_unique_ids() {
1414 let secret = TEST_VALID_SECRET.to_string();
1415
1416 let name1 = Arc::new(Name::new(
1417 "org".to_string(),
1418 "namespace".to_string(),
1419 "unique-1".to_string(),
1420 ));
1421 let name2 = Arc::new(Name::new(
1422 "org".to_string(),
1423 "namespace".to_string(),
1424 "unique-2".to_string(),
1425 ));
1426
1427 let app1 = App::new_with_secret_async(name1, secret.clone())
1428 .await
1429 .expect("Failed to create app1");
1430
1431 let app2 = App::new_with_secret_async(name2, secret)
1432 .await
1433 .expect("Failed to create app2");
1434
1435 assert_ne!(app1.id(), app2.id(), "Apps should have different IDs");
1436 }
1437
1438 #[tokio::test]
1440 async fn test_create_session_async_runtime_spawning() {
1441 let base_name = SlimName::from_strings(["org", "namespace", "runtime-spawn-test"]);
1442 let (provider_config, verifier_config) = create_test_configs(TEST_VALID_SECRET);
1443
1444 let app = App::new_async(base_name, provider_config, verifier_config)
1445 .await
1446 .expect("Failed to create app");
1447
1448 let session_config = SessionConfig {
1449 session_type: SessionType::PointToPoint,
1450 enable_mls: false,
1451 max_retries: Some(3),
1452 interval: Some(std::time::Duration::from_millis(100)),
1453 metadata: std::collections::HashMap::new(),
1454 };
1455
1456 let destination = Arc::new(Name::new(
1457 "org".to_string(),
1458 "test".to_string(),
1459 "spawn-dest".to_string(),
1460 ));
1461
1462 let result = app.create_session_async(session_config, destination).await;
1464
1465 let _ = result;
1467 }
1468
1469 #[tokio::test]
1471 async fn test_listen_for_session_timeout_futures_timer() {
1472 let base_name = SlimName::from_strings(["org", "namespace", "listen-timeout-test"]);
1473 let (provider_config, verifier_config) = create_test_configs(TEST_VALID_SECRET);
1474
1475 let app = App::new_async(base_name, provider_config, verifier_config)
1476 .await
1477 .expect("Failed to create app");
1478
1479 let result = app
1481 .listen_for_session_async(Some(std::time::Duration::from_millis(50)))
1482 .await;
1483
1484 assert!(result.is_err(), "Should timeout when no session arrives");
1485 if let Err(e) = result {
1486 let error_msg = format!("{:?}", e);
1487 assert!(
1488 error_msg.contains("timed out") || error_msg.contains("timeout"),
1489 "Error should mention timeout"
1490 );
1491 }
1492 }
1493
1494 #[tokio::test]
1496 async fn test_listen_for_session_no_incoming() {
1497 let base_name = SlimName::from_strings(["org", "namespace", "no-incoming-test"]);
1498 let (provider_config, verifier_config) = create_test_configs(TEST_VALID_SECRET);
1499
1500 let app = App::new_async(base_name, provider_config, verifier_config)
1501 .await
1502 .expect("Failed to create app");
1503
1504 let result = app
1506 .listen_for_session_async(Some(std::time::Duration::from_millis(30)))
1507 .await;
1508
1509 assert!(result.is_err(), "Should timeout when no session arrives");
1510 if let Err(e) = result {
1511 let error_msg = format!("{:?}", e);
1512 assert!(
1513 error_msg.contains("timed out") || error_msg.contains("timeout"),
1514 "Error should mention timeout"
1515 );
1516 }
1517 }
1518
1519 #[tokio::test]
1521 async fn test_new_async_uses_internal_creation() {
1522 let base_name = SlimName::from_strings(["org", "namespace", "internal-test"]);
1523 let (provider_config, verifier_config) = create_test_configs(TEST_VALID_SECRET);
1524
1525 let result = App::new_async(base_name, provider_config, verifier_config).await;
1527
1528 assert!(result.is_ok(), "Should create app via internal function");
1529 let app = result.unwrap();
1530 assert!(app.id() > 0);
1531 assert!(!app.name().to_string().is_empty());
1532 }
1533}