Skip to main content

room_cli/plugin/
status.rs

1use super::{BoxFuture, CommandContext, CommandInfo, ParamSchema, ParamType, Plugin, PluginResult};
2
3/// `/set_status` plugin — sets the sender's presence status.
4///
5/// The plugin returns [`PluginResult::SetStatus`] which the broker interprets
6/// to update the `StatusMap` and broadcast the announcement. The plugin itself
7/// never touches broker state directly.
8pub 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}