mirror of
https://github.com/TheFunny/TelegramTwitterMediaBot.git
synced 2026-10-05 01:12:06 +00:00
test: drive a real Bot against a stand-in API
Every test went through `MockSender`, so `media_sender`'s `Bot` implementation — the URL it builds, the multipart it sends, the per-chat limiter and the bot-wide budget it charges — was never exercised, and neither was any handler reached from a real update. The two things that made that hard are gone: - `media_sender::test_support::fake_api::FakeApi` is a stand-in for `api.telegram.org`: a `tokio` TCP listener that reads one HTTP/1.1 request (JSON or multipart), records it and answers the smallest result the method needs. No new dependency, and `Bot::new(token).set_api_url(api.url())` points a real `Bot` at it. Note for future tests: teloxide keys methods by payload type, so the path is `SendMediaGroup`, not `sendMediaGroup`. - `message_handler` built its own `AppContext::from_statics` internally, so no test could reach its branches; its body is now `handle_message(ctx, bot, message)` with `message_handler` as the thin `dptree` entry. Tests: a media group through the real `Bot` (asserting the multipart fields — chat, media URL, caption — and that the send charged the chat's limiter), the forward button through the real callback path (`CopyMessages`, `DeleteMessage`, `AnswerCallbackQuery` with the prompt's ids and the toast text), and `handle_message` twice (a prompt reply becoming an `EditMessageCaption`, and a supported link in a group producing the one explanatory `SendMessage`). Also closes the redirect-hop gap left open by the download guard: the live `a_redirect_into_the_hosts_network_is_refused` follows a public redirector to `169.254.169.254` and asserts the policy refuses the hop (verified against httpbin.org here, and by mutation — disabling the hop check fails it). Docs: AGENTS.md's testing conventions and untested-modules list (the Bot implementation and the handler branches are covered now; `main.rs`'s startup/shutdown and its `dptree` tree still are not). `cargo fmt`, `cargo clippy --workspace --all-targets --locked -- -D warnings`, `cargo test --workspace --locked` (201 passed, 16 ignored) clean.
This commit is contained in:
@@ -1396,6 +1396,50 @@ mod tests {
|
||||
assert_eq!(sender.calls(), vec!["send_animation", "send_animation"]);
|
||||
}
|
||||
|
||||
/// A media group through a **real** `Bot` — its request building, the
|
||||
/// per-chat limiter, the bot-wide budget — against a stand-in API. The
|
||||
/// scripted mock bypasses `media_sender`'s implementation entirely, so a
|
||||
/// call site that stops charging the limiters (or a broken request shape)
|
||||
/// is invisible to every other test.
|
||||
#[tokio::test]
|
||||
async fn a_media_group_reaches_the_api_through_a_real_bot() {
|
||||
use crate::media_sender::test_support::fake_api::FakeApi;
|
||||
use teloxide::Bot;
|
||||
|
||||
let api = FakeApi::start().await;
|
||||
let bot = Bot::new("42:TEST").set_api_url(api.url());
|
||||
let stores = TestStores::new();
|
||||
let ctx = stores.ctx(&bot);
|
||||
// A chat of its own: the limiter buckets are process-wide.
|
||||
let mut task = sequence_task("https://cdn.example/1.jpg");
|
||||
if let Task::SendMediaSequence { chat_id, .. } = &mut task {
|
||||
*chat_id = 987_654;
|
||||
}
|
||||
let bucket = crate::rate_limit::limiter_for(987_654);
|
||||
let before = bucket.tokens();
|
||||
|
||||
let outcome = send_media_sequence(&ctx, &task).await;
|
||||
eprintln!(
|
||||
"SCRATCH send methods={:?} outcome={outcome:?}",
|
||||
api.methods()
|
||||
);
|
||||
assert!(outcome.is_ok());
|
||||
|
||||
// The request teloxide built: one group, the URL, the caption on the
|
||||
// first item.
|
||||
assert_eq!(api.methods(), vec!["SendMediaGroup"]);
|
||||
let body = api.body("SendMediaGroup");
|
||||
assert_eq!(body["chat_id"], 987_654);
|
||||
assert_eq!(body["media"][0]["media"], "https://cdn.example/1.jpg");
|
||||
assert_eq!(body["media"][0]["caption"], "cap");
|
||||
// …and the send charged the pace limiter before it went out.
|
||||
let after = bucket.tokens();
|
||||
assert!(
|
||||
after < before,
|
||||
"a send must charge the chat's budget ({before} -> {after})"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn forward_classifies_retry_after_and_permanent() {
|
||||
use teloxide::types::Seconds;
|
||||
|
||||
Reference in New Issue
Block a user