feat: add MockLlmProvider for integration testing
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>master
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue