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("").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); }