95 lines
3.3 KiB
Rust
95 lines
3.3 KiB
Rust
use mcp_server::semantic::store::SemanticStore;
|
|
use mcp_server::semantic::SemanticConfig;
|
|
|
|
#[tokio::test]
|
|
async fn test_hybrid_search_combines_keyword_and_vector() {
|
|
let config = SemanticConfig {
|
|
base_dir: "./tests/data/test_hybrid_data".to_string(),
|
|
dimension: 768,
|
|
model_name: "bge-base-en-v1.5".to_string(),
|
|
};
|
|
|
|
let store = SemanticStore::new(&config).await.unwrap();
|
|
|
|
let embedding1 = vec![1.0_f32; 768];
|
|
let embedding2 = vec![0.0_f32; 768];
|
|
let embedding3 = {
|
|
let mut v = vec![0.0_f32; 768];
|
|
v[767] = 1.0;
|
|
v
|
|
};
|
|
|
|
store.add_fact("test_namespace", "Rust programming language", &embedding1, None).await.unwrap();
|
|
store.add_fact("test_namespace", "Python programming language", &embedding2, None).await.unwrap();
|
|
store.add_fact("test_namespace", "JavaScript programming language", &embedding3, None).await.unwrap();
|
|
store.add_fact("other_namespace", "Rust programming is great", &embedding1, None).await.unwrap();
|
|
|
|
// Query similar to embedding1 (all 1s)
|
|
let query_embedding = vec![1.0_f32; 768];
|
|
let results = store.hybrid_search("Rust", &query_embedding, 2).await.unwrap();
|
|
|
|
assert_eq!(results.len(), 2);
|
|
assert!(results[0].content.contains("Rust"));
|
|
assert!(results[1].content.contains("Rust"));
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_hybrid_search_with_no_keyword_matches() {
|
|
let config = SemanticConfig {
|
|
base_dir: "./tests/data/test_hybrid_no_keyword".to_string(),
|
|
dimension: 3,
|
|
model_name: "test".to_string(),
|
|
};
|
|
|
|
let store = SemanticStore::new(&config).await.unwrap();
|
|
|
|
let embedding = vec![1.0_f32, 0.0, 0.0];
|
|
store.add_fact("test", "Content without keyword", &embedding, None).await.unwrap();
|
|
|
|
// Keyword has no matches — falls back to vector search, so results are non-empty
|
|
let query_embedding = vec![1.0_f32, 0.0, 0.0];
|
|
let results = store.hybrid_search("Nonexistent", &query_embedding, 1).await.unwrap();
|
|
|
|
assert!(!results.is_empty(), "Should fall back to vector search when keyword matches nothing");
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_hybrid_search_with_no_vector_matches() {
|
|
let config = SemanticConfig {
|
|
base_dir: "./tests/data/test_hybrid_no_vector".to_string(),
|
|
dimension: 3,
|
|
model_name: "test".to_string(),
|
|
};
|
|
|
|
let store = SemanticStore::new(&config).await.unwrap();
|
|
|
|
let embedding = vec![1.0_f32, 0.0, 0.0];
|
|
store.add_fact("test", "Rust programming", &embedding, None).await.unwrap();
|
|
|
|
// Orthogonal query vector — keyword still matches
|
|
let query_embedding = vec![0.0_f32, 0.0, 1.0];
|
|
let results = store.hybrid_search("Rust", &query_embedding, 1).await.unwrap();
|
|
|
|
assert_eq!(results.len(), 1);
|
|
assert!(results[0].content.contains("Rust"));
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_logging_in_unauthenticated_mode() {
|
|
use mcp_server::logging::FileLogger;
|
|
use std::fs;
|
|
|
|
let log_path = "./test_unauth_log.txt";
|
|
let _ = fs::remove_file(log_path);
|
|
|
|
let logger = FileLogger::new(log_path.to_string());
|
|
logger.log("GET", "/health", "200");
|
|
|
|
assert!(fs::metadata(log_path).is_ok());
|
|
let log_content = fs::read_to_string(log_path).unwrap();
|
|
assert!(log_content.contains("GET /health"));
|
|
assert!(log_content.contains("200"));
|
|
|
|
fs::remove_file(log_path).ok();
|
|
}
|