construct/channels/
cli.rs1use super::traits::{Channel, ChannelMessage, SendMessage};
2use async_trait::async_trait;
3use tokio::io::{self, AsyncBufReadExt, BufReader};
4use uuid::Uuid;
5
6pub struct CliChannel;
8
9impl CliChannel {
10 pub fn new() -> Self {
11 Self
12 }
13}
14
15#[async_trait]
16impl Channel for CliChannel {
17 fn name(&self) -> &str {
18 "cli"
19 }
20
21 async fn send(&self, message: &SendMessage) -> anyhow::Result<()> {
22 println!("{}", message.content);
23 Ok(())
24 }
25
26 async fn listen(&self, tx: tokio::sync::mpsc::Sender<ChannelMessage>) -> anyhow::Result<()> {
27 let stdin = io::stdin();
28 let reader = BufReader::new(stdin);
29 let mut lines = reader.lines();
30
31 while let Ok(Some(line)) = lines.next_line().await {
32 let line = line.trim().to_string();
33 if line.is_empty() {
34 continue;
35 }
36 if line == "/quit" || line == "/exit" {
37 break;
38 }
39
40 let msg = ChannelMessage {
41 id: Uuid::new_v4().to_string(),
42 sender: "user".to_string(),
43 reply_target: "user".to_string(),
44 content: line,
45 channel: "cli".to_string(),
46 timestamp: std::time::SystemTime::now()
47 .duration_since(std::time::UNIX_EPOCH)
48 .unwrap_or_default()
49 .as_secs(),
50 thread_ts: None,
51 interruption_scope_id: None,
52 attachments: vec![],
53 };
54
55 if tx.send(msg).await.is_err() {
56 break;
57 }
58 }
59 Ok(())
60 }
61}
62
63#[cfg(test)]
64mod tests {
65 use super::*;
66
67 #[test]
68 fn cli_channel_name() {
69 assert_eq!(CliChannel::new().name(), "cli");
70 }
71
72 #[tokio::test]
73 async fn cli_channel_send_does_not_panic() {
74 let ch = CliChannel::new();
75 let result = ch
76 .send(&SendMessage {
77 content: "hello".into(),
78 recipient: "user".into(),
79 subject: None,
80 thread_ts: None,
81 cancellation_token: None,
82 })
83 .await;
84 assert!(result.is_ok());
85 }
86
87 #[tokio::test]
88 async fn cli_channel_send_empty_message() {
89 let ch = CliChannel::new();
90 let result = ch
91 .send(&SendMessage {
92 content: String::new(),
93 recipient: String::new(),
94 subject: None,
95 thread_ts: None,
96 cancellation_token: None,
97 })
98 .await;
99 assert!(result.is_ok());
100 }
101
102 #[tokio::test]
103 async fn cli_channel_health_check() {
104 let ch = CliChannel::new();
105 assert!(ch.health_check().await);
106 }
107
108 #[test]
109 fn channel_message_struct() {
110 let msg = ChannelMessage {
111 id: "test-id".into(),
112 sender: "user".into(),
113 reply_target: "user".into(),
114 content: "hello".into(),
115 channel: "cli".into(),
116 timestamp: 1_234_567_890,
117 thread_ts: None,
118 interruption_scope_id: None,
119 attachments: vec![],
120 };
121 assert_eq!(msg.id, "test-id");
122 assert_eq!(msg.sender, "user");
123 assert_eq!(msg.reply_target, "user");
124 assert_eq!(msg.content, "hello");
125 assert_eq!(msg.channel, "cli");
126 assert_eq!(msg.timestamp, 1_234_567_890);
127 }
128
129 #[test]
130 fn channel_message_clone() {
131 let msg = ChannelMessage {
132 id: "id".into(),
133 sender: "s".into(),
134 reply_target: "s".into(),
135 content: "c".into(),
136 channel: "ch".into(),
137 timestamp: 0,
138 thread_ts: None,
139 interruption_scope_id: None,
140 attachments: vec![],
141 };
142 let cloned = msg.clone();
143 assert_eq!(cloned.id, msg.id);
144 assert_eq!(cloned.content, msg.content);
145 }
146}