spec_ai_core/agent/transcription_providers/
mock.rs

1//! Mock Transcription Provider for Testing
2
3use crate::agent::transcription::{
4    TranscriptionConfig, TranscriptionEvent, TranscriptionProvider, TranscriptionProviderKind,
5    TranscriptionProviderMetadata,
6};
7use anyhow::Result;
8use async_stream::stream;
9use async_trait::async_trait;
10use futures::Stream;
11use std::pin::Pin;
12
13/// Mock transcription provider for testing
14#[derive(Debug, Clone)]
15pub struct MockTranscriptionProvider {
16    /// Predefined transcriptions to emit
17    transcriptions: Vec<String>,
18    /// Provider name
19    name: String,
20}
21
22impl MockTranscriptionProvider {
23    /// Create a new mock provider with default transcriptions
24    pub fn new() -> Self {
25        Self {
26            transcriptions: vec![
27                "Hello, this is a test transcription.".to_string(),
28                "The audio is being transcribed in real-time.".to_string(),
29                "This is a mock provider for testing purposes.".to_string(),
30            ],
31            name: "Mock Transcription Provider".to_string(),
32        }
33    }
34
35    /// Create a mock provider with custom transcriptions
36    pub fn with_transcriptions(transcriptions: Vec<String>) -> Self {
37        Self {
38            transcriptions,
39            name: "Mock Transcription Provider".to_string(),
40        }
41    }
42
43    /// Set the provider name
44    pub fn with_name(mut self, name: impl Into<String>) -> Self {
45        self.name = name.into();
46        self
47    }
48}
49
50impl Default for MockTranscriptionProvider {
51    fn default() -> Self {
52        Self::new()
53    }
54}
55
56#[async_trait]
57impl TranscriptionProvider for MockTranscriptionProvider {
58    async fn start_transcription(
59        &self,
60        config: &TranscriptionConfig,
61    ) -> Result<Pin<Box<dyn Stream<Item = Result<TranscriptionEvent>> + Send>>> {
62        let transcriptions = self.transcriptions.clone();
63        let chunk_duration = config.chunk_duration_secs;
64        let total_duration = config.duration_secs.unwrap_or(30);
65
66        let stream = stream! {
67            // Emit started event
68            yield Ok(TranscriptionEvent::Started {
69                timestamp: std::time::SystemTime::now(),
70            });
71
72            let mut chunk_id = 0;
73            let num_chunks = (total_duration as f64 / chunk_duration).ceil() as usize;
74            let chunk_duration_ms = (chunk_duration * 1000.0) as u64;
75
76            for i in 0..num_chunks {
77                // Wait for chunk duration
78                tokio::time::sleep(tokio::time::Duration::from_millis(chunk_duration_ms)).await;
79
80                // Get transcription (cycle through available ones)
81                let text = transcriptions[i % transcriptions.len()].clone();
82
83                // Emit transcription event
84                yield Ok(TranscriptionEvent::Transcription {
85                    chunk_id,
86                    text,
87                    timestamp: std::time::SystemTime::now(),
88                });
89
90                chunk_id += 1;
91            }
92
93            // Emit completed event
94            yield Ok(TranscriptionEvent::Completed {
95                timestamp: std::time::SystemTime::now(),
96                total_chunks: chunk_id,
97            });
98        };
99
100        Ok(Box::pin(stream))
101    }
102
103    fn metadata(&self) -> TranscriptionProviderMetadata {
104        TranscriptionProviderMetadata {
105            name: self.name.clone(),
106            supported_models: vec!["mock-model".to_string()],
107            supports_streaming: true,
108            supported_languages: vec!["en".to_string(), "es".to_string(), "fr".to_string()],
109        }
110    }
111
112    fn kind(&self) -> TranscriptionProviderKind {
113        TranscriptionProviderKind::Mock
114    }
115
116    async fn health_check(&self) -> Result<bool> {
117        Ok(true)
118    }
119}
120
121#[cfg(test)]
122mod tests {
123    use super::*;
124    use futures::StreamExt;
125
126    #[tokio::test]
127    async fn test_mock_provider() {
128        let provider = MockTranscriptionProvider::new();
129        let config = TranscriptionConfig {
130            duration_secs: Some(1),
131            chunk_duration_secs: 0.1,
132            ..Default::default()
133        };
134
135        let mut stream = provider.start_transcription(&config).await.unwrap();
136
137        let mut events = Vec::new();
138        while let Some(event) = stream.next().await {
139            events.push(event.unwrap());
140        }
141
142        // Should have: Started + multiple Transcriptions + Completed
143        assert!(!events.is_empty());
144        assert!(matches!(events[0], TranscriptionEvent::Started { .. }));
145        assert!(matches!(
146            events[events.len() - 1],
147            TranscriptionEvent::Completed { .. }
148        ));
149    }
150
151    #[test]
152    fn test_mock_provider_metadata() {
153        let provider = MockTranscriptionProvider::new();
154        let metadata = provider.metadata();
155
156        assert_eq!(metadata.name, "Mock Transcription Provider");
157        assert!(metadata.supports_streaming);
158        assert!(!metadata.supported_models.is_empty());
159    }
160}