agent_tui/daemon/usecases/
recording.rs

1use 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    // ========================================================================
104    // RecordStartUseCase Tests (Error paths)
105    // ========================================================================
106
107    #[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    // ========================================================================
136    // RecordStopUseCase Tests (Error paths)
137    // ========================================================================
138
139    #[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    // ========================================================================
172    // RecordStatusUseCase Tests (Error paths)
173    // ========================================================================
174
175    #[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}