use cached_context::{CacheConfig, CacheStore};
use rmcp::model::CallToolResult;
async fn create_test_store(temp_dir: &tempfile::TempDir) -> CacheStore {
let config = CacheConfig {
db_path: temp_dir.path().join("test_cache.db"),
session_id: "test-session".to_string(),
workdir: temp_dir.path().to_path_buf(),
};
let store = CacheStore::new(config).expect("Failed to create cache store");
store.init().await.expect("Failed to init cache store");
store
}
fn get_text_from_result(result: &CallToolResult) -> String {
result
.content
.first()
.and_then(|c| c.as_text())
.map(|t| t.text.clone())
.unwrap_or_default()
}
#[tokio::test]
async fn test_mcp_read_file_first_time() {
let temp_dir = tempfile::tempdir().expect("Failed to create temp dir");
let test_file = temp_dir.path().join("test.txt");
std::fs::write(&test_file, "line1\nline2\nline3\n").expect("Failed to write test file");
let store = create_test_store(&temp_dir).await;
let service = cached_context::mcp::CachebroMcpService::new(store);
let result = service
.read_file(test_file.to_string_lossy().to_string(), None, None, false)
.await
.expect("read_file failed");
let text = get_text_from_result(&result);
assert!(
!text.contains("[unchanged]"),
"First read should return full content"
);
assert!(text.contains("line1"), "Should contain first line");
assert!(text.contains("line2"), "Should contain second line");
assert!(text.contains("line3"), "Should contain third line");
}
#[tokio::test]
async fn test_mcp_read_file_second_read_unchanged() {
let temp_dir = tempfile::tempdir().expect("Failed to create temp dir");
let test_file = temp_dir.path().join("test.txt");
std::fs::write(&test_file, "line1\nline2\nline3\n").expect("Failed to write test file");
let store = create_test_store(&temp_dir).await;
let service = cached_context::mcp::CachebroMcpService::new(store);
let _ = service
.read_file(test_file.to_string_lossy().to_string(), None, None, false)
.await
.expect("First read_file failed");
let result = service
.read_file(test_file.to_string_lossy().to_string(), None, None, false)
.await
.expect("read_file failed");
let text = get_text_from_result(&result);
assert!(
text.contains("unchanged"),
"Second read should return 'unchanged', got: {}",
text
);
}
#[tokio::test]
async fn test_mcp_read_file_changed() {
let temp_dir = tempfile::tempdir().expect("Failed to create temp dir");
let test_file = temp_dir.path().join("test.txt");
std::fs::write(&test_file, "line1\nline2\nline3\n").expect("Failed to write test file");
let store = create_test_store(&temp_dir).await;
let service = cached_context::mcp::CachebroMcpService::new(store);
let _ = service
.read_file(test_file.to_string_lossy().to_string(), None, None, false)
.await
.expect("First read_file failed");
std::fs::write(&test_file, "line1\nline2 modified\nline3\n")
.expect("Failed to modify test file");
let result = service
.read_file(test_file.to_string_lossy().to_string(), None, None, false)
.await
.expect("read_file failed");
let text = get_text_from_result(&result);
assert!(
text.contains("lines changed"),
"Changed file should return diff with lines changed info, got: {}",
text
);
assert!(
text.contains("---") || text.contains("+++") || text.contains("@@"),
"Should contain diff markers"
);
}
#[tokio::test]
async fn test_mcp_read_file_partial() {
let temp_dir = tempfile::tempdir().expect("Failed to create temp dir");
let test_file = temp_dir.path().join("test.txt");
std::fs::write(
&test_file,
"line1\nline2\nline3\nline4\nline5\n",
)
.expect("Failed to write test file");
let store = create_test_store(&temp_dir).await;
let service = cached_context::mcp::CachebroMcpService::new(store);
let result = service
.read_file(
test_file.to_string_lossy().to_string(),
Some(2),
Some(2),
false,
)
.await
.expect("read_file failed");
let text = get_text_from_result(&result);
assert!(
text.contains("line2") && text.contains("line3"),
"Should contain lines 2 and 3, got: {}",
text
);
assert!(
!text.contains("line1"),
"Should not contain line1"
);
}
#[tokio::test]
async fn test_mcp_cache_status() {
let temp_dir = tempfile::tempdir().expect("Failed to create temp dir");
let test_file = temp_dir.path().join("test.txt");
std::fs::write(&test_file, "test content for tracking").expect("Failed to write test file");
let store = create_test_store(&temp_dir).await;
let service = cached_context::mcp::CachebroMcpService::new(store);
let _ = service
.read_file(test_file.to_string_lossy().to_string(), None, None, false)
.await
.expect("read_file failed");
let result = service.cache_status().await.expect("cache_status failed");
let text = get_text_from_result(&result);
assert!(
text.contains("cached-context status"),
"Should contain status header"
);
assert!(
text.contains("Files tracked:"),
"Should contain files tracked info"
);
}
#[tokio::test]
async fn test_mcp_cache_clear() {
let temp_dir = tempfile::tempdir().expect("Failed to create temp dir");
let test_file = temp_dir.path().join("test.txt");
std::fs::write(&test_file, "test content").expect("Failed to write test file");
let store = create_test_store(&temp_dir).await;
let service = cached_context::mcp::CachebroMcpService::new(store);
let _ = service
.read_file(test_file.to_string_lossy().to_string(), None, None, false)
.await
.expect("read_file failed");
let result = service.cache_clear().await.expect("cache_clear failed");
let text = get_text_from_result(&result);
assert!(
text.contains("Cache cleared"),
"Should confirm cache cleared"
);
let status = service.cache_status().await.expect("cache_status failed");
let status_text = get_text_from_result(&status);
assert!(
status_text.contains("Files tracked: 0"),
"Cache should be empty after clear"
);
}
#[tokio::test]
async fn test_mcp_read_files_batch() {
let temp_dir = tempfile::tempdir().expect("Failed to create temp dir");
let file1 = temp_dir.path().join("file1.txt");
let file2 = temp_dir.path().join("file2.txt");
std::fs::write(&file1, "content of file 1").expect("Failed to write file1");
std::fs::write(&file2, "content of file 2").expect("Failed to write file2");
let store = create_test_store(&temp_dir).await;
let service = cached_context::mcp::CachebroMcpService::new(store);
let result = service
.read_files(vec![
file1.to_string_lossy().to_string(),
file2.to_string_lossy().to_string(),
])
.await
.expect("read_files failed");
let text = get_text_from_result(&result);
assert!(text.contains("file1.txt"), "Should contain file1");
assert!(text.contains("file2.txt"), "Should contain file2");
assert!(
text.contains("content of file 1"),
"Should contain content of file 1"
);
}
#[tokio::test]
async fn test_mcp_server_info() {
let temp_dir = tempfile::tempdir().expect("Failed to create temp dir");
let store = create_test_store(&temp_dir).await;
let service = cached_context::mcp::CachebroMcpService::new(store);
let info = service.get_info();
assert_eq!(info.server_info.name, "cached-context");
assert_eq!(info.server_info.version, "0.2.1");
}
#[tokio::test]
async fn test_mcp_read_files_batch_mixed_states() {
let temp_dir = tempfile::tempdir().expect("Failed to create temp dir");
let file1 = temp_dir.path().join("file1.txt");
let file2 = temp_dir.path().join("file2.txt");
let file3 = temp_dir.path().join("file3.txt");
std::fs::write(&file1, "file1 content\n").expect("Failed to write file1");
std::fs::write(&file2, "file2 original\nline2\n").expect("Failed to write file2");
std::fs::write(&file3, "file3 content\n").expect("Failed to write file3");
let store = create_test_store(&temp_dir).await;
let service = cached_context::mcp::CachebroMcpService::new(store);
let _ = service
.read_file(file1.to_string_lossy().to_string(), None, None, false)
.await
.expect("First read file1 failed");
let _ = service
.read_file(file2.to_string_lossy().to_string(), None, None, false)
.await
.expect("First read file2 failed");
std::fs::write(&file2, "file2 modified\nline2\n").expect("Failed to modify file2");
let result = service
.read_files(vec![
file1.to_string_lossy().to_string(),
file2.to_string_lossy().to_string(),
file3.to_string_lossy().to_string(),
])
.await
.expect("read_files failed");
let text = get_text_from_result(&result);
assert!(
text.contains("unchanged"),
"file1 should show as unchanged, got: {}",
text
);
assert!(
text.contains("lines changed out of"),
"file2 should show changed header, got: {}",
text
);
assert!(
text.contains("] ==="),
"Changed file header should end with '] ===', got: {}",
text
);
assert!(
text.contains("file3 content"),
"file3 should show full content, got: {}",
text
);
}
#[tokio::test]
async fn test_mcp_token_savings_footer() {
let temp_dir = tempfile::tempdir().expect("Failed to create temp dir");
let test_file = temp_dir.path().join("test.txt");
std::fs::write(&test_file, "some content for token test\n").expect("Failed to write test file");
let store = create_test_store(&temp_dir).await;
let service = cached_context::mcp::CachebroMcpService::new(store);
let _ = service
.read_file(test_file.to_string_lossy().to_string(), None, None, false)
.await
.expect("First read failed");
let result = service
.read_file(test_file.to_string_lossy().to_string(), None, None, false)
.await
.expect("Second read failed");
let text = get_text_from_result(&result);
assert!(
text.contains("tokens saved this session"),
"Cached response should include token savings footer, got: {}",
text
);
}
#[tokio::test]
async fn test_mcp_read_file_force() {
let temp_dir = tempfile::tempdir().expect("Failed to create temp dir");
let test_file = temp_dir.path().join("test.txt");
std::fs::write(&test_file, "original content\nline 2\n").expect("Failed to write test file");
let store = create_test_store(&temp_dir).await;
let service = cached_context::mcp::CachebroMcpService::new(store);
let _ = service
.read_file(test_file.to_string_lossy().to_string(), None, None, false)
.await
.expect("First read failed");
let result = service
.read_file(test_file.to_string_lossy().to_string(), None, None, true)
.await
.expect("Force read failed");
let text = get_text_from_result(&result);
assert!(
!text.contains("unchanged"),
"Force read should NOT return 'unchanged', got: {}",
text
);
assert!(
text.contains("original content"),
"Force read should return full content, got: {}",
text
);
assert!(
text.contains("line 2"),
"Force read should return all lines, got: {}",
text
);
assert!(
!text.contains("tokens saved this session"),
"Force read should NOT have token savings footer, got: {}",
text
);
}