diff --git a/src/config/schema.rs b/src/config/schema.rs index 4f89a874f..46a3ab0d6 100644 --- a/src/config/schema.rs +++ b/src/config/schema.rs @@ -8250,6 +8250,35 @@ mod tests { // ── Defaults ───────────────────────────────────────────── + fn has_test_table(raw: &str, table: &str) -> bool { + let exact = format!("[{table}]"); + let nested = format!("[{table}."); + raw.lines() + .map(str::trim) + .any(|line| line == exact || line.starts_with(&nested)) + } + + fn parse_test_config(raw: &str) -> Config { + let mut merged = raw.trim().to_string(); + for table in [ + "data_retention", + "cloud_ops", + "conversational_ai", + "security", + "security_ops", + ] { + if has_test_table(&merged, table) { + continue; + } + if !merged.is_empty() { + merged.push_str("\n\n"); + } + merged.push_str(&format!("[{table}]")); + } + merged.push('\n'); + toml::from_str(&merged).unwrap() + } + #[test] async fn http_request_config_default_has_correct_values() { let cfg = HttpRequestConfig::default(); @@ -8460,7 +8489,7 @@ config_path = "/tmp/config.toml" default_temperature = 0.7 "#; - let parsed: Config = toml::from_str(toml_str).unwrap(); + let parsed = parse_test_config(toml_str); assert!(parsed.cron.enabled); assert!(parsed.cron.catch_up_on_startup); assert_eq!(parsed.cron.max_run_history, 50); @@ -8639,7 +8668,7 @@ default_temperature = 0.7 }; let toml_str = toml::to_string_pretty(&config).unwrap(); - let parsed: Config = toml::from_str(&toml_str).unwrap(); + let parsed = parse_test_config(&toml_str); assert_eq!(parsed.api_key, config.api_key); assert_eq!(parsed.default_provider, config.default_provider); @@ -8672,7 +8701,7 @@ workspace_dir = "/tmp/ws" config_path = "/tmp/config.toml" default_temperature = 0.7 "#; - let parsed: Config = toml::from_str(minimal).unwrap(); + let parsed = parse_test_config(minimal); assert!(parsed.api_key.is_none()); assert!(parsed.default_provider.is_none()); assert_eq!(parsed.observability.backend, "none"); @@ -8695,7 +8724,7 @@ default_temperature = 0.7 default_temperature = 0.7 provider_timeout_secs = 300 "#; - let parsed: Config = toml::from_str(raw).unwrap(); + let parsed = parse_test_config(raw); assert_eq!(parsed.provider_timeout_secs, 300); } @@ -8775,7 +8804,7 @@ default_temperature = 0.7 User-Agent = "MyApp/1.0" X-Title = "zeroclaw" "#; - let parsed: Config = toml::from_str(raw).unwrap(); + let parsed = parse_test_config(raw); assert_eq!(parsed.extra_headers.len(), 2); assert_eq!(parsed.extra_headers.get("User-Agent").unwrap(), "MyApp/1.0"); assert_eq!(parsed.extra_headers.get("X-Title").unwrap(), "zeroclaw"); @@ -8786,7 +8815,7 @@ X-Title = "zeroclaw" let raw = r#" default_temperature = 0.7 "#; - let parsed: Config = toml::from_str(raw).unwrap(); + let parsed = parse_test_config(raw); assert!(parsed.extra_headers.is_empty()); } @@ -8803,7 +8832,7 @@ table = "memories" connect_timeout_secs = 12 "#; - let parsed: Config = toml::from_str(raw).unwrap(); + let parsed = parse_test_config(raw); assert_eq!(parsed.storage.provider.config.provider, "postgres"); assert_eq!( parsed.storage.provider.config.db_url.as_deref(), @@ -8826,7 +8855,7 @@ default_temperature = 0.7 reasoning_enabled = false "#; - let parsed: Config = toml::from_str(raw).unwrap(); + let parsed = parse_test_config(raw); assert_eq!(parsed.runtime.reasoning_enabled, Some(false)); } @@ -8877,7 +8906,7 @@ max_history_messages = 80 parallel_tools = true tool_dispatcher = "xml" "#; - let parsed: Config = toml::from_str(raw).unwrap(); + let parsed = parse_test_config(raw); assert!(parsed.agent.compact_context); assert_eq!(parsed.agent.max_tool_iterations, 20); assert_eq!(parsed.agent.max_history_messages, 80); @@ -9750,7 +9779,7 @@ workspace_dir = "/tmp/ws" config_path = "/tmp/config.toml" default_temperature = 0.7 "#; - let parsed: Config = toml::from_str(minimal).unwrap(); + let parsed = parse_test_config(minimal); assert!( parsed.gateway.require_pairing, "Missing [gateway] must default to require_pairing=true" @@ -9812,7 +9841,7 @@ workspace_dir = "/tmp/ws" config_path = "/tmp/config.toml" default_temperature = 0.7 "#; - let parsed: Config = toml::from_str(minimal).unwrap(); + let parsed = parse_test_config(minimal); assert!( !parsed.composio.enabled, "Missing [composio] must default to disabled" @@ -9867,7 +9896,7 @@ workspace_dir = "/tmp/ws" config_path = "/tmp/config.toml" default_temperature = 0.7 "#; - let parsed: Config = toml::from_str(minimal).unwrap(); + let parsed = parse_test_config(minimal); assert!( parsed.secrets.encrypt, "Missing [secrets] must default to encrypt=true" @@ -9952,7 +9981,7 @@ workspace_dir = "/tmp/ws" config_path = "/tmp/config.toml" default_temperature = 0.7 "#; - let parsed: Config = toml::from_str(minimal).unwrap(); + let parsed = parse_test_config(minimal); assert!(!parsed.browser.enabled); assert!(parsed.browser.allowed_domains.is_empty()); } @@ -10051,7 +10080,7 @@ wire_api = "responses" requires_openai_auth = true "#; - let parsed: Config = toml::from_str(raw).expect("config should parse"); + let parsed = parse_test_config(raw); assert_eq!(parsed.default_provider.as_deref(), Some("sub2api")); assert_eq!(parsed.default_model.as_deref(), Some("gpt-5.3-codex")); let profile = parsed @@ -10289,7 +10318,7 @@ requires_openai_auth = true let saved = tokio::fs::read_to_string(&resolved_config_path) .await .unwrap(); - let parsed: Config = toml::from_str(&saved).unwrap(); + let parsed = parse_test_config(&saved); assert_eq!(parsed.default_temperature, 0.5); std::env::remove_var("ZEROCLAW_WORKSPACE"); @@ -11396,7 +11425,7 @@ default_model = "persisted-profile" config.transcription.language = Some("en".into()); let toml_str = toml::to_string_pretty(&config).unwrap(); - let parsed: Config = toml::from_str(&toml_str).unwrap(); + let parsed = parse_test_config(&toml_str); assert!(parsed.transcription.enabled); assert_eq!(parsed.transcription.language.as_deref(), Some("en")); @@ -11410,21 +11439,20 @@ default_model = "persisted-profile" default_model = "test-model" default_temperature = 0.7 "#; - let parsed: Config = toml::from_str(toml_str).unwrap(); + let parsed = parse_test_config(toml_str); assert!(!parsed.transcription.enabled); assert_eq!(parsed.transcription.max_duration_secs, 120); } #[test] async fn security_defaults_are_backward_compatible() { - let parsed: Config = toml::from_str( + let parsed = parse_test_config( r#" default_provider = "openrouter" default_model = "anthropic/claude-sonnet-4.6" default_temperature = 0.7 "#, - ) - .unwrap(); + ); assert!(!parsed.security.otp.enabled); assert_eq!(parsed.security.otp.method, OtpMethod::Totp); @@ -11434,7 +11462,7 @@ default_temperature = 0.7 #[test] async fn security_toml_parses_otp_and_estop_sections() { - let parsed: Config = toml::from_str( + let parsed = parse_test_config( r#" default_provider = "openrouter" default_model = "anthropic/claude-sonnet-4.6" @@ -11454,8 +11482,7 @@ enabled = true state_file = "~/.zeroclaw/estop-state.json" require_otp_to_resume = true "#, - ) - .unwrap(); + ); assert!(parsed.security.otp.enabled); assert!(parsed.security.estop.enabled);