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_m7_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);
}
#[test]
fn cross_object_matching_prevented() {
let path = test_dir("cross_object");
let schema = Mapping::builder()
.field("product", FieldType::Text)
.field("offers", FieldType::Nested)
.field("offers.seller", FieldType::Keyword)
.field("offers.price", FieldType::Keyword)
.build();
let mut index = Index::create_with_mapping(&path, schema).unwrap();
index
.bulk(vec![json!({
"product": "laptop",
"offers": [
{"seller": "Alice", "price": "999"},
{"seller": "Bob", "price": "1299"}
]
})])
.unwrap();
let results = search(
&mut index,
json!({
"nested": {
"path": "offers",
"query": {
"bool": {
"must": [
{"term": {"offers.seller": "Alice"}},
{"term": {"offers.price": "1299"}}
]
}
}
}
}),
10,
);
assert_eq!(
results.total_hits().value,
0,
"cross-object match should be prevented: Alice doesn't sell at 1299"
);
let results = search(
&mut index,
json!({
"nested": {
"path": "offers",
"query": {
"bool": {
"must": [
{"term": {"offers.seller": "Alice"}},
{"term": {"offers.price": "999"}}
]
}
}
}
}),
10,
);
assert_eq!(
results.total_hits().value,
1,
"Alice selling at 999 should match"
);
cleanup(&path);
}
#[test]
fn nested_returns_parent() {
let path = test_dir("parent_return");
let schema = Mapping::builder()
.field("title", FieldType::Text)
.field("tags", FieldType::Nested)
.field("tags.name", FieldType::Keyword)
.field("tags.score", FieldType::Keyword)
.build();
let mut index = Index::create_with_mapping(&path, schema).unwrap();
index
.bulk(vec![
json!({
"title": "article one",
"tags": [
{"name": "rust", "score": "high"},
{"name": "search", "score": "medium"}
]
}),
json!({
"title": "article two",
"tags": [
{"name": "python", "score": "high"}
]
}),
])
.unwrap();
let results = search(
&mut index,
json!({
"nested": {
"path": "tags",
"query": {"term": {"tags.name": "rust"}}
}
}),
10,
);
assert_eq!(results.total_hits().value, 1);
let source = results.hit(0).unwrap().source().unwrap();
assert_eq!(source["title"], "article one");
let results = search(
&mut index,
json!({
"nested": {
"path": "tags",
"query": {"term": {"tags.name": "python"}}
}
}),
10,
);
assert_eq!(results.total_hits().value, 1);
let source = results.hit(0).unwrap().source().unwrap();
assert_eq!(source["title"], "article two");
cleanup(&path);
}
#[test]
fn nested_matches_correct_parent() {
let path = test_dir("correct_parent");
let schema = Mapping::builder()
.field("product", FieldType::Text)
.field("offers", FieldType::Nested)
.field("offers.seller", FieldType::Keyword)
.build();
let mut index = Index::create_with_mapping(&path, schema).unwrap();
index
.bulk(vec![
json!({"product": "laptop", "offers": [{"seller": "alice"}]}),
json!({"product": "phone", "offers": [{"seller": "bob"}]}),
json!({"product": "tablet", "offers": [{"seller": "charlie"}]}),
json!({"product": "monitor", "offers": [{"seller": "diana"}, {"seller": "eve"}]}),
])
.unwrap();
let results = search(
&mut index,
json!({
"nested": {
"path": "offers",
"query": {"term": {"offers.seller": "diana"}}
}
}),
10,
);
assert_eq!(results.total_hits().value, 1);
let source = results.hit(0).unwrap().source().unwrap();
assert_eq!(source["product"], "monitor");
let results = search(
&mut index,
json!({
"nested": {
"path": "offers",
"query": {"term": {"offers.seller": "bob"}}
}
}),
10,
);
assert_eq!(results.total_hits().value, 1);
let source = results.hit(0).unwrap().source().unwrap();
assert_eq!(source["product"], "phone");
cleanup(&path);
}
#[test]
fn multiple_nested_objects() {
let path = test_dir("multi_nested");
let schema = Mapping::builder()
.field("name", FieldType::Text)
.field("skills", FieldType::Nested)
.field("skills.name", FieldType::Keyword)
.build();
let mut index = Index::create_with_mapping(&path, schema).unwrap();
index
.bulk(vec![json!({
"name": "developer",
"skills": [
{"name": "rust"},
{"name": "python"},
{"name": "javascript"}
]
})])
.unwrap();
let results = search(
&mut index,
json!({
"nested": {
"path": "skills",
"query": {"term": {"skills.name": "python"}}
}
}),
10,
);
assert_eq!(results.total_hits().value, 1);
let results = search(
&mut index,
json!({
"nested": {
"path": "skills",
"query": {"term": {"skills.name": "go"}}
}
}),
10,
);
assert_eq!(results.total_hits().value, 0);
cleanup(&path);
}