use agentdb::{AgentDB, TriModalQuery, VectorEntry};
use serde_json::json;
fn open() -> AgentDB {
AgentDB::open(":memory:").expect("failed to open in-memory db")
}
fn bias_vec(idx: usize, dim: usize) -> Vec<f32> {
let mut v = vec![0.1f32; dim];
v[idx % dim] = 0.9;
v
}
fn seed_db(db: &AgentDB) {
let col = db.vectors().collection("docs", 4).unwrap();
let entries = vec![
("d1", bias_vec(0, 4), "rust programming language systems"),
("d2", bias_vec(1, 4), "python machine learning data science"),
("d3", bias_vec(2, 4), "rust async tokio runtime"),
("d4", bias_vec(3, 4), "sql database query optimization"),
];
for (id, vec, text) in &entries {
col.upsert_with_text(
VectorEntry {
id: id.to_string(),
vector: vec.clone(),
metadata: Some(json!({ "tag": id })),
},
text,
)
.unwrap();
}
let graph = db.memory();
graph.add_node("anchor", "session", None).unwrap();
graph.add_node("d1", "doc", None).unwrap();
graph.add_node("d2", "doc", None).unwrap();
graph.add_node("d3", "doc", None).unwrap();
graph.add_node("d4", "doc", None).unwrap();
graph.add_edge("anchor", "d1", "related", 0.9).unwrap();
graph.add_edge("anchor", "d3", "related", 0.7).unwrap();
graph.add_edge("d1", "d2", "linked", 0.5).unwrap();
}
#[test]
fn tri_modal_basic_all_channels() {
let db = open();
seed_db(&db);
let query = TriModalQuery {
anchor_node: "anchor".to_string(),
embedding: bias_vec(0, 4), text_query: "rust".to_string(), collection: "docs".to_string(),
graph_depth: 2,
top_k: 4,
alpha: 0.4,
beta: 0.3,
gamma: 0.3,
filter: None,
};
let results = db.tri_modal_query(&query).unwrap();
assert!(!results.is_empty(), "should return results");
assert!(results.len() <= 4, "should respect top_k");
for w in results.windows(2) {
assert!(
w[0].rank_score >= w[1].rank_score,
"results must be sorted by rank_score desc"
);
}
let d1 = results.iter().find(|r| r.id == "d1");
assert!(d1.is_some(), "d1 should appear in results");
}
#[test]
fn tri_modal_pure_vector_mode() {
let db = open();
seed_db(&db);
let query_vector = bias_vec(1, 4);
let tri_results = db
.tri_modal_query(&TriModalQuery {
anchor_node: "anchor".to_string(),
embedding: query_vector.clone(),
text_query: String::new(),
collection: "docs".to_string(),
graph_depth: 2,
top_k: 4,
alpha: 1.0,
beta: 0.0,
gamma: 0.0,
filter: None,
})
.unwrap();
assert!(!tri_results.is_empty(), "pure vector mode should return results");
for r in &tri_results {
assert!(r.graph_weight.is_none(), "graph_weight should be None in pure vector mode");
assert!(r.fts_rank.is_none(), "fts_rank should be None in pure vector mode");
assert!(r.vector_score.is_some(), "vector_score should be present");
}
let top = &tri_results[0];
assert_eq!(top.id, "d2", "d2 should be the top result in pure vector mode");
}
#[test]
fn tri_modal_pure_fts_mode() {
let db = open();
seed_db(&db);
let results = db
.tri_modal_query(&TriModalQuery {
anchor_node: "anchor".to_string(),
embedding: vec![0.0; 4],
text_query: "python machine learning".to_string(),
collection: "docs".to_string(),
graph_depth: 0,
top_k: 4,
alpha: 0.0,
beta: 0.0,
gamma: 1.0,
filter: None,
})
.unwrap();
assert!(!results.is_empty(), "pure FTS mode should return results");
for r in &results {
assert!(r.vector_score.is_none(), "vector_score should be None in pure FTS mode");
assert!(r.graph_weight.is_none(), "graph_weight should be None in pure FTS mode");
assert!(r.fts_rank.is_some(), "fts_rank should be present");
}
let top = &results[0];
assert_eq!(top.id, "d2", "d2 should be the top FTS result for 'python machine learning'");
}
#[test]
fn tri_modal_weight_validation_rejects_bad_weights() {
let db = open();
seed_db(&db);
let result = db.tri_modal_query(&TriModalQuery {
anchor_node: "anchor".to_string(),
embedding: bias_vec(0, 4),
text_query: "rust".to_string(),
collection: "docs".to_string(),
graph_depth: 2,
top_k: 4,
alpha: 0.5,
beta: 0.5,
gamma: 0.5, filter: None,
});
assert!(result.is_err(), "should reject weights that don't sum to ~1.0");
let err_msg = result.unwrap_err().to_string();
assert!(
err_msg.contains("alpha") || err_msg.contains("gamma") || err_msg.contains("1.0"),
"error message should mention weight constraint"
);
}
#[test]
fn tri_modal_weight_validation_accepts_valid_weights() {
let db = open();
seed_db(&db);
let result = db.tri_modal_query(&TriModalQuery {
anchor_node: "anchor".to_string(),
embedding: bias_vec(0, 4),
text_query: "rust".to_string(),
collection: "docs".to_string(),
graph_depth: 2,
top_k: 4,
alpha: 1.0 / 3.0,
beta: 1.0 / 3.0,
gamma: 1.0 / 3.0,
filter: None,
});
assert!(result.is_ok(), "equal thirds should be accepted (within tolerance)");
}
#[test]
fn tri_modal_empty_collection_returns_empty() {
let db = open();
let results = db
.tri_modal_query(&TriModalQuery {
anchor_node: "nonexistent".to_string(),
embedding: vec![0.1, 0.2, 0.3, 0.4],
text_query: "anything".to_string(),
collection: "empty_col".to_string(),
graph_depth: 2,
top_k: 10,
alpha: 0.4,
beta: 0.3,
gamma: 0.3,
filter: None,
})
.unwrap();
assert!(results.is_empty(), "empty collection should return empty results");
}
#[test]
fn tri_modal_no_fts_text_skips_fts() {
let db = open();
seed_db(&db);
let results = db
.tri_modal_query(&TriModalQuery {
anchor_node: "anchor".to_string(),
embedding: bias_vec(0, 4),
text_query: String::new(), collection: "docs".to_string(),
graph_depth: 2,
top_k: 4,
alpha: 0.5,
beta: 0.3,
gamma: 0.2,
filter: None,
})
.unwrap();
assert!(!results.is_empty(), "should return results even without FTS query");
for r in &results {
assert!(r.fts_rank.is_none(), "fts_rank should be None when text_query is empty");
}
}
#[test]
fn tri_modal_respects_top_k() {
let db = open();
seed_db(&db);
let results = db
.tri_modal_query(&TriModalQuery {
anchor_node: "anchor".to_string(),
embedding: bias_vec(0, 4),
text_query: "rust".to_string(),
collection: "docs".to_string(),
graph_depth: 3,
top_k: 2,
alpha: 0.34,
beta: 0.33,
gamma: 0.33,
filter: None,
})
.unwrap();
assert!(results.len() <= 2, "should return at most top_k results");
}