use std::collections::HashMap;
use xz_embed::{SearchResult, rrf_fusion};
fn make_search_result(id: &str, score: f32) -> SearchResult {
SearchResult {
id: id.to_string(),
score,
metadata: HashMap::new(),
content: None,
channel: None,
}
}
#[test]
fn test_rrf_overlap() {
let vector_results = vec![make_search_result("doc1", 0.9), make_search_result("doc2", 0.8)];
let keyword_results = vec![("doc1".to_string(), 0.85), ("doc3".to_string(), 0.75)];
let results = rrf_fusion(&vector_results, &keyword_results, 60.0);
assert_eq!(results[0].id, "doc1", "overlap doc should rank first");
assert_eq!(results.len(), 3, "should have 3 unique docs");
let doc1 = results.iter().find(|r| r.id == "doc1").unwrap();
let doc2 = results.iter().find(|r| r.id == "doc2").unwrap();
let doc3 = results.iter().find(|r| r.id == "doc3").unwrap();
assert!(
doc1.fused_score > doc2.fused_score,
"overlap score {} should exceed single-vector score {}",
doc1.fused_score,
doc2.fused_score
);
assert!(
doc1.fused_score > doc3.fused_score,
"overlap score {} should exceed single-keyword score {}",
doc1.fused_score,
doc3.fused_score
);
assert!(doc1.vector_score > 0.0, "doc1 should have vector_score");
assert!(doc1.keyword_score > 0.0, "doc1 should have keyword_score");
assert!(doc2.vector_score > 0.0, "doc2 should have vector_score");
assert_eq!(doc2.keyword_score, 0.0, "doc2 should have no keyword score");
assert_eq!(doc3.vector_score, 0.0, "doc3 should have no vector score");
assert!(doc3.keyword_score > 0.0, "doc3 should have keyword_score");
}
#[test]
fn test_rrf_disjoint() {
let vector_results = vec![
make_search_result("v1", 0.9),
make_search_result("v2", 0.8),
make_search_result("v3", 0.7),
];
let keyword_results = vec![("k1".to_string(), 0.9), ("k2".to_string(), 0.8)];
let results = rrf_fusion(&vector_results, &keyword_results, 60.0);
assert_eq!(results.len(), 5, "all 5 disjoint docs should appear");
for w in results.windows(2) {
assert!(
w[0].fused_score >= w[1].fused_score,
"results should be sorted descending by fused_score: {} >= {}",
w[0].fused_score,
w[1].fused_score
);
}
let top = &results[0];
let second = &results[1];
assert!(
(top.fused_score - second.fused_score).abs() < f32::EPSILON,
"top two disjoint results with same rank should have nearly equal fused_score"
);
let v1 = results.iter().find(|r| r.id == "v1").unwrap();
assert!(v1.vector_score > 0.0, "v1 should have vector_score");
assert_eq!(v1.keyword_score, 0.0, "v1 should have no keyword score");
let k1 = results.iter().find(|r| r.id == "k1").unwrap();
assert_eq!(k1.vector_score, 0.0, "k1 should have no vector score");
assert!(k1.keyword_score > 0.0, "k1 should have keyword_score");
}
#[test]
fn test_rrf_single_channel() {
let vector_results = vec![
make_search_result("a", 0.9),
make_search_result("b", 0.7),
make_search_result("c", 0.5),
];
let keyword_results: Vec<(String, f32)> = vec![];
let results = rrf_fusion(&vector_results, &keyword_results, 60.0);
assert_eq!(results.len(), 3, "should have 3 results from vector channel only");
assert_eq!(results[0].id, "a", "highest vector score should rank first");
assert_eq!(results[1].id, "b", "middle vector score should rank second");
assert_eq!(results[2].id, "c", "lowest vector score should rank third");
for r in &results {
assert!(r.vector_score > 0.0, "{} should have vector_score", r.id);
assert_eq!(r.keyword_score, 0.0, "{} should have no keyword score", r.id);
}
}
#[test]
fn test_rrf_nan_scores() {
let vector_results =
vec![make_search_result("nan_doc", f32::NAN), make_search_result("normal", 0.8)];
let keyword_results = vec![("normal".to_string(), 0.7), ("extra".to_string(), 0.5)];
let results = rrf_fusion(&vector_results, &keyword_results, 60.0);
assert!(!results.is_empty(), "should produce results even with NaN scores");
let nan_doc = results.iter().find(|r| r.id == "nan_doc");
assert!(nan_doc.is_some(), "NaN-scored doc should be in fusion results");
let nd = nan_doc.unwrap();
assert!(nd.vector_score.is_nan(), "NaN input score should remain NaN in output");
assert!(nd.keyword_score == 0.0, "NaN doc has no keyword match");
let normal = results.iter().find(|r| r.id == "normal").unwrap();
assert!(normal.vector_score > 0.0, "normal doc should have vector_score");
assert!(normal.keyword_score > 0.0, "normal doc should have keyword_score");
let kw_nan: Vec<(String, f32)> = vec![("nan_kw".to_string(), f32::NAN)];
let results2 = rrf_fusion(&[], &kw_nan, 60.0);
assert_eq!(results2.len(), 1, "NaN keyword result should appear");
assert!(results2[0].keyword_score.is_nan(), "NaN keyword score preserved");
}