// figment::Jail::expect_with dictates closures returning figment's own // (large) error type; the lint has nothing actionable here. #![allow(clippy::result_large_err)] use cm_config::{AppConfig, AuthMode, ConfigError, DeployTarget, LlmProviderKind}; const AIR_GAPPED_TOML: &str = r#" deploy_target = "air_gapped" listen_addr = "0.0.0.0:8080" [database] url = "postgres://clawmates:pw@db:5432/clawmates" [llm] provider = "openai_compat" base_url = "http://local-llm:8000/v1" model = "qwen2.5-72b-instruct" [auth] mode = "local" "#; const CLOUD_TOML: &str = r#" deploy_target = "cloud" listen_addr = "0.0.0.0:8080" [database] url = "postgres://clawmates:pw@db:5432/clawmates" max_connections = 32 [llm] provider = "anthropic" model = "claude-sonnet-4-5" [auth] mode = "oidc" issuer_url = "https://idp.example.com" client_id = "clawmates" "#; #[test] fn loads_air_gapped_config_from_toml() { figment::Jail::expect_with(|jail| { jail.create_file("clawmates.toml", AIR_GAPPED_TOML)?; let cfg = AppConfig::load_from(&jail.directory().join("clawmates.toml")).unwrap(); assert_eq!(cfg.deploy_target, DeployTarget::AirGapped); assert_eq!(cfg.listen_addr.port(), 8080); assert_eq!( cfg.database.url, "postgres://clawmates:pw@db:5432/clawmates" ); assert_eq!(cfg.llm.provider, LlmProviderKind::OpenAiCompat); assert_eq!( cfg.llm.base_url.as_deref(), Some("http://local-llm:8000/v1") ); assert_eq!(cfg.auth.mode, AuthMode::Local); Ok(()) }); } #[test] fn loads_cloud_config_with_oidc() { figment::Jail::expect_with(|jail| { jail.create_file("clawmates.toml", CLOUD_TOML)?; let cfg = AppConfig::load_from(&jail.directory().join("clawmates.toml")).unwrap(); assert_eq!(cfg.deploy_target, DeployTarget::Cloud); assert_eq!(cfg.database.max_connections, 32); assert_eq!(cfg.llm.provider, LlmProviderKind::Anthropic); assert_eq!(cfg.auth.mode, AuthMode::Oidc); assert_eq!( cfg.auth.issuer_url.as_deref(), Some("https://idp.example.com") ); Ok(()) }); } #[test] fn env_overrides_toml_values() { figment::Jail::expect_with(|jail| { jail.create_file("clawmates.toml", AIR_GAPPED_TOML)?; jail.set_env("CLAWMATES_DATABASE__URL", "postgres://other:pw@x:5432/y"); jail.set_env("CLAWMATES_LLM__MODEL", "llama-3.1-8b-instruct"); let cfg = AppConfig::load_from(&jail.directory().join("clawmates.toml")).unwrap(); assert_eq!(cfg.database.url, "postgres://other:pw@x:5432/y"); assert_eq!(cfg.llm.model, "llama-3.1-8b-instruct"); Ok(()) }); } #[test] fn database_max_connections_defaults_to_ten() { figment::Jail::expect_with(|jail| { jail.create_file("clawmates.toml", AIR_GAPPED_TOML)?; let cfg = AppConfig::load_from(&jail.directory().join("clawmates.toml")).unwrap(); assert_eq!(cfg.database.max_connections, 10); Ok(()) }); } #[test] fn openai_compat_without_base_url_is_rejected() { figment::Jail::expect_with(|jail| { let toml = AIR_GAPPED_TOML.replace("base_url = \"http://local-llm:8000/v1\"\n", ""); jail.create_file("clawmates.toml", &toml)?; let err = AppConfig::load_from(&jail.directory().join("clawmates.toml")).unwrap_err(); assert!(matches!(err, ConfigError::Invalid(msg) if msg.contains("base_url"))); Ok(()) }); } #[test] fn scripted_provider_requires_scenario_path() { figment::Jail::expect_with(|jail| { let toml = AIR_GAPPED_TOML .replace("provider = \"openai_compat\"", "provider = \"scripted\"") .replace("base_url = \"http://local-llm:8000/v1\"\n", ""); jail.create_file("clawmates.toml", &toml)?; let err = AppConfig::load_from(&jail.directory().join("clawmates.toml")).unwrap_err(); assert!(matches!(err, ConfigError::Invalid(msg) if msg.contains("scenario_path"))); Ok(()) }); } #[test] fn oidc_mode_requires_issuer_and_client_id() { figment::Jail::expect_with(|jail| { let toml = CLOUD_TOML.replace("issuer_url = \"https://idp.example.com\"\n", ""); jail.create_file("clawmates.toml", &toml)?; let err = AppConfig::load_from(&jail.directory().join("clawmates.toml")).unwrap_err(); assert!(matches!(err, ConfigError::Invalid(msg) if msg.contains("issuer_url"))); Ok(()) }); } #[test] fn missing_file_is_a_clear_error() { figment::Jail::expect_with(|jail| { let err = AppConfig::load_from(&jail.directory().join("absent.toml")).unwrap_err(); assert!(matches!(err, ConfigError::Load(_))); Ok(()) }); } #[test] fn s3_backend_requires_endpoint_and_bucket() { figment::Jail::expect_with(|jail| { let toml = format!("{AIR_GAPPED_TOML}\n[storage]\ndata_dir = \"./data\"\nbackend = \"s3\"\n"); jail.create_file("clawmates.toml", &toml)?; let err = AppConfig::load_from(&jail.directory().join("clawmates.toml")).unwrap_err(); assert!(matches!(err, ConfigError::Invalid(msg) if msg.contains("s3_endpoint"))); Ok(()) }); } #[test] fn clerk_mode_requires_the_instance_issuer() { figment::Jail::expect_with(|jail| { let toml = AIR_GAPPED_TOML.replace("mode = \"local\"", "mode = \"clerk\""); jail.create_file("clawmates.toml", &toml)?; let err = AppConfig::load_from(&jail.directory().join("clawmates.toml")).unwrap_err(); assert!(matches!(err, ConfigError::Invalid(msg) if msg.contains("clerk"))); let toml = toml.replace( "mode = \"clerk\"", "mode = \"clerk\"\nissuer_url = \"https://acme.clerk.accounts.dev\"", ); jail.create_file("clawmates.toml", &toml)?; let config = AppConfig::load_from(&jail.directory().join("clawmates.toml")).unwrap(); assert_eq!(config.auth.mode, AuthMode::Clerk); Ok(()) }); }