use std::time::Duration;
use crate::channels::telegram::edit_retry::{self, EditErr};
use crate::channels::telegram::send::best_effort_note;
use teloxide::prelude::*;
const FOUR29_BODY: &str = r#"{"ok":false,"error_code":429,"description":"Too Many Requests: retry after 1","parameters":{"retry_after":1}}"#;
#[test]
fn classify_str_parses_teloxide_retry_after_marker() {
match edit_retry::classify_str("Retry after 12s") {
EditErr::RetryAfter(wait) => assert_eq!(wait, Duration::from_secs(12)),
EditErr::Fatal(e) => panic!("expected RetryAfter, got Fatal: {e}"),
}
}
#[test]
fn classify_str_falls_back_on_rich_429_marker() {
match edit_retry::classify_str("rich render failed: (429) too many requests") {
EditErr::RetryAfter(wait) => assert_eq!(wait, Duration::from_secs(30)),
EditErr::Fatal(e) => panic!("expected RetryAfter, got Fatal: {e}"),
}
}
#[test]
fn classify_str_fatal_errors_stay_fatal() {
match edit_retry::classify_str("chat not found") {
EditErr::RetryAfter(wait) => panic!("expected Fatal, got RetryAfter {wait:?}"),
EditErr::Fatal(e) => assert_eq!(e, "chat not found"),
}
}
#[tokio::test]
async fn classify_reads_typed_teloxide_retry_after() {
let mut server = mockito::Server::new_async().await;
let mock = server
.mock("POST", "/botTESTTOKEN/EditMessageText")
.with_status(429)
.with_header("content-type", "application/json")
.with_header("retry-after", "1")
.with_body(FOUR29_BODY)
.expect(1)
.create_async()
.await;
let bot = Bot::with_client(
"TESTTOKEN",
reqwest_teloxide::Client::builder().build().unwrap(),
)
.set_api_url(server.url().parse().unwrap());
let err = bot
.edit_message_text(
teloxide::types::ChatId(1),
teloxide::types::MessageId(7),
"x",
)
.await
.expect_err("expected the 429 to surface as an error");
match edit_retry::classify(&err) {
EditErr::RetryAfter(wait) => assert_eq!(wait, Duration::from_secs(1)),
EditErr::Fatal(e) => panic!("expected RetryAfter from typed 429, got Fatal: {e}"),
}
mock.assert_async().await;
}
#[tokio::test]
async fn best_effort_note_defers_one_retry_past_the_429_window() {
let mut server = mockito::Server::new_async().await;
let first = server
.mock("POST", "/botTESTTOKEN/SendMessage")
.with_status(429)
.with_header("content-type", "application/json")
.with_header("retry-after", "1")
.with_body(FOUR29_BODY)
.expect(1)
.create_async()
.await;
let second = server
.mock("POST", "/botTESTTOKEN/SendMessage")
.with_status(200)
.with_header("content-type", "application/json")
.with_body(
r#"{"ok":true,"result":{"message_id":42,"date":1756166400,"chat":{"id":1,"first_name":"t","type":"private"},"text":"hello"}}"#,
)
.expect(1)
.create_async()
.await;
let bot = Bot::with_client(
"TESTTOKEN",
reqwest_teloxide::Client::builder().build().unwrap(),
)
.set_api_url(server.url().parse().unwrap());
best_effort_note(
&bot,
teloxide::types::ChatId(1),
None,
"hello",
None,
"test",
"edit-retry",
"wire test",
)
.await;
first.assert_async().await;
tokio::time::sleep(Duration::from_millis(2500)).await;
second.assert_async().await;
}