use luci::index::Index;
use luci::mapping::{FieldType, Mapping};
use serde_json::json;
fn search(
index: &mut Index,
query: serde_json::Value,
size: usize,
) -> luci::search::results::SearchResults {
let expr = luci::search::expression::parse_search(query, size).unwrap();
index.search(&expr).unwrap()
}
fn test_dir(name: &str) -> std::path::PathBuf {
let dir =
std::env::temp_dir().join(format!("luci_m6_integration_{}_{name}", std::process::id()));
let _ = std::fs::remove_dir_all(&dir);
dir
}
fn cleanup(path: &std::path::Path) {
let _ = std::fs::remove_dir_all(path);
}
fn build_geo_index(name: &str) -> (std::path::PathBuf, Index) {
let path = test_dir(name);
let schema = Mapping::builder()
.field("name", FieldType::Text)
.field("city", FieldType::Keyword)
.field("location", FieldType::GeoPoint)
.build();
let index = Index::create_with_mapping(&path, schema).unwrap();
index.bulk(vec![
json!({"name": "Times Square", "city": "nyc", "location": {"lat": 40.7580, "lon": -73.9855}}),
json!({"name": "Central Park", "city": "nyc", "location": {"lat": 40.7829, "lon": -73.9654}}),
json!({"name": "Brooklyn Bridge", "city": "nyc", "location": {"lat": 40.7061, "lon": -73.9969}}),
json!({"name": "Big Ben", "city": "london", "location": {"lat": 51.5007, "lon": -0.1246}}),
json!({"name": "Tower Bridge", "city": "london", "location": {"lat": 51.5055, "lon": -0.0754}}),
json!({"name": "Eiffel Tower", "city": "paris", "location": {"lat": 48.8584, "lon": 2.2945}}),
]).unwrap();
(path, index)
}
#[test]
fn geo_distance_query() {
let (path, mut index) = build_geo_index("distance");
let results = search(
&mut index,
json!({
"geo_distance": {
"distance": "5km",
"location": {"lat": 40.7580, "lon": -73.9855}
}
}),
10,
);
assert!(results.total_hits().value >= 2);
assert!(results.total_hits().value <= 3);
for hit in results.iter() {
let source = hit.source().unwrap();
assert_eq!(source["city"], "nyc");
}
cleanup(&path);
}
#[test]
fn geo_distance_large_radius() {
let (path, mut index) = build_geo_index("large_radius");
let results = search(
&mut index,
json!({
"geo_distance": {
"distance": "1000km",
"location": {"lat": 51.5, "lon": -0.1}
}
}),
10,
);
assert!(results.total_hits().value >= 3);
let cities: Vec<String> = results
.iter()
.filter_map(|h| h.source()?.get("city")?.as_str().map(String::from))
.collect();
assert!(cities.iter().any(|c| c == "london"));
assert!(cities.iter().any(|c| c == "paris"));
assert!(!cities.iter().any(|c| c == "nyc"));
cleanup(&path);
}
#[test]
fn geo_bounding_box_query() {
let (path, mut index) = build_geo_index("bbox");
let results = search(
&mut index,
json!({
"geo_bounding_box": {
"location": {
"top_left": {"lat": 40.80, "lon": -74.02},
"bottom_right": {"lat": 40.70, "lon": -73.95}
}
}
}),
10,
);
assert!(results.total_hits().value >= 2);
for hit in results.iter() {
let source = hit.source().unwrap();
assert_eq!(source["city"], "nyc");
}
cleanup(&path);
}
#[test]
fn geo_plus_text_query() {
let (path, mut index) = build_geo_index("combined");
let results = search(
&mut index,
json!({
"bool": {
"must": [{"match": {"name": "bridge"}}],
"filter": [{
"geo_distance": {
"distance": "100km",
"location": {"lat": 51.5, "lon": -0.1}
}
}]
}
}),
10,
);
assert_eq!(results.total_hits().value, 1);
let source = results.hit(0).unwrap().source().unwrap();
assert_eq!(source["name"], "Tower Bridge");
cleanup(&path);
}