use super::*;
use crate::client::RagClient;
use tempfile::TempDir;
use tokio_util::sync::CancellationToken;
#[tokio::test]
async fn test_new_creates_server() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("db").to_string_lossy().to_string();
let cache_path = temp_dir.path().join("cache.json");
let client = RagClient::new_with_db_path(&db_path, cache_path).await;
assert!(client.is_ok(), "Client creation should succeed");
let client = client.unwrap();
assert_eq!(client.embedding_dimension(), 384);
let client = RagMcpServer::with_client(Arc::new(client));
assert!(client.is_ok(), "Server creation should succeed");
}
#[tokio::test]
async fn test_get_info() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("db").to_string_lossy().to_string();
let cache_path = temp_dir.path().join("cache.json");
let client = RagClient::new_with_db_path(&db_path, cache_path)
.await
.unwrap();
let client = RagMcpServer::with_client(Arc::new(client)).unwrap();
let info = client.get_info();
assert_eq!(info.server_info.name, "project");
assert!(info.server_info.title.is_some());
assert!(info.instructions.is_some());
assert!(info.capabilities.tools.is_some());
assert!(info.capabilities.prompts.is_some());
}
#[test]
fn test_normalize_path_valid() {
let temp_dir = TempDir::new().unwrap();
let path = temp_dir.path().to_string_lossy().to_string();
let normalized = RagClient::normalize_path(&path);
assert!(normalized.is_ok());
let normalized_path = normalized.unwrap();
assert!(!normalized_path.is_empty());
}
#[test]
fn test_normalize_path_nonexistent() {
let result = RagClient::normalize_path("/nonexistent/path/12345");
assert!(result.is_err());
}
#[test]
fn test_normalize_path_current_dir() {
let result = RagClient::normalize_path(".");
assert!(result.is_ok());
let normalized = result.unwrap();
assert!(!normalized.is_empty());
}
#[tokio::test]
async fn test_do_index_empty_directory() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("db").to_string_lossy().to_string();
let cache_path = temp_dir.path().join("cache.json");
let client = RagClient::new_with_db_path(&db_path, cache_path)
.await
.unwrap();
let data_dir = temp_dir.path().join("data");
std::fs::create_dir(&data_dir).unwrap();
let result = crate::client::indexing::do_index(
&client,
data_dir.to_string_lossy().to_string(),
None,
vec![],
vec![],
1024 * 1024,
None,
None,
CancellationToken::new(),
)
.await;
assert!(result.is_ok());
let response = result.unwrap();
assert_eq!(response.mode, IndexingMode::Full);
assert_eq!(response.files_indexed, 0);
assert!(!response.errors.is_empty());
}
#[tokio::test]
async fn test_do_index_with_files() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("db").to_string_lossy().to_string();
let cache_path = temp_dir.path().join("cache.json");
let client = RagClient::new_with_db_path(&db_path, cache_path)
.await
.unwrap();
let data_dir = temp_dir.path().join("data");
std::fs::create_dir(&data_dir).unwrap();
let test_file = data_dir.join("test.rs");
std::fs::write(&test_file, "fn main() { println!(\"test\"); }").unwrap();
let result = crate::client::indexing::do_index(
&client,
data_dir.to_string_lossy().to_string(),
Some("test-project".to_string()),
vec![],
vec![],
1024 * 1024,
None,
None,
CancellationToken::new(),
)
.await;
assert!(result.is_ok());
let response = result.unwrap();
assert_eq!(response.mode, IndexingMode::Full);
assert_eq!(response.files_indexed, 1);
assert!(response.chunks_created > 0);
assert!(response.embeddings_generated > 0);
}
#[tokio::test]
async fn test_do_index_with_exclude_patterns() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("db").to_string_lossy().to_string();
let cache_path = temp_dir.path().join("cache.json");
let client = RagClient::new_with_db_path(&db_path, cache_path)
.await
.unwrap();
let data_dir = temp_dir.path().join("data");
std::fs::create_dir(&data_dir).unwrap();
std::fs::write(data_dir.join("include.rs"), "fn test() {}").unwrap();
std::fs::write(data_dir.join("exclude.txt"), "exclude this").unwrap();
let result = crate::client::indexing::do_index(
&client,
data_dir.to_string_lossy().to_string(),
None,
vec![],
vec!["**/*.txt".to_string()],
1024 * 1024,
None,
None,
CancellationToken::new(),
)
.await;
assert!(result.is_ok());
let response = result.unwrap();
assert!(response.files_indexed >= 1);
}
#[tokio::test]
async fn test_do_incremental_update_no_cache() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("db").to_string_lossy().to_string();
let cache_path = temp_dir.path().join("cache.json");
let client = RagClient::new_with_db_path(&db_path, cache_path)
.await
.unwrap();
let data_dir = temp_dir.path().join("data");
std::fs::create_dir(&data_dir).unwrap();
std::fs::write(data_dir.join("test.rs"), "fn main() {}").unwrap();
let result = crate::client::indexing::do_incremental_update(
&client,
data_dir.to_string_lossy().to_string(),
None,
vec![],
vec![],
1024 * 1024,
None,
None,
CancellationToken::new(),
)
.await;
assert!(result.is_ok());
let response = result.unwrap();
assert_eq!(response.mode, IndexingMode::Incremental);
}
#[tokio::test]
async fn test_do_index_smart_new_codebase() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("db").to_string_lossy().to_string();
let cache_path = temp_dir.path().join("cache.json");
let client = RagClient::new_with_db_path(&db_path, cache_path)
.await
.unwrap();
let data_dir = temp_dir.path().join("data");
std::fs::create_dir(&data_dir).unwrap();
std::fs::write(data_dir.join("test.rs"), "fn main() {}").unwrap();
let result = crate::client::indexing::do_index_smart(
&client,
data_dir.to_string_lossy().to_string(),
None,
vec![],
vec![],
1024 * 1024,
None,
None,
CancellationToken::new(),
)
.await;
assert!(result.is_ok());
let response = result.unwrap();
assert_eq!(response.mode, IndexingMode::Full);
}
#[tokio::test]
async fn test_server_cloneable() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("db").to_string_lossy().to_string();
let cache_path = temp_dir.path().join("cache.json");
let client = RagClient::new_with_db_path(&db_path, cache_path)
.await
.unwrap();
let _cloned = client.clone();
}
#[tokio::test]
async fn test_tool_query_codebase_with_empty_index() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("db").to_string_lossy().to_string();
let cache_path = temp_dir.path().join("cache.json");
let client = RagClient::new_with_db_path(&db_path, cache_path)
.await
.unwrap();
let server = RagMcpServer::with_client(Arc::new(client)).unwrap();
let req = QueryRequest {
query: "test query".to_string(),
path: None,
project: None,
limit: 10,
min_score: 0.7,
hybrid: true,
};
let result = server.client().query_codebase(req).await;
assert!(result.is_ok());
let response = result.unwrap();
assert_eq!(response.results.len(), 0);
}
#[tokio::test]
async fn test_tool_query_codebase_validation_failure() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("db").to_string_lossy().to_string();
let cache_path = temp_dir.path().join("cache.json");
let client = RagClient::new_with_db_path(&db_path, cache_path)
.await
.unwrap();
let _server = RagMcpServer::with_client(Arc::new(client)).unwrap();
let req = QueryRequest {
query: " ".to_string(), path: None,
project: None,
limit: 10,
min_score: 0.7,
hybrid: true,
};
let result = req.validate();
assert!(result.is_err());
assert!(result.unwrap_err().contains("cannot be empty"));
}
#[tokio::test]
async fn test_tool_get_statistics_empty_index() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("db").to_string_lossy().to_string();
let cache_path = temp_dir.path().join("cache.json");
let client = RagClient::new_with_db_path(&db_path, cache_path)
.await
.unwrap();
let server = RagMcpServer::with_client(Arc::new(client)).unwrap();
let result = server.client().get_statistics().await;
assert!(result.is_ok());
let response = result.unwrap();
assert_eq!(response.total_files, 0);
assert_eq!(response.total_chunks, 0);
assert_eq!(response.total_embeddings, 0);
}
#[tokio::test]
async fn test_tool_get_statistics_with_data() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("db").to_string_lossy().to_string();
let cache_path = temp_dir.path().join("cache.json");
let client = RagClient::new_with_db_path(&db_path, cache_path)
.await
.unwrap();
let data_dir = temp_dir.path().join("data");
std::fs::create_dir(&data_dir).unwrap();
std::fs::write(data_dir.join("test.rs"), "fn main() {}").unwrap();
let _index_result = crate::client::indexing::do_index(
&client,
data_dir.to_string_lossy().to_string(),
None,
vec![],
vec![],
1024 * 1024,
None,
None,
CancellationToken::new(),
)
.await
.unwrap();
let server = RagMcpServer::with_client(Arc::new(client)).unwrap();
let result = server.client().get_statistics().await;
assert!(result.is_ok());
let response = result.unwrap();
assert!(response.total_files > 0);
assert!(response.total_chunks > 0);
assert!(response.total_embeddings > 0);
}
#[tokio::test]
async fn test_tool_clear_index() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("db").to_string_lossy().to_string();
let cache_path = temp_dir.path().join("cache.json");
let client = RagClient::new_with_db_path(&db_path, cache_path)
.await
.unwrap();
let data_dir = temp_dir.path().join("data");
std::fs::create_dir(&data_dir).unwrap();
std::fs::write(data_dir.join("test.rs"), "fn main() {}").unwrap();
let _index_result = crate::client::indexing::do_index(
&client,
data_dir.to_string_lossy().to_string(),
None,
vec![],
vec![],
1024 * 1024,
None,
None,
CancellationToken::new(),
)
.await
.unwrap();
let server = RagMcpServer::with_client(Arc::new(client)).unwrap();
let result = server.client().clear_index().await;
assert!(result.is_ok());
let response = result.unwrap();
assert!(response.success);
let stats = server.client().get_statistics().await.unwrap();
assert_eq!(stats.total_files, 0);
assert_eq!(stats.total_chunks, 0);
}
#[tokio::test]
async fn test_tool_search_by_filters_validation_failure() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("db").to_string_lossy().to_string();
let cache_path = temp_dir.path().join("cache.json");
let client = RagClient::new_with_db_path(&db_path, cache_path)
.await
.unwrap();
let _server = RagMcpServer::with_client(Arc::new(client)).unwrap();
let req = AdvancedSearchRequest {
query: "test".to_string(),
path: None,
project: None,
limit: 10,
min_score: 0.7,
file_extensions: vec!["".to_string()],
languages: vec![],
path_patterns: vec![],
};
let result = req.validate();
assert!(result.is_err());
assert!(
result
.unwrap_err()
.contains("file extension cannot be empty")
);
}
#[tokio::test]
async fn test_tool_search_by_filters_valid_request() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("db").to_string_lossy().to_string();
let cache_path = temp_dir.path().join("cache.json");
let client = RagClient::new_with_db_path(&db_path, cache_path)
.await
.unwrap();
let server = RagMcpServer::with_client(Arc::new(client)).unwrap();
let req = AdvancedSearchRequest {
query: "test".to_string(),
path: None,
project: None,
limit: 10,
min_score: 0.7,
file_extensions: vec!["rs".to_string()],
languages: vec!["Rust".to_string()],
path_patterns: vec!["src/**".to_string()],
};
let result = server.client().search_with_filters(req).await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_tool_search_git_history_validation_failure() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("db").to_string_lossy().to_string();
let cache_path = temp_dir.path().join("cache.json");
let client = RagClient::new_with_db_path(&db_path, cache_path)
.await
.unwrap();
let _server = RagMcpServer::with_client(Arc::new(client)).unwrap();
let req = SearchGitHistoryRequest {
query: " ".to_string(),
path: ".".to_string(),
project: None,
branch: None,
max_commits: 10,
limit: 10,
min_score: 0.7,
author: None,
since: None,
until: None,
file_pattern: None,
};
let result = req.validate();
assert!(result.is_err());
assert!(result.unwrap_err().contains("cannot be empty"));
}
#[tokio::test]
async fn test_tool_search_git_history_nonexistent_path() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("db").to_string_lossy().to_string();
let cache_path = temp_dir.path().join("cache.json");
let client = RagClient::new_with_db_path(&db_path, cache_path)
.await
.unwrap();
let _server = RagMcpServer::with_client(Arc::new(client)).unwrap();
let req = SearchGitHistoryRequest {
query: "test".to_string(),
path: "/nonexistent/path".to_string(),
project: None,
branch: None,
max_commits: 10,
limit: 10,
min_score: 0.7,
author: None,
since: None,
until: None,
file_pattern: None,
};
let result = req.validate();
assert!(result.is_err());
assert!(result.unwrap_err().contains("does not exist"));
}
#[tokio::test]
async fn test_prompt_index_with_path() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("db").to_string_lossy().to_string();
let cache_path = temp_dir.path().join("cache.json");
let client = RagClient::new_with_db_path(&db_path, cache_path)
.await
.unwrap();
let server = RagMcpServer::with_client(Arc::new(client)).unwrap();
let args = serde_json::json!({
"path": "/test/path"
});
let result = server.index_prompt(Parameters(args)).await;
assert!(result.is_ok());
let prompt_result = result.unwrap();
assert!(prompt_result.description.is_some());
assert!(!prompt_result.messages.is_empty());
let debug_str = format!("{:?}", prompt_result.messages[0].content);
assert!(debug_str.contains("/test/path"));
}
#[tokio::test]
async fn test_prompt_index_default_path() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("db").to_string_lossy().to_string();
let cache_path = temp_dir.path().join("cache.json");
let client = RagClient::new_with_db_path(&db_path, cache_path)
.await
.unwrap();
let server = RagMcpServer::with_client(Arc::new(client)).unwrap();
let args = serde_json::json!({});
let result = server.index_prompt(Parameters(args)).await;
assert!(result.is_ok());
let prompt_result = result.unwrap();
assert!(prompt_result.description.is_some());
assert!(!prompt_result.messages.is_empty());
let debug_str = format!("{:?}", prompt_result.messages[0].content);
assert!(debug_str.contains("'.'"));
}
#[tokio::test]
async fn test_prompt_query_with_query() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("db").to_string_lossy().to_string();
let cache_path = temp_dir.path().join("cache.json");
let client = RagClient::new_with_db_path(&db_path, cache_path)
.await
.unwrap();
let server = RagMcpServer::with_client(Arc::new(client)).unwrap();
let args = serde_json::json!({
"query": "test query"
});
let result = server.query_prompt(Parameters(args)).await;
assert!(result.is_ok());
let messages = result.unwrap();
assert!(!messages.is_empty());
let debug_str = format!("{:?}", messages[0].content);
assert!(debug_str.contains("test query"));
}
#[tokio::test]
async fn test_prompt_query_default() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("db").to_string_lossy().to_string();
let cache_path = temp_dir.path().join("cache.json");
let client = RagClient::new_with_db_path(&db_path, cache_path)
.await
.unwrap();
let server = RagMcpServer::with_client(Arc::new(client)).unwrap();
let args = serde_json::json!({});
let result = server.query_prompt(Parameters(args)).await;
assert!(result.is_ok());
let messages = result.unwrap();
assert!(!messages.is_empty());
}
#[tokio::test]
async fn test_prompt_stats() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("db").to_string_lossy().to_string();
let cache_path = temp_dir.path().join("cache.json");
let client = RagClient::new_with_db_path(&db_path, cache_path)
.await
.unwrap();
let server = RagMcpServer::with_client(Arc::new(client)).unwrap();
let result = server.stats_prompt().await;
assert!(!result.is_empty());
let debug_str = format!("{:?}", result[0].content);
assert!(debug_str.contains("statistics"));
}
#[tokio::test]
async fn test_prompt_clear() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("db").to_string_lossy().to_string();
let cache_path = temp_dir.path().join("cache.json");
let client = RagClient::new_with_db_path(&db_path, cache_path)
.await
.unwrap();
let server = RagMcpServer::with_client(Arc::new(client)).unwrap();
let result = server.clear_prompt().await;
assert!(!result.is_empty());
let debug_str = format!("{:?}", result[0].content);
assert!(debug_str.contains("clear"));
}
#[tokio::test]
async fn test_prompt_search_with_query() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("db").to_string_lossy().to_string();
let cache_path = temp_dir.path().join("cache.json");
let client = RagClient::new_with_db_path(&db_path, cache_path)
.await
.unwrap();
let server = RagMcpServer::with_client(Arc::new(client)).unwrap();
let args = serde_json::json!({
"query": "advanced search"
});
let result = server.search_prompt(Parameters(args)).await;
assert!(result.is_ok());
let messages = result.unwrap();
assert!(!messages.is_empty());
let debug_str = format!("{:?}", messages[0].content);
assert!(debug_str.contains("advanced search"));
}
#[tokio::test]
async fn test_prompt_git_search_with_query_and_path() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("db").to_string_lossy().to_string();
let cache_path = temp_dir.path().join("cache.json");
let client = RagClient::new_with_db_path(&db_path, cache_path)
.await
.unwrap();
let server = RagMcpServer::with_client(Arc::new(client)).unwrap();
let args = serde_json::json!({
"query": "git search",
"path": "/repo/path"
});
let result = server.git_search_prompt(Parameters(args)).await;
assert!(result.is_ok());
let messages = result.unwrap();
assert!(!messages.is_empty());
let debug_str = format!("{:?}", messages[0].content);
assert!(debug_str.contains("git search"));
assert!(debug_str.contains("/repo/path"));
}
#[tokio::test]
async fn test_prompt_git_search_default_path() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("db").to_string_lossy().to_string();
let cache_path = temp_dir.path().join("cache.json");
let client = RagClient::new_with_db_path(&db_path, cache_path)
.await
.unwrap();
let server = RagMcpServer::with_client(Arc::new(client)).unwrap();
let args = serde_json::json!({
"query": "git search"
});
let result = server.git_search_prompt(Parameters(args)).await;
assert!(result.is_ok());
let messages = result.unwrap();
assert!(!messages.is_empty());
let debug_str = format!("{:?}", messages[0].content);
assert!(debug_str.contains("git search"));
assert!(debug_str.contains("'.'"));
}
#[tokio::test]
async fn test_server_info_completeness() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("db").to_string_lossy().to_string();
let cache_path = temp_dir.path().join("cache.json");
let client = RagClient::new_with_db_path(&db_path, cache_path)
.await
.unwrap();
let server = RagMcpServer::with_client(Arc::new(client)).unwrap();
let info = server.get_info();
assert_eq!(info.server_info.name, "project");
assert!(info.server_info.title.is_some());
assert_eq!(
info.server_info.title.as_deref().unwrap(),
"Project RAG - Code Understanding with Semantic Search"
);
assert_eq!(info.server_info.version, env!("CARGO_PKG_VERSION"));
assert!(info.capabilities.tools.is_some());
assert!(info.capabilities.prompts.is_some());
assert!(info.instructions.is_some());
let instructions = info.instructions.as_deref().unwrap();
assert!(instructions.contains("RAG-based"));
assert!(instructions.contains("index_codebase"));
assert!(instructions.contains("query_codebase"));
assert!(instructions.contains("search_by_filters"));
}
#[tokio::test]
async fn test_client_accessor() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("db").to_string_lossy().to_string();
let cache_path = temp_dir.path().join("cache.json");
let client = RagClient::new_with_db_path(&db_path, cache_path)
.await
.unwrap();
let server = RagMcpServer::with_client(Arc::new(client)).unwrap();
let client_ref = server.client();
assert_eq!(client_ref.embedding_dimension(), 384);
}