agent_tui/daemon/usecases/
input.rs1use std::sync::Arc;
2
3use crate::common::mutex_lock_or_recover;
4
5use crate::daemon::domain::{
6 KeydownInput, KeydownOutput, KeystrokeInput, KeystrokeOutput, KeyupInput, KeyupOutput,
7 TypeInput, TypeOutput,
8};
9use crate::daemon::error::SessionError;
10use crate::daemon::repository::SessionRepository;
11
12pub trait KeystrokeUseCase: Send + Sync {
13 fn execute(&self, input: KeystrokeInput) -> Result<KeystrokeOutput, SessionError>;
14}
15
16pub struct KeystrokeUseCaseImpl<R: SessionRepository> {
17 repository: Arc<R>,
18}
19
20impl<R: SessionRepository> KeystrokeUseCaseImpl<R> {
21 pub fn new(repository: Arc<R>) -> Self {
22 Self { repository }
23 }
24}
25
26impl<R: SessionRepository> KeystrokeUseCase for KeystrokeUseCaseImpl<R> {
27 fn execute(&self, input: KeystrokeInput) -> Result<KeystrokeOutput, SessionError> {
28 let session = self.repository.resolve(input.session_id.as_deref())?;
29 let session_guard = mutex_lock_or_recover(&session);
30
31 session_guard.keystroke(&input.key)?;
32
33 Ok(KeystrokeOutput { success: true })
34 }
35}
36
37pub trait TypeUseCase: Send + Sync {
38 fn execute(&self, input: TypeInput) -> Result<TypeOutput, SessionError>;
39}
40
41pub struct TypeUseCaseImpl<R: SessionRepository> {
42 repository: Arc<R>,
43}
44
45impl<R: SessionRepository> TypeUseCaseImpl<R> {
46 pub fn new(repository: Arc<R>) -> Self {
47 Self { repository }
48 }
49}
50
51impl<R: SessionRepository> TypeUseCase for TypeUseCaseImpl<R> {
52 fn execute(&self, input: TypeInput) -> Result<TypeOutput, SessionError> {
53 let session = self.repository.resolve(input.session_id.as_deref())?;
54 let session_guard = mutex_lock_or_recover(&session);
55
56 session_guard.type_text(&input.text)?;
57
58 Ok(TypeOutput { success: true })
59 }
60}
61
62pub trait KeydownUseCase: Send + Sync {
63 fn execute(&self, input: KeydownInput) -> Result<KeydownOutput, SessionError>;
64}
65
66pub struct KeydownUseCaseImpl<R: SessionRepository> {
67 repository: Arc<R>,
68}
69
70impl<R: SessionRepository> KeydownUseCaseImpl<R> {
71 pub fn new(repository: Arc<R>) -> Self {
72 Self { repository }
73 }
74}
75
76impl<R: SessionRepository> KeydownUseCase for KeydownUseCaseImpl<R> {
77 fn execute(&self, input: KeydownInput) -> Result<KeydownOutput, SessionError> {
78 let session = self.repository.resolve(input.session_id.as_deref())?;
79 let mut session_guard = mutex_lock_or_recover(&session);
80
81 session_guard.keydown(&input.key)?;
82
83 Ok(KeydownOutput { success: true })
84 }
85}
86
87pub trait KeyupUseCase: Send + Sync {
88 fn execute(&self, input: KeyupInput) -> Result<KeyupOutput, SessionError>;
89}
90
91pub struct KeyupUseCaseImpl<R: SessionRepository> {
92 repository: Arc<R>,
93}
94
95impl<R: SessionRepository> KeyupUseCaseImpl<R> {
96 pub fn new(repository: Arc<R>) -> Self {
97 Self { repository }
98 }
99}
100
101impl<R: SessionRepository> KeyupUseCase for KeyupUseCaseImpl<R> {
102 fn execute(&self, input: KeyupInput) -> Result<KeyupOutput, SessionError> {
103 let session = self.repository.resolve(input.session_id.as_deref())?;
104 let mut session_guard = mutex_lock_or_recover(&session);
105
106 session_guard.keyup(&input.key)?;
107
108 Ok(KeyupOutput { success: true })
109 }
110}
111
112#[cfg(test)]
113mod tests {
114 use super::*;
115 use crate::daemon::domain::SessionId;
116 use crate::daemon::test_support::{MockError, MockSessionRepository};
117
118 #[test]
123 fn test_keystroke_usecase_returns_error_when_no_active_session() {
124 let repo = Arc::new(MockSessionRepository::new());
125 let usecase = KeystrokeUseCaseImpl::new(repo);
126
127 let input = KeystrokeInput {
128 session_id: None,
129 key: "Enter".to_string(),
130 };
131
132 let result = usecase.execute(input);
133 assert!(matches!(result, Err(SessionError::NoActiveSession)));
134 }
135
136 #[test]
137 fn test_keystroke_usecase_returns_error_when_session_not_found() {
138 let repo = Arc::new(
139 MockSessionRepository::builder()
140 .with_resolve_error(MockError::NotFound("missing".to_string()))
141 .build(),
142 );
143 let usecase = KeystrokeUseCaseImpl::new(repo);
144
145 let input = KeystrokeInput {
146 session_id: Some(SessionId::new("missing")),
147 key: "Tab".to_string(),
148 };
149
150 let result = usecase.execute(input);
151 assert!(matches!(result, Err(SessionError::NotFound(_))));
152 }
153
154 #[test]
159 fn test_type_usecase_returns_error_when_no_active_session() {
160 let repo = Arc::new(MockSessionRepository::new());
161 let usecase = TypeUseCaseImpl::new(repo);
162
163 let input = TypeInput {
164 session_id: None,
165 text: "hello world".to_string(),
166 };
167
168 let result = usecase.execute(input);
169 assert!(matches!(result, Err(SessionError::NoActiveSession)));
170 }
171
172 #[test]
173 fn test_type_usecase_returns_error_when_session_not_found() {
174 let repo = Arc::new(
175 MockSessionRepository::builder()
176 .with_resolve_error(MockError::NotFound("missing".to_string()))
177 .build(),
178 );
179 let usecase = TypeUseCaseImpl::new(repo);
180
181 let input = TypeInput {
182 session_id: Some(SessionId::new("missing")),
183 text: "test text".to_string(),
184 };
185
186 let result = usecase.execute(input);
187 assert!(matches!(result, Err(SessionError::NotFound(_))));
188 }
189
190 #[test]
195 fn test_keydown_usecase_returns_error_when_no_active_session() {
196 let repo = Arc::new(MockSessionRepository::new());
197 let usecase = KeydownUseCaseImpl::new(repo);
198
199 let input = KeydownInput {
200 session_id: None,
201 key: "Ctrl".to_string(),
202 };
203
204 let result = usecase.execute(input);
205 assert!(matches!(result, Err(SessionError::NoActiveSession)));
206 }
207
208 #[test]
209 fn test_keydown_usecase_returns_error_when_session_not_found() {
210 let repo = Arc::new(
211 MockSessionRepository::builder()
212 .with_resolve_error(MockError::NotFound("missing".to_string()))
213 .build(),
214 );
215 let usecase = KeydownUseCaseImpl::new(repo);
216
217 let input = KeydownInput {
218 session_id: Some(SessionId::new("missing")),
219 key: "Shift".to_string(),
220 };
221
222 let result = usecase.execute(input);
223 assert!(matches!(result, Err(SessionError::NotFound(_))));
224 }
225
226 #[test]
231 fn test_keyup_usecase_returns_error_when_no_active_session() {
232 let repo = Arc::new(MockSessionRepository::new());
233 let usecase = KeyupUseCaseImpl::new(repo);
234
235 let input = KeyupInput {
236 session_id: None,
237 key: "Ctrl".to_string(),
238 };
239
240 let result = usecase.execute(input);
241 assert!(matches!(result, Err(SessionError::NoActiveSession)));
242 }
243
244 #[test]
245 fn test_keyup_usecase_returns_error_when_session_not_found() {
246 let repo = Arc::new(
247 MockSessionRepository::builder()
248 .with_resolve_error(MockError::NotFound("missing".to_string()))
249 .build(),
250 );
251 let usecase = KeyupUseCaseImpl::new(repo);
252
253 let input = KeyupInput {
254 session_id: Some(SessionId::new("missing")),
255 key: "Alt".to_string(),
256 };
257
258 let result = usecase.execute(input);
259 assert!(matches!(result, Err(SessionError::NotFound(_))));
260 }
261}