#[test]
fn test_circuit_breaker_all_states_multiple() {
let config = CircuitBreakerConfig {
failure_threshold: 2,
..Default::default()
};
let breaker = CircuitBreaker::new(config);
let n1 = NodeId("n1".to_string());
let n2 = NodeId("n2".to_string());
breaker.record_failure(&n1);
breaker.record_failure(&n1);
breaker.record_success(&n2);
let states = breaker.all_states();
assert_eq!(states.len(), 2);
let n1_state = states.iter().find(|(id, _)| *id == n1).map(|(_, s)| *s);
let n2_state = states.iter().find(|(id, _)| *id == n2).map(|(_, s)| *s);
assert_eq!(n1_state, Some(CircuitState::Open));
assert_eq!(n2_state, Some(CircuitState::Closed));
}
#[test]
fn test_circuit_breaker_unknown_node_defaults_closed() {
let breaker = CircuitBreaker::default();
let unknown = NodeId("unknown".to_string());
assert_eq!(breaker.state(&unknown), CircuitState::Closed);
assert!(!breaker.is_open(&unknown));
}
#[test]
fn test_circuit_breaker_record_success_in_open_resets() {
let config = CircuitBreakerConfig {
failure_threshold: 2,
..Default::default()
};
let breaker = CircuitBreaker::new(config);
let node = NodeId("node".to_string());
breaker.record_failure(&node);
breaker.record_failure(&node);
assert_eq!(breaker.state(&node), CircuitState::Open);
breaker.record_success(&node);
assert_eq!(breaker.state(&node), CircuitState::Closed);
}
#[test]
fn test_circuit_breaker_failure_in_half_open_reopens() {
let config = CircuitBreakerConfig {
failure_threshold: 2,
half_open_successes: 3,
reset_timeout: Duration::from_millis(10),
};
let breaker = CircuitBreaker::new(config);
let node = NodeId("node".to_string());
breaker.record_failure(&node);
breaker.record_failure(&node);
assert_eq!(breaker.state(&node), CircuitState::Open);
std::thread::sleep(Duration::from_millis(20));
assert!(!breaker.is_open(&node));
assert_eq!(breaker.state(&node), CircuitState::HalfOpen);
breaker.record_failure(&node);
assert_eq!(breaker.state(&node), CircuitState::Open);
}
#[test]
fn test_circuit_breaker_failure_while_already_open() {
let config = CircuitBreakerConfig {
failure_threshold: 2,
..Default::default()
};
let breaker = CircuitBreaker::new(config);
let node = NodeId("node".to_string());
breaker.record_failure(&node);
breaker.record_failure(&node);
assert_eq!(breaker.state(&node), CircuitState::Open);
breaker.record_failure(&node);
assert_eq!(breaker.state(&node), CircuitState::Open);
}
#[test]
fn test_circuit_breaker_is_open_before_timeout() {
let config = CircuitBreakerConfig {
failure_threshold: 2,
reset_timeout: Duration::from_secs(60), ..Default::default()
};
let breaker = CircuitBreaker::new(config);
let node = NodeId("node".to_string());
breaker.record_failure(&node);
breaker.record_failure(&node);
assert!(breaker.is_open(&node));
}
#[test]
fn test_circuit_breaker_half_open_partial_success() {
let config = CircuitBreakerConfig {
failure_threshold: 2,
half_open_successes: 3,
reset_timeout: Duration::from_millis(10),
};
let breaker = CircuitBreaker::new(config);
let node = NodeId("node".to_string());
breaker.record_failure(&node);
breaker.record_failure(&node);
std::thread::sleep(Duration::from_millis(20));
assert!(!breaker.is_open(&node));
breaker.record_success(&node);
breaker.record_success(&node);
assert_eq!(breaker.state(&node), CircuitState::HalfOpen);
breaker.record_success(&node);
assert_eq!(breaker.state(&node), CircuitState::Closed);
}