use anyhow::Result;
use chrono::Utc;
use mockall::mock;
use mockall::predicate::*;
mock! {
pub ProviderExecutor {
fn execute_prompt(
&self,
prompt: &str,
identity_role: &str,
working_dir: &std::path::Path,
) -> Result<String>;
fn health_check(&self) -> Result<bool>;
fn get_capabilities(&self) -> ProviderCapabilities;
}
}
mock! {
pub TaskHandler {
fn handle(&self, task_id: &str, prompt: &str) -> Result<TaskResult>;
fn can_handle(&self, task_type: &str) -> bool;
}
}
mock! {
pub TemplateStorage {
fn save(&self, name: &str, content: &str) -> Result<()>;
fn load(&self, name: &str) -> Result<Option<String>>;
fn delete(&self, name: &str) -> Result<bool>;
fn list(&self) -> Result<Vec<String>>;
}
}
mock! {
pub SessionLifecycle {
fn initialize(&mut self) -> Result<()>;
fn shutdown(&mut self) -> Result<()>;
fn get_status(&self) -> SessionStatus;
fn is_ready(&self) -> bool;
}
}
#[derive(Debug, Clone)]
pub struct ProviderCapabilities {
pub supports_json_output: bool,
pub supports_streaming: bool,
pub max_context_tokens: usize,
}
impl Default for ProviderCapabilities {
fn default() -> Self {
Self {
supports_json_output: true,
supports_streaming: false,
max_context_tokens: 4096,
}
}
}
#[derive(Debug, Clone)]
pub struct TaskResult {
pub task_id: String,
pub success: bool,
pub output: String,
pub duration_ms: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SessionStatus {
Active,
Idle,
Busy,
Terminated,
}
mod provider_executor_tests {
use super::*;
#[test]
fn test_execute_prompt_success() {
let mut mock = MockProviderExecutor::new();
mock.expect_execute_prompt()
.times(1) .withf(|prompt, _, _| prompt.contains("test")) .returning(|_, _, _| Ok("Success response".to_string()));
let result = mock.execute_prompt("test prompt", "assistant", std::path::Path::new("/tmp"));
assert!(result.is_ok());
assert_eq!(result.unwrap(), "Success response");
}
#[test]
fn test_execute_prompt_error() {
let mut mock = MockProviderExecutor::new();
mock.expect_execute_prompt()
.times(1)
.returning(|_, _, _| Err(anyhow::anyhow!("Provider unavailable")));
let result = mock.execute_prompt("any prompt", "assistant", std::path::Path::new("/tmp"));
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("unavailable"));
}
#[test]
fn test_multiple_prompt_executions() {
let mut mock = MockProviderExecutor::new();
let call_count = std::sync::atomic::AtomicUsize::new(0);
mock.expect_execute_prompt()
.times(3)
.returning(move |prompt, _, _| {
let count = call_count.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
Ok(format!("Response {} for: {}", count, prompt))
});
let r1 = mock.execute_prompt("first", "a", std::path::Path::new("/"));
let r2 = mock.execute_prompt("second", "a", std::path::Path::new("/"));
let r3 = mock.execute_prompt("third", "a", std::path::Path::new("/"));
assert_eq!(r1.unwrap(), "Response 0 for: first");
assert_eq!(r2.unwrap(), "Response 1 for: second");
assert_eq!(r3.unwrap(), "Response 2 for: third");
}
#[test]
fn test_health_check_and_capabilities() {
let mut mock = MockProviderExecutor::new();
mock.expect_health_check().times(1).returning(|| Ok(true));
mock.expect_get_capabilities()
.times(1)
.returning(|| ProviderCapabilities {
supports_json_output: true,
supports_streaming: true,
max_context_tokens: 8192,
});
assert!(mock.health_check().unwrap());
let caps = mock.get_capabilities();
assert!(caps.supports_streaming);
assert_eq!(caps.max_context_tokens, 8192);
}
}
mod task_handler_tests {
use super::*;
#[test]
fn test_task_handler_with_argument_matching() {
let mut mock = MockTaskHandler::new();
mock.expect_handle()
.withf(|task_id, _| task_id.starts_with("task-"))
.times(2)
.returning(|id, prompt| {
Ok(TaskResult {
task_id: id.to_string(),
success: true,
output: format!("Processed: {}", prompt),
duration_ms: 100,
})
});
let r1 = mock.handle("task-001", "Do something");
let r2 = mock.handle("task-002", "Do something else");
assert!(r1.unwrap().success);
assert!(r2.unwrap().success);
}
#[test]
fn test_can_handle_task_types() {
let mut mock = MockTaskHandler::new();
mock.expect_can_handle()
.withf(|task_type| task_type == "code_review")
.returning(|_| true);
mock.expect_can_handle()
.withf(|task_type| task_type == "deployment")
.returning(|_| false);
assert!(mock.can_handle("code_review"));
assert!(!mock.can_handle("deployment"));
}
}
mod template_storage_tests {
use super::*;
#[test]
fn test_template_crud_operations() {
let mut mock = MockTemplateStorage::new();
mock.expect_save()
.with(eq("my_template"), eq("template content"))
.times(1)
.returning(|_, _| Ok(()));
mock.expect_load()
.with(eq("my_template"))
.times(1)
.returning(|_| Ok(Some("template content".to_string())));
mock.expect_delete()
.with(eq("my_template"))
.times(1)
.returning(|_| Ok(true));
assert!(mock.save("my_template", "template content").is_ok());
assert_eq!(
mock.load("my_template").unwrap(),
Some("template content".to_string())
);
assert!(mock.delete("my_template").unwrap());
}
#[test]
fn test_template_list() {
let mut mock = MockTemplateStorage::new();
mock.expect_list().times(1).returning(|| Ok(vec![]));
let result = mock.list().unwrap();
assert!(result.is_empty());
}
#[test]
fn test_load_nonexistent_template() {
let mut mock = MockTemplateStorage::new();
mock.expect_load()
.with(eq("nonexistent"))
.times(1)
.returning(|_| Ok(None));
let result = mock.load("nonexistent").unwrap();
assert!(result.is_none());
}
}
mod session_lifecycle_tests {
use super::*;
#[test]
fn test_session_initialization() {
let mut mock = MockSessionLifecycle::new();
mock.expect_initialize().times(1).returning(|| Ok(()));
mock.expect_is_ready().times(1).returning(|| true);
mock.expect_get_status()
.times(1)
.returning(|| SessionStatus::Active);
assert!(mock.initialize().is_ok());
assert!(mock.is_ready());
assert_eq!(mock.get_status(), SessionStatus::Active);
}
#[test]
fn test_session_shutdown() {
let mut mock = MockSessionLifecycle::new();
mock.expect_shutdown().times(1).returning(|| Ok(()));
mock.expect_get_status()
.times(1)
.returning(|| SessionStatus::Terminated);
assert!(mock.shutdown().is_ok());
assert_eq!(mock.get_status(), SessionStatus::Terminated);
}
#[test]
fn test_session_init_failure() {
let mut mock = MockSessionLifecycle::new();
mock.expect_initialize()
.times(1)
.returning(|| Err(anyhow::anyhow!("Failed to connect to backend")));
mock.expect_is_ready().times(1).returning(|| false);
let result = mock.initialize();
assert!(result.is_err());
assert!(!mock.is_ready());
}
}
mod integration_pattern_tests {
use super::*;
#[test]
fn test_task_execution_workflow() {
let mut session_mock = MockSessionLifecycle::new();
let mut task_mock = MockTaskHandler::new();
let mut storage_mock = MockTemplateStorage::new();
session_mock
.expect_initialize()
.times(1)
.returning(|| Ok(()));
session_mock.expect_is_ready().times(1).returning(|| true);
storage_mock
.expect_load()
.with(eq("default_task"))
.times(1)
.returning(|_| Ok(Some("Task template: {prompt}".to_string())));
task_mock.expect_handle().times(1).returning(|id, _| {
Ok(TaskResult {
task_id: id.to_string(),
success: true,
output: "Task completed".to_string(),
duration_ms: 50,
})
});
session_mock.initialize().unwrap();
assert!(session_mock.is_ready());
let template = storage_mock.load("default_task").unwrap().unwrap();
assert!(template.contains("Task template"));
let result = task_mock.handle("task-123", "Execute test").unwrap();
assert!(result.success);
}
#[test]
fn test_no_shutdown_on_healthy_session() {
let mut mock = MockSessionLifecycle::new();
mock.expect_shutdown().times(0);
mock.expect_is_ready().times(1).returning(|| true);
if !mock.is_ready() {
let _ = mock.shutdown();
}
}
#[test]
fn test_specific_template_operations() {
let mut mock = MockTemplateStorage::new();
mock.expect_save()
.with(
eq("production_config"),
function(|content: &str| content.len() > 10),
)
.times(1)
.returning(|_, _| Ok(()));
mock.save("production_config", "This is a long configuration content")
.unwrap();
}
}
mod advanced_patterns {
use super::*;
#[test]
fn test_ordered_operations() {
let mut mock = MockTemplateStorage::new();
let mut seq = mockall::Sequence::new();
mock.expect_save()
.times(1)
.in_sequence(&mut seq)
.returning(|_, _| Ok(()));
mock.expect_load()
.times(1)
.in_sequence(&mut seq)
.returning(|_| Ok(Some("content".to_string())));
mock.expect_delete()
.times(1)
.in_sequence(&mut seq)
.returning(|_| Ok(true));
mock.save("test", "content").unwrap();
mock.load("test").unwrap();
mock.delete("test").unwrap();
}
#[test]
fn test_returning_complex_types() {
let mut mock = MockProviderExecutor::new();
mock.expect_execute_prompt()
.times(1)
.returning(|prompt, _, _| {
let response = format!(
"{{\"status\": \"ok\", \"input\": \"{}\", \"timestamp\": {}}}",
prompt,
Utc::now().timestamp()
);
Ok(response)
});
let result = mock
.execute_prompt("test", "role", std::path::Path::new("/"))
.unwrap();
assert!(result.contains("status"));
assert!(result.contains("ok"));
}
#[test]
fn test_retry_behavior() {
let mut mock = MockProviderExecutor::new();
let attempt = std::sync::atomic::AtomicUsize::new(0);
mock.expect_execute_prompt()
.times(3)
.returning(move |_, _, _| {
let current = attempt.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
if current < 2 {
Err(anyhow::anyhow!("Temporary failure"))
} else {
Ok("Success after retry".to_string())
}
});
let mut result = Err(anyhow::anyhow!("initial"));
for _ in 0..3 {
result = mock.execute_prompt("test", "role", std::path::Path::new("/"));
if result.is_ok() {
break;
}
}
assert!(result.is_ok());
assert_eq!(result.unwrap(), "Success after retry");
}
}