use std::collections::{HashMap, HashSet, VecDeque};
use std::sync::Arc;
use async_trait::async_trait;
use crate::client::{InlineButton, OutgoingMessage, SentMessage, TelegramBot, TelegramError};
#[allow(clippy::disallowed_types)]
type Guarded<T> = std::sync::Mutex<T>;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Call {
Send(OutgoingMessage),
Edit {
chat_id: i64,
message_id: i64,
text: String,
buttons: Vec<Vec<InlineButton>>,
},
Delete {
chat_id: i64,
message_id: i64,
},
AnswerCallback {
callback_query_id: String,
text: Option<String>,
},
}
#[derive(Debug, Default)]
struct FakeState {
calls: Vec<Call>,
next_id: HashMap<i64, i64>,
messages: HashSet<(i64, i64)>,
failures: VecDeque<TelegramError>,
}
#[derive(Debug, Clone, Default)]
pub struct FakeTelegramBot {
state: Arc<Guarded<FakeState>>,
}
impl FakeTelegramBot {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn calls(&self) -> Vec<Call> {
self.state.lock().expect("fake telegram lock").calls.clone()
}
#[must_use]
pub fn sent(&self) -> Vec<OutgoingMessage> {
self.calls()
.into_iter()
.filter_map(|call| match call {
Call::Send(message) => Some(message),
_ => None,
})
.collect()
}
pub fn fail_next(&self, error: TelegramError) {
self.state
.lock()
.expect("fake telegram lock")
.failures
.push_back(error);
}
fn pop_failure(&self) -> Option<TelegramError> {
self.state
.lock()
.expect("fake telegram lock")
.failures
.pop_front()
}
}
#[async_trait]
impl TelegramBot for FakeTelegramBot {
async fn send_message(&self, message: &OutgoingMessage) -> Result<SentMessage, TelegramError> {
if let Some(error) = self.pop_failure() {
return Err(error);
}
let mut state = self.state.lock().expect("fake telegram lock");
let chat = state.next_id.entry(message.chat_id).or_insert(0);
*chat += 1;
let message_id = *chat;
state.messages.insert((message.chat_id, message_id));
state.calls.push(Call::Send(message.clone()));
Ok(SentMessage {
chat_id: message.chat_id,
message_id,
})
}
async fn edit_message(
&self,
chat_id: i64,
message_id: i64,
text: &str,
buttons: &[Vec<InlineButton>],
) -> Result<(), TelegramError> {
if let Some(error) = self.pop_failure() {
return Err(error);
}
let mut state = self.state.lock().expect("fake telegram lock");
if !state.messages.contains(&(chat_id, message_id)) {
return Err(TelegramError::Invalid(
"message to edit not found".to_owned(),
));
}
state.calls.push(Call::Edit {
chat_id,
message_id,
text: text.to_owned(),
buttons: buttons.to_vec(),
});
Ok(())
}
async fn delete_message(&self, chat_id: i64, message_id: i64) -> Result<(), TelegramError> {
if let Some(error) = self.pop_failure() {
return Err(error);
}
let mut state = self.state.lock().expect("fake telegram lock");
if !state.messages.remove(&(chat_id, message_id)) {
return Err(TelegramError::Invalid(
"message to delete not found".to_owned(),
));
}
state.calls.push(Call::Delete {
chat_id,
message_id,
});
Ok(())
}
async fn answer_callback(
&self,
callback_query_id: &str,
text: Option<&str>,
) -> Result<(), TelegramError> {
if let Some(error) = self.pop_failure() {
return Err(error);
}
let mut state = self.state.lock().expect("fake telegram lock");
state.calls.push(Call::AnswerCallback {
callback_query_id: callback_query_id.to_owned(),
text: text.map(str::to_owned),
});
Ok(())
}
}