PicoBot/src/command/handlers/delete_topic.rs

182 lines
5.5 KiB
Rust

use crate::command::context::CommandContext;
use crate::command::handler::{CommandHandler, CommandMetadata};
use crate::command::handlers::list_topics::TopicSummary;
use crate::command::response::{CommandError, CommandResponse, MessageKind};
use crate::command::Command;
use crate::gateway::session::SessionManager;
use crate::storage::SessionStore;
use async_trait::async_trait;
use std::sync::Arc;
/// 删除话题命令处理器
pub struct DeleteTopicCommandHandler {
store: Arc<SessionStore>,
session_manager: Option<SessionManager>,
}
impl DeleteTopicCommandHandler {
pub fn new(store: Arc<SessionStore>) -> Self {
Self {
store,
session_manager: None,
}
}
pub fn with_session_manager(mut self, session_manager: SessionManager) -> Self {
self.session_manager = Some(session_manager);
self
}
}
#[async_trait]
impl CommandHandler for DeleteTopicCommandHandler {
fn can_handle(&self, cmd: &Command) -> bool {
matches!(cmd, Command::DeleteTopic { .. })
}
fn metadata(&self) -> Option<CommandMetadata> {
Some(CommandMetadata {
name: "delete",
description: "删除指定话题",
usage: "/delete <topic_id>",
})
}
async fn handle(
&self,
cmd: Command,
ctx: CommandContext,
) -> Result<CommandResponse, CommandError> {
match cmd {
Command::DeleteTopic { topic_id } => handle_delete_topic(self, topic_id, ctx).await,
_ => unreachable!(),
}
}
}
async fn handle_delete_topic(
handler: &DeleteTopicCommandHandler,
topic_id: String,
ctx: CommandContext,
) -> Result<CommandResponse, CommandError> {
let session_id = ctx
.session_id
.as_deref()
.ok_or_else(|| CommandError::new("NO_SESSION", "No active session"))?;
// 验证话题存在
let topic = handler
.store
.get_topic(&topic_id)
.map_err(|e| CommandError::new("GET_TOPIC_ERROR", e.to_string()))?
.ok_or_else(|| {
CommandError::new("TOPIC_NOT_FOUND", format!("Topic not found: {}", topic_id))
})?;
let topic_title = topic.title.clone();
// 删除话题(存储层方法已存在)
handler
.store
.delete_topic(&topic_id)
.map_err(|e| CommandError::new("DELETE_TOPIC_ERROR", e.to_string()))?;
// 查询更新后的话题列表,返回给前端刷新侧边栏
let topics = handler
.store
.list_topics(session_id)
.map_err(|e| CommandError::new("LIST_TOPICS_ERROR", e.to_string()))?;
let topic_summaries: Vec<TopicSummary> = topics
.into_iter()
.map(|t| TopicSummary {
topic_id: t.id,
session_id: t.session_id,
title: t.title,
description: t.description.filter(|d| !d.is_empty()),
message_count: t.message_count,
created_at: t.created_at,
last_active_at: t.last_active_at,
})
.collect();
let topics_json =
serde_json::to_string(&topic_summaries)
.map_err(|e| CommandError::new("SERIALIZE_ERROR", e.to_string()))?;
let message = format!("✓ 已删除话题: {}", topic_title);
Ok(CommandResponse::success(ctx.request_id)
.with_message(MessageKind::Notification, &message)
.with_metadata("topics", &topics_json)
.with_metadata("deleted_topic_id", &topic_id))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::storage::SessionStore;
use std::sync::Arc;
fn create_test_handler() -> DeleteTopicCommandHandler {
let store = Arc::new(SessionStore::in_memory().unwrap());
DeleteTopicCommandHandler::new(store)
}
#[tokio::test]
async fn test_delete_topic_success() {
let handler = create_test_handler();
let store = handler.store.clone();
// 先创建 session 和 topic
let session = store.create_session("test_channel", Some("test")).unwrap();
let topic = store
.create_topic(&session.id, "test topic", None)
.unwrap();
let ctx = CommandContext::new("test", "test_channel")
.with_session_id(&session.id)
.with_chat_id(&session.id);
let cmd = Command::DeleteTopic {
topic_id: topic.id.clone(),
};
let result = handler.handle(cmd, ctx).await;
assert!(result.is_ok());
let resp = result.unwrap();
assert!(resp.success);
assert!(resp.metadata.contains_key("deleted_topic_id"));
// 验证话题已被删除
let deleted = store.get_topic(&topic.id).unwrap();
assert!(deleted.is_none());
}
#[tokio::test]
async fn test_delete_nonexistent_topic() {
let handler = create_test_handler();
let store = handler.store.clone();
let session = store.create_session("test_channel", Some("test")).unwrap();
let ctx = CommandContext::new("test", "test_channel")
.with_session_id(&session.id)
.with_chat_id(&session.id);
let cmd = Command::DeleteTopic {
topic_id: "nonexistent".to_string(),
};
let result = handler.handle(cmd, ctx).await;
assert!(result.is_err());
}
#[test]
fn test_can_handle() {
let handler = create_test_handler();
assert!(handler.can_handle(&Command::DeleteTopic {
topic_id: "test".to_string()
}));
assert!(!handler.can_handle(&Command::Help));
}
}