lifecycle.rs (5008B)
1 #[cfg(feature = "memory")] 2 use radroots_sdk::{ClientBuilder, capability::CapabilityId, error::ErrorKind}; 3 4 const CLIENT_SOURCE: &str = include_str!("../src/client.rs"); 5 const MANIFEST: &str = include_str!("../Cargo.toml"); 6 7 #[test] 8 fn clean_default_path_contains_no_implicit_resource_or_worker_authority() { 9 let production = CLIENT_SOURCE 10 .split_once("#[cfg(all(test, feature = \"memory\"))]") 11 .expect("production client boundary") 12 .0; 13 for forbidden in [ 14 "keyring", 15 "reqwest", 16 "radrootsd", 17 "std::fs", 18 "tokio::fs", 19 "tokio::spawn", 20 "std::thread::spawn", 21 "Runtime::new", 22 "File::create", 23 "impl Drop for Client", 24 ] { 25 assert!( 26 !production.contains(forbidden), 27 "clean client path contains implicit authority `{forbidden}`" 28 ); 29 } 30 assert!(MANIFEST.contains("default = [\"memory\"]")); 31 assert!(!MANIFEST.contains("default = [\"native\"]")); 32 assert!(!MANIFEST.contains("default = [\"full\"]")); 33 } 34 35 #[cfg(feature = "memory")] 36 #[tokio::test(flavor = "multi_thread", worker_threads = 2)] 37 async fn memory_client_is_passive_clone_shared_and_explicitly_closed() { 38 let generation = 39 radroots_storage::event::SourceGeneration::new([41; 32]).expect("non-zero generation"); 40 let client = ClientBuilder::memory(generation).build().expect("client"); 41 42 let first_report = client.capabilities(); 43 let second_report = client.capabilities(); 44 assert_eq!(first_report, second_report); 45 let storage = first_report 46 .get(CapabilityId::CANONICAL_STORAGE) 47 .expect("canonical storage status"); 48 assert!(storage.is_compiled()); 49 assert!(storage.is_configured()); 50 51 let unpolled_close = client.close(); 52 drop(unpolled_close); 53 assert!(client.storage().is_ok()); 54 55 let first = client.clone(); 56 let second = client.clone(); 57 let first_close = tokio::spawn(async move { first.close().await }); 58 let second_close = tokio::spawn(async move { second.close().await }); 59 let outcomes = [ 60 first_close.await.expect("first close task"), 61 second_close.await.expect("second close task"), 62 ]; 63 assert!(outcomes.iter().all(|outcome| { 64 outcome.is_ok() 65 || outcome 66 .as_ref() 67 .is_err_and(|error| error.kind() == ErrorKind::CloseInProgress) 68 })); 69 if !client.is_closed() { 70 client.close().await.expect("complete close"); 71 } 72 73 assert!(client.is_closed()); 74 assert_eq!( 75 client 76 .storage() 77 .err() 78 .expect("closed client rejects storage") 79 .kind(), 80 ErrorKind::ClientClosed 81 ); 82 assert_eq!( 83 client 84 .capabilities() 85 .get(CapabilityId::CANONICAL_STORAGE) 86 .expect("closed storage status") 87 .availability(), 88 radroots_sdk::capability::Availability::Unavailable 89 ); 90 } 91 92 #[cfg(all(feature = "memory", feature = "sqlite"))] 93 #[tokio::test] 94 async fn sqlite_resources_exist_only_after_explicit_open_and_close_across_clones() { 95 use radroots_sdk::storage::{SqliteOpenMode, SqliteOptions, SqlitePaths}; 96 use radroots_storage::status::{ShutdownState, StorageBackend}; 97 98 let directory = tempfile::tempdir().expect("temporary directory"); 99 let empty_builder = ClientBuilder::new(); 100 assert!( 101 directory 102 .path() 103 .read_dir() 104 .expect("read tempdir") 105 .next() 106 .is_none() 107 ); 108 drop(empty_builder); 109 110 let paths = SqlitePaths::from_directory(directory.path()).expect("SQLite paths"); 111 let generation = 112 radroots_storage::event::SourceGeneration::new([42; 32]).expect("non-zero generation"); 113 let options = SqliteOptions::new(paths, SqliteOpenMode::Create) 114 .with_source_generation(generation, 1) 115 .expect("source generation"); 116 assert!( 117 directory 118 .path() 119 .read_dir() 120 .expect("read tempdir") 121 .next() 122 .is_none() 123 ); 124 125 let client = ClientBuilder::sqlite(options) 126 .await 127 .expect("explicit SQLite open") 128 .build() 129 .expect("client"); 130 assert!( 131 directory 132 .path() 133 .read_dir() 134 .expect("read tempdir") 135 .next() 136 .is_some() 137 ); 138 let status = client.storage_status().await.expect("storage status"); 139 assert_eq!(status.backend(), StorageBackend::Sqlite); 140 assert_eq!(status.shutdown(), ShutdownState::Open); 141 142 let clone = client.clone(); 143 let unpolled_close = clone.close(); 144 drop(unpolled_close); 145 assert!(client.storage().is_ok()); 146 client.close().await.expect("explicit close"); 147 assert!(clone.is_closed()); 148 assert_eq!( 149 clone 150 .storage_status() 151 .await 152 .expect_err("closed clone rejects inspection") 153 .kind(), 154 ErrorKind::ClientClosed 155 ); 156 }