use std::collections::BTreeMap; use crank_config::{ ConfigSource, DiagnosticCode, FieldMode, ProcessKind, ProcessScope, field_registry, parse_migrator, parse_process, }; const TEST_MASTER_KEY: &str = "test-master-key-00000000000000000000000000000000"; fn required_admin() -> BTreeMap { [ ("CRANK_MASTER_KEY", TEST_MASTER_KEY), ("CRANK_SESSION_SECRET", "session"), ("CRANK_PASSWORD_PEPPER", "pepper"), ("CRANK_BOOTSTRAP_ADMIN_EMAIL", "owner@example.test"), ("CRANK_BOOTSTRAP_ADMIN_PASSWORD", "password"), ] .into_iter() .map(|(key, value)| (key.to_owned(), value.to_owned())) .collect() } fn required_mcp() -> BTreeMap { [("CRANK_MASTER_KEY", TEST_MASTER_KEY)] .into_iter() .map(|(key, value)| (key.to_owned(), value.to_owned())) .collect() } #[test] fn migrator_projection_requires_only_database_configuration() { let config = parse_migrator(ConfigSource::from_utf8(BTreeMap::new())) .expect("database defaults are sufficient for the controlled migration job"); assert_eq!(config.database.host, "postgres"); assert_eq!(config.database.port, 5432); assert_eq!(config.fingerprint().len(), 64); let debug = format!("{config:?}"); assert!( !debug.contains("crank"), "database password must remain redacted" ); } #[test] fn migrator_ignores_service_configuration_and_rejects_database_typos() { let mut values = BTreeMap::from([ ("CRANK_MASTER_KEY".to_owned(), "secret-canary".to_owned()), ( "CRANK_SESSION_SECRET".to_owned(), "secret-canary".to_owned(), ), ("CRANK_MCP_REFRESH_MS".to_owned(), "invalid".to_owned()), ]); parse_migrator(ConfigSource::from_utf8(values.clone())) .expect("service fields are outside the database-only projection"); values.insert("POSTGRES_PORRT".to_owned(), "5432".to_owned()); let error = parse_migrator(ConfigSource::from_utf8(values)).unwrap_err(); assert!( error .diagnostics() .iter() .any(|diagnostic| diagnostic.code == DiagnosticCode::UnknownField) ); assert!(!error.to_string().contains("secret-canary")); } fn source_for( field: &crank_config::FieldSpec, value: String, ) -> (ProcessKind, BTreeMap) { let kind = match field.process { ProcessScope::McpServer => ProcessKind::McpServer, ProcessScope::Shared | ProcessScope::AdminApi => ProcessKind::AdminApi, }; let mut vars = match kind { ProcessKind::AdminApi => required_admin(), ProcessKind::McpServer => required_mcp(), }; vars.insert(field.env_name.to_owned(), value); match field.env_name { "POSTGRES_MAX_CONNECTIONS" => { vars.insert("POSTGRES_MIN_CONNECTIONS".into(), "0".into()); } "POSTGRES_MIN_CONNECTIONS" => { vars.insert("POSTGRES_MAX_CONNECTIONS".into(), "1024".into()); } "CRANK_ADMIN_RATE_LIMIT_RPS" => { vars.insert("CRANK_ADMIN_RATE_LIMIT_BURST".into(), "1000000".into()); } "CRANK_ADMIN_RATE_LIMIT_BURST" => { vars.insert("CRANK_ADMIN_RATE_LIMIT_RPS".into(), "1".into()); } "CRANK_MCP_RATE_LIMIT_RPS" => { vars.insert("CRANK_MCP_RATE_LIMIT_BURST".into(), "1000000".into()); } "CRANK_MCP_RATE_LIMIT_BURST" => { vars.insert("CRANK_MCP_RATE_LIMIT_RPS".into(), "1".into()); } "OTEL_BSP_MAX_QUEUE_SIZE" => { vars.insert("OTEL_BSP_MAX_EXPORT_BATCH_SIZE".into(), "1".into()); } "OTEL_BSP_MAX_EXPORT_BATCH_SIZE" => { vars.insert("OTEL_BSP_MAX_QUEUE_SIZE".into(), "65536".into()); } _ => {} } (kind, vars) } #[test] fn external_reference_contract_is_default_off_and_validates_prefixes_and_limits() { let config = parse_process( ProcessKind::AdminApi, ConfigSource::from_utf8(required_admin()), ) .expect("the external reference contract has safe defaults"); let references = &config.admin().unwrap().external_references; assert!(references.allowed_url_prefixes.is_empty()); assert_eq!(references.max_depth, 8); assert_eq!(references.max_documents, 32); assert_eq!(references.max_fetch_bytes, 262_144); assert_eq!(references.fetch_timeout_ms, 10_000); assert_eq!(references.max_expanded_nodes, 10_000); let mut allowed = required_admin(); allowed.insert( "CRANK_IMPORT_EXTERNAL_REFERENCE_ALLOWED_URL_PREFIXES".to_owned(), "https://schemas.example.test/openapi/,https://schemas.example.test/openapi/".to_owned(), ); let config = parse_process(ProcessKind::AdminApi, ConfigSource::from_utf8(allowed)).unwrap(); assert_eq!( config .admin() .unwrap() .external_references .allowed_url_prefixes, vec!["https://schemas.example.test/openapi/".to_owned()] ); let mut invalid = required_admin(); invalid.insert( "CRANK_IMPORT_EXTERNAL_REFERENCE_ALLOWED_URL_PREFIXES".to_owned(), "https://user:secret@schemas.example.test/openapi/".to_owned(), ); let error = parse_process(ProcessKind::AdminApi, ConfigSource::from_utf8(invalid)) .expect_err("userinfo must never become an operator allow rule"); assert!(error.diagnostics().iter().any(|diagnostic| { diagnostic.code == DiagnosticCode::InvalidType && diagnostic.field == "import.external_references.allowed_url_prefixes" })); let mut out_of_range = required_admin(); out_of_range.insert( "CRANK_IMPORT_EXTERNAL_REFERENCE_MAX_DOCUMENTS".to_owned(), "33".to_owned(), ); let error = parse_process(ProcessKind::AdminApi, ConfigSource::from_utf8(out_of_range)) .expect_err("graph document limit must be bounded"); assert!(error.diagnostics().iter().any(|diagnostic| { diagnostic.code == DiagnosticCode::OutOfRange && diagnostic.field == "import.external_references.max_documents" })); } #[test] fn registry_covers_exactly_the_65_observed_runtime_names() { let registry = field_registry(); assert_eq!(registry.len(), 65); let unique = registry .iter() .map(|field| field.env_name) .collect::>(); assert_eq!(unique.len(), registry.len()); assert!(!unique.contains("CRANK_RUNTIME_MAX_CONCURRENT_WINDOW")); assert!(!unique.contains("CRANK_RUNTIME_MAX_CONCURRENT_JOBS")); for field in registry { assert!(!field.semantic_path.is_empty()); assert!(!field.env_name.is_empty()); assert!(!field.value_type.is_empty()); if let (Some(minimum), Some(maximum)) = (field.minimum, field.maximum) { assert!(minimum <= maximum, "{}", field.env_name); } } assert_eq!( registry .iter() .find(|field| field.env_name == "CRANK_CACHE_DEFAULT_TTL_MS") .unwrap() .mode, FieldMode::DeprecatedNoEffect ); } #[test] fn defaults_are_preserved_and_invalid_values_never_fall_back() { let valid = parse_process( ProcessKind::AdminApi, ConfigSource::from_utf8(required_admin()), ) .expect("minimal admin config"); let admin = valid.admin().expect("admin projection"); assert_eq!(admin.database.port, 5432); assert_eq!(admin.session_ttl_hours, 24); assert_eq!(admin.rate_limit.requests_per_second, 30); for (name, value) in [ ("POSTGRES_PORT", "bad"), ("CRANK_MASTER_KEY", "too-short"), ("CRANK_SESSION_TTL_HOURS", "bad"), ("CRANK_ADMIN_RATE_LIMIT_RPS", "bad"), ("CRANK_TRUSTED_PROXY_IPS", "not-an-ip"), ] { let mut vars = required_admin(); vars.insert(name.to_owned(), value.to_owned()); let error = parse_process(ProcessKind::AdminApi, ConfigSource::from_utf8(vars)).unwrap_err(); let path = field_registry() .iter() .find(|field| field.env_name == name) .unwrap() .semantic_path; assert!(error.diagnostics().iter().any(|item| item.field == path)); } } #[test] fn database_forms_conflict_and_owned_typos_fail_closed() { let mut vars = required_admin(); vars.insert( "CRANK_DATABASE_URL".into(), "postgres://user:secret@db/crank".into(), ); vars.insert("POSTGRES_HOST".into(), "db".into()); let error = parse_process(ProcessKind::AdminApi, ConfigSource::from_utf8(vars)).unwrap_err(); assert!( error .diagnostics() .iter() .any(|item| item.code == DiagnosticCode::Conflict) ); let mut vars = required_admin(); vars.insert("CRANK_SESION_SECRET".into(), "canary".into()); let error = parse_process(ProcessKind::AdminApi, ConfigSource::from_utf8(vars)).unwrap_err(); assert!( error .diagnostics() .iter() .any(|item| item.code == DiagnosticCode::UnknownField) ); assert!(!error.to_string().contains("canary")); for ghost in [ "CRANK_RUNTIME_MAX_CONCURRENT_WINDOW", "CRANK_RUNTIME_MAX_CONCURRENT_JOBS", ] { let mut vars = required_admin(); vars.insert(ghost.into(), "4".into()); let error = parse_process(ProcessKind::AdminApi, ConfigSource::from_utf8(vars)).unwrap_err(); assert!(error.diagnostics().iter().any(|item| { item.code == DiagnosticCode::UnknownField && item.field == "environment.unknown" })); } } #[test] fn exact_deployment_only_names_are_known_but_never_runtime_fields() { let mut vars = required_admin(); for name in crank_config::deployment_field_registry() { vars.insert((*name).to_owned(), "deployment-value".to_owned()); } let config = parse_process(ProcessKind::AdminApi, ConfigSource::from_utf8(vars)).unwrap(); assert!(config.admin().is_some()); assert!( field_registry() .iter() .all(|field| { !crank_config::deployment_field_registry().contains(&field.env_name) }) ); } #[test] fn parsed_but_unused_cache_ttl_is_an_explicit_non_pass_contract() { let mut vars = required_admin(); vars.insert("CRANK_CACHE_DEFAULT_TTL_MS".into(), "5000".into()); let error = parse_process(ProcessKind::AdminApi, ConfigSource::from_utf8(vars)).unwrap_err(); assert!(error.diagnostics().iter().any(|item| { item.code == DiagnosticCode::DeprecatedNoEffect && item.field == "cache.default_ttl_ms" })); } #[cfg(unix)] #[test] fn os_source_rejects_non_utf8_without_echoing_bytes() { use std::{ffi::OsString, os::unix::ffi::OsStringExt}; let error = ConfigSource::from_os_iter([( OsString::from("CRANK_MASTER_KEY"), OsString::from_vec(vec![0xff, b'S', b'E', b'C', b'R', b'E', b'T']), )]) .unwrap_err(); assert!( error .diagnostics() .iter() .any(|item| item.code == DiagnosticCode::InvalidEncoding) ); assert!(!error.to_string().contains("SECRET")); } #[cfg(unix)] #[test] fn os_source_ignores_unrelated_invalid_or_unbounded_values() { use std::{ffi::OsString, os::unix::ffi::OsStringExt}; ConfigSource::from_os_iter([ ( OsString::from_vec(vec![0xff]), OsString::from_vec(vec![0xff]), ), ( OsString::from("JAVA_TOOL_OPTIONS"), OsString::from("x".repeat(20_000)), ), (OsString::from("LANG"), OsString::from_vec(vec![0xff, b'x'])), ]) .expect("unrelated OS state is outside the runtime contract"); } #[test] fn process_specific_fields_and_zero_ports_fail_closed() { let mut mcp = required_mcp(); mcp.insert("CRANK_SESSION_SECRET".into(), "wrong-process".into()); let error = parse_process(ProcessKind::McpServer, ConfigSource::from_utf8(mcp)).unwrap_err(); assert!(error.diagnostics().iter().any(|item| { item.code == DiagnosticCode::UnknownField && item.field == "admin.session.secret" })); for (kind, name, required) in [ (ProcessKind::AdminApi, "CRANK_ADMIN_BIND", required_admin()), ( ProcessKind::AdminApi, "CRANK_ADMIN_METRICS_BIND", required_admin(), ), (ProcessKind::McpServer, "CRANK_MCP_BIND", required_mcp()), ( ProcessKind::McpServer, "CRANK_MCP_METRICS_BIND", required_mcp(), ), ] { let mut vars = required; vars.insert(name.into(), "127.0.0.1:0".into()); let error = parse_process(kind, ConfigSource::from_utf8(vars)).unwrap_err(); let path = field_registry() .iter() .find(|field| field.env_name == name) .unwrap() .semantic_path; assert!( error .diagnostics() .iter() .any(|item| { item.code == DiagnosticCode::OutOfRange && item.field == path }) ); } } #[test] fn production_admin_non_loopback_requires_https_base_url() { let mut vars = required_admin(); vars.insert("CRANK_ENVIRONMENT".into(), "production".into()); vars.insert("CRANK_ADMIN_BIND".into(), "0.0.0.0:3001".into()); vars.insert("CRANK_BASE_URL".into(), "http://crank.example.test".into()); let error = parse_process(ProcessKind::AdminApi, ConfigSource::from_utf8(vars)).unwrap_err(); assert!(error.diagnostics().iter().any(|item| { item.code == DiagnosticCode::UnsafeCombination && item.field == "admin.exposure.tls" })); let mut safe = required_admin(); safe.insert("CRANK_ENVIRONMENT".into(), "production".into()); safe.insert("CRANK_ADMIN_BIND".into(), "0.0.0.0:3001".into()); safe.insert("CRANK_BASE_URL".into(), "https://crank.example.test".into()); parse_process(ProcessKind::AdminApi, ConfigSource::from_utf8(safe)).unwrap(); } #[test] fn whitespace_secrets_and_consumer_invalid_values_fail_in_the_leaf_parser() { for name in [ "CRANK_MASTER_KEY", "CRANK_SESSION_SECRET", "CRANK_PASSWORD_PEPPER", "CRANK_BOOTSTRAP_ADMIN_PASSWORD", ] { let mut vars = required_admin(); vars.insert(name.into(), " ".into()); assert!( parse_process(ProcessKind::AdminApi, ConfigSource::from_utf8(vars)).is_err(), "{name}" ); } for (name, value) in [ ("CRANK_OUTBOUND_ALLOWED_HOSTS", "example.test:443"), ("CRANK_DATABASE_URL", "postgres://db/crank?sslmode=bogus"), ("CRANK_ENVIRONMENT", "bad environment"), ("CRANK_SENTRY_DSN", "not-a-dsn"), ("OTEL_EXPORTER_OTLP_HEADERS", "bad name=value"), ( "CRANK_BASE_URL", "https://user:secret@example.test/path?token=x", ), ] { let mut vars = required_admin(); vars.insert(name.into(), value.into()); let error = parse_process(ProcessKind::AdminApi, ConfigSource::from_utf8(vars)).expect_err(name); assert!( error .diagnostics() .iter() .any(|item| item.code == DiagnosticCode::InvalidType), "{name}: {error}" ); } } #[test] fn cross_field_and_typed_boundaries_fail_closed() { let cases = [ ("POSTGRES_MAX_CONNECTIONS", "1025"), ("CRANK_ADMIN_RATE_LIMIT_RPS", "100001"), ("CRANK_ADMIN_RATE_LIMIT_BURST", "0"), ("CRANK_OUTBOUND_MAX_REQUEST_BYTES", "67108865"), ("CRANK_OUTBOUND_MAX_RESPONSE_BYTES", "67108865"), ("OTEL_BSP_SCHEDULE_DELAY", "bad"), ("OTEL_BSP_EXPORT_TIMEOUT", "300001"), ("CRANK_BASE_URL", "file:///tmp/config"), ]; for (name, value) in cases { let mut vars = required_admin(); vars.insert(name.to_owned(), value.to_owned()); let error = parse_process(ProcessKind::AdminApi, ConfigSource::from_utf8(vars)).expect_err(name); assert!( error.diagnostics().iter().any(|item| item.field == field_registry() .iter() .find(|field| field.env_name == name) .unwrap() .semantic_path), "{name}: {error}" ); } let mut vars = required_admin(); vars.insert("POSTGRES_MIN_CONNECTIONS".into(), "21".into()); let error = parse_process(ProcessKind::AdminApi, ConfigSource::from_utf8(vars)).unwrap_err(); assert!( error .diagnostics() .iter() .any(|item| item.code == DiagnosticCode::UnsafeCombination) ); let mut vars = required_admin(); vars.insert("CRANK_CACHE_BACKEND".into(), "valkey".into()); vars.insert("CRANK_CACHE_URL".into(), "https://not-a-cache.test".into()); let error = parse_process(ProcessKind::AdminApi, ConfigSource::from_utf8(vars)).unwrap_err(); assert!( error .diagnostics() .iter() .any(|item| item.field == "cache.url") ); } #[test] fn inclusive_edges_and_legacy_boolean_spellings_are_explicit() { let mut vars = required_admin(); vars.extend([ ("POSTGRES_PORT".into(), "65535".into()), ("POSTGRES_MAX_CONNECTIONS".into(), "1024".into()), ("POSTGRES_MIN_CONNECTIONS".into(), "0".into()), ("CRANK_RUNTIME_MAX_CONCURRENT_UNARY".into(), "1".into()), ("CRANK_OUTBOUND_MAX_REQUEST_BYTES".into(), "67108864".into()), ( "CRANK_OUTBOUND_MAX_RESPONSE_BYTES".into(), "67108864".into(), ), ("CRANK_TRUSTED_PROXY_IPS".into(), "127.0.0.1,::1".into()), ("CRANK_DEMO_SEED".into(), "off".into()), ]); let config = parse_process(ProcessKind::AdminApi, ConfigSource::from_utf8(vars)).unwrap(); let admin = config.admin().unwrap(); assert_eq!(admin.database.port, 65_535); assert_eq!(admin.database.pool.max_connections, 1024); assert_eq!(admin.database.pool.min_connections, 0); assert_eq!(admin.runtime.max_concurrent_unary, 1); assert_eq!(admin.trusted_proxy_ips.len(), 2); assert!(!admin.demo_seed); } #[test] fn admin_and_mcp_share_one_normalized_foundation() { let mut vars = required_admin(); vars.extend([ ("CRANK_BASE_URL".into(), "https://crank.example.test".into()), ("CRANK_RUNTIME_MAX_CONCURRENT_UNARY".into(), "72".into()), ( "CRANK_OUTBOUND_ALLOWED_HOSTS".into(), "api.example.test".into(), ), ("POSTGRES_MAX_CONNECTIONS".into(), "24".into()), ]); let admin = parse_process(ProcessKind::AdminApi, ConfigSource::from_utf8(vars.clone())).unwrap(); let mut mcp_vars = vars; for key in [ "CRANK_SESSION_SECRET", "CRANK_PASSWORD_PEPPER", "CRANK_BOOTSTRAP_ADMIN_EMAIL", "CRANK_BOOTSTRAP_ADMIN_PASSWORD", ] { mcp_vars.remove(key); } let mcp = parse_process(ProcessKind::McpServer, ConfigSource::from_utf8(mcp_vars)).unwrap(); let admin = admin.admin().unwrap(); let mcp = mcp.mcp().unwrap(); assert_eq!(admin.database.host, mcp.database.host); assert_eq!( admin.database.pool.max_connections, mcp.database.pool.max_connections ); assert_eq!(admin.runtime.base_url, mcp.runtime.base_url); assert_eq!( admin.runtime.max_concurrent_unary, mcp.runtime.max_concurrent_unary ); assert_eq!( admin.runtime.outbound.allowed_hosts, mcp.runtime.outbound.allowed_hosts ); } #[test] fn every_bounded_numeric_field_accepts_edges_and_rejects_outside_values() { for field in field_registry().iter().filter(|field| { field.mode == FieldMode::Effective && field.minimum.is_some() && field.maximum.is_some() }) { let minimum = field.minimum.unwrap(); let maximum = field.maximum.unwrap(); for accepted in [minimum, maximum] { let (kind, vars) = source_for(field, accepted.to_string()); parse_process(kind, ConfigSource::from_utf8(vars)) .unwrap_or_else(|error| panic!("{}={accepted}: {error}", field.env_name)); } for rejected in [ if minimum == 0 { "-1".to_owned() } else { (minimum - 1).to_string() }, (maximum + 1).to_string(), ] { let (kind, vars) = source_for(field, rejected); let error = parse_process(kind, ConfigSource::from_utf8(vars)).expect_err(field.env_name); assert!( error .diagnostics() .iter() .any(|item| item.field == field.semantic_path), "{}: {error}", field.env_name ); } for malformed in ["-1", "184467440737095516160", "1.5", " 1", "1\n"] { let (kind, vars) = source_for(field, malformed.to_owned()); let error = parse_process(kind, ConfigSource::from_utf8(vars)).expect_err(field.env_name); assert!( error .diagnostics() .iter() .any(|item| item.field == field.semantic_path), "{}={malformed:?}: {error}", field.env_name ); } } } #[test] fn compatibility_values_and_otel_precedence_remain_explicit() { let mut vars = required_admin(); vars.extend([ ("CRANK_CACHE_BACKEND".into(), "redis".into()), ( "CRANK_CACHE_URL".into(), "redis://cache.example.test:6379".into(), ), ( "OTEL_EXPORTER_OTLP_ENDPOINT".into(), "https://generic.example.test".into(), ), ( "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT".into(), "https://traces.example.test".into(), ), ("OTEL_EXPORTER_OTLP_TIMEOUT".into(), "10000".into()), ("OTEL_EXPORTER_OTLP_TRACES_TIMEOUT".into(), "5000".into()), ]); let config = parse_process(ProcessKind::AdminApi, ConfigSource::from_utf8(vars)).unwrap(); let admin = config.admin().unwrap(); assert_eq!( admin.runtime.cache.backend, crank_config::CacheBackend::Redis ); assert_eq!( admin.observability.otlp.endpoint.as_deref(), Some("https://generic.example.test") ); assert_eq!( admin.observability.otlp.traces_endpoint.as_deref(), Some("https://traces.example.test") ); assert_eq!(admin.observability.otlp.timeout.as_deref(), Some("10000")); assert_eq!( admin.observability.otlp.traces_timeout.as_deref(), Some("5000") ); assert_eq!(config.deprecations().len(), 1); assert_eq!(config.deprecations()[0].field, "cache.backend"); }