mirror of
https://github.com/meilisearch/MeiliSearch
synced 2024-12-25 14:10:06 +01:00
630 lines
18 KiB
Rust
630 lines
18 KiB
Rust
use std::convert::Into;
|
|
|
|
use serde_json::json;
|
|
use serde_json::Value;
|
|
use std::cell::RefCell;
|
|
use std::sync::Mutex;
|
|
|
|
#[macro_use]
|
|
mod common;
|
|
|
|
#[actix_rt::test]
|
|
async fn placeholder_search_with_limit() {
|
|
let mut server = common::Server::test_server().await;
|
|
|
|
let query = json! ({
|
|
"limit": 3
|
|
});
|
|
|
|
test_post_get_search!(server, query, |response, status_code| {
|
|
assert_eq!(status_code, 200);
|
|
assert_eq!(response["hits"].as_array().unwrap().len(), 3);
|
|
});
|
|
}
|
|
|
|
#[actix_rt::test]
|
|
async fn placeholder_search_with_offset() {
|
|
let mut server = common::Server::test_server().await;
|
|
|
|
let query = json!({
|
|
"limit": 6,
|
|
});
|
|
|
|
// hack to take a value out of macro (must implement UnwindSafe)
|
|
let expected = Mutex::new(RefCell::new(Vec::new()));
|
|
|
|
test_post_get_search!(server, query, |response, status_code| {
|
|
assert_eq!(status_code, 200);
|
|
// take results at offset 3 as reference
|
|
let lock = expected.lock().unwrap();
|
|
lock.replace(response["hits"].as_array().unwrap()[3..6].to_vec());
|
|
});
|
|
let expected = expected.into_inner().unwrap().into_inner();
|
|
|
|
let query = json!({
|
|
"limit": 3,
|
|
"offset": 3,
|
|
});
|
|
test_post_get_search!(server, query, |response, status_code| {
|
|
assert_eq!(status_code, 200);
|
|
let response = response["hits"].as_array().unwrap();
|
|
assert_eq!(&expected, response);
|
|
});
|
|
}
|
|
|
|
#[actix_rt::test]
|
|
async fn placeholder_search_with_attribute_to_highlight_wildcard() {
|
|
// there should be no highlight in placeholder search
|
|
let mut server = common::Server::test_server().await;
|
|
|
|
let query = json!({
|
|
"limit": 1,
|
|
"attributesToHighlight": ["*"]
|
|
});
|
|
|
|
test_post_get_search!(server, query, |response, status_code| {
|
|
assert_eq!(status_code, 200);
|
|
let result = response["hits"].as_array().unwrap()[0].as_object().unwrap();
|
|
for value in result.values() {
|
|
assert!(value.to_string().find("<em>").is_none());
|
|
}
|
|
});
|
|
}
|
|
|
|
#[actix_rt::test]
|
|
async fn placeholder_search_with_matches() {
|
|
// matches is always empty
|
|
let mut server = common::Server::test_server().await;
|
|
|
|
let query = json!({
|
|
"matches": true
|
|
});
|
|
|
|
test_post_get_search!(server, query, |response, status_code| {
|
|
assert_eq!(status_code, 200);
|
|
let result = response["hits"]
|
|
.as_array()
|
|
.unwrap()
|
|
.iter()
|
|
.map(|v| v.as_object().unwrap()["_matchesInfo"].clone())
|
|
.all(|m| m.as_object().unwrap().is_empty());
|
|
assert!(result);
|
|
});
|
|
}
|
|
|
|
#[actix_rt::test]
|
|
async fn placeholder_search_witch_crop() {
|
|
// placeholder search crop always crop from beggining
|
|
let mut server = common::Server::test_server().await;
|
|
|
|
let query = json!({
|
|
"attributesToCrop": ["about"],
|
|
"cropLength": 20
|
|
});
|
|
|
|
test_post_get_search!(server, query, |response, status_code| {
|
|
assert_eq!(status_code, 200);
|
|
|
|
let hits = response["hits"].as_array().unwrap();
|
|
|
|
for hit in hits {
|
|
let hit = hit.as_object().unwrap();
|
|
let formatted = hit["_formatted"].as_object().unwrap();
|
|
|
|
let about = hit["about"].as_str().unwrap();
|
|
let about_formatted = formatted["about"].as_str().unwrap();
|
|
// the formatted about length should be about 20 characters long
|
|
assert!(about_formatted.len() < 20 + 10);
|
|
// the formatted part should be located at the beginning of the original one
|
|
assert_eq!(about.find(&about_formatted).unwrap(), 0);
|
|
}
|
|
});
|
|
}
|
|
|
|
#[actix_rt::test]
|
|
async fn placeholder_search_with_attributes_to_retrieve() {
|
|
let mut server = common::Server::test_server().await;
|
|
|
|
let query = json!({
|
|
"limit": 1,
|
|
"attributesToRetrieve": ["gender", "about"],
|
|
});
|
|
|
|
test_post_get_search!(server, query, |response, _status_code| {
|
|
let hit = response["hits"].as_array().unwrap()[0].as_object().unwrap();
|
|
assert_eq!(hit.values().count(), 2);
|
|
let _ = hit["gender"];
|
|
let _ = hit["about"];
|
|
});
|
|
}
|
|
|
|
#[actix_rt::test]
|
|
async fn placeholder_search_with_filter() {
|
|
let mut server = common::Server::test_server().await;
|
|
|
|
let query = json!({
|
|
"filters": "color='green'"
|
|
});
|
|
|
|
test_post_get_search!(server, query, |response, _status_code| {
|
|
let hits = response["hits"].as_array().unwrap();
|
|
assert!(hits.iter().all(|v| v["color"].as_str().unwrap() == "Green"));
|
|
});
|
|
|
|
let query = json!({
|
|
"filters": "tags=bug"
|
|
});
|
|
|
|
test_post_get_search!(server, query, |response, _status_code| {
|
|
let hits = response["hits"].as_array().unwrap();
|
|
let value = Value::String(String::from("bug"));
|
|
assert!(hits
|
|
.iter()
|
|
.all(|v| v["tags"].as_array().unwrap().contains(&value)));
|
|
});
|
|
|
|
let query = json!({
|
|
"filters": "color='green' AND (tags='bug' OR tags='wontfix')"
|
|
});
|
|
test_post_get_search!(server, query, |response, _status_code| {
|
|
let hits = response["hits"].as_array().unwrap();
|
|
let bug = Value::String(String::from("bug"));
|
|
let wontfix = Value::String(String::from("wontfix"));
|
|
assert!(hits.iter().all(|v| v["color"].as_str().unwrap() == "Green"
|
|
&& v["tags"].as_array().unwrap().contains(&bug)
|
|
|| v["tags"].as_array().unwrap().contains(&wontfix)));
|
|
});
|
|
}
|
|
|
|
#[actix_rt::test]
|
|
async fn placeholder_test_faceted_search_valid() {
|
|
let mut server = common::Server::test_server().await;
|
|
|
|
// simple tests on attributes with string value
|
|
let body = json!({
|
|
"attributesForFaceting": ["color"]
|
|
});
|
|
|
|
server.update_all_settings(body).await;
|
|
|
|
let query = json!({
|
|
"facetFilters": ["color:green"]
|
|
});
|
|
|
|
test_post_get_search!(server, query, |response, _status_code| {
|
|
assert!(!response.get("hits").unwrap().as_array().unwrap().is_empty());
|
|
assert!(response
|
|
.get("hits")
|
|
.unwrap()
|
|
.as_array()
|
|
.unwrap()
|
|
.iter()
|
|
.all(|value| value.get("color").unwrap() == "Green"));
|
|
});
|
|
|
|
let query = json!({
|
|
"facetFilters": [["color:blue"]]
|
|
});
|
|
|
|
test_post_get_search!(server, query, |response, _status_code| {
|
|
assert!(!response.get("hits").unwrap().as_array().unwrap().is_empty());
|
|
assert!(response
|
|
.get("hits")
|
|
.unwrap()
|
|
.as_array()
|
|
.unwrap()
|
|
.iter()
|
|
.all(|value| value.get("color").unwrap() == "blue"));
|
|
});
|
|
|
|
let query = json!({
|
|
"facetFilters": ["color:Blue"]
|
|
});
|
|
|
|
test_post_get_search!(server, query, |response, _status_code| {
|
|
assert!(!response.get("hits").unwrap().as_array().unwrap().is_empty());
|
|
assert!(response
|
|
.get("hits")
|
|
.unwrap()
|
|
.as_array()
|
|
.unwrap()
|
|
.iter()
|
|
.all(|value| value.get("color").unwrap() == "blue"));
|
|
});
|
|
|
|
// test on arrays: ["tags:bug"]
|
|
let body = json!({
|
|
"attributesForFaceting": ["color", "tags"]
|
|
});
|
|
|
|
server.update_all_settings(body).await;
|
|
|
|
let query = json!({
|
|
"facetFilters": ["tags:bug"]
|
|
});
|
|
test_post_get_search!(server, query, |response, _status_code| {
|
|
assert!(!response.get("hits").unwrap().as_array().unwrap().is_empty());
|
|
assert!(response
|
|
.get("hits")
|
|
.unwrap()
|
|
.as_array()
|
|
.unwrap()
|
|
.iter()
|
|
.all(|value| value
|
|
.get("tags")
|
|
.unwrap()
|
|
.as_array()
|
|
.unwrap()
|
|
.contains(&Value::String("bug".to_owned()))));
|
|
});
|
|
|
|
// test and: ["color:blue", "tags:bug"]
|
|
let query = json!({
|
|
"facetFilters": ["color:blue", "tags:bug"]
|
|
});
|
|
test_post_get_search!(server, query, |response, _status_code| {
|
|
assert!(!response.get("hits").unwrap().as_array().unwrap().is_empty());
|
|
assert!(response
|
|
.get("hits")
|
|
.unwrap()
|
|
.as_array()
|
|
.unwrap()
|
|
.iter()
|
|
.all(|value| value.get("color").unwrap() == "blue"
|
|
&& value
|
|
.get("tags")
|
|
.unwrap()
|
|
.as_array()
|
|
.unwrap()
|
|
.contains(&Value::String("bug".to_owned()))));
|
|
});
|
|
|
|
// test or: [["color:blue", "color:green"]]
|
|
let query = json!({
|
|
"facetFilters": [["color:blue", "color:green"]]
|
|
});
|
|
test_post_get_search!(server, query, |response, _status_code| {
|
|
assert!(!response.get("hits").unwrap().as_array().unwrap().is_empty());
|
|
assert!(response
|
|
.get("hits")
|
|
.unwrap()
|
|
.as_array()
|
|
.unwrap()
|
|
.iter()
|
|
.all(|value| value.get("color").unwrap() == "blue"
|
|
|| value.get("color").unwrap() == "Green"));
|
|
});
|
|
// test and-or: ["tags:bug", ["color:blue", "color:green"]]
|
|
let query = json!({
|
|
"facetFilters": ["tags:bug", ["color:blue", "color:green"]]
|
|
});
|
|
test_post_get_search!(server, query, |response, _status_code| {
|
|
assert!(!response.get("hits").unwrap().as_array().unwrap().is_empty());
|
|
assert!(response
|
|
.get("hits")
|
|
.unwrap()
|
|
.as_array()
|
|
.unwrap()
|
|
.iter()
|
|
.all(|value| value
|
|
.get("tags")
|
|
.unwrap()
|
|
.as_array()
|
|
.unwrap()
|
|
.contains(&Value::String("bug".to_owned()))
|
|
&& (value.get("color").unwrap() == "blue"
|
|
|| value.get("color").unwrap() == "Green")));
|
|
});
|
|
}
|
|
|
|
#[actix_rt::test]
|
|
async fn placeholder_test_faceted_search_invalid() {
|
|
let mut server = common::Server::test_server().await;
|
|
|
|
//no faceted attributes set
|
|
let query = json!({
|
|
"facetFilters": ["color:blue"]
|
|
});
|
|
test_post_get_search!(server, query, |_response, status_code| assert_ne!(
|
|
status_code,
|
|
202
|
|
));
|
|
|
|
let body = json!({
|
|
"attributesForFaceting": ["color", "tags"]
|
|
});
|
|
server.update_all_settings(body).await;
|
|
// empty arrays are error
|
|
// []
|
|
let query = json!({
|
|
"facetFilters": []
|
|
});
|
|
test_post_get_search!(server, query, |_response, status_code| assert_ne!(
|
|
status_code,
|
|
202
|
|
));
|
|
// [[]]
|
|
let query = json!({
|
|
"facetFilters": [[]]
|
|
});
|
|
test_post_get_search!(server, query, |_response, status_code| assert_ne!(
|
|
status_code,
|
|
202
|
|
));
|
|
// ["color:green", []]
|
|
let query = json!({
|
|
"facetFilters": ["color:green", []]
|
|
});
|
|
test_post_get_search!(server, query, |_response, status_code| assert_ne!(
|
|
status_code,
|
|
202
|
|
));
|
|
|
|
// too much depth
|
|
// [[[]]]
|
|
let query = json!({
|
|
"facetFilters": [[[]]]
|
|
});
|
|
test_post_get_search!(server, query, |_response, status_code| assert_ne!(
|
|
status_code,
|
|
202
|
|
));
|
|
// [["color:green", ["color:blue"]]]
|
|
let query = json!({
|
|
"facetFilters": [["color:green", ["color:blue"]]]
|
|
});
|
|
test_post_get_search!(server, query, |_response, status_code| assert_ne!(
|
|
status_code,
|
|
202
|
|
));
|
|
// "color:green"
|
|
let query = json!({
|
|
"facetFilters": "color:green"
|
|
});
|
|
test_post_get_search!(server, query, |_response, status_code| assert_ne!(
|
|
status_code,
|
|
202
|
|
));
|
|
}
|
|
|
|
#[actix_rt::test]
|
|
async fn placeholder_test_facet_count() {
|
|
let mut server = common::Server::test_server().await;
|
|
|
|
// test without facet distribution
|
|
let query = json!({});
|
|
test_post_get_search!(server, query, |response, _status_code| {
|
|
assert!(response.get("exhaustiveFacetsCount").is_none());
|
|
assert!(response.get("facetsDistribution").is_none());
|
|
});
|
|
|
|
// test no facets set, search on color
|
|
let query = json!({
|
|
"facetsDistribution": ["color"]
|
|
});
|
|
test_post_get_search!(server, query.clone(), |_response, status_code| {
|
|
assert_eq!(status_code, 400);
|
|
});
|
|
|
|
let body = json!({
|
|
"attributesForFaceting": ["color", "tags"]
|
|
});
|
|
server.update_all_settings(body).await;
|
|
// same as before, but now facets are set:
|
|
test_post_get_search!(server, query, |response, _status_code| {
|
|
println!("{}", response);
|
|
assert!(response.get("exhaustiveFacetsCount").is_some());
|
|
assert_eq!(
|
|
response
|
|
.get("facetsDistribution")
|
|
.unwrap()
|
|
.as_object()
|
|
.unwrap()
|
|
.values()
|
|
.count(),
|
|
1
|
|
);
|
|
});
|
|
// searching on color and tags
|
|
let query = json!({
|
|
"facetsDistribution": ["color", "tags"]
|
|
});
|
|
test_post_get_search!(server, query, |response, _status_code| {
|
|
let facets = response
|
|
.get("facetsDistribution")
|
|
.unwrap()
|
|
.as_object()
|
|
.unwrap();
|
|
assert_eq!(facets.values().count(), 2);
|
|
assert_ne!(
|
|
!facets
|
|
.get("color")
|
|
.unwrap()
|
|
.as_object()
|
|
.unwrap()
|
|
.values()
|
|
.count(),
|
|
0
|
|
);
|
|
assert_ne!(
|
|
!facets
|
|
.get("tags")
|
|
.unwrap()
|
|
.as_object()
|
|
.unwrap()
|
|
.values()
|
|
.count(),
|
|
0
|
|
);
|
|
});
|
|
// wildcard
|
|
let query = json!({
|
|
"facetsDistribution": ["*"]
|
|
});
|
|
test_post_get_search!(server, query, |response, _status_code| {
|
|
assert_eq!(
|
|
response
|
|
.get("facetsDistribution")
|
|
.unwrap()
|
|
.as_object()
|
|
.unwrap()
|
|
.values()
|
|
.count(),
|
|
2
|
|
);
|
|
});
|
|
// wildcard with other attributes:
|
|
let query = json!({
|
|
"facetsDistribution": ["color", "*"]
|
|
});
|
|
test_post_get_search!(server, query, |response, _status_code| {
|
|
assert_eq!(
|
|
response
|
|
.get("facetsDistribution")
|
|
.unwrap()
|
|
.as_object()
|
|
.unwrap()
|
|
.values()
|
|
.count(),
|
|
2
|
|
);
|
|
});
|
|
|
|
// empty facet list
|
|
let query = json!({
|
|
"facetsDistribution": []
|
|
});
|
|
test_post_get_search!(server, query, |response, _status_code| {
|
|
assert_eq!(
|
|
response
|
|
.get("facetsDistribution")
|
|
.unwrap()
|
|
.as_object()
|
|
.unwrap()
|
|
.values()
|
|
.count(),
|
|
0
|
|
);
|
|
});
|
|
|
|
// attr not set as facet passed:
|
|
let query = json!({
|
|
"facetsDistribution": ["gender"]
|
|
});
|
|
test_post_get_search!(server, query, |_response, status_code| {
|
|
assert_eq!(status_code, 400);
|
|
});
|
|
}
|
|
|
|
#[actix_rt::test]
|
|
#[should_panic]
|
|
async fn placeholder_test_bad_facet_distribution() {
|
|
let mut server = common::Server::test_server().await;
|
|
// string instead of array:
|
|
let query = json!({
|
|
"facetsDistribution": "color"
|
|
});
|
|
test_post_get_search!(server, query, |_response, _status_code| {});
|
|
|
|
// invalid value in array:
|
|
let query = json!({
|
|
"facetsDistribution": ["color", true]
|
|
});
|
|
test_post_get_search!(server, query, |_response, _status_code| {});
|
|
}
|
|
|
|
#[actix_rt::test]
|
|
async fn placeholder_test_sort() {
|
|
let mut server = common::Server::test_server().await;
|
|
|
|
let body = json!({
|
|
"rankingRules": ["asc(age)"],
|
|
"attributesForFaceting": ["color"]
|
|
});
|
|
server.update_all_settings(body).await;
|
|
let query = json!({});
|
|
test_post_get_search!(server, query, |response, _status_code| {
|
|
let hits = response["hits"].as_array().unwrap();
|
|
hits.iter()
|
|
.map(|v| v["age"].as_u64().unwrap())
|
|
.fold(0, |prev, cur| {
|
|
assert!(cur >= prev);
|
|
cur
|
|
});
|
|
});
|
|
|
|
let query = json!({
|
|
"facetFilters": ["color:green"]
|
|
});
|
|
test_post_get_search!(server, query, |response, _status_code| {
|
|
let hits = response["hits"].as_array().unwrap();
|
|
hits.iter()
|
|
.map(|v| v["age"].as_u64().unwrap())
|
|
.fold(0, |prev, cur| {
|
|
assert!(cur >= prev);
|
|
cur
|
|
});
|
|
});
|
|
}
|
|
|
|
#[actix_rt::test]
|
|
async fn placeholder_search_with_empty_query() {
|
|
let mut server = common::Server::test_server().await;
|
|
|
|
let query = json! ({
|
|
"q": "",
|
|
"limit": 3
|
|
});
|
|
|
|
test_post_get_search!(server, query, |response, status_code| {
|
|
eprintln!("{}", response);
|
|
assert_eq!(status_code, 200);
|
|
assert_eq!(response["hits"].as_array().unwrap().len(), 3);
|
|
});
|
|
}
|
|
|
|
#[actix_rt::test]
|
|
async fn test_filter_nb_hits_search_placeholder() {
|
|
let mut server = common::Server::with_uid("test");
|
|
|
|
let body = json!({
|
|
"uid": "test",
|
|
"primaryKey": "id",
|
|
});
|
|
|
|
server.create_index(body).await;
|
|
let documents = json!([
|
|
{
|
|
"id": 1,
|
|
"content": "a",
|
|
"color": "green",
|
|
"size": 1,
|
|
},
|
|
{
|
|
"id": 2,
|
|
"content": "a",
|
|
"color": "green",
|
|
"size": 2,
|
|
},
|
|
{
|
|
"id": 3,
|
|
"content": "a",
|
|
"color": "blue",
|
|
"size": 3,
|
|
},
|
|
]);
|
|
|
|
server.add_or_update_multiple_documents(documents).await;
|
|
let (response, _) = server.search_post(json!({})).await;
|
|
assert_eq!(response["nbHits"], 3);
|
|
|
|
server.update_distinct_attribute(json!("color")).await;
|
|
|
|
let (response, _) = server.search_post(json!({})).await;
|
|
assert_eq!(response["nbHits"], 2);
|
|
|
|
let (response, _) = server.search_post(json!({"filters": "size < 3"})).await;
|
|
println!("result: {}", response);
|
|
assert_eq!(response["nbHits"], 1);
|
|
}
|