An easy-to-host PDS on the ATProtocol, iPhone and MacOS. Maintain control of your keys and data, always.
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

test(identity-wallet): replace real Keychain with in-memory store in tests

Redirects all keychain.rs operations to a process-local HashMap under
#[cfg(test)] so that `cargo test -p identity-wallet` never touches the
macOS Keychain and never triggers a password prompt.

Production paths are unchanged; the security-framework import and all
three password functions are gated with #[cfg(not(test))].

authored by

Malpercio and committed by
Tangled
4ed7ae3b 6bc45479

+49
+49
apps/identity-wallet/src-tauri/src/keychain.rs
··· 3 3 //! All items are stored as `kSecClassGenericPassword` under 4 4 //! service `"ezpds-identity-wallet"`. Use the `SERVICE` constant 5 5 //! to ensure consistency. 6 + //! 7 + //! In test builds (`#[cfg(test)]`), all Keychain operations are redirected to an 8 + //! in-memory store so that tests never touch the real macOS Keychain and never 9 + //! trigger a password prompt. 6 10 11 + #[cfg(not(test))] 7 12 use security_framework::passwords::{ 8 13 delete_generic_password, get_generic_password, set_generic_password, 9 14 }; ··· 14 19 pub enum KeychainError { 15 20 #[error("keychain error: {0}")] 16 21 Security(#[from] security_framework::base::Error), 22 + /// Returned by the in-memory test store when an item is not found. 23 + #[cfg(test)] 24 + #[error("item not found")] 25 + NotFound, 17 26 } 18 27 19 28 /// Store arbitrary bytes in the Keychain under the given account name. 20 29 /// 21 30 /// Creates the entry if it doesn't exist, or updates it if it does. 22 31 pub fn store_item(account: &str, data: &[u8]) -> Result<(), KeychainError> { 32 + #[cfg(test)] 33 + { 34 + test_store::get() 35 + .lock() 36 + .unwrap() 37 + .insert(account.to_string(), data.to_vec()); 38 + return Ok(()); 39 + } 40 + #[cfg(not(test))] 23 41 set_generic_password(SERVICE, account, data).map_err(KeychainError::Security) 24 42 } 25 43 ··· 27 45 /// 28 46 /// Returns `Err` with `errSecItemNotFound` if no entry exists. 29 47 pub fn get_item(account: &str) -> Result<Vec<u8>, KeychainError> { 48 + #[cfg(test)] 49 + { 50 + return test_store::get() 51 + .lock() 52 + .unwrap() 53 + .get(account) 54 + .cloned() 55 + .ok_or(KeychainError::NotFound); 56 + } 57 + #[cfg(not(test))] 30 58 get_generic_password(SERVICE, account).map_err(KeychainError::Security) 31 59 } 32 60 ··· 34 62 /// 35 63 /// Returns `Ok(())` on successful deletion, or `Err` if the item doesn't exist. 36 64 pub fn delete_item(account: &str) -> Result<(), KeychainError> { 65 + #[cfg(test)] 66 + { 67 + test_store::get().lock().unwrap().remove(account); 68 + return Ok(()); 69 + } 70 + #[cfg(not(test))] 37 71 delete_generic_password(SERVICE, account).map_err(KeychainError::Security) 38 72 } 39 73 ··· 42 76 pub fn is_not_found(err: &KeychainError) -> bool { 43 77 match err { 44 78 KeychainError::Security(e) => e.code() == -25300, 79 + #[cfg(test)] 80 + KeychainError::NotFound => true, 81 + } 82 + } 83 + 84 + /// In-memory Keychain substitute used exclusively in test builds. 85 + #[cfg(test)] 86 + mod test_store { 87 + use std::collections::HashMap; 88 + use std::sync::{Mutex, OnceLock}; 89 + 90 + static STORE: OnceLock<Mutex<HashMap<String, Vec<u8>>>> = OnceLock::new(); 91 + 92 + pub fn get() -> &'static Mutex<HashMap<String, Vec<u8>>> { 93 + STORE.get_or_init(|| Mutex::new(HashMap::new())) 45 94 } 46 95 }