use std::time::Duration;
#[tokio::test]
async fn test_can_call() -> anyhow::Result<()> {
let cb = nobreak::breaker();
let res: String = cb
.call_async(|| async { Ok::<_, anyhow::Error>("output".to_string()) })
.await
.map_err(|e| e.into_inner().unwrap())?;
assert_eq!("output", res.as_str());
Ok(())
}
#[tokio::test]
async fn test_opens_after_failures() {
let cb = nobreak::builder()
.with_failure_threshold(3)
.with_open_duration(Duration::from_secs(60))
.build();
for _ in 0..3 {
let _ = cb
.call_async(|| async { Err::<String, _>(anyhow::anyhow!("fail")) })
.await;
}
assert_eq!(nobreak::CircuitState::Open, cb.state());
let res: Result<String, _> = cb
.call_async(|| async { Ok::<_, anyhow::Error>("should not run".to_string()) })
.await;
assert!(matches!(res, Err(nobreak::CircuitError::Open)));
}
#[tokio::test]
async fn test_half_open_recovery() {
let cb = nobreak::builder()
.with_failure_threshold(2)
.with_success_threshold(2)
.with_open_duration(Duration::from_millis(10))
.build();
for _ in 0..2 {
let _ = cb
.call_async(|| async { Err::<String, _>(anyhow::anyhow!("fail")) })
.await;
}
assert_eq!(nobreak::CircuitState::Open, cb.state());
tokio::time::sleep(Duration::from_millis(20)).await;
let res = cb
.call_async(|| async { Ok::<String, anyhow::Error>("ok".to_string()) })
.await;
assert!(res.is_ok());
let res = cb
.call_async(|| async { Ok::<String, anyhow::Error>("ok".to_string()) })
.await;
assert!(res.is_ok());
assert_eq!(nobreak::CircuitState::Closed, cb.state());
}
#[tokio::test]
async fn test_half_open_failure() {
let cb = nobreak::builder()
.with_failure_threshold(2)
.with_success_threshold(2)
.with_open_duration(Duration::from_millis(10))
.build();
for _ in 0..2 {
let _ = cb
.call_async(|| async { Err::<String, _>(anyhow::anyhow!("fail")) })
.await;
}
tokio::time::sleep(Duration::from_millis(20)).await;
let _ = cb
.call_async(|| async { Err::<String, _>(anyhow::anyhow!("still failing")) })
.await;
assert_eq!(nobreak::CircuitState::Open, cb.state());
}
#[tokio::test]
async fn test_success_resets_failure_count() {
let cb = nobreak::builder()
.with_failure_threshold(3)
.with_open_duration(Duration::from_secs(60))
.build();
for _ in 0..2 {
let _ = cb
.call_async(|| async { Err::<String, _>(anyhow::anyhow!("fail")) })
.await;
}
let _ = cb
.call_async(|| async { Ok::<String, anyhow::Error>("ok".to_string()) })
.await;
for _ in 0..2 {
let _ = cb
.call_async(|| async { Err::<String, _>(anyhow::anyhow!("fail")) })
.await;
}
assert_eq!(nobreak::CircuitState::Closed, cb.state());
}
#[tokio::test]
async fn test_failed_error() {
let cb = nobreak::breaker();
let res = cb
.call_async(|| async { Err::<String, _>(anyhow::anyhow!("boom")) })
.await;
if let Err(nobreak::CircuitError::Failed { error }) = res {
assert_eq!("boom", error.to_string());
} else {
panic!("expected failed error");
}
}
#[test]
fn test_sync_call() -> anyhow::Result<()> {
let cb = nobreak::breaker();
let res = cb
.call(|| Ok::<_, anyhow::Error>("hello".to_string()))
.map_err(|e| e.into_inner().unwrap())?;
assert_eq!("hello", res.as_str());
Ok(())
}
#[test]
fn test_sync_opens_after_failures() {
let cb = nobreak::builder()
.with_failure_threshold(2)
.with_open_duration(Duration::from_secs(60))
.build();
for _ in 0..2 {
let _ = cb.call(|| Err::<String, _>(anyhow::anyhow!("fail")));
}
let res = cb.call(|| Ok::<_, anyhow::Error>("should not run".to_string()));
assert!(matches!(res, Err(nobreak::CircuitError::Open)));
}
#[test]
fn test_sync_half_open_recovery() {
let cb = nobreak::builder()
.with_failure_threshold(2)
.with_success_threshold(2)
.with_open_duration(Duration::from_millis(10))
.build();
for _ in 0..2 {
let _ = cb.call(|| Err::<String, _>(anyhow::anyhow!("fail")));
}
std::thread::sleep(Duration::from_millis(20));
let res = cb.call(|| Ok::<_, anyhow::Error>("ok".to_string()));
assert!(res.is_ok());
let res = cb.call(|| Ok::<_, anyhow::Error>("ok".to_string()));
assert!(res.is_ok());
assert_eq!(nobreak::CircuitState::Closed, cb.state());
}