diff --git a/src/main.rs b/src/main.rs index 81eaa10..86e85f0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -32,27 +32,30 @@ async fn main() -> Result<(), error::BotError> { .parse() .map_err(|e| error::BotError::Config(format!("invalid PORT: {e}")))?; let listener = tokio::net::TcpListener::bind(addr).await?; + let mut missing = Vec::new(); let redis_value = match std::env::var("REDIS_URL") { Ok(value) if !value.trim().is_empty() => value, _ => { - tracing::warn!("REDIS_URL is not configured; serving configuration setup mode"); - let app = router_configuration_setup(vec!["REDIS_URL".into()]).merge(web::router()); - axum::serve(listener, app).await?; - return Ok(()); + missing.push("REDIS_URL".to_owned()); + String::new() } }; let encryption_key = match encryption_key_from_env() { Ok(key) => key, Err(_) => { - tracing::warn!( - "CONFIG_ENCRYPTION_KEY is not configured or invalid; serving setup mode" - ); - let app = router_configuration_setup(vec!["CONFIG_ENCRYPTION_KEY".into()]) - .merge(web::router()); - axum::serve(listener, app).await?; - return Ok(()); + missing.push("CONFIG_ENCRYPTION_KEY".to_owned()); + [0_u8; 32] } }; + if !missing.is_empty() { + tracing::warn!( + ?missing, + "required bootstrap environment is incomplete; serving setup mode" + ); + let app = router_configuration_setup(missing).merge(web::router()); + axum::serve(listener, app).await?; + return Ok(()); + } // The Redis URL is the only bootstrap credential in the migration path. Until // the bootstrap route is wired, retain the legacy parser solely to obtain it. let bootstrap_config = match Config::from_env() { diff --git a/src/notify.rs b/src/notify.rs index 268136b..06ea600 100644 --- a/src/notify.rs +++ b/src/notify.rs @@ -866,6 +866,28 @@ mod tests { assert!(!body.windows(4).any(|part| part == b"pass")); } + #[tokio::test] + async fn configuration_setup_status_lists_both_bootstrap_requirements() { + let response = + router_configuration_setup(vec!["REDIS_URL".into(), "CONFIG_ENCRYPTION_KEY".into()]) + .oneshot( + Request::builder() + .method("GET") + .uri("/api/status") + .body(Body::empty()) + .unwrap(), + ) + .await + .unwrap(); + let body = axum::body::to_bytes(response.into_body(), usize::MAX) + .await + .unwrap(); + let value: serde_json::Value = serde_json::from_slice(&body).unwrap(); + assert_eq!(value["missing"].as_array().unwrap().len(), 2); + assert_eq!(value["missing"][0], "REDIS_URL"); + assert_eq!(value["missing"][1], "CONFIG_ENCRYPTION_KEY"); + } + #[tokio::test] async fn failed_bootstrap_does_not_consume_initialization_slot() { let app = router_with_worker_state_runtime_bootstrap(