use crate::collection::types::Collection;
use crate::distance::DistanceMetric;
use crate::guardrails::{GuardRails, QueryLimits};
use crate::point::Point;
use std::collections::HashMap;
use std::sync::Arc;
use tempfile::TempDir;
fn make_collection(dir: &TempDir) -> Collection {
let col = Collection::create(dir.path().join("e2e_col"), 4, DistanceMetric::Cosine)
.expect("create failed");
let points: Vec<Point> = (0u64..10)
.map(|i| {
#[allow(clippy::cast_precision_loss)]
Point::new(
i,
vec![i as f32 / 10.0, 0.1, 0.1, 0.1],
Some(serde_json::json!({
"idx": i,
"category": if i % 2 == 0 { "even" } else { "odd" },
"alt_vec": [i as f64 / 10.0, 0.2, 0.2, 0.2]
})),
)
})
.collect();
col.upsert(points).expect("upsert failed");
col
}
#[test]
fn test_execute_query_str_parses_and_executes() {
let dir = TempDir::new().unwrap();
let col = make_collection(&dir);
let params = HashMap::new();
let result = col.execute_query_str("SELECT * FROM col LIMIT 5;", ¶ms);
assert!(result.is_ok(), "execute_query_str should succeed");
assert!(result.unwrap().len() <= 5);
}
#[test]
fn test_execute_query_str_caches_repeated_calls() {
let dir = TempDir::new().unwrap();
let col = make_collection(&dir);
let params = HashMap::new();
let sql = "SELECT * FROM col LIMIT 3;";
let r1 = col
.execute_query_str(sql, ¶ms)
.expect("first call failed");
let r2 = col
.execute_query_str(sql, ¶ms)
.expect("second call failed");
assert_eq!(r1.len(), r2.len(), "Cached and fresh results should match");
}
#[test]
fn test_execute_query_str_rejects_invalid_sql() {
let dir = TempDir::new().unwrap();
let col = make_collection(&dir);
let params = HashMap::new();
let result = col.execute_query_str("NOT VALID SQL !!!", ¶ms);
assert!(result.is_err(), "Invalid SQL should return an error");
}
#[test]
fn test_execute_query_str_metadata_filter() {
let dir = TempDir::new().unwrap();
let col = make_collection(&dir);
let params = HashMap::new();
let result = col
.execute_query_str(
"SELECT * FROM col WHERE category = 'even' LIMIT 10;",
¶ms,
)
.expect("query failed");
for r in &result {
if let Some(ref payload) = r.point.payload {
assert_eq!(
payload.get("category").and_then(|v| v.as_str()),
Some("even"),
"Non-even category in result: {:?}",
payload
);
}
}
}
#[test]
fn test_e2e_guardrails_cardinality_respected() {
let dir = TempDir::new().unwrap();
let mut col = make_collection(&dir);
let limits = QueryLimits {
max_cardinality: 3, ..QueryLimits::default()
};
col.guard_rails = Arc::new(GuardRails::with_limits(limits));
let params = HashMap::new();
let result = col.execute_query_str("SELECT * FROM col LIMIT 10;", ¶ms);
assert!(result.is_err(), "Cardinality guardrail should fire");
let err = result.unwrap_err().to_string();
assert!(
err.contains("Guard-rail") || err.contains("cardinality") || err.contains("Cardinality"),
"Unexpected error message: {err}"
);
}
#[test]
fn test_e2e_guardrails_timeout_zero_disables_check() {
let dir = TempDir::new().unwrap();
let mut col = make_collection(&dir);
col.guard_rails = Arc::new(GuardRails::with_limits(QueryLimits {
timeout_ms: 0,
..QueryLimits::default()
}));
let params = HashMap::new();
let result = col.execute_query_str("SELECT * FROM col LIMIT 5;", ¶ms);
assert!(
result.is_ok(),
"timeout_ms=0 should disable the guard-rail, not reject the query"
);
}
#[test]
fn test_e2e_guardrails_circuit_breaker_state() {
let dir = TempDir::new().unwrap();
let mut col = make_collection(&dir);
col.guard_rails = Arc::new(GuardRails::with_limits(QueryLimits {
max_cardinality: 1, circuit_failure_threshold: 2,
circuit_recovery_seconds: 60,
..QueryLimits::default()
}));
let params = HashMap::new();
let sql = "SELECT * FROM col LIMIT 10;";
let _ = col.execute_query_str(sql, ¶ms); let _ = col.execute_query_str(sql, ¶ms);
let state = col.guard_rails.circuit_breaker.state();
assert_eq!(
state,
crate::guardrails::CircuitState::Open,
"Circuit breaker should open after 2 failures"
);
}
#[test]
fn test_e2e_match_single_pattern_no_panic() {
let dir = TempDir::new().unwrap();
let col = make_collection(&dir);
let params = HashMap::new();
let sql = "MATCH (a) RETURN a LIMIT 5;";
let query = crate::velesql::Parser::parse(sql).expect("parse failed");
let _ = col.execute_query(&query, ¶ms);
}
#[test]
fn test_e2e_similarity_primary_vector_field() {
let dir = TempDir::new().unwrap();
let col = make_collection(&dir);
let mut params = HashMap::new();
params.insert("v".to_string(), serde_json::json!([0.5, 0.1, 0.1, 0.1]));
let result = col
.execute_query_str(
"SELECT * FROM col WHERE similarity(vector, $v) > 0.5 LIMIT 5;",
¶ms,
)
.expect("primary vector similarity should succeed");
for r in &result {
assert!(r.score >= 0.5, "Score {} below threshold 0.5", r.score);
}
}
#[test]
fn test_e2e_similarity_named_payload_vector_field() {
let dir = TempDir::new().unwrap();
let col = make_collection(&dir);
let mut params = HashMap::new();
params.insert("v".to_string(), serde_json::json!([0.5, 0.2, 0.2, 0.2]));
let result = col.execute_query_str(
"SELECT * FROM col WHERE similarity(alt_vec, $v) > 0.0 LIMIT 10;",
¶ms,
);
match &result {
Err(e) => {
let msg = e.to_string();
assert!(
!msg.contains("Only 'vector' field is supported"),
"Multi-vector restriction should be removed: {msg}"
);
}
Ok(results) => {
for r in results {
assert!(r.score >= 0.0, "Negative score: {}", r.score);
}
}
}
}
#[test]
fn test_e2e_cbo_with_vector_and_filter_no_panic() {
let dir = TempDir::new().unwrap();
let col = make_collection(&dir);
let mut params = HashMap::new();
params.insert("v".to_string(), serde_json::json!([0.5, 0.1, 0.1, 0.1]));
let result = col.execute_query_str(
"SELECT * FROM col WHERE vector NEAR $v AND category = 'even' LIMIT 5;",
¶ms,
);
match result {
Ok(results) => {
for r in &results {
if let Some(ref payload) = r.point.payload {
assert_eq!(
payload.get("category").and_then(|v| v.as_str()),
Some("even")
);
}
}
}
Err(e) => {
let msg = e.to_string();
assert!(
msg.contains("Guard-rail") || msg.contains("Query"),
"Unexpected CBO error: {msg}"
);
}
}
}