Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 16 additions & 4 deletions teloxide_tests/src/server/routes/edit_message_text.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
use std::sync::Mutex;

use actix_web::{error::ErrorBadRequest, web, Responder};
use actix_web::{error::ErrorBadRequest, web, Responder, ResponseError};
use serde::Deserialize;
use teloxide::types::{LinkPreviewOptions, MessageEntity, ParseMode, ReplyMarkup};
use teloxide::{
types::{LinkPreviewOptions, MessageEntity, ParseMode, ReplyMarkup},
ApiError,
};

use super::{check_if_message_exists, BodyChatId};
use super::{BodyChatId, BotApiError};
use crate::{
server::{routes::make_telegram_result, EditedMessageText},
state::State,
Expand Down Expand Up @@ -33,7 +36,16 @@ pub async fn edit_message_text(
) {
(Some(_), Some(message_id), None) => {
let mut lock = state.lock().unwrap();
check_if_message_exists!(lock, message_id);
let Some(old_message) = lock.messages.get_message(message_id) else {
return BotApiError::new(ApiError::MessageToEditNotFound).error_response();
};

let old_reply_markup = old_message
.reply_markup()
.map(|kb| ReplyMarkup::InlineKeyboard(kb.clone()));
if old_message.text() == Some(&body.text) && old_reply_markup == body.reply_markup {
return BotApiError::new(ApiError::MessageNotModified).error_response();
}

lock.messages
.edit_message_field(message_id, "text", body.text.clone());
Expand Down
47 changes: 45 additions & 2 deletions teloxide_tests/src/server/routes/mod.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
use std::{collections::HashMap, str::from_utf8};

use actix_web::HttpResponse;
use actix_web::{error::ResponseError, http::header::ContentType, HttpResponse};
use futures_util::{stream::StreamExt as _, TryStreamExt};
use rand::distributions::{Alphanumeric, DistString};
use serde::{Deserialize, Serialize};
use serde_json::json;
use teloxide::types::{Chat, MessageEntity, ParseMode, Seconds};
use teloxide::{
types::{Chat, MessageEntity, ParseMode, Seconds},
ApiError,
};

use crate::dataset::{MockPrivateChat, MockSupergroupChat};

Expand Down Expand Up @@ -167,6 +170,46 @@ pub trait SerializeRawFields {
Self: Sized;
}

#[derive(Debug, Serialize)]
struct TelegramResponse {
ok: bool,
description: String,
}

#[derive(Debug, PartialEq, Hash, Eq, Clone)]
struct BotApiError {
error: ApiError,
}

impl BotApiError {
pub fn new(error: ApiError) -> Self {
Self { error }
}
}

impl std::fmt::Display for BotApiError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.error.fmt(f)
}
}

impl ResponseError for BotApiError {
fn status_code(&self) -> actix_web::http::StatusCode {
actix_web::http::StatusCode::BAD_REQUEST
}

fn error_response(&self) -> HttpResponse<actix_web::body::BoxBody> {
let response = TelegramResponse {
ok: false,
description: self.error.to_string(),
};
HttpResponse::build(self.status_code())
.insert_header(ContentType::json())
.body(serde_json::to_string(&response).unwrap())
}
}

// TODO: replace usages with appropriate error values from teloxide::ApiError.
macro_rules! check_if_message_exists {
($lock:expr, $msg_id:expr) => {
if $lock.messages.get_message($msg_id).is_none() {
Expand Down
52 changes: 42 additions & 10 deletions teloxide_tests/src/tests.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::{
fmt::Display,
sync::{atomic::AtomicBool, Arc},
sync::{Arc, RwLock},
thread,
};

Expand Down Expand Up @@ -185,6 +185,8 @@ pub enum AllCommands {
#[command()]
Edit,
#[command()]
EditUnchanged,
#[command()]
Delete,
#[command()]
DeleteBatch,
Expand Down Expand Up @@ -264,6 +266,11 @@ async fn handler(
.link_preview_options(link_preview_options)
.await?;
}
AllCommands::EditUnchanged => {
bot.edit_message_text(msg.chat.id, sent_message.id, msg.text().unwrap())
.link_preview_options(link_preview_options)
.await?;
}
AllCommands::Delete => {
bot.delete_message(msg.chat.id, sent_message.id).await?;
}
Expand Down Expand Up @@ -554,22 +561,33 @@ async fn test_panic() {
}

pub struct MyErrorHandler {
some_bool: Arc<AtomicBool>,
errors: Arc<RwLock<Vec<String>>>,
}

impl MyErrorHandler {
pub fn new() -> Self {
Self {
errors: Arc::new(RwLock::new(vec![])),
}
}

pub fn errors(&self) -> Vec<String> {
self.errors.read().unwrap().clone()
}
}

impl<E> ErrorHandler<E> for MyErrorHandler
where
E: std::fmt::Debug + Display + 'static + Sync + Send,
{
fn handle_error(self: Arc<Self>, _error: E) -> BoxFuture<'static, ()> {
fn handle_error(self: Arc<Self>, error: E) -> BoxFuture<'static, ()> {
thread::spawn(|| {
respond_to_error();
})
.join()
.unwrap();

self.some_bool
.swap(true, std::sync::atomic::Ordering::SeqCst);
self.errors.write().unwrap().push(format!("{error:?}"));
Box::pin(async {})
}
}
Expand All @@ -585,14 +603,14 @@ async fn respond_to_error() {
#[tokio::test]
async fn test_error_handler() {
let mut bot = MockBot::new(MockMessageText::new().text("/panic"), get_schema());
let some_bool = Arc::new(AtomicBool::new(false));
let error_handler = Arc::new(MyErrorHandler::new());
bot.error_handler(error_handler.clone());

bot.error_handler(Arc::new(MyErrorHandler {
some_bool: some_bool.clone(),
}));
bot.dispatch_and_check_last_text("Error detected!").await;

assert!(some_bool.load(std::sync::atomic::Ordering::SeqCst));
let errors = error_handler.errors();
assert_eq!(errors.len(), 1);
assert!(errors[0].contains("Message not found"));
}

#[tokio::test]
Expand Down Expand Up @@ -981,6 +999,20 @@ async fn test_edit_message() {
);
}

#[tokio::test]
async fn test_edit_message_unchanged() {
let mut bot = MockBot::new(MockMessageText::new().text("/editunchanged"), get_schema());
let error_handler = Arc::new(MyErrorHandler::new());
bot.error_handler(error_handler.clone());

bot.dispatch().await;

assert!(bot.get_responses().edited_messages_text.is_empty());
let errors = error_handler.errors();
assert_eq!(errors.len(), 1);
assert_eq!(errors[0], "Api(MessageNotModified)");
}

#[tokio::test]
async fn test_edit_caption() {
let mut bot = MockBot::new(MockMessageText::new().text("/editcaption"), get_schema());
Expand Down
Loading