room_cli/plugin/
status.rs1use super::{BoxFuture, CommandContext, CommandInfo, ParamSchema, ParamType, Plugin, PluginResult};
2
3pub struct StatusPlugin;
9
10impl Plugin for StatusPlugin {
11 fn name(&self) -> &str {
12 "status"
13 }
14
15 fn commands(&self) -> Vec<CommandInfo> {
16 vec![CommandInfo {
17 name: "set_status".to_owned(),
18 description: "Set your presence status".to_owned(),
19 usage: "/set_status <status>".to_owned(),
20 params: vec![ParamSchema {
21 name: "status".to_owned(),
22 param_type: ParamType::Text,
23 required: false,
24 description: "Status text (omit to clear)".to_owned(),
25 }],
26 }]
27 }
28
29 fn handle(&self, ctx: CommandContext) -> BoxFuture<'_, anyhow::Result<PluginResult>> {
30 Box::pin(async move {
31 let status = ctx.params.join(" ");
32 Ok(PluginResult::SetStatus(status))
33 })
34 }
35}
36
37#[cfg(test)]
38mod tests {
39 use super::*;
40 use crate::plugin::{ChatWriter, HistoryReader, RoomMetadata, UserInfo};
41 use chrono::Utc;
42 use std::collections::HashMap;
43 use std::sync::{atomic::AtomicU64, Arc};
44 use tempfile::NamedTempFile;
45 use tokio::sync::Mutex;
46
47 fn make_ctx(params: Vec<String>, path: &std::path::Path) -> CommandContext {
48 let clients = Arc::new(Mutex::new(HashMap::new()));
49 let chat_path = Arc::new(path.to_path_buf());
50 let room_id = Arc::new("test-room".to_owned());
51 let seq = Arc::new(AtomicU64::new(0));
52
53 CommandContext {
54 command: "set_status".to_owned(),
55 params,
56 sender: "alice".to_owned(),
57 room_id: "test-room".to_owned(),
58 message_id: "msg-1".to_owned(),
59 timestamp: Utc::now(),
60 history: HistoryReader::new(path, "alice"),
61 writer: ChatWriter::new(&clients, &chat_path, &room_id, &seq, "status"),
62 metadata: RoomMetadata {
63 online_users: vec![UserInfo {
64 username: "alice".to_owned(),
65 status: String::new(),
66 }],
67 host: Some("alice".to_owned()),
68 message_count: 0,
69 },
70 available_commands: vec![],
71 }
72 }
73
74 #[tokio::test]
75 async fn set_status_single_word() {
76 let tmp = NamedTempFile::new().unwrap();
77 let ctx = make_ctx(vec!["busy".to_owned()], tmp.path());
78 let result = StatusPlugin.handle(ctx).await.unwrap();
79 let PluginResult::SetStatus(status) = result else {
80 panic!("expected SetStatus");
81 };
82 assert_eq!(status, "busy");
83 }
84
85 #[tokio::test]
86 async fn set_status_multi_word() {
87 let tmp = NamedTempFile::new().unwrap();
88 let ctx = make_ctx(
89 vec!["reviewing".to_owned(), "PR".to_owned(), "#42".to_owned()],
90 tmp.path(),
91 );
92 let result = StatusPlugin.handle(ctx).await.unwrap();
93 let PluginResult::SetStatus(status) = result else {
94 panic!("expected SetStatus");
95 };
96 assert_eq!(status, "reviewing PR #42");
97 }
98
99 #[tokio::test]
100 async fn set_status_empty_clears() {
101 let tmp = NamedTempFile::new().unwrap();
102 let ctx = make_ctx(vec![], tmp.path());
103 let result = StatusPlugin.handle(ctx).await.unwrap();
104 let PluginResult::SetStatus(status) = result else {
105 panic!("expected SetStatus");
106 };
107 assert_eq!(status, "");
108 }
109
110 #[test]
111 fn status_plugin_commands() {
112 let cmds = StatusPlugin.commands();
113 assert_eq!(cmds.len(), 1);
114 assert_eq!(cmds[0].name, "set_status");
115 assert!(!cmds[0].params[0].required);
116 }
117}