use super::*;
#[test]
fn health_endpoint_returns_json_200_regardless_of_readiness()
-> Result<(), Box<dyn std::error::Error>> {
let readiness = SharedReadinessState::new(ReadinessState::default());
let server = start_health_server(loopback_ephemeral()?, readiness)?;
let response = get(server.local_addr(), "/health")?;
server.shutdown()?;
assert_status(&response, 200);
assert!(response.contains("Content-Type: application/json\r\n"));
let body = json_body(&response)?;
assert_eq!(body["status"], "healthy");
Ok(())
}
#[test]
fn ready_endpoint_returns_503_before_main_listener_binds() -> Result<(), Box<dyn std::error::Error>>
{
let readiness = SharedReadinessState::new(ReadinessState::new(
true,
false,
ClusterReadiness::NotConfigured,
));
let server = start_health_server(loopback_ephemeral()?, readiness)?;
let response = get(server.local_addr(), "/ready")?;
server.shutdown()?;
assert_status(&response, 503);
assert!(response.contains("Content-Type: application/json\r\n"));
let body = json_body(&response)?;
assert_eq!(body["ready"], false);
assert_eq!(body["unmet_conditions"][0], "listener_bound");
Ok(())
}
#[test]
fn ready_endpoint_returns_200_after_all_startup_gates() -> Result<(), Box<dyn std::error::Error>> {
let readiness = SharedReadinessState::new(ReadinessState::ready_without_cluster());
let server = start_health_server(loopback_ephemeral()?, readiness)?;
let response = get(server.local_addr(), "/ready")?;
server.shutdown()?;
assert_status(&response, 200);
let body = json_body(&response)?;
assert_eq!(body["ready"], true);
let Some(unmet_conditions) = body["unmet_conditions"].as_array() else {
return Err("unmet_conditions should be an array".into());
};
assert!(unmet_conditions.is_empty());
Ok(())
}
#[test]
fn ready_endpoint_updates_from_shared_readiness_state() -> Result<(), Box<dyn std::error::Error>> {
let readiness = SharedReadinessState::new(ReadinessState::default());
let server = start_health_server(loopback_ephemeral()?, readiness.clone())?;
let response = get(server.local_addr(), "/ready")?;
assert_status(&response, 503);
readiness.set_config_loaded(true);
readiness.set_listener_bound(true);
let response = get(server.local_addr(), "/ready")?;
server.shutdown()?;
assert_status(&response, 200);
Ok(())
}
#[test]
fn clustered_ready_transitions_503_to_200_when_membership_established()
-> Result<(), Box<dyn std::error::Error>> {
let readiness = SharedReadinessState::new(ReadinessState::new(
true,
true,
ClusterReadiness::Configured {
membership_established: false,
},
));
let server = start_health_server(loopback_ephemeral()?, readiness.clone())?;
let response = get(server.local_addr(), "/ready")?;
assert_status(&response, 503);
let body = json_body(&response)?;
assert_eq!(body["ready"], false);
assert_eq!(
body["unmet_conditions"][0],
serde_json::to_value(ReadinessCondition::ClusterMembershipEstablished)?
);
readiness.set_cluster_membership_established(true);
let response = get(server.local_addr(), "/ready")?;
server.shutdown()?;
assert_status(&response, 200);
let body = json_body(&response)?;
assert_eq!(body["ready"], true);
let Some(unmet_conditions) = body["unmet_conditions"].as_array() else {
return Err("unmet_conditions should be an array".into());
};
assert!(unmet_conditions.is_empty());
Ok(())
}
#[test]
fn cluster_readiness_is_listed_when_configured_but_not_joined()
-> Result<(), Box<dyn std::error::Error>> {
let readiness = SharedReadinessState::new(ReadinessState::new(
true,
true,
ClusterReadiness::Configured {
membership_established: false,
},
));
let response = response_for_request(b"GET /ready HTTP/1.1\r\n\r\n", &served(readiness))?;
let response = String::from_utf8(response)?;
assert_status(&response, 503);
let body = json_body(&response)?;
assert_eq!(
body["unmet_conditions"][0],
serde_json::to_value(ReadinessCondition::ClusterMembershipEstablished)?
);
Ok(())
}
#[test]
fn unloadable_conversations_route_answers_the_operator_a_json_shape()
-> Result<(), Box<dyn std::error::Error>> {
let readiness = SharedReadinessState::new(ReadinessState::default());
let server = start_health_server(loopback_ephemeral()?, readiness)?;
let response = get(server.local_addr(), "/unloadable-conversations")?;
let unknown = get(server.local_addr(), "/unloadable-conversations-typo")?;
server.shutdown()?;
assert_status(&unknown, 404);
assert_status(&response, 200);
assert!(
response.contains("Content-Type: application/json\r\n"),
"the unloadable-conversations route must answer JSON: {response}"
);
let body = json_body(&response)?;
assert_eq!(
body["count"], 0,
"a server with no participant record attached refuses nothing: {body}"
);
assert_eq!(
body["participant_installed"], false,
"no participant record is attached to this server, and the surface must say so \
rather than let a zero count read as a clean node: {body}"
);
let Some(conversations) = body["conversations"].as_array() else {
return Err("conversations should be an array".into());
};
assert!(
conversations.is_empty(),
"no conversation was refused: {conversations:?}"
);
Ok(())
}
#[test]
fn unsupported_paths_are_not_served() -> Result<(), Box<dyn std::error::Error>> {
let readiness = SharedReadinessState::default();
let response = response_for_request(b"GET /unknown HTTP/1.1\r\n\r\n", &served(readiness))?;
let response = String::from_utf8(response)?;
assert_status(&response, 404);
Ok(())
}
#[test]
fn unsupported_methods_on_health_paths_are_rejected() -> Result<(), Box<dyn std::error::Error>> {
let readiness = SharedReadinessState::default();
let response = response_for_request(b"POST /health HTTP/1.1\r\n\r\n", &served(readiness))?;
let response = String::from_utf8(response)?;
assert_status(&response, 405);
Ok(())
}
#[test]
fn health_reports_every_bound_wire_address_without_opening_a_socket()
-> Result<(), Box<dyn std::error::Error>> {
let state = served(SharedReadinessState::default());
let addresses = vec!["127.0.0.1:7600".parse()?, "100.64.0.1:7600".parse()?];
state
.listen_addresses
.set(addresses)
.map_err(|_| "addresses were already installed")?;
let response = String::from_utf8(response_for_request(
b"GET /health HTTP/1.1\r\n\r\n",
&state,
)?)?;
let body = json_body(&response)?;
assert_eq!(
body["listen_addresses"],
serde_json::json!(["127.0.0.1:7600", "100.64.0.1:7600"])
);
Ok(())
}