use crate::channels::telegram::handler::send_retrying_rate_limit;
use std::cell::Cell;
use std::time::Duration;
use teloxide::types::Seconds;
fn rate_limited<T>() -> Result<T, teloxide::RequestError> {
Err(teloxide::RequestError::RetryAfter(Seconds::from_seconds(0)))
}
#[tokio::test]
async fn waits_out_rate_limits_then_delivers() {
let calls = Cell::new(0u32);
let out = send_retrying_rate_limit("test send", || {
let n = calls.get() + 1;
calls.set(n);
async move { if n <= 2 { rate_limited() } else { Ok(42u32) } }
})
.await;
assert_eq!(out.unwrap(), 42);
assert_eq!(calls.get(), 3, "two rate-limited attempts, then success");
}
#[tokio::test]
async fn exhausted_retries_propagate_the_error() {
let calls = Cell::new(0u32);
let out: Result<(), _> = send_retrying_rate_limit("test send", || {
calls.set(calls.get() + 1);
async { rate_limited() }
})
.await;
assert!(matches!(out, Err(teloxide::RequestError::RetryAfter(_))));
assert_eq!(calls.get(), 4);
}
#[tokio::test]
async fn non_rate_limit_error_propagates_immediately() {
let calls = Cell::new(0u32);
let out: Result<(), _> = send_retrying_rate_limit("test send", || {
calls.set(calls.get() + 1);
async { Err(teloxide::RequestError::Api(teloxide::ApiError::BotBlocked)) }
})
.await;
assert!(matches!(
out,
Err(teloxide::RequestError::Api(teloxide::ApiError::BotBlocked))
));
assert_eq!(calls.get(), 1, "no retries for non-429 errors");
}
#[tokio::test]
async fn first_try_success_sends_once() {
let calls = Cell::new(0u32);
let out = send_retrying_rate_limit("test send", || {
calls.set(calls.get() + 1);
async { Ok::<_, teloxide::RequestError>("ok") }
})
.await;
assert_eq!(out.unwrap(), "ok");
assert_eq!(calls.get(), 1);
}
#[tokio::test(start_paused = true)]
async fn oversized_windows_are_capped_not_slept_in_full() {
let calls = Cell::new(0u32);
let start = tokio::time::Instant::now();
let out: Result<(), _> = send_retrying_rate_limit("test send", || {
calls.set(calls.get() + 1);
async {
Err(teloxide::RequestError::RetryAfter(Seconds::from_seconds(
8288,
)))
}
})
.await;
let elapsed = start.elapsed();
assert!(
matches!(out, Err(teloxide::RequestError::RetryAfter(_))),
"exhausted retries still propagate the error"
);
assert_eq!(calls.get(), 4, "1 initial attempt + 3 retries");
assert_eq!(
elapsed,
Duration::from_secs(90),
"3 capped waits of 30s, not the 3x8288s inline hostage of #1064"
);
}
#[tokio::test(start_paused = true)]
async fn small_windows_are_waited_in_full() {
let calls = Cell::new(0u32);
let start = tokio::time::Instant::now();
let out = send_retrying_rate_limit("test send", || {
let n = calls.get() + 1;
calls.set(n);
async move {
if n == 1 {
Err(teloxide::RequestError::RetryAfter(Seconds::from_seconds(5)))
} else {
Ok(7u32)
}
}
})
.await;
assert_eq!(out.unwrap(), 7);
assert_eq!(start.elapsed(), Duration::from_secs(5));
}
#[test]
fn clamp_inline_wait_respects_the_cap_boundary() {
use crate::channels::telegram::rate_limit::clamp_inline_wait;
assert_eq!(
clamp_inline_wait(Duration::from_secs(0)),
(Duration::from_secs(0), false)
);
assert_eq!(
clamp_inline_wait(Duration::from_secs(5)),
(Duration::from_secs(5), false)
);
assert_eq!(
clamp_inline_wait(Duration::from_secs(30)),
(Duration::from_secs(30), false),
"exactly the cap is not a capped wait"
);
assert_eq!(
clamp_inline_wait(Duration::from_secs(31)),
(Duration::from_secs(30), true)
);
assert_eq!(
clamp_inline_wait(Duration::from_secs(8288)),
(Duration::from_secs(30), true),
"the observed flood-ban window clamps to the cap"
);
}