diff --git a/src/config/schema.rs b/src/config/schema.rs index 8469980d0..bf5cf4353 100644 --- a/src/config/schema.rs +++ b/src/config/schema.rs @@ -6919,7 +6919,7 @@ impl Config { path = %config.config_path.display(), workspace = %config.workspace_dir.display(), source = resolution_source.as_str(), - initialized = false, + initialized = true, "Config loaded" ); Ok(config) @@ -8237,9 +8237,11 @@ async fn sync_directory(path: &Path) -> Result<()> { #[cfg(test)] mod tests { use super::*; + use std::io; #[cfg(unix)] use std::os::unix::fs::PermissionsExt; use std::path::PathBuf; + use std::sync::{Arc, Mutex as StdMutex}; #[cfg(unix)] use tempfile::TempDir; use tokio::sync::{Mutex, MutexGuard}; @@ -8275,6 +8277,36 @@ mod tests { assert!(c.config_path.to_string_lossy().contains("config.toml")); } + #[derive(Clone, Default)] + struct SharedLogBuffer(Arc>>); + + struct SharedLogWriter(Arc>>); + + impl SharedLogBuffer { + fn captured(&self) -> String { + String::from_utf8(self.0.lock().unwrap().clone()).unwrap() + } + } + + impl<'a> tracing_subscriber::fmt::MakeWriter<'a> for SharedLogBuffer { + type Writer = SharedLogWriter; + + fn make_writer(&'a self) -> Self::Writer { + SharedLogWriter(self.0.clone()) + } + } + + impl io::Write for SharedLogWriter { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.0.lock().unwrap().extend_from_slice(buf); + Ok(buf.len()) + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } + } + #[test] async fn config_dir_creation_error_mentions_openrc_and_path() { let msg = config_dir_creation_error(Path::new("/etc/zeroclaw")); @@ -10703,6 +10735,59 @@ default_model = "legacy-model" let _ = fs::remove_dir_all(temp_home).await; } + #[test] + async fn load_or_init_logs_existing_config_as_initialized() { + let _env_guard = env_override_lock().await; + let temp_home = + std::env::temp_dir().join(format!("zeroclaw_test_home_{}", uuid::Uuid::new_v4())); + let workspace_dir = temp_home.join("profile-a"); + let config_path = workspace_dir.join("config.toml"); + + fs::create_dir_all(&workspace_dir).await.unwrap(); + fs::write( + &config_path, + r#"default_temperature = 0.7 +default_model = "persisted-profile" +"#, + ) + .await + .unwrap(); + + let original_home = std::env::var("HOME").ok(); + std::env::set_var("HOME", &temp_home); + std::env::set_var("ZEROCLAW_WORKSPACE", &workspace_dir); + + let capture = SharedLogBuffer::default(); + let subscriber = tracing_subscriber::fmt() + .with_ansi(false) + .without_time() + .with_target(false) + .with_writer(capture.clone()) + .finish(); + let dispatch = tracing::Dispatch::new(subscriber); + let guard = tracing::dispatcher::set_default(&dispatch); + + let config = Config::load_or_init().await.unwrap(); + + drop(guard); + let logs = capture.captured(); + + assert_eq!(config.workspace_dir, workspace_dir.join("workspace")); + assert_eq!(config.config_path, config_path); + assert_eq!(config.default_model.as_deref(), Some("persisted-profile")); + assert!(logs.contains("Config loaded"), "{logs}"); + assert!(logs.contains("initialized=true"), "{logs}"); + assert!(!logs.contains("initialized=false"), "{logs}"); + + std::env::remove_var("ZEROCLAW_WORKSPACE"); + if let Some(home) = original_home { + std::env::set_var("HOME", home); + } else { + std::env::remove_var("HOME"); + } + let _ = fs::remove_dir_all(temp_home).await; + } + #[test] async fn env_override_empty_values_ignored() { let _env_guard = env_override_lock().await;