use codex_memory::{
error::Result,
models::{SearchParams, SearchStrategy},
storage::Storage,
};
use tests::common::test_db_manager::TestDatabaseManager;
use uuid::Uuid;
#[tokio::test]
async fn test_basic_text_search() -> Result<()> {
let mut manager = TestDatabaseManager::new()?;
let pool = manager.setup_test_database().await?;
let storage = Storage::new(pool);
let id1 = storage
.store(
"Database performance optimization techniques",
"Technical documentation".to_string(),
"Guide for optimizing database queries".to_string(),
Some(vec!["database".to_string(), "performance".to_string()]),
)
.await?;
let id2 = storage
.store(
"JavaScript performance tips",
"Programming guide".to_string(),
"Best practices for JavaScript optimization".to_string(),
Some(vec!["javascript".to_string(), "performance".to_string()]),
)
.await?;
let search_params = SearchParams {
query: "database optimization".to_string(),
similarity_threshold: 0.1, max_results: 10,
..Default::default()
};
let results = storage.search_memories(search_params).await?;
assert!(!results.is_empty(), "Search should return results");
let database_result = results.iter().find(|r| r.memory.id == id1);
let javascript_result = results.iter().find(|r| r.memory.id == id2);
assert!(database_result.is_some(), "Should find database memory");
if let (Some(db), Some(js)) = (database_result, javascript_result) {
assert!(db.combined_score >= js.combined_score,
"Database memory should rank higher than JavaScript for 'database optimization' query");
}
manager.cleanup().await?;
Ok(())
}
#[tokio::test]
async fn test_tag_filtering() -> Result<()> {
let mut manager = TestDatabaseManager::new()?;
let pool = manager.setup_test_database().await?;
let storage = Storage::new(pool);
let _id1 = storage
.store(
"Content about databases",
"Database content".to_string(),
"Information about databases".to_string(),
Some(vec!["database".to_string(), "sql".to_string()]),
)
.await?;
let _id2 = storage
.store(
"Content about databases",
"Web development content".to_string(),
"Information about web development".to_string(),
Some(vec!["javascript".to_string(), "web".to_string()]),
)
.await?;
let _id3 = storage
.store(
"Content about databases and performance",
"Performance content".to_string(),
"Database performance information".to_string(),
Some(vec!["database".to_string(), "performance".to_string()]),
)
.await?;
let search_params = SearchParams {
query: "databases".to_string(),
tag_filter: Some(vec!["database".to_string()]),
similarity_threshold: 0.1,
max_results: 10,
..Default::default()
};
let results = storage.search_memories(search_params).await?;
assert!(!results.is_empty(), "Search should return tagged results");
for result in results {
assert!(
result.memory.tags.contains(&"database".to_string()),
"All results should have the 'database' tag"
);
}
manager.cleanup().await?;
Ok(())
}
#[tokio::test]
async fn test_progressive_search_strategy() -> Result<()> {
let mut manager = TestDatabaseManager::new()?;
let pool = manager.setup_test_database().await?;
let storage = Storage::new(pool);
let _id = storage
.store(
"Machine learning algorithms for pattern recognition",
"AI research".to_string(),
"Overview of ML algorithms".to_string(),
Some(vec!["machine-learning".to_string(), "ai".to_string()]),
)
.await?;
let search_params_high = SearchParams {
query: "machine learning".to_string(),
similarity_threshold: 0.9, max_results: 10,
..Default::default()
};
let results_high = storage
.search_memories_progressive_with_metadata(search_params_high)
.await?;
let search_params_medium = SearchParams {
query: "pattern recognition".to_string(),
similarity_threshold: 0.7,
max_results: 10,
..Default::default()
};
let results_medium = storage
.search_memories_progressive_with_metadata(search_params_medium)
.await?;
assert!(
results_high.metadata.stage_used >= 1 && results_high.metadata.stage_used <= 3,
"Stage should be between 1 and 3"
);
assert!(
results_medium.metadata.stage_used >= 1 && results_medium.metadata.stage_used <= 3,
"Stage should be between 1 and 3"
);
assert!(!results_high.metadata.stage_description.is_empty());
assert!(!results_medium.metadata.stage_description.is_empty());
manager.cleanup().await?;
Ok(())
}
#[tokio::test]
async fn test_different_search_strategies() -> Result<()> {
let mut manager = TestDatabaseManager::new()?;
let pool = manager.setup_test_database().await?;
let storage = Storage::new(pool);
let _id = storage
.store(
"Database optimization techniques",
"Technical guide".to_string(),
"Performance tuning for databases".to_string(),
Some(vec!["database".to_string(), "optimization".to_string()]),
)
.await?;
let search_params_tags = SearchParams {
query: "optimization".to_string(),
search_strategy: SearchStrategy::TagsFirst,
similarity_threshold: 0.1,
max_results: 10,
..Default::default()
};
let results_tags = storage.search_memories(search_params_tags).await?;
let search_params_content = SearchParams {
query: "optimization".to_string(),
search_strategy: SearchStrategy::ContentFirst,
similarity_threshold: 0.1,
max_results: 10,
..Default::default()
};
let results_content = storage.search_memories(search_params_content).await?;
let search_params_hybrid = SearchParams {
query: "optimization".to_string(),
search_strategy: SearchStrategy::Hybrid,
similarity_threshold: 0.1,
max_results: 10,
..Default::default()
};
let results_hybrid = storage.search_memories(search_params_hybrid).await?;
assert!(!results_tags.is_empty() || !results_content.is_empty() || !results_hybrid.is_empty(),
"At least one search strategy should return results");
manager.cleanup().await?;
Ok(())
}
#[tokio::test]
async fn test_search_performance() -> Result<()> {
let mut manager = TestDatabaseManager::new()?;
let pool = manager.setup_test_database().await?;
let storage = Storage::new(pool);
for i in 0..50 {
storage
.store(
&format!("Performance test content number {}", i),
format!("Test context {}", i),
format!("Test summary {}", i),
Some(vec![
"performance".to_string(),
format!("test-{}", i),
]),
)
.await?;
}
let start = std::time::Instant::now();
let search_params = SearchParams {
query: "performance test".to_string(),
max_results: 20,
..Default::default()
};
let results = storage.search_memories(search_params).await?;
let duration = start.elapsed();
assert!(!results.is_empty(), "Search should return results");
assert!(
duration.as_millis() < 200,
"Search should complete within 200ms, took {}ms",
duration.as_millis()
);
println!("Search completed in {}ms with {} results",
duration.as_millis(), results.len());
manager.cleanup().await?;
Ok(())
}
#[tokio::test]
async fn test_search_with_empty_database() -> Result<()> {
let mut manager = TestDatabaseManager::new()?;
let pool = manager.setup_test_database().await?;
let storage = Storage::new(pool);
let search_params = SearchParams {
query: "nonexistent content".to_string(),
max_results: 10,
..Default::default()
};
let results = storage.search_memories(search_params).await?;
assert!(results.is_empty(), "Empty database should return no results");
manager.cleanup().await?;
Ok(())
}
#[tokio::test]
async fn test_search_result_scoring() -> Result<()> {
let mut manager = TestDatabaseManager::new()?;
let pool = manager.setup_test_database().await?;
let storage = Storage::new(pool);
let id1 = storage
.store(
"Database optimization is crucial for performance",
"High relevance content".to_string(),
"Database optimization techniques".to_string(),
Some(vec!["database".to_string(), "optimization".to_string()]),
)
.await?;
let id2 = storage
.store(
"General optimization principles",
"Medium relevance content".to_string(),
"General optimization guide".to_string(),
Some(vec!["optimization".to_string()]),
)
.await?;
let _id3 = storage
.store(
"Web development tutorials",
"Low relevance content".to_string(),
"Web programming guide".to_string(),
Some(vec!["web".to_string(), "development".to_string()]),
)
.await?;
let search_params = SearchParams {
query: "database optimization".to_string(),
similarity_threshold: 0.1,
max_results: 10,
..Default::default()
};
let results = storage.search_memories(search_params).await?;
assert!(!results.is_empty(), "Should return search results");
for i in 1..results.len() {
assert!(
results[i-1].combined_score >= results[i].combined_score,
"Results should be sorted by score in descending order"
);
}
let top_result = &results[0];
assert!(
top_result.memory.id == id1 || top_result.memory.id == id2,
"Top result should be one of the optimization-related memories"
);
manager.cleanup().await?;
Ok(())
}
#[tokio::test]
async fn test_search_with_recency_boost() -> Result<()> {
let mut manager = TestDatabaseManager::new()?;
let pool = manager.setup_test_database().await?;
let storage = Storage::new(pool);
let _id = storage
.store(
"Recent optimization techniques",
"Recent content".to_string(),
"Latest optimization methods".to_string(),
Some(vec!["optimization".to_string(), "recent".to_string()]),
)
.await?;
let search_params_with_boost = SearchParams {
query: "optimization".to_string(),
boost_recent: true,
similarity_threshold: 0.1,
max_results: 10,
..Default::default()
};
let results_with_boost = storage.search_memories(search_params_with_boost).await?;
let search_params_no_boost = SearchParams {
query: "optimization".to_string(),
boost_recent: false,
similarity_threshold: 0.1,
max_results: 10,
..Default::default()
};
let results_no_boost = storage.search_memories(search_params_no_boost).await?;
assert!(!results_with_boost.is_empty() || !results_no_boost.is_empty(),
"At least one search configuration should return results");
manager.cleanup().await?;
Ok(())
}
#[tokio::test]
async fn test_search_max_results_limit() -> Result<()> {
let mut manager = TestDatabaseManager::new()?;
let pool = manager.setup_test_database().await?;
let storage = Storage::new(pool);
for i in 0..20 {
storage
.store(
&format!("Test content number {}", i),
format!("Context {}", i),
format!("Summary {}", i),
Some(vec!["test".to_string()]),
)
.await?;
}
let search_params = SearchParams {
query: "test".to_string(),
max_results: 5,
similarity_threshold: 0.1,
..Default::default()
};
let results = storage.search_memories(search_params).await?;
assert!(
results.len() <= 5,
"Should return at most 5 results, got {}",
results.len()
);
manager.cleanup().await?;
Ok(())
}