feat: add MockLlmProvider for integration testing

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
master
oabrivard 3 months ago
parent ecf95ffe35
commit 17e054c257

@ -0,0 +1,134 @@
//! Mock LLM provider for integration testing.
use std::sync::Arc;
use async_trait::async_trait;
use serde_json::{json, Value};
use crate::errors::AppError;
use super::LlmProvider;
/// A mock LLM provider that returns deterministic responses.
pub struct MockLlmProvider {
default_category: String,
search_urls: Vec<String>,
link_urls: Vec<String>,
}
impl MockLlmProvider {
pub fn new() -> Self {
Self {
default_category: "Autre".to_string(),
search_urls: Vec::new(),
link_urls: Vec::new(),
}
}
pub fn with_default_category(mut self, category: &str) -> Self {
self.default_category = category.to_string();
self
}
pub fn with_search_urls(mut self, urls: Vec<String>) -> Self {
self.search_urls = urls;
self
}
pub fn with_link_urls(mut self, urls: Vec<String>) -> Self {
self.link_urls = urls;
self
}
pub fn into_arc(self) -> Arc<dyn LlmProvider> {
Arc::new(self)
}
}
#[async_trait]
impl LlmProvider for MockLlmProvider {
fn provider_id(&self) -> &str {
"mock"
}
async fn call_llm(
&self,
_model: &str,
system_prompt: &str,
user_prompt: &str,
_response_schema: &Value,
) -> Result<Value, AppError> {
let sys_lower = system_prompt.to_lowercase();
// Classify/summarize call
if sys_lower.contains("classer") {
let title = user_prompt
.lines()
.find(|l| l.starts_with("Titre : "))
.map(|l| l.trim_start_matches("Titre : ").to_string())
.unwrap_or_else(|| "Mock Article".to_string());
return Ok(json!({
"title": title,
"summary": format!("Mock summary for: {}", title),
"category": self.default_category,
}));
}
// Link extraction call
if sys_lower.contains("liens") {
return Ok(json!({ "urls": self.link_urls }));
}
// Search call
if sys_lower.contains("precis") {
let items: Vec<Value> = self.search_urls.iter().map(|url| {
json!({
"title": format!("Search result: {}", url),
"url": url,
"summary": format!("Mock search summary for {}", url),
})
}).collect();
return Ok(json!({ "category_0": items }));
}
Ok(json!({"result": "mock response"}))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn mock_provider_returns_classify_response() {
let provider = MockLlmProvider::new().with_default_category("AI News");
let result = provider
.call_llm("model", "Tu dois classer l'article", "Titre : GPT-7\n\nContenu...", &json!({}))
.await
.unwrap();
assert_eq!(result["title"], "GPT-7");
assert_eq!(result["category"], "AI News");
}
#[tokio::test]
async fn mock_provider_returns_search_response() {
let provider = MockLlmProvider::new()
.with_search_urls(vec!["http://example.com/a".into()]);
let result = provider
.call_llm("model", "Tu es un assistant IA precis", "Recherche...", &json!({}))
.await
.unwrap();
let items = result["category_0"].as_array().unwrap();
assert_eq!(items.len(), 1);
}
#[tokio::test]
async fn mock_provider_returns_link_extraction() {
let provider = MockLlmProvider::new()
.with_link_urls(vec!["http://example.com/post-1".into()]);
let result = provider
.call_llm("model", "Tu dois identifier les liens", "Links...", &json!({}))
.await
.unwrap();
let urls = result["urls"].as_array().unwrap();
assert_eq!(urls.len(), 1);
}
}

@ -6,6 +6,7 @@
pub mod anthropic;
pub mod factory;
pub mod gemini;
pub mod mock;
pub mod openai;
pub mod schema;

Loading…
Cancel
Save