tgbot 0.47.0

A Telegram Bot library
Documentation
#![allow(missing_docs)]
use std::{sync::Mutex, time::Instant};

use futures_util::stream::StreamExt;
use mockito::{Server, ServerGuard};
use tgbot::{
    api::{Client, DownloadFileError, ExecuteError},
    types::Close,
};

struct Cx {
    server: ServerGuard,
    client: Client,
}

impl Cx {
    async fn new_async() -> Self {
        let server = Server::new_async().await;
        let host = server.url();
        Self {
            server,
            client: Client::new("-token").unwrap().with_host(host).with_max_retry_after(0),
        }
    }

    async fn execute_close(&self) -> Result<bool, ExecuteError> {
        self.client.execute(Close).await
    }

    async fn execute_close_without_retries(&self) -> Result<bool, ExecuteError> {
        self.client.clone().with_max_retries(0).execute(Close).await
    }

    fn set_close_response(&mut self, body: &str) {
        self.server.mock("GET", "/bot-token/close").with_body(body).create();
    }

    fn set_close_response_retry(&mut self, retry_after: usize) {
        let counter = Mutex::new(0u8);
        self.server
            .mock("GET", "/bot-token/close")
            .with_body_from_request(move |_| {
                let mut val = counter.lock().unwrap();
                if *val < 2 {
                    *val += 1;
                    serde_json::to_string(&serde_json::json!({
                        "ok": false,
                        "description": format!("test {val}"),
                        "parameters": {"retry_after": retry_after}
                    }))
                    .unwrap()
                    .into()
                } else {
                    *val = 0;
                    r#"{"ok": true, "result": true}"#.into()
                }
            })
            .create();
    }
}

#[tokio::test]
async fn execute() {
    let mut cx = Cx::new_async().await;
    cx.set_close_response(r#"{"ok": true, "result": true}"#);
    cx.execute_close().await.unwrap();

    cx.set_close_response(r#"{"ok": false, "description": "test"}"#);
    let err = cx.execute_close().await.unwrap_err();
    assert_eq!(
        err.to_string(),
        "failed to execute method: a telegram error has occurred: description=test"
    );

    cx.set_close_response(r#"invalid-data"#);
    let err = cx.execute_close().await.unwrap_err();
    assert!(
        err.to_string()
            .starts_with("failed to execute method: error decoding response body"),
    );
    let ExecuteError::Http(err) = err else {
        panic!("expected an HTTP error");
    };
    assert_eq!(
        err.url().unwrap().as_str(),
        format!("{}/bot[TOKEN]/close", cx.server.url())
    );

    cx.set_close_response_retry(0);
    cx.execute_close().await.unwrap();
    let err = cx.execute_close_without_retries().await.unwrap_err();
    assert_eq!(
        err.to_string(),
        "failed to execute method: a telegram error has occurred: description=test 1; retry_after=0"
    );
    cx.execute_close().await.unwrap();

    cx.set_close_response_retry(10000000000000000);
    let start = Instant::now();
    cx.execute_close().await.unwrap();
    let end = Instant::now();
    let duration = end - start;
    assert!(duration.as_secs() <= 1)
}

#[tokio::test]
async fn download_file() {
    let mut server = Server::new_async().await;
    server
        .mock("GET", "/file/bot-token/file-ok")
        .with_body(b"file-data")
        .create();
    server
        .mock("GET", "/file/bot-token/file-err")
        .with_body("test-error")
        .with_status(400)
        .create();
    server
        .mock("GET", "/file/bot-token/file-http-err")
        .with_status(500)
        .with_chunked_body(|_| Err(std::io::Error::other("test error")))
        .create();
    let client = Client::new("-token").unwrap().with_host(server.url());
    let mut stream = client.download_file("file-ok").await.unwrap();
    let mut buf = Vec::new();
    while let Some(chunk) = stream.next().await {
        let chunk = chunk.unwrap();
        buf.extend(chunk);
    }
    assert_eq!(&buf[..], b"file-data");

    match client.download_file("file-err").await {
        Ok(_) => panic!("Got an unexpected stream"),
        Err(err) => assert_eq!(err.to_string(), "failed to download file: status=400 text=test-error"),
    };

    let Err(DownloadFileError::Http(err)) = client.download_file("file-http-err").await else {
        panic!("expected an HTTP error");
    };
    assert_eq!(
        err.url().unwrap().as_str(),
        format!("{}/file/bot[TOKEN]/file-http-err", server.url())
    );
}