mod common;
use common::TestSettings;
use rcman::{SettingsConfig, SettingsManager};
use serde_json::json;
use std::sync::atomic::{AtomicU32, Ordering};
use tempfile::TempDir;
static TEST_COUNTER: AtomicU32 = AtomicU32::new(0);
fn unique_app_name() -> String {
let count = TEST_COUNTER.fetch_add(1, Ordering::SeqCst);
format!("rcman-test-{}-{}", std::process::id(), count)
}
fn create_manager_with_credentials() -> (TempDir, SettingsManager<rcman::JsonStorage, TestSettings>)
{
let temp_dir = TempDir::new().unwrap();
let app_name = unique_app_name();
let config = SettingsConfig::builder(&app_name, "1.0.0")
.with_config_dir(temp_dir.path())
.with_schema::<TestSettings>()
.with_credentials()
.build();
let manager = SettingsManager::new(config).unwrap();
(temp_dir, manager)
}
#[test]
#[ignore = "Requires Secret Service daemon (not available in CI)"]
fn test_secret_not_in_json_file() {
let (temp_dir, manager) = create_manager_with_credentials();
let _ = manager.get_all().unwrap();
manager
.save_setting("api", "key", &json!("super_secret_api_key_123"))
.unwrap();
let settings_path = temp_dir.path().join("settings.json");
if settings_path.exists() {
let content = std::fs::read_to_string(&settings_path).unwrap();
assert!(!content.contains("super_secret_api_key_123"));
assert!(!content.contains("api.key"));
}
}
#[test]
#[ignore = "Requires Secret Service daemon (not available in CI)"]
fn test_secret_retrieved_correctly() {
let (_temp_dir, manager) = create_manager_with_credentials();
let _ = manager.get_all().unwrap();
manager
.save_setting("api", "key", &json!("my_secret_value"))
.unwrap();
let metadata = manager.metadata().unwrap();
let api_key_meta = metadata.get("api.key").unwrap();
assert_eq!(api_key_meta.value, Some(json!("my_secret_value")));
}
#[test]
#[ignore = "Requires Secret Service daemon (not available in CI)"]
fn test_reset_secret_clears_value() {
let (_temp_dir, manager) = create_manager_with_credentials();
let _ = manager.get_all().unwrap();
manager
.save_setting("api", "key", &json!("secret_to_reset"))
.unwrap();
let metadata = manager.metadata().unwrap();
assert_eq!(
metadata.get("api.key").unwrap().value,
Some(json!("secret_to_reset"))
);
let default_value = manager.reset_setting("api", "key").unwrap();
assert_eq!(default_value, json!(""));
let metadata = manager.metadata().unwrap();
assert_eq!(metadata.get("api.key").unwrap().value, Some(json!("")));
}
#[test]
#[ignore = "Requires Secret Service daemon (not available in CI)"]
fn test_reset_all_clears_secrets() {
let (_temp_dir, manager) = create_manager_with_credentials();
let _ = manager.get_all().unwrap();
manager
.save_setting("api", "key", &json!("will_be_cleared"))
.unwrap();
manager.reset_all().unwrap();
let metadata = manager.metadata().unwrap();
let api_key_value = metadata.get("api.key").unwrap().value.clone();
assert_eq!(api_key_value, Some(json!("")));
}
#[test]
#[ignore = "Requires Secret Service daemon (not available in CI)"]
fn test_secret_default_not_stored() {
let (_temp_dir, manager) = create_manager_with_credentials();
let _ = manager.get_all().unwrap();
manager
.save_setting("api", "key", &json!("temporary_key"))
.unwrap();
manager.save_setting("api", "key", &json!("")).unwrap();
let metadata = manager.metadata().unwrap();
assert_eq!(metadata.get("api.key").unwrap().value, Some(json!("")));
}
#[test]
#[ignore = "Requires Secret Service daemon (not available in CI)"]
fn test_multiple_secrets() {
let (_temp_dir, manager) = create_manager_with_credentials();
let _ = manager.get_all().unwrap();
manager
.save_setting("api", "key", &json!("secret1"))
.unwrap();
manager
.save_setting("ui", "theme", &json!("light"))
.unwrap();
let metadata = manager.metadata().unwrap();
assert_eq!(
metadata.get("api.key").unwrap().value,
Some(json!("secret1"))
);
assert_eq!(
metadata.get("ui.theme").unwrap().value,
Some(json!("light"))
);
}
#[cfg(any(feature = "keychain", feature = "encrypted-file"))]
#[test]
fn test_credentials_manager_available() {
let (_temp_dir, manager) = create_manager_with_credentials();
assert!(manager.credentials().is_some());
}
#[cfg(any(feature = "keychain", feature = "encrypted-file"))]
#[test]
fn test_credentials_not_available_when_disabled() {
let temp_dir = TempDir::new().unwrap();
let app_name = unique_app_name();
let config = SettingsConfig::builder(&app_name, "1.0.0")
.with_config_dir(temp_dir.path())
.build();
let manager = SettingsManager::new(config).unwrap();
assert!(manager.credentials().is_none());
}
#[cfg(all(feature = "keychain", any(target_os = "android", target_os = "ios")))]
#[test]
fn test_mobile_keychain_store_retrieve_remove() {
let (_temp_dir, manager) = create_manager_with_credentials();
manager
.save_setting("api", "key", &json!("mobile_secret_123"))
.unwrap();
let metadata = manager.metadata().unwrap();
assert_eq!(
metadata.get("api.key").unwrap().value,
Some(json!("mobile_secret_123"))
);
let reset_val = manager.reset_setting("api", "key").unwrap();
assert!(reset_val == json!("") || reset_val.is_null());
}
#[test]
#[ignore = "Requires Secret Service daemon (not available in CI)"]
fn test_secret_persists_across_sessions() {
let temp_dir = TempDir::new().unwrap();
let config_path = temp_dir.path().to_path_buf();
let app_name = unique_app_name();
{
let config = SettingsConfig::builder(&app_name, "1.0.0")
.with_config_dir(&config_path)
.with_schema::<TestSettings>()
.with_credentials()
.build();
let manager = SettingsManager::new(config).unwrap();
manager.get_all().unwrap();
manager
.save_setting("api", "key", &json!("persistent_secret"))
.unwrap();
}
{
let config = SettingsConfig::builder(&app_name, "1.0.0")
.with_config_dir(&config_path)
.with_schema::<TestSettings>()
.with_credentials()
.build();
let manager = SettingsManager::new(config).unwrap();
let metadata = manager.metadata().unwrap();
let value = metadata.get("api.key").unwrap().value.clone();
#[cfg(feature = "keychain")]
assert_eq!(value, Some(json!("persistent_secret")));
#[cfg(not(feature = "keychain"))]
let _ = value;
}
}
#[test]
fn test_secret_has_correct_metadata() {
let (_temp_dir, manager) = create_manager_with_credentials();
let metadata = manager.metadata().unwrap();
let api_key_meta = metadata.get("api.key").unwrap();
#[cfg(any(feature = "keychain", feature = "encrypted-file"))]
assert!(api_key_meta.is_secret());
#[cfg(not(any(feature = "keychain", feature = "encrypted-file")))]
assert!(!api_key_meta.is_secret());
let theme_meta = metadata.get("ui.theme").unwrap();
assert!(!theme_meta.is_secret());
}
#[cfg(all(feature = "keychain", feature = "encrypted-file"))]
#[test]
fn test_encrypted_fallback_with_env_password() {
use rcman::SecretPasswordSource;
let temp_dir = TempDir::new().unwrap();
let app_name = unique_app_name();
let fallback_path = temp_dir.path().join("secrets.enc.json");
let env_var = format!("PASS_{}", unique_app_name().replace("-", "_"));
unsafe { std::env::set_var(&env_var, "super-secure-password") };
let config = SettingsConfig::builder(&app_name, "1.0.0")
.with_config_dir(temp_dir.path())
.with_schema::<TestSettings>()
.with_encrypted_fallback(
&fallback_path,
SecretPasswordSource::Environment(env_var.clone()),
)
.build();
let manager = SettingsManager::new(config).unwrap();
manager
.save_setting("api", "key", &json!("secret-value"))
.unwrap();
let val = manager.get_value("api.key").unwrap();
assert_eq!(val, json!("secret-value"));
unsafe { std::env::remove_var(&env_var) };
}
#[cfg(all(feature = "keychain", feature = "encrypted-file"))]
#[test]
fn test_encrypted_fallback_with_file_password() {
use rcman::SecretPasswordSource;
let temp_dir = TempDir::new().unwrap();
let app_name = unique_app_name();
let fallback_path = temp_dir.path().join("secrets.enc.json");
let password_path = temp_dir.path().join("password.txt");
std::fs::write(&password_path, "file-password-123").unwrap();
let config = SettingsConfig::builder(&app_name, "1.0.0")
.with_config_dir(temp_dir.path())
.with_schema::<TestSettings>()
.with_encrypted_fallback(&fallback_path, SecretPasswordSource::File(password_path))
.build();
let manager = SettingsManager::new(config).unwrap();
manager
.save_setting("api", "key", &json!("secret-value"))
.unwrap();
let val = manager.get_value("api.key").unwrap();
assert_eq!(val, json!("secret-value"));
}