use crate::common::TestDatabaseManager;
use anyhow::Result;
use codex_memory::models::{SearchParams, SearchStrategy};
use codex_memory::Storage;
use serial_test::serial;
#[tokio::test]
#[serial]
async fn test_search_fallback_behavior() -> Result<()> {
let mut manager = TestDatabaseManager::new()?;
let pool = manager.setup_test_database().await?;
let storage = Storage::new(pool);
let _id = storage
.store(
"Test content for fallback behavior",
"Test context".to_string(),
"Test summary".to_string(),
Some(vec!["test".to_string()]),
)
.await?;
let params = SearchParams {
query: "test content".to_string(),
similarity_threshold: 0.1,
..Default::default()
};
let results = storage.search_memories(params).await?;
assert!(results.len() <= 1, "Should handle search gracefully");
manager.cleanup().await?;
Ok(())
}
#[tokio::test]
#[serial]
async fn test_search_fallback_with_simple_query() -> Result<()> {
let mut manager = TestDatabaseManager::new()?;
let pool = manager.setup_test_database().await?;
let storage = Storage::new(pool);
let _id1 = storage
.store(
"Rust programming language tutorial",
"Programming tutorial".to_string(),
"Learn Rust programming basics".to_string(),
Some(vec!["rust".to_string(), "programming".to_string()]),
)
.await?;
let _id2 = storage
.store(
"Python machine learning guide",
"ML guide".to_string(),
"Python for machine learning".to_string(),
Some(vec!["python".to_string(), "ml".to_string()]),
)
.await?;
let params = SearchParams {
query: "programming".to_string(),
similarity_threshold: 0.1,
max_results: 10,
..Default::default()
};
let results = storage.search_memories(params).await?;
assert!(
!results.is_empty(),
"Should find results with fallback search"
);
for result in &results {
assert!(!result.memory.content.is_empty());
assert!(!result.memory.context.is_empty());
assert!(!result.memory.summary.is_empty());
assert!(result.combined_score >= 0.0);
}
manager.cleanup().await?;
Ok(())
}
#[tokio::test]
#[serial]
async fn test_search_fallback_with_tag_filter() -> Result<()> {
let mut manager = TestDatabaseManager::new()?;
let pool = manager.setup_test_database().await?;
let storage = Storage::new(pool);
let _id1 = storage
.store(
"Rust web development",
"Web development context".to_string(),
"Building web apps with Rust".to_string(),
Some(vec!["rust".to_string(), "web".to_string()]),
)
.await?;
let _id2 = storage
.store(
"Python data science",
"Data science context".to_string(),
"Data analysis with Python".to_string(),
Some(vec!["python".to_string(), "data".to_string()]),
)
.await?;
let params = SearchParams {
query: "development".to_string(),
tag_filter: Some(vec!["rust".to_string()]),
similarity_threshold: 0.1,
max_results: 10,
..Default::default()
};
let results = storage.search_memories(params).await?;
for result in &results {
assert!(
result.memory.tags.contains(&"rust".to_string()),
"Result should contain filtered tag 'rust': {:?}",
result.memory.tags
);
}
manager.cleanup().await?;
Ok(())
}
#[tokio::test]
#[serial]
async fn test_search_fallback_empty_results() -> Result<()> {
let mut manager = TestDatabaseManager::new()?;
let pool = manager.setup_test_database().await?;
let storage = Storage::new(pool);
let _id = storage
.store(
"Blockchain technology overview",
"Blockchain context".to_string(),
"Understanding blockchain fundamentals".to_string(),
Some(vec!["blockchain".to_string(), "crypto".to_string()]),
)
.await?;
let params = SearchParams {
query: "cooking recipes italian pasta".to_string(),
similarity_threshold: 0.8, ..Default::default()
};
let results = storage.search_memories(params).await?;
assert!(
results.len() < 2,
"Should return few or no results for unrelated search, got: {}",
results.len()
);
manager.cleanup().await?;
Ok(())
}
#[tokio::test]
#[serial]
async fn test_search_result_sorting() -> Result<()> {
let mut manager = TestDatabaseManager::new()?;
let pool = manager.setup_test_database().await?;
let storage = Storage::new(pool);
for i in 1..=5 {
storage
.store(
&format!("Programming tutorial part {}", i),
format!("Tutorial context {}", i),
format!("Learning programming part {}", i),
Some(vec!["programming".to_string(), "tutorial".to_string()]),
)
.await?;
}
let params = SearchParams {
query: "programming tutorial".to_string(),
similarity_threshold: 0.1,
max_results: 10,
..Default::default()
};
let results = storage.search_memories(params).await?;
for i in 1..results.len() {
assert!(
results[i - 1].combined_score >= results[i].combined_score,
"Results should be sorted by descending score: {} >= {}",
results[i - 1].combined_score,
results[i].combined_score
);
}
manager.cleanup().await?;
Ok(())
}
#[tokio::test]
#[serial]
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 1..=10 {
storage
.store(
&format!("Test content number {}", i),
format!("Test context {}", i),
format!("Test summary {}", i),
Some(vec!["test".to_string()]),
)
.await?;
}
let params = SearchParams {
query: "test content".to_string(),
max_results: 3,
similarity_threshold: 0.1,
..Default::default()
};
let results = storage.search_memories(params).await?;
assert!(
results.len() <= 3,
"Should respect max_results limit, got: {} results",
results.len()
);
manager.cleanup().await?;
Ok(())
}
#[tokio::test]
#[serial]
async fn test_search_different_strategies_fallback() -> Result<()> {
let mut manager = TestDatabaseManager::new()?;
let pool = manager.setup_test_database().await?;
let storage = Storage::new(pool);
let _id = storage
.store(
"Artificial intelligence research",
"AI research context".to_string(),
"Current trends in AI research".to_string(),
Some(vec!["ai".to_string(), "research".to_string()]),
)
.await?;
let strategies = vec![
SearchStrategy::TagsFirst,
SearchStrategy::ContentFirst,
SearchStrategy::Hybrid,
];
for strategy in strategies {
let params = SearchParams {
query: "artificial intelligence".to_string(),
search_strategy: strategy.clone(),
similarity_threshold: 0.1,
..Default::default()
};
let results = storage.search_memories(params).await;
assert!(
results.is_ok(),
"Search should succeed for strategy: {:?}",
strategy
);
let results = results?;
if !results.is_empty() {
assert!(
results[0].combined_score >= 0.0,
"Results should have valid scores"
);
}
}
manager.cleanup().await?;
Ok(())
}
#[tokio::test]
#[serial]
async fn test_search_empty_query() -> Result<()> {
let mut manager = TestDatabaseManager::new()?;
let pool = manager.setup_test_database().await?;
let storage = Storage::new(pool);
let _id = storage
.store(
"Some content",
"Some context".to_string(),
"Some summary".to_string(),
Some(vec!["tag".to_string()]),
)
.await?;
let params = SearchParams {
query: "".to_string(),
..Default::default()
};
let results = storage.search_memories(params).await?;
assert!(
results.is_empty() || !results.is_empty(),
"Search with empty query should not crash"
);
manager.cleanup().await?;
Ok(())
}
#[tokio::test]
#[serial]
async fn test_search_similarity_threshold_filtering() -> 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",
"ML context".to_string(),
"Overview of ML algorithms".to_string(),
Some(vec!["ml".to_string(), "algorithms".to_string()]),
)
.await?;
let high_threshold_params = SearchParams {
query: "machine learning".to_string(),
similarity_threshold: 0.99,
..Default::default()
};
let high_results = storage.search_memories(high_threshold_params).await?;
let low_threshold_params = SearchParams {
query: "machine learning".to_string(),
similarity_threshold: 0.1,
..Default::default()
};
let low_results = storage.search_memories(low_threshold_params).await?;
assert!(
low_results.len() >= high_results.len(),
"Lower threshold should return more results: {} vs {}",
low_results.len(),
high_results.len()
);
manager.cleanup().await?;
Ok(())
}
#[tokio::test]
#[serial]
async fn test_recency_boost_application() -> Result<()> {
let mut manager = TestDatabaseManager::new()?;
let pool = manager.setup_test_database().await?;
let storage = Storage::new(pool);
let _id1 = storage
.store(
"First memory",
"Context 1".to_string(),
"Summary 1".to_string(),
Some(vec!["test".to_string()]),
)
.await?;
tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
let _id2 = storage
.store(
"Second memory newer",
"Context 2".to_string(),
"Summary 2".to_string(),
Some(vec!["test".to_string()]),
)
.await?;
let normal_params = SearchParams {
query: "memory".to_string(),
boost_recent: false,
similarity_threshold: 0.1,
..Default::default()
};
let normal_results = storage.search_memories(normal_params).await?;
let boosted_params = SearchParams {
query: "memory".to_string(),
boost_recent: true,
similarity_threshold: 0.1,
..Default::default()
};
let boosted_results = storage.search_memories(boosted_params).await?;
assert!(
!normal_results.is_empty(),
"Normal search should find results"
);
assert!(
!boosted_results.is_empty(),
"Boosted search should find results"
);
for result in &boosted_results {
assert!(
result.combined_score >= 0.0,
"Boosted scores should be non-negative"
);
}
manager.cleanup().await?;
Ok(())
}
#[tokio::test]
#[serial]
async fn test_search_no_embeddings_flag_disabled() -> Result<()> {
let mut manager = TestDatabaseManager::new()?;
let pool = manager.setup_test_database().await?;
let storage = Storage::new(pool);
let _id = storage
.store(
"Test content for disabled embeddings",
"Test context".to_string(),
"Test summary".to_string(),
Some(vec!["test".to_string()]),
)
.await?;
let params = SearchParams {
query: "test content".to_string(),
use_tag_embedding: false,
use_content_embedding: false,
similarity_threshold: 0.1,
..Default::default()
};
let results = storage.search_memories(params).await?;
if !results.is_empty() {
for result in &results {
assert!(result.combined_score >= 0.0);
assert!(!result.memory.content.is_empty());
}
}
manager.cleanup().await?;
Ok(())
}
#[tokio::test]
#[serial]
async fn test_search_weight_combinations() -> Result<()> {
let mut manager = TestDatabaseManager::new()?;
let pool = manager.setup_test_database().await?;
let storage = Storage::new(pool);
let _id = storage
.store(
"Deep learning neural networks",
"Neural networks context".to_string(),
"Introduction to deep learning".to_string(),
Some(vec![
"deep-learning".to_string(),
"neural-networks".to_string(),
]),
)
.await?;
let weight_configs = vec![
(1.0, 0.0), (0.0, 1.0), (0.5, 0.5), (0.3, 0.7), ];
for (tag_weight, content_weight) in weight_configs {
let params = SearchParams {
query: "deep learning".to_string(),
tag_weight,
content_weight,
similarity_threshold: 0.1,
..Default::default()
};
let results = storage.search_memories(params).await?;
if !results.is_empty() {
assert!(
results[0].combined_score >= 0.0,
"Scores should be non-negative with weights {}, {}",
tag_weight,
content_weight
);
}
}
manager.cleanup().await?;
Ok(())
}