agent_tui/daemon/usecases/
recording.rs1use std::sync::Arc;
2
3use crate::common::mutex_lock_or_recover;
4
5use crate::daemon::domain::{
6 RecordStartInput, RecordStartOutput, RecordStatusInput, RecordStatusOutput, RecordStopInput,
7 RecordStopOutput,
8};
9use crate::daemon::error::SessionError;
10use crate::daemon::repository::SessionRepository;
11
12pub trait RecordStartUseCase: Send + Sync {
13 fn execute(&self, input: RecordStartInput) -> Result<RecordStartOutput, SessionError>;
14}
15
16pub struct RecordStartUseCaseImpl<R: SessionRepository> {
17 repository: Arc<R>,
18}
19
20impl<R: SessionRepository> RecordStartUseCaseImpl<R> {
21 pub fn new(repository: Arc<R>) -> Self {
22 Self { repository }
23 }
24}
25
26impl<R: SessionRepository> RecordStartUseCase for RecordStartUseCaseImpl<R> {
27 fn execute(&self, input: RecordStartInput) -> Result<RecordStartOutput, SessionError> {
28 let session = self.repository.resolve(input.session_id.as_deref())?;
29 let mut session_guard = mutex_lock_or_recover(&session);
30
31 session_guard.start_recording();
32
33 Ok(RecordStartOutput {
34 session_id: session_guard.id.clone(),
35 success: true,
36 })
37 }
38}
39
40pub trait RecordStopUseCase: Send + Sync {
41 fn execute(&self, input: RecordStopInput) -> Result<RecordStopOutput, SessionError>;
42}
43
44pub struct RecordStopUseCaseImpl<R: SessionRepository> {
45 repository: Arc<R>,
46}
47
48impl<R: SessionRepository> RecordStopUseCaseImpl<R> {
49 pub fn new(repository: Arc<R>) -> Self {
50 Self { repository }
51 }
52}
53
54impl<R: SessionRepository> RecordStopUseCase for RecordStopUseCaseImpl<R> {
55 fn execute(&self, input: RecordStopInput) -> Result<RecordStopOutput, SessionError> {
56 let session = self.repository.resolve(input.session_id.as_deref())?;
57 let mut session_guard = mutex_lock_or_recover(&session);
58
59 let frames = session_guard.stop_recording();
60 let (cols, rows) = session_guard.size();
61 let format = input.format.unwrap_or_else(|| "asciicast".to_string());
62
63 Ok(RecordStopOutput {
64 session_id: session_guard.id.clone(),
65 frame_count: frames.len(),
66 frames,
67 format,
68 cols,
69 rows,
70 })
71 }
72}
73
74pub trait RecordStatusUseCase: Send + Sync {
75 fn execute(&self, input: RecordStatusInput) -> Result<RecordStatusOutput, SessionError>;
76}
77
78pub struct RecordStatusUseCaseImpl<R: SessionRepository> {
79 repository: Arc<R>,
80}
81
82impl<R: SessionRepository> RecordStatusUseCaseImpl<R> {
83 pub fn new(repository: Arc<R>) -> Self {
84 Self { repository }
85 }
86}
87
88impl<R: SessionRepository> RecordStatusUseCase for RecordStatusUseCaseImpl<R> {
89 fn execute(&self, input: RecordStatusInput) -> Result<RecordStatusOutput, SessionError> {
90 let session = self.repository.resolve(input.session_id.as_deref())?;
91 let session_guard = mutex_lock_or_recover(&session);
92
93 Ok(session_guard.recording_status())
94 }
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100 use crate::daemon::domain::SessionId;
101 use crate::daemon::test_support::{MockError, MockSessionRepository};
102
103 #[test]
108 fn test_record_start_usecase_returns_error_when_no_active_session() {
109 let repo = Arc::new(MockSessionRepository::new());
110 let usecase = RecordStartUseCaseImpl::new(repo);
111
112 let input = RecordStartInput { session_id: None };
113
114 let result = usecase.execute(input);
115 assert!(matches!(result, Err(SessionError::NoActiveSession)));
116 }
117
118 #[test]
119 fn test_record_start_usecase_returns_error_when_session_not_found() {
120 let repo = Arc::new(
121 MockSessionRepository::builder()
122 .with_resolve_error(MockError::NotFound("missing".to_string()))
123 .build(),
124 );
125 let usecase = RecordStartUseCaseImpl::new(repo);
126
127 let input = RecordStartInput {
128 session_id: Some(SessionId::new("missing")),
129 };
130
131 let result = usecase.execute(input);
132 assert!(matches!(result, Err(SessionError::NotFound(_))));
133 }
134
135 #[test]
140 fn test_record_stop_usecase_returns_error_when_no_active_session() {
141 let repo = Arc::new(MockSessionRepository::new());
142 let usecase = RecordStopUseCaseImpl::new(repo);
143
144 let input = RecordStopInput {
145 session_id: None,
146 format: None,
147 };
148
149 let result = usecase.execute(input);
150 assert!(matches!(result, Err(SessionError::NoActiveSession)));
151 }
152
153 #[test]
154 fn test_record_stop_usecase_returns_error_when_session_not_found() {
155 let repo = Arc::new(
156 MockSessionRepository::builder()
157 .with_resolve_error(MockError::NotFound("missing".to_string()))
158 .build(),
159 );
160 let usecase = RecordStopUseCaseImpl::new(repo);
161
162 let input = RecordStopInput {
163 session_id: Some(SessionId::new("missing")),
164 format: Some("json".to_string()),
165 };
166
167 let result = usecase.execute(input);
168 assert!(matches!(result, Err(SessionError::NotFound(_))));
169 }
170
171 #[test]
176 fn test_record_status_usecase_returns_error_when_no_active_session() {
177 let repo = Arc::new(MockSessionRepository::new());
178 let usecase = RecordStatusUseCaseImpl::new(repo);
179
180 let input = RecordStatusInput { session_id: None };
181
182 let result = usecase.execute(input);
183 assert!(matches!(result, Err(SessionError::NoActiveSession)));
184 }
185
186 #[test]
187 fn test_record_status_usecase_returns_error_when_session_not_found() {
188 let repo = Arc::new(
189 MockSessionRepository::builder()
190 .with_resolve_error(MockError::NotFound("missing".to_string()))
191 .build(),
192 );
193 let usecase = RecordStatusUseCaseImpl::new(repo);
194
195 let input = RecordStatusInput {
196 session_id: Some(SessionId::new("missing")),
197 };
198
199 let result = usecase.execute(input);
200 assert!(matches!(result, Err(SessionError::NotFound(_))));
201 }
202}