story-kit: merge 316_refactor_abstract_bot_transport_layer_for_multi_platform_support
This commit is contained in:
117
server/src/whatsapp.rs
Normal file
117
server/src/whatsapp.rs
Normal file
@@ -0,0 +1,117 @@
|
||||
//! WhatsApp stub implementation of [`ChatTransport`].
|
||||
//!
|
||||
//! This is a placeholder transport that logs operations and returns stub
|
||||
//! values. It exists to prove the transport abstraction works with a
|
||||
//! second platform and will be replaced with a real WhatsApp Business API
|
||||
//! integration in the future.
|
||||
|
||||
use async_trait::async_trait;
|
||||
|
||||
use crate::slog;
|
||||
use crate::transport::{ChatTransport, MessageId};
|
||||
|
||||
/// Stub WhatsApp transport.
|
||||
///
|
||||
/// All methods log the operation and return success with placeholder values.
|
||||
/// Message editing is not supported by WhatsApp — `edit_message` sends a
|
||||
/// new message instead (TODO: implement via WhatsApp Business API).
|
||||
pub struct WhatsAppTransport {
|
||||
/// Counter for generating unique stub message IDs.
|
||||
next_id: std::sync::atomic::AtomicU64,
|
||||
}
|
||||
|
||||
impl WhatsAppTransport {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
next_id: std::sync::atomic::AtomicU64::new(1),
|
||||
}
|
||||
}
|
||||
|
||||
fn next_message_id(&self) -> String {
|
||||
let id = self
|
||||
.next_id
|
||||
.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
||||
format!("whatsapp-stub-{id}")
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for WhatsAppTransport {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ChatTransport for WhatsAppTransport {
|
||||
async fn send_message(
|
||||
&self,
|
||||
room_id: &str,
|
||||
plain: &str,
|
||||
_html: &str,
|
||||
) -> Result<MessageId, String> {
|
||||
// TODO: Send via WhatsApp Business API
|
||||
let msg_id = self.next_message_id();
|
||||
slog!(
|
||||
"[whatsapp-stub] send_message to {room_id}: {plain:.80} (id={msg_id})"
|
||||
);
|
||||
Ok(msg_id)
|
||||
}
|
||||
|
||||
async fn edit_message(
|
||||
&self,
|
||||
room_id: &str,
|
||||
original_message_id: &str,
|
||||
plain: &str,
|
||||
html: &str,
|
||||
) -> Result<(), String> {
|
||||
// WhatsApp does not support message editing.
|
||||
// Send a new message instead.
|
||||
slog!(
|
||||
"[whatsapp-stub] edit_message (original={original_message_id}) — \
|
||||
WhatsApp does not support edits, sending new message"
|
||||
);
|
||||
self.send_message(room_id, plain, html).await.map(|_| ())
|
||||
}
|
||||
|
||||
async fn send_typing(&self, room_id: &str, typing: bool) -> Result<(), String> {
|
||||
// TODO: Send typing indicator via WhatsApp Business API
|
||||
slog!("[whatsapp-stub] send_typing to {room_id}: typing={typing}");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn send_message_returns_unique_ids() {
|
||||
let transport = WhatsAppTransport::new();
|
||||
let id1 = transport
|
||||
.send_message("room1", "hello", "<p>hello</p>")
|
||||
.await
|
||||
.unwrap();
|
||||
let id2 = transport
|
||||
.send_message("room1", "world", "<p>world</p>")
|
||||
.await
|
||||
.unwrap();
|
||||
assert_ne!(id1, id2, "each message should get a unique ID");
|
||||
assert!(id1.starts_with("whatsapp-stub-"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn edit_message_succeeds() {
|
||||
let transport = WhatsAppTransport::new();
|
||||
let result = transport
|
||||
.edit_message("room1", "msg-1", "updated", "<p>updated</p>")
|
||||
.await;
|
||||
assert!(result.is_ok(), "edit should succeed (sends new message)");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn send_typing_succeeds() {
|
||||
let transport = WhatsAppTransport::new();
|
||||
assert!(transport.send_typing("room1", true).await.is_ok());
|
||||
assert!(transport.send_typing("room1", false).await.is_ok());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user