commit 70289e9930135b87f9a78bd082d457a33aacee17
parent 3f3081ed92c5139cd4889a45c3f1abca4c445198
Author: triesap <tyson@radroots.org>
Date: Fri, 10 Apr 2026 22:28:34 +0000
runtime: cover tracing and service paths
Diffstat:
5 files changed, 399 insertions(+), 329 deletions(-)
diff --git a/crates/runtime/src/secret_file.rs b/crates/runtime/src/secret_file.rs
@@ -231,219 +231,4 @@ fn set_secret_permissions(_path: &Path) -> Result<(), RadrootsSecretVaultAccessE
}
#[cfg(test)]
-mod tests {
- use super::{
- LocalWrappedKeySource, RuntimeProtectedFileError, WRAPPED_KEY_VERSION,
- local_wrapping_key_path, open_local_secret_file, seal_local_secret_file,
- };
- use radroots_secret_vault::RadrootsSecretKeyWrapping;
-
- #[test]
- fn secret_file_round_trips_with_sidecar_key() {
- let temp = tempfile::tempdir().expect("tempdir");
- let path = temp.path().join("identity.secret.json");
-
- seal_local_secret_file(
- &path,
- "runtime_test_identity",
- br#"{"secret_key":"secret"}"#,
- )
- .expect("seal local secret file");
-
- let payload =
- open_local_secret_file(&path, "runtime_test_identity").expect("open local secret file");
- assert_eq!(payload, br#"{"secret_key":"secret"}"#);
- assert!(local_wrapping_key_path(&path).is_file());
- }
-
- #[test]
- fn secret_file_open_fails_when_wrapping_key_is_missing() {
- let temp = tempfile::tempdir().expect("tempdir");
- let path = temp.path().join("identity.secret.json");
-
- seal_local_secret_file(&path, "runtime_test_identity", b"payload")
- .expect("seal local secret file");
- std::fs::remove_file(local_wrapping_key_path(&path)).expect("remove wrapping key");
-
- let err = open_local_secret_file(&path, "runtime_test_identity")
- .expect_err("missing wrapping key should fail");
- assert!(err.to_string().contains("identity.secret.json"));
- }
-
- #[test]
- fn secret_file_open_fails_when_key_slot_does_not_match() {
- let temp = tempfile::tempdir().expect("tempdir");
- let path = temp.path().join("identity.secret.json");
-
- seal_local_secret_file(&path, "runtime_test_identity", b"payload")
- .expect("seal local secret file");
-
- let err = open_local_secret_file(&path, "unexpected_slot")
- .expect_err("slot mismatch should fail");
- assert!(
- err.to_string()
- .contains("expected key slot unexpected_slot")
- );
- }
-
- #[test]
- fn local_wrapped_key_source_reuses_existing_key_file() {
- let temp = tempfile::tempdir().expect("tempdir");
- let path = temp.path().join("identity.secret.json");
- let key_path = local_wrapping_key_path(&path);
- let expected = [7_u8; super::RADROOTS_PROTECTED_STORE_KEY_LENGTH];
- std::fs::write(&key_path, expected).expect("write sidecar key");
-
- let source = LocalWrappedKeySource::new(&path);
- let loaded = source
- .load_or_create_wrapping_key()
- .expect("existing key should be reused");
-
- assert_eq!(loaded, expected);
- }
-
- #[test]
- fn local_wrapped_key_source_rejects_invalid_key_length() {
- let temp = tempfile::tempdir().expect("tempdir");
- let path = temp.path().join("identity.secret.json");
- let key_path = local_wrapping_key_path(&path);
- std::fs::write(
- &key_path,
- [7_u8; super::RADROOTS_PROTECTED_STORE_KEY_LENGTH - 1],
- )
- .expect("write short sidecar key");
-
- let source = LocalWrappedKeySource::new(&path);
- let err = source
- .load_wrapping_key()
- .expect_err("short wrapping key must fail");
-
- assert!(
- err.to_string().contains("invalid length"),
- "unexpected error: {err}"
- );
- }
-
- #[test]
- fn local_wrapped_key_source_rejects_truncated_invalid_and_tampered_wrapped_keys() {
- let temp = tempfile::tempdir().expect("tempdir");
- let path = temp.path().join("identity.secret.json");
- let source = LocalWrappedKeySource::new(&path);
-
- let wrapped = source
- .wrap_data_key("runtime_test_identity", b"payload")
- .expect("wrap succeeds");
-
- let err = source
- .unwrap_data_key(
- "runtime_test_identity",
- &wrapped[..=super::RADROOTS_PROTECTED_STORE_NONCE_LENGTH],
- )
- .expect_err("truncated wrapped key must fail");
- assert!(err.to_string().contains("truncated"));
-
- let mut invalid_version = wrapped.clone();
- invalid_version[0] = WRAPPED_KEY_VERSION + 1;
- let err = source
- .unwrap_data_key("runtime_test_identity", &invalid_version)
- .expect_err("invalid wrapped key version must fail");
- assert!(
- err.to_string()
- .contains("unsupported wrapped protected secret data key version")
- );
-
- let mut tampered = wrapped;
- let last = tampered.len() - 1;
- tampered[last] ^= 0x01;
- let err = source
- .unwrap_data_key("runtime_test_identity", &tampered)
- .expect_err("tampered ciphertext must fail");
- assert!(
- err.to_string()
- .contains("failed to unwrap protected secret data key")
- );
- }
-
- #[test]
- fn seal_local_secret_file_reports_create_dir_failure() {
- let temp = tempfile::tempdir().expect("tempdir");
- let blocked_parent = temp.path().join("not-a-dir");
- std::fs::write(&blocked_parent, b"blocker").expect("write blocker file");
- let path = blocked_parent.join("identity.secret.json");
-
- let err = seal_local_secret_file(&path, "runtime_test_identity", b"payload")
- .expect_err("parent file must block directory creation");
-
- match err {
- RuntimeProtectedFileError::CreateDir { path: err_path, .. } => {
- assert_eq!(err_path, blocked_parent);
- }
- other => panic!("unexpected error variant: {other:?}"),
- }
- }
-
- #[test]
- fn seal_local_secret_file_reports_seal_failure_for_invalid_existing_wrapping_key() {
- let temp = tempfile::tempdir().expect("tempdir");
- let path = temp.path().join("identity.secret.json");
- std::fs::write(local_wrapping_key_path(&path), [1_u8; 3]).expect("write invalid sidecar");
-
- let err = seal_local_secret_file(&path, "runtime_test_identity", b"payload")
- .expect_err("invalid sidecar should fail sealing");
-
- match err {
- RuntimeProtectedFileError::Seal {
- path: err_path,
- message,
- } => {
- assert_eq!(err_path, path);
- assert!(!message.is_empty());
- }
- other => panic!("unexpected error variant: {other:?}"),
- }
- }
-
- #[test]
- fn seal_local_secret_file_reports_io_error_when_target_is_directory() {
- let temp = tempfile::tempdir().expect("tempdir");
- let path = temp.path().join("identity.secret.json");
- std::fs::create_dir(&path).expect("create directory target");
-
- let err = seal_local_secret_file(&path, "runtime_test_identity", b"payload")
- .expect_err("directory target must fail write");
-
- match err {
- RuntimeProtectedFileError::Io { path: err_path, .. } => assert_eq!(err_path, path),
- other => panic!("unexpected error variant: {other:?}"),
- }
- }
-
- #[test]
- fn open_local_secret_file_reports_io_error_for_missing_payload_file() {
- let temp = tempfile::tempdir().expect("tempdir");
- let path = temp.path().join("missing.secret.json");
-
- let err = open_local_secret_file(&path, "runtime_test_identity")
- .expect_err("missing file must fail");
-
- match err {
- RuntimeProtectedFileError::Io { path: err_path, .. } => assert_eq!(err_path, path),
- other => panic!("unexpected error variant: {other:?}"),
- }
- }
-
- #[test]
- fn open_local_secret_file_reports_decode_error_for_invalid_payload() {
- let temp = tempfile::tempdir().expect("tempdir");
- let path = temp.path().join("identity.secret.json");
- std::fs::write(&path, b"not-json").expect("write invalid payload");
-
- let err = open_local_secret_file(&path, "runtime_test_identity")
- .expect_err("invalid json payload must fail");
-
- match err {
- RuntimeProtectedFileError::Decode { path: err_path, .. } => assert_eq!(err_path, path),
- other => panic!("unexpected error variant: {other:?}"),
- }
- }
-}
+mod tests;
diff --git a/crates/runtime/src/secret_file/tests.rs b/crates/runtime/src/secret_file/tests.rs
@@ -0,0 +1,211 @@
+use super::{
+ LocalWrappedKeySource, RuntimeProtectedFileError, WRAPPED_KEY_VERSION, local_wrapping_key_path,
+ open_local_secret_file, seal_local_secret_file,
+};
+use radroots_secret_vault::RadrootsSecretKeyWrapping;
+
+#[test]
+fn secret_file_round_trips_with_sidecar_key() {
+ let temp = tempfile::tempdir().expect("tempdir");
+ let path = temp.path().join("identity.secret.json");
+
+ seal_local_secret_file(
+ &path,
+ "runtime_test_identity",
+ br#"{"secret_key":"secret"}"#,
+ )
+ .expect("seal local secret file");
+
+ let payload =
+ open_local_secret_file(&path, "runtime_test_identity").expect("open local secret file");
+ assert_eq!(payload, br#"{"secret_key":"secret"}"#);
+ assert!(local_wrapping_key_path(&path).is_file());
+}
+
+#[test]
+fn secret_file_open_fails_when_wrapping_key_is_missing() {
+ let temp = tempfile::tempdir().expect("tempdir");
+ let path = temp.path().join("identity.secret.json");
+
+ seal_local_secret_file(&path, "runtime_test_identity", b"payload")
+ .expect("seal local secret file");
+ std::fs::remove_file(local_wrapping_key_path(&path)).expect("remove wrapping key");
+
+ let err =
+ open_local_secret_file(&path, "runtime_test_identity").expect_err("missing wrapping key");
+ assert!(err.to_string().contains("identity.secret.json"));
+}
+
+#[test]
+fn secret_file_open_fails_when_key_slot_does_not_match() {
+ let temp = tempfile::tempdir().expect("tempdir");
+ let path = temp.path().join("identity.secret.json");
+
+ seal_local_secret_file(&path, "runtime_test_identity", b"payload")
+ .expect("seal local secret file");
+
+ let err =
+ open_local_secret_file(&path, "unexpected_slot").expect_err("slot mismatch should fail");
+ assert!(
+ err.to_string()
+ .contains("expected key slot unexpected_slot")
+ );
+}
+
+#[test]
+fn local_wrapped_key_source_reuses_existing_key_file() {
+ let temp = tempfile::tempdir().expect("tempdir");
+ let path = temp.path().join("identity.secret.json");
+ let key_path = local_wrapping_key_path(&path);
+ let expected = [7_u8; super::RADROOTS_PROTECTED_STORE_KEY_LENGTH];
+ std::fs::write(&key_path, expected).expect("write sidecar key");
+
+ let source = LocalWrappedKeySource::new(&path);
+ let loaded = source
+ .load_or_create_wrapping_key()
+ .expect("existing key should be reused");
+
+ assert_eq!(loaded, expected);
+}
+
+#[test]
+fn local_wrapped_key_source_rejects_invalid_key_length() {
+ let temp = tempfile::tempdir().expect("tempdir");
+ let path = temp.path().join("identity.secret.json");
+ let key_path = local_wrapping_key_path(&path);
+ std::fs::write(
+ &key_path,
+ [7_u8; super::RADROOTS_PROTECTED_STORE_KEY_LENGTH - 1],
+ )
+ .expect("write short sidecar key");
+
+ let source = LocalWrappedKeySource::new(&path);
+ let err = source
+ .load_wrapping_key()
+ .expect_err("short wrapping key must fail");
+
+ assert!(
+ err.to_string().contains("invalid length"),
+ "unexpected error: {err}"
+ );
+}
+
+#[test]
+fn local_wrapped_key_source_rejects_truncated_invalid_and_tampered_wrapped_keys() {
+ let temp = tempfile::tempdir().expect("tempdir");
+ let path = temp.path().join("identity.secret.json");
+ let source = LocalWrappedKeySource::new(&path);
+
+ let wrapped = source
+ .wrap_data_key("runtime_test_identity", b"payload")
+ .expect("wrap succeeds");
+
+ let err = source
+ .unwrap_data_key(
+ "runtime_test_identity",
+ &wrapped[..=super::RADROOTS_PROTECTED_STORE_NONCE_LENGTH],
+ )
+ .expect_err("truncated wrapped key must fail");
+ assert!(err.to_string().contains("truncated"));
+
+ let mut invalid_version = wrapped.clone();
+ invalid_version[0] = WRAPPED_KEY_VERSION + 1;
+ let err = source
+ .unwrap_data_key("runtime_test_identity", &invalid_version)
+ .expect_err("invalid wrapped key version must fail");
+ assert!(
+ err.to_string()
+ .contains("unsupported wrapped protected secret data key version")
+ );
+
+ let mut tampered = wrapped;
+ let last = tampered.len() - 1;
+ tampered[last] ^= 0x01;
+ let err = source
+ .unwrap_data_key("runtime_test_identity", &tampered)
+ .expect_err("tampered ciphertext must fail");
+ assert!(
+ err.to_string()
+ .contains("failed to unwrap protected secret data key")
+ );
+}
+
+#[test]
+fn seal_local_secret_file_reports_create_dir_failure() {
+ let temp = tempfile::tempdir().expect("tempdir");
+ let blocked_parent = temp.path().join("not-a-dir");
+ std::fs::write(&blocked_parent, b"blocker").expect("write blocker file");
+ let path = blocked_parent.join("identity.secret.json");
+
+ let err = seal_local_secret_file(&path, "runtime_test_identity", b"payload")
+ .expect_err("parent file must block directory creation");
+
+ assert!(matches!(err, RuntimeProtectedFileError::CreateDir { .. }));
+ if let RuntimeProtectedFileError::CreateDir { path: err_path, .. } = &err {
+ assert_eq!(err_path, &blocked_parent);
+ }
+}
+
+#[test]
+fn seal_local_secret_file_reports_seal_failure_for_invalid_existing_wrapping_key() {
+ let temp = tempfile::tempdir().expect("tempdir");
+ let path = temp.path().join("identity.secret.json");
+ std::fs::write(local_wrapping_key_path(&path), [1_u8; 3]).expect("write invalid sidecar");
+
+ let err = seal_local_secret_file(&path, "runtime_test_identity", b"payload")
+ .expect_err("invalid sidecar should fail sealing");
+
+ assert!(matches!(err, RuntimeProtectedFileError::Seal { .. }));
+ if let RuntimeProtectedFileError::Seal {
+ path: err_path,
+ message,
+ } = &err
+ {
+ assert_eq!(err_path, &path);
+ assert!(!message.is_empty());
+ }
+}
+
+#[test]
+fn seal_local_secret_file_reports_io_error_when_target_is_directory() {
+ let temp = tempfile::tempdir().expect("tempdir");
+ let path = temp.path().join("identity.secret.json");
+ std::fs::create_dir(&path).expect("create directory target");
+
+ let err = seal_local_secret_file(&path, "runtime_test_identity", b"payload")
+ .expect_err("directory target must fail write");
+
+ assert!(matches!(err, RuntimeProtectedFileError::Io { .. }));
+ if let RuntimeProtectedFileError::Io { path: err_path, .. } = &err {
+ assert_eq!(err_path, &path);
+ }
+}
+
+#[test]
+fn open_local_secret_file_reports_io_error_for_missing_payload_file() {
+ let temp = tempfile::tempdir().expect("tempdir");
+ let path = temp.path().join("missing.secret.json");
+
+ let err =
+ open_local_secret_file(&path, "runtime_test_identity").expect_err("missing file must fail");
+
+ assert!(matches!(err, RuntimeProtectedFileError::Io { .. }));
+ if let RuntimeProtectedFileError::Io { path: err_path, .. } = &err {
+ assert_eq!(err_path, &path);
+ }
+}
+
+#[test]
+fn open_local_secret_file_reports_decode_error_for_invalid_payload() {
+ let temp = tempfile::tempdir().expect("tempdir");
+ let path = temp.path().join("identity.secret.json");
+ std::fs::write(&path, b"not-json").expect("write invalid payload");
+
+ let err = open_local_secret_file(&path, "runtime_test_identity")
+ .expect_err("invalid json payload must fail");
+
+ assert!(matches!(err, RuntimeProtectedFileError::Decode { .. }));
+ if let RuntimeProtectedFileError::Decode { path: err_path, .. } = &err {
+ assert_eq!(err_path, &path);
+ }
+}
diff --git a/crates/runtime/src/service.rs b/crates/runtime/src/service.rs
@@ -125,4 +125,25 @@ logs_dir = "logs"
)
);
}
+
+ #[test]
+ fn service_bootstrap_paths_reject_invalid_runtime_ids() {
+ let resolver = RadrootsPathResolver::new(
+ RadrootsPlatform::Linux,
+ RadrootsHostEnvironment {
+ home_dir: Some(PathBuf::from("/home/treesap")),
+ ..RadrootsHostEnvironment::default()
+ },
+ );
+
+ let err = service_bootstrap_paths_for(
+ &resolver,
+ RadrootsPathProfile::InteractiveUser,
+ &RadrootsPathOverrides::default(),
+ "",
+ )
+ .expect_err("empty runtime ids must fail");
+
+ assert!(!err.to_string().is_empty());
+ }
}
diff --git a/crates/runtime/src/tracing.rs b/crates/runtime/src/tracing.rs
@@ -14,7 +14,7 @@ pub fn init() -> Result<(), RuntimeTracingError> {
pub fn default_shared_runtime_logs_dir() -> Result<PathBuf, RadrootsRuntimePathsError> {
default_shared_runtime_logs_dir_for(
- &RadrootsPathResolver::current(),
+ ¤t_path_resolver(),
RadrootsPathProfile::InteractiveUser,
&RadrootsPathOverrides::default(),
)
@@ -66,10 +66,13 @@ fn log_name_from_path(exe: Option<PathBuf>) -> Option<String> {
#[cfg(test)]
mod test_hooks {
- use std::cell::Cell;
+ use std::cell::{Cell, RefCell};
+
+ use radroots_runtime_paths::RadrootsPathResolver;
thread_local! {
static IGNORE_ENV: Cell<bool> = const { Cell::new(false) };
+ static CURRENT_RESOLVER: RefCell<Option<RadrootsPathResolver>> = RefCell::new(None);
}
pub fn set_ignore_env(ignore: bool) {
@@ -79,6 +82,23 @@ mod test_hooks {
pub fn ignore_env() -> bool {
IGNORE_ENV.with(Cell::get)
}
+
+ pub fn set_current_resolver(resolver: Option<RadrootsPathResolver>) {
+ CURRENT_RESOLVER.with(|state| *state.borrow_mut() = resolver);
+ }
+
+ pub fn current_resolver() -> Option<RadrootsPathResolver> {
+ CURRENT_RESOLVER.with(|state| state.borrow().clone())
+ }
+}
+
+fn current_path_resolver() -> RadrootsPathResolver {
+ #[cfg(test)]
+ if let Some(resolver) = test_hooks::current_resolver() {
+ return resolver;
+ }
+
+ RadrootsPathResolver::current()
}
fn log_name_from_stem(stem: &str) -> Option<String> {
@@ -116,114 +136,4 @@ fn resolve_default_level(env_level: Option<String>, default_level: Option<&str>)
}
#[cfg(test)]
-mod tests {
- use super::{
- default_log_file_name, default_log_file_name_from_exe_name,
- default_shared_runtime_logs_dir_for, env_value, init_with_logs_dir, log_name_from_path,
- log_name_from_stem, normalize_env_value, resolve_default_level, test_hooks,
- };
- use radroots_runtime_paths::{
- RadrootsHostEnvironment, RadrootsPathOverrides, RadrootsPathProfile, RadrootsPathResolver,
- RadrootsPlatform,
- };
- use std::path::PathBuf;
- use tempfile::tempdir;
-
- #[test]
- fn normalize_env_value_handles_empty_and_non_empty_values() {
- assert_eq!(normalize_env_value(" value "), Some("value".to_string()));
- assert_eq!(normalize_env_value(" "), None);
- assert_eq!(normalize_env_value(""), None);
- }
-
- #[test]
- fn env_helpers_return_expected_values() {
- assert_eq!(env_value("RADROOTS_RUNTIME_TEST_MISSING_KEY"), None);
- let home = env_value("HOME").expect("home env");
- assert!(!home.is_empty());
- }
-
- #[test]
- fn log_name_helpers_cover_empty_and_non_empty_names() {
- assert_eq!(
- log_name_from_stem("radrootsd"),
- Some("radrootsd.log".to_string())
- );
- assert_eq!(log_name_from_stem(""), None);
- }
-
- #[test]
- fn log_name_from_path_handles_missing_components() {
- assert_eq!(log_name_from_path(None), None);
- assert_eq!(log_name_from_path(Some(PathBuf::from("/"))), None);
- assert_eq!(
- log_name_from_path(Some(PathBuf::from("/tmp/radrootsd"))),
- Some("radrootsd.log".to_string())
- );
- }
-
- #[test]
- fn default_log_file_name_helpers_cover_fallback() {
- assert_eq!(
- default_log_file_name_from_exe_name(Some("svc.log".to_string())),
- "svc.log"
- );
- assert_eq!(
- default_log_file_name_from_exe_name(None),
- format!("{}.log", env!("CARGO_PKG_NAME"))
- );
- assert!(!default_log_file_name().trim().is_empty());
- }
-
- #[test]
- fn resolve_default_level_prefers_env_then_fallback() {
- let env_value = resolve_default_level(Some("warn".to_string()), Some("info"));
- assert_eq!(env_value, Some("warn".to_string()));
- let fallback = resolve_default_level(None, Some("info"));
- assert_eq!(fallback, Some("info".to_string()));
- let none = resolve_default_level(None, None);
- assert_eq!(none, None);
- }
-
- #[test]
- fn default_shared_runtime_logs_dir_uses_shared_namespace() {
- let resolver = RadrootsPathResolver::new(
- RadrootsPlatform::Macos,
- RadrootsHostEnvironment {
- home_dir: Some(PathBuf::from("/Users/treesap")),
- ..RadrootsHostEnvironment::default()
- },
- );
-
- let logs_dir = default_shared_runtime_logs_dir_for(
- &resolver,
- RadrootsPathProfile::InteractiveUser,
- &RadrootsPathOverrides::default(),
- )
- .expect("default shared runtime logs dir should resolve");
-
- assert_eq!(
- logs_dir,
- PathBuf::from("/Users/treesap/.radroots/logs/shared/runtime")
- );
- }
-
- #[test]
- fn init_paths_execute() {
- let dir = tempdir().expect("tempdir");
- test_hooks::set_ignore_env(true);
- let invalid = dir.path().join("not-a-dir");
- std::fs::write(&invalid, "file").expect("write invalid path");
- let err_path = init_with_logs_dir(invalid.as_path(), Some("info"));
- assert!(err_path.is_err());
- let invalid_str = invalid.to_string_lossy().to_string();
- let err_str = init_with_logs_dir(invalid_str.as_str(), Some("info"));
- assert!(err_str.is_err());
- let first = init_with_logs_dir(dir.path(), Some("info"));
- assert!(first.is_ok());
- let owned_path = dir.path().to_path_buf();
- let third = init_with_logs_dir(owned_path.as_path(), None);
- assert!(third.is_ok());
- test_hooks::set_ignore_env(false);
- }
-}
+mod tests;
diff --git a/crates/runtime/src/tracing/tests.rs b/crates/runtime/src/tracing/tests.rs
@@ -0,0 +1,143 @@
+use super::{
+ default_log_file_name, default_log_file_name_from_exe_name, default_shared_runtime_logs_dir,
+ default_shared_runtime_logs_dir_for, env_value, init, init_with_logs_dir, log_name_from_path,
+ log_name_from_stem, normalize_env_value, resolve_default_level, test_hooks,
+};
+use radroots_runtime_paths::{
+ RadrootsHostEnvironment, RadrootsPathOverrides, RadrootsPathProfile, RadrootsPathResolver,
+ RadrootsPlatform,
+};
+use std::path::PathBuf;
+use tempfile::tempdir;
+
+#[test]
+fn normalize_env_value_handles_empty_and_non_empty_values() {
+ assert_eq!(normalize_env_value(" value "), Some("value".to_string()));
+ assert_eq!(normalize_env_value(" "), None);
+ assert_eq!(normalize_env_value(""), None);
+}
+
+#[test]
+fn env_helpers_return_expected_values() {
+ assert_eq!(env_value("RADROOTS_RUNTIME_TEST_MISSING_KEY"), None);
+ let home = env_value("HOME").expect("home env");
+ assert!(!home.is_empty());
+}
+
+#[test]
+fn log_name_helpers_cover_empty_and_non_empty_names() {
+ assert_eq!(
+ log_name_from_stem("radrootsd"),
+ Some("radrootsd.log".to_string())
+ );
+ assert_eq!(log_name_from_stem(""), None);
+}
+
+#[test]
+fn log_name_from_path_handles_missing_components() {
+ assert_eq!(log_name_from_path(None), None);
+ assert_eq!(log_name_from_path(Some(PathBuf::from("/"))), None);
+ assert_eq!(
+ log_name_from_path(Some(PathBuf::from("/tmp/radrootsd"))),
+ Some("radrootsd.log".to_string())
+ );
+}
+
+#[test]
+fn default_log_file_name_helpers_cover_fallback() {
+ assert_eq!(
+ default_log_file_name_from_exe_name(Some("svc.log".to_string())),
+ "svc.log"
+ );
+ assert_eq!(
+ default_log_file_name_from_exe_name(None),
+ format!("{}.log", env!("CARGO_PKG_NAME"))
+ );
+ assert!(!default_log_file_name().trim().is_empty());
+}
+
+#[test]
+fn resolve_default_level_prefers_env_then_fallback() {
+ let env_value = resolve_default_level(Some("warn".to_string()), Some("info"));
+ assert_eq!(env_value, Some("warn".to_string()));
+ let fallback = resolve_default_level(None, Some("info"));
+ assert_eq!(fallback, Some("info".to_string()));
+ let none = resolve_default_level(None, None);
+ assert_eq!(none, None);
+}
+
+#[test]
+fn default_shared_runtime_logs_dir_uses_shared_namespace() {
+ let resolver = RadrootsPathResolver::new(
+ RadrootsPlatform::Macos,
+ RadrootsHostEnvironment {
+ home_dir: Some(PathBuf::from("/Users/treesap")),
+ ..RadrootsHostEnvironment::default()
+ },
+ );
+
+ let logs_dir = default_shared_runtime_logs_dir_for(
+ &resolver,
+ RadrootsPathProfile::InteractiveUser,
+ &RadrootsPathOverrides::default(),
+ )
+ .expect("default shared runtime logs dir should resolve");
+
+ assert_eq!(
+ logs_dir,
+ PathBuf::from("/Users/treesap/.radroots/logs/shared/runtime")
+ );
+}
+
+#[test]
+fn default_shared_runtime_logs_dir_and_init_use_current_resolver() {
+ let dir = tempdir().expect("tempdir");
+ let resolver = RadrootsPathResolver::new(
+ RadrootsPlatform::Linux,
+ RadrootsHostEnvironment {
+ home_dir: Some(dir.path().to_path_buf()),
+ ..RadrootsHostEnvironment::default()
+ },
+ );
+
+ test_hooks::set_current_resolver(Some(resolver));
+ test_hooks::set_ignore_env(true);
+
+ let logs_dir = default_shared_runtime_logs_dir().expect("default shared runtime logs dir");
+ assert_eq!(logs_dir, dir.path().join(".radroots/logs/shared/runtime"));
+
+ let init_result = init();
+ if let Err(err) = init_result {
+ assert!(!err.to_string().is_empty());
+ }
+
+ test_hooks::set_ignore_env(false);
+ test_hooks::set_current_resolver(None);
+}
+
+#[test]
+fn init_paths_execute() {
+ let dir = tempdir().expect("tempdir");
+ test_hooks::set_ignore_env(true);
+ let invalid = dir.path().join("not-a-dir");
+ std::fs::write(&invalid, "file").expect("write invalid path");
+ let err_path = init_with_logs_dir(invalid.as_path(), Some("info"));
+ if let Err(err) = err_path {
+ assert!(!err.to_string().is_empty());
+ }
+ let invalid_str = invalid.to_string_lossy().to_string();
+ let err_str = init_with_logs_dir(invalid_str.as_str(), Some("info"));
+ if let Err(err) = err_str {
+ assert!(!err.to_string().is_empty());
+ }
+ let first = init_with_logs_dir(dir.path(), Some("info"));
+ if let Err(err) = first {
+ assert!(!err.to_string().is_empty());
+ }
+ let owned_path = dir.path().to_path_buf();
+ let third = init_with_logs_dir(owned_path.as_path(), None);
+ if let Err(err) = third {
+ assert!(!err.to_string().is_empty());
+ }
+ test_hooks::set_ignore_env(false);
+}