use super::*;
use crate::core::models::openai::messages::{ChatMessage, MessageContent, MessageRole};
use crate::core::models::openai::{ChatChoice, Usage};
use std::sync::Arc;
fn create_user_message(content: &str) -> ChatMessage {
ChatMessage {
role: MessageRole::User,
content: Some(MessageContent::Text(content.to_string())),
name: None,
function_call: None,
tool_calls: None,
tool_call_id: None,
audio: None,
}
}
fn create_assistant_message(content: &str) -> ChatMessage {
ChatMessage {
role: MessageRole::Assistant,
content: Some(MessageContent::Text(content.to_string())),
name: None,
function_call: None,
tool_calls: None,
tool_call_id: None,
audio: None,
}
}
fn create_test_request() -> ChatCompletionRequest {
ChatCompletionRequest {
model: "gpt-4".to_string(),
messages: vec![create_user_message("Hello")],
..Default::default()
}
}
fn create_test_response() -> ChatCompletionResponse {
ChatCompletionResponse {
id: "chatcmpl-123".to_string(),
object: "chat.completion".to_string(),
created: 1234567890,
model: "gpt-4".to_string(),
choices: vec![ChatChoice {
index: 0,
message: create_assistant_message("Hello! How can I help you?"),
finish_reason: Some("stop".to_string()),
logprobs: None,
}],
usage: Some(Usage {
prompt_tokens: 10,
completion_tokens: 8,
total_tokens: 18,
prompt_tokens_details: None,
completion_tokens_details: None,
thinking_usage: None,
}),
system_fingerprint: None,
}
}
#[tokio::test]
async fn test_llm_cache_creation() {
let cache = LLMCache::memory_only();
assert!(!cache.is_redis_available().await);
}
#[tokio::test]
async fn test_llm_cache_chat_miss() {
let cache = LLMCache::memory_only();
let request = create_test_request();
let result = cache.get_chat_response(&request).await.unwrap();
assert!(result.is_none());
}
#[tokio::test]
async fn test_llm_cache_chat_hit() {
let cache = LLMCache::memory_only();
let request = create_test_request();
let response = create_test_response();
cache
.cache_chat_response(&request, response.clone())
.await
.unwrap();
let result = cache.get_chat_response(&request).await.unwrap();
assert!(result.is_some());
assert_eq!(result.as_ref().unwrap().id.as_str(), response.id.as_str());
}
#[tokio::test]
async fn test_llm_cache_chat_hit_reuses_shared_payload() {
let cache = LLMCache::memory_only();
let request = create_test_request();
let response = create_test_response();
cache.cache_chat_response(&request, response).await.unwrap();
let first = cache
.get_chat_response(&request)
.await
.unwrap()
.expect("first cache hit");
let second = cache
.get_chat_response(&request)
.await
.unwrap()
.expect("second cache hit");
assert!(Arc::ptr_eq(&first, &second));
}
#[tokio::test]
async fn test_llm_cache_chat_invalidate() {
let cache = LLMCache::memory_only();
let request = create_test_request();
let response = create_test_response();
cache.cache_chat_response(&request, response).await.unwrap();
let invalidated = cache.invalidate_chat(&request).await.unwrap();
assert!(invalidated);
let result = cache.get_chat_response(&request).await.unwrap();
assert!(result.is_none());
}
#[tokio::test]
async fn test_llm_cache_streaming_not_cached() {
let cache = LLMCache::memory_only();
let mut request = create_test_request();
request.stream = Some(true);
let response = create_test_response();
cache.cache_chat_response(&request, response).await.unwrap();
let result = cache.get_chat_response(&request).await.unwrap();
assert!(result.is_none());
}
#[tokio::test]
async fn test_llm_cache_user_specific() {
let config = LLMCacheConfig::memory_only().with_user_specific();
let cache = LLMCache::new(config, None);
let request = create_test_request();
let response = create_test_response();
cache
.cache_chat_response_with_user(&request, response.clone(), Some("user1"))
.await
.unwrap();
let result = cache
.get_chat_response_with_user(&request, Some("user1"))
.await
.unwrap();
assert!(result.is_some());
let result = cache
.get_chat_response_with_user(&request, Some("user2"))
.await
.unwrap();
assert!(result.is_none());
}
#[tokio::test]
async fn test_llm_cache_embedding() {
let cache = LLMCache::memory_only();
let request = EmbeddingRequest {
model: "text-embedding-ada-002".to_string(),
input: serde_json::json!("Test input"),
user: None,
};
let response = EmbeddingResponse {
object: "list".to_string(),
data: vec![],
model: "text-embedding-ada-002".to_string(),
usage: crate::core::models::openai::EmbeddingUsage {
prompt_tokens: 3,
total_tokens: 3,
},
};
cache
.cache_embedding_response(&request, response.clone())
.await
.unwrap();
let result = cache.get_embedding_response(&request).await.unwrap();
assert!(result.is_some());
assert_eq!(
result.as_ref().unwrap().model.as_str(),
response.model.as_str()
);
}
#[tokio::test]
async fn test_llm_cache_embedding_hit_reuses_shared_payload() {
let cache = LLMCache::memory_only();
let request = EmbeddingRequest {
model: "text-embedding-ada-002".to_string(),
input: serde_json::json!("Test input"),
user: None,
};
let response = EmbeddingResponse {
object: "list".to_string(),
data: vec![],
model: "text-embedding-ada-002".to_string(),
usage: crate::core::models::openai::EmbeddingUsage {
prompt_tokens: 3,
total_tokens: 3,
},
};
cache
.cache_embedding_response(&request, response)
.await
.unwrap();
let first = cache
.get_embedding_response(&request)
.await
.unwrap()
.expect("first cache hit");
let second = cache
.get_embedding_response(&request)
.await
.unwrap()
.expect("second cache hit");
assert!(Arc::ptr_eq(&first, &second));
}
#[tokio::test]
async fn test_llm_cache_stats() {
let cache = LLMCache::memory_only();
let request = create_test_request();
let response = create_test_response();
let _ = cache.get_chat_response(&request).await; cache.cache_chat_response(&request, response).await.unwrap(); let _ = cache.get_chat_response(&request).await;
let stats = cache.chat_stats();
assert_eq!(stats.memory_hits, 1);
assert_eq!(stats.memory_misses, 1);
}
#[tokio::test]
async fn test_llm_cache_combined_stats() {
let cache = LLMCache::memory_only();
let combined = cache.combined_stats();
assert_eq!(combined.total_hits(), 0);
assert_eq!(combined.hit_rate(), 0.0);
}
#[tokio::test]
async fn test_llm_cache_clear() {
let cache = LLMCache::memory_only();
let request = create_test_request();
let response = create_test_response();
cache.cache_chat_response(&request, response).await.unwrap();
cache.clear().await.unwrap();
let result = cache.get_chat_response(&request).await.unwrap();
assert!(result.is_none());
}
#[test]
fn test_cached_chat_response() {
let response = create_test_response();
let cached = CachedChatResponse::new(response.clone(), "gpt-4".to_string());
assert!(cached.cached);
assert_eq!(cached.model, "gpt-4");
assert!(cached.cached_at > 0);
let shared = cached.response_arc();
assert!(Arc::ptr_eq(&shared, &cached.response));
assert_eq!(shared.id.as_str(), response.id.as_str());
}
#[test]
fn test_llm_cache_config_default() {
let config = LLMCacheConfig::default();
assert_eq!(config.chat_ttl, Duration::from_secs(3600));
assert_eq!(config.embedding_ttl, Duration::from_secs(86400));
assert!(!config.user_specific);
}
#[test]
fn test_llm_cache_config_builder() {
let config = LLMCacheConfig::default()
.with_chat_ttl(Duration::from_secs(1800))
.with_embedding_ttl(Duration::from_secs(7200))
.with_user_specific();
assert_eq!(config.chat_ttl, Duration::from_secs(1800));
assert_eq!(config.embedding_ttl, Duration::from_secs(7200));
assert!(config.user_specific);
}
#[tokio::test]
async fn test_invalidate_chat_with_user_honors_user_specific_key() {
let config = LLMCacheConfig::memory_only().with_user_specific();
let cache = LLMCache::new(config, None);
let request = create_test_request();
let response = create_test_response();
cache
.cache_chat_response_with_user(&request, response, Some("user1"))
.await
.unwrap();
assert!(
cache
.get_chat_response_with_user(&request, Some("user1"))
.await
.unwrap()
.is_some()
);
let invalidated = cache
.invalidate_chat_with_user(&request, Some("user1"))
.await
.unwrap();
assert!(invalidated);
assert!(
cache
.get_chat_response_with_user(&request, Some("user1"))
.await
.unwrap()
.is_none()
);
}