use std::sync::Arc;
use tokio::sync::Mutex;
use turboclaude_protocol::ProtocolMessage;
#[derive(Debug, Clone, Default)]
pub struct MockConfig {
pub simulate_hooks: bool,
pub simulate_permissions: bool,
pub response_delay: Option<std::time::Duration>,
pub fail_after_n_messages: Option<usize>,
}
#[derive(Clone)]
pub struct MockCliTransport {
response_queue: Arc<Mutex<Vec<ProtocolMessage>>>,
sent_messages: Arc<Mutex<Vec<ProtocolMessage>>>,
config: MockConfig,
message_count: Arc<Mutex<usize>>,
}
impl MockCliTransport {
pub fn new() -> Self {
Self {
response_queue: Arc::new(Mutex::new(Vec::new())),
sent_messages: Arc::new(Mutex::new(Vec::new())),
config: MockConfig::default(),
message_count: Arc::new(Mutex::new(0)),
}
}
pub fn with_config(config: MockConfig) -> Self {
Self {
response_queue: Arc::new(Mutex::new(Vec::new())),
sent_messages: Arc::new(Mutex::new(Vec::new())),
config,
message_count: Arc::new(Mutex::new(0)),
}
}
pub async fn enqueue_response(&self, message: ProtocolMessage) {
self.response_queue.lock().await.push(message);
}
pub async fn sent_messages(&self) -> Vec<ProtocolMessage> {
self.sent_messages.lock().await.clone()
}
pub async fn clear_sent_messages(&self) {
self.sent_messages.lock().await.clear();
}
pub async fn message_count(&self) -> usize {
*self.message_count.lock().await
}
}
impl Default for MockCliTransport {
fn default() -> Self {
Self::new()
}
}
impl MockCliTransport {
pub async fn send_message(
&self,
message: serde_json::Value,
) -> turboclaude_transport::Result<()> {
let mut count = self.message_count.lock().await;
*count += 1;
if let Some(fail_after) = self.config.fail_after_n_messages
&& *count > fail_after
{
return Err(turboclaude_transport::TransportError::Other(
"Mock transport configured to fail".to_string(),
));
}
if let Ok(json_str) = serde_json::to_string(&message)
&& let Ok(parsed) = ProtocolMessage::from_json(&json_str)
{
self.sent_messages.lock().await.push(parsed);
}
if let Some(delay) = self.config.response_delay {
tokio::time::sleep(delay).await;
}
Ok(())
}
pub async fn recv_message(&self) -> turboclaude_transport::Result<Option<serde_json::Value>> {
if let Some(delay) = self.config.response_delay {
tokio::time::sleep(delay).await;
}
let mut queue = self.response_queue.lock().await;
if let Some(message) = queue.pop() {
let json = message.to_json().map_err(|e| {
turboclaude_transport::TransportError::Serialization(format!("{}", e))
})?;
Ok(Some(serde_json::from_str(&json).map_err(|e| {
turboclaude_transport::TransportError::Serialization(format!("{}", e))
})?))
} else {
Ok(None)
}
}
pub async fn is_alive(&self) -> bool {
true
}
pub async fn kill(&self) -> turboclaude_transport::Result<()> {
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use turboclaude_protocol::ProtocolErrorMessage;
#[tokio::test]
async fn test_mock_transport_send_recv() {
let mock = MockCliTransport::new();
let response = turboclaude_protocol::QueryResponse {
message: turboclaude_protocol::Message {
id: "msg_123".to_string(),
message_type: "message".to_string(),
role: turboclaude_protocol::message::MessageRole::Assistant,
content: vec![turboclaude_protocol::ContentBlock::Text {
text: "test response".to_string(),
}],
model: "claude-3-5-sonnet".to_string(),
stop_reason: turboclaude_protocol::types::StopReason::EndTurn,
stop_sequence: None,
created_at: "2024-01-01T00:00:00Z".to_string(),
usage: turboclaude_protocol::types::Usage {
input_tokens: 10,
output_tokens: 5,
},
cache_usage: Default::default(),
},
is_complete: true,
};
mock.enqueue_response(turboclaude_protocol::ProtocolMessage::Response(response))
.await;
let msg = mock.recv_message().await.unwrap();
assert!(msg.is_some());
}
#[tokio::test]
async fn test_mock_transport_tracking() {
let mock = MockCliTransport::new();
let error_msg = ProtocolMessage::Error(ProtocolErrorMessage {
code: "test_error".to_string(),
message: "Test error message".to_string(),
details: None,
});
let json = error_msg.to_json().unwrap();
let test_json: serde_json::Value = serde_json::from_str(&json).unwrap();
mock.send_message(test_json).await.unwrap();
let sent = mock.sent_messages().await;
assert!(!sent.is_empty());
}
#[tokio::test]
async fn test_mock_transport_fail_after() {
let config = MockConfig {
fail_after_n_messages: Some(2),
..Default::default()
};
let mock = MockCliTransport::with_config(config);
let test_json = serde_json::json!({"type": "test"});
assert!(mock.send_message(test_json.clone()).await.is_ok());
assert!(mock.send_message(test_json.clone()).await.is_ok());
assert!(mock.send_message(test_json).await.is_err());
}
#[tokio::test]
async fn test_mock_transport_delay() {
use std::time::Instant;
let config = MockConfig {
response_delay: Some(std::time::Duration::from_millis(50)),
..Default::default()
};
let mock = MockCliTransport::with_config(config);
let start = Instant::now();
let test_json = serde_json::json!({"type": "test"});
mock.send_message(test_json).await.unwrap();
let elapsed = start.elapsed();
assert!(elapsed >= std::time::Duration::from_millis(50));
}
#[tokio::test]
async fn test_mock_transport_is_alive() {
let mock = MockCliTransport::new();
assert!(mock.is_alive().await);
}
#[tokio::test]
async fn test_mock_transport_kill() {
let mock = MockCliTransport::new();
assert!(mock.kill().await.is_ok());
assert!(mock.is_alive().await);
}
}