diff --git a/CHANGELOG.md b/CHANGELOG.md index 44ccab2d5..f6f40e3e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ - Add support for error code reporting (#703) - Allow the dashboard to query private servers (#732) - Add telemetry (#720) + - Add post route for search (#735) ## v0.10.1 diff --git a/Cargo.lock b/Cargo.lock index de6c9650a..fc0d453bb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1604,6 +1604,7 @@ dependencies = [ "serde", "serde_json", "serde_qs", + "serde_url_params", "sha2", "siphasher", "slice-group-by", @@ -2476,6 +2477,16 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_url_params" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d24680ccd1ad7cdee9e8affa70f37d081b3d14d3800d33a28f474d0f7a55f305" +dependencies = [ + "serde", + "url", +] + [[package]] name = "serde_urlencoded" version = "0.6.1" diff --git a/meilisearch-http/Cargo.toml b/meilisearch-http/Cargo.toml index 67abfb35b..87862e7d0 100644 --- a/meilisearch-http/Cargo.toml +++ b/meilisearch-http/Cargo.toml @@ -73,6 +73,7 @@ optional = true [dev-dependencies] tempdir = "0.3.7" tokio = { version = "0.2.18", features = ["macros", "time"] } +serde_url_params = "0.2.0" [dev-dependencies.assert-json-diff] git = "https://github.com/qdequele/assert-json-diff" diff --git a/meilisearch-http/src/main.rs b/meilisearch-http/src/main.rs index c60cb2f1a..d7edcce6d 100644 --- a/meilisearch-http/src/main.rs +++ b/meilisearch-http/src/main.rs @@ -69,7 +69,8 @@ async fn main() -> Result<(), MainError> { .wrap( Cors::new() .send_wildcard() - .allowed_header("x-meili-api-key") + .allowed_headers(vec!["content-type","x-meili-api-key"]) + .max_age(86_400) // 24h .finish(), ) .wrap(middleware::Logger::default()) diff --git a/meilisearch-http/src/routes/search.rs b/meilisearch-http/src/routes/search.rs index 0d698eafb..9fff7d6d2 100644 --- a/meilisearch-http/src/routes/search.rs +++ b/meilisearch-http/src/routes/search.rs @@ -3,12 +3,12 @@ use std::collections::{HashSet, HashMap}; use log::warn; use actix_web::web; use actix_web::HttpResponse; -use actix_web_macros::get; -use serde::Deserialize; +use actix_web_macros::{get, post}; +use serde::{Deserialize, Serialize}; use serde_json::Value; use crate::error::{Error, FacetCountError, ResponseError}; -use crate::helpers::meilisearch::IndexSearchExt; +use crate::helpers::meilisearch::{IndexSearchExt, SearchResult}; use crate::helpers::Authentication; use crate::routes::IndexParam; use crate::Data; @@ -17,12 +17,13 @@ use meilisearch_core::facets::FacetFilter; use meilisearch_schema::{Schema, FieldId}; pub fn services(cfg: &mut web::ServiceConfig) { - cfg.service(search_with_url_query); + cfg.service(search_with_post) + .service(search_with_url_query); } -#[derive(Deserialize)] +#[derive(Serialize, Deserialize)] #[serde(rename_all = "camelCase", deny_unknown_fields)] -struct SearchQuery { +pub struct SearchQuery { q: String, offset: Option, limit: Option, @@ -42,126 +43,176 @@ async fn search_with_url_query( path: web::Path, params: web::Query, ) -> Result { - let index = data - .db - .open_index(&path.index_uid) - .ok_or(Error::index_not_found(&path.index_uid))?; - - let reader = data.db.main_read_txn()?; - let schema = index - .main - .schema(&reader)? - .ok_or(Error::internal("Impossible to retrieve the schema"))?; - - let mut search_builder = index.new_search(params.q.clone()); - - if let Some(offset) = params.offset { - search_builder.offset(offset); - } - if let Some(limit) = params.limit { - search_builder.limit(limit); - } - - let available_attributes = schema.displayed_name(); - let mut restricted_attributes: HashSet<&str>; - match ¶ms.attributes_to_retrieve { - Some(attributes_to_retrieve) => { - let attributes_to_retrieve: HashSet<&str> = attributes_to_retrieve.split(',').collect(); - if attributes_to_retrieve.contains("*") { - restricted_attributes = available_attributes.clone(); - } else { - restricted_attributes = HashSet::new(); - for attr in attributes_to_retrieve { - if available_attributes.contains(attr) { - restricted_attributes.insert(attr); - search_builder.add_retrievable_field(attr.to_string()); - } else { - warn!("The attributes {:?} present in attributesToCrop parameter doesn't exist", attr); - } - } - } - }, - None => { - restricted_attributes = available_attributes.clone(); - } - } - - if let Some(ref facet_filters) = params.facet_filters { - let attrs = index.main.attributes_for_faceting(&reader)?.unwrap_or_default(); - search_builder.add_facet_filters(FacetFilter::from_str(facet_filters, &schema, &attrs)?); - } - - if let Some(facets) = ¶ms.facets_distribution { - match index.main.attributes_for_faceting(&reader)? { - Some(ref attrs) => { - let field_ids = prepare_facet_list(&facets, &schema, attrs)?; - search_builder.add_facets(field_ids); - }, - None => return Err(FacetCountError::NoFacetSet.into()), - } - } - - if let Some(attributes_to_crop) = ¶ms.attributes_to_crop { - let default_length = params.crop_length.unwrap_or(200); - let mut final_attributes: HashMap = HashMap::new(); - - for attribute in attributes_to_crop.split(',') { - let mut attribute = attribute.split(':'); - let attr = attribute.next(); - let length = attribute.next().and_then(|s| s.parse().ok()).unwrap_or(default_length); - match attr { - Some("*") => { - for attr in &restricted_attributes { - final_attributes.insert(attr.to_string(), length); - } - }, - Some(attr) => { - if available_attributes.contains(attr) { - final_attributes.insert(attr.to_string(), length); - } else { - warn!("The attributes {:?} present in attributesToCrop parameter doesn't exist", attr); - } - }, - None => (), - } - } - - search_builder.attributes_to_crop(final_attributes); - } - - if let Some(attributes_to_highlight) = ¶ms.attributes_to_highlight { - let mut final_attributes: HashSet = HashSet::new(); - for attribute in attributes_to_highlight.split(',') { - if attribute == "*" { - for attr in &restricted_attributes { - final_attributes.insert(attr.to_string()); - } - } else { - if available_attributes.contains(attribute) { - final_attributes.insert(attribute.to_string()); - } else { - warn!("The attributes {:?} present in attributesToHighlight parameter doesn't exist", attribute); - } - } - } - - search_builder.attributes_to_highlight(final_attributes); - } - - if let Some(filters) = ¶ms.filters { - search_builder.filters(filters.to_string()); - } - - if let Some(matches) = params.matches { - if matches { - search_builder.get_matches(); - } - } - let search_result = search_builder.search(&reader)?; - + let search_result = params.search(&path.index_uid, data)?; Ok(HttpResponse::Ok().json(search_result)) } +#[derive(Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub struct SearchQueryPost { + q: String, + offset: Option, + limit: Option, + attributes_to_retrieve: Option>, + attributes_to_crop: Option>, + crop_length: Option, + attributes_to_highlight: Option>, + filters: Option, + matches: Option, + facet_filters: Option, + facets_distribution: Option>, +} + +impl From for SearchQuery { + fn from(other: SearchQueryPost) -> SearchQuery { + SearchQuery { + q: other.q, + offset: other.offset, + limit: other.limit, + attributes_to_retrieve: other.attributes_to_retrieve.map(|attrs| attrs.join(",")), + attributes_to_crop: other.attributes_to_crop.map(|attrs| attrs.join(",")), + crop_length: other.crop_length, + attributes_to_highlight: other.attributes_to_highlight.map(|attrs| attrs.join(",")), + filters: other.filters, + matches: other.matches, + facet_filters: other.facet_filters.map(|f| f.to_string()), + facets_distribution: other.facets_distribution.map(|f| format!("{:?}", f)), + } + } +} + +#[post("/indexes/{index_uid}/search", wrap = "Authentication::Public")] +async fn search_with_post( + data: web::Data, + path: web::Path, + params: web::Json, +) -> Result { + let query: SearchQuery = params.0.into(); + let search_result = query.search(&path.index_uid, data)?; + Ok(HttpResponse::Ok().json(search_result)) +} + +impl SearchQuery { + fn search(&self, index_uid: &str, data: web::Data) -> Result { + let index = data + .db + .open_index(index_uid) + .ok_or(Error::index_not_found(index_uid))?; + + let reader = data.db.main_read_txn()?; + let schema = index + .main + .schema(&reader)? + .ok_or(Error::internal("Impossible to retrieve the schema"))?; + + let mut search_builder = index.new_search(self.q.clone()); + + if let Some(offset) = self.offset { + search_builder.offset(offset); + } + if let Some(limit) = self.limit { + search_builder.limit(limit); + } + + let available_attributes = schema.displayed_name(); + let mut restricted_attributes: HashSet<&str>; + match &self.attributes_to_retrieve { + Some(attributes_to_retrieve) => { + let attributes_to_retrieve: HashSet<&str> = attributes_to_retrieve.split(',').collect(); + if attributes_to_retrieve.contains("*") { + restricted_attributes = available_attributes.clone(); + } else { + restricted_attributes = HashSet::new(); + for attr in attributes_to_retrieve { + if available_attributes.contains(attr) { + restricted_attributes.insert(attr); + search_builder.add_retrievable_field(attr.to_string()); + } else { + warn!("The attributes {:?} present in attributesToCrop parameter doesn't exist", attr); + } + } + } + }, + None => { + restricted_attributes = available_attributes.clone(); + } + } + + if let Some(ref facet_filters) = self.facet_filters { + let attrs = index.main.attributes_for_faceting(&reader)?.unwrap_or_default(); + search_builder.add_facet_filters(FacetFilter::from_str(facet_filters, &schema, &attrs)?); + } + + if let Some(facets) = &self.facets_distribution { + match index.main.attributes_for_faceting(&reader)? { + Some(ref attrs) => { + let field_ids = prepare_facet_list(&facets, &schema, attrs)?; + search_builder.add_facets(field_ids); + }, + None => return Err(FacetCountError::NoFacetSet.into()), + } + } + + if let Some(attributes_to_crop) = &self.attributes_to_crop { + let default_length = self.crop_length.unwrap_or(200); + let mut final_attributes: HashMap = HashMap::new(); + + for attribute in attributes_to_crop.split(',') { + let mut attribute = attribute.split(':'); + let attr = attribute.next(); + let length = attribute.next().and_then(|s| s.parse().ok()).unwrap_or(default_length); + match attr { + Some("*") => { + for attr in &restricted_attributes { + final_attributes.insert(attr.to_string(), length); + } + }, + Some(attr) => { + if available_attributes.contains(attr) { + final_attributes.insert(attr.to_string(), length); + } else { + warn!("The attributes {:?} present in attributesToCrop parameter doesn't exist", attr); + } + }, + None => (), + } + } + + search_builder.attributes_to_crop(final_attributes); + } + + if let Some(attributes_to_highlight) = &self.attributes_to_highlight { + let mut final_attributes: HashSet = HashSet::new(); + for attribute in attributes_to_highlight.split(',') { + if attribute == "*" { + for attr in &restricted_attributes { + final_attributes.insert(attr.to_string()); + } + } else { + if available_attributes.contains(attribute) { + final_attributes.insert(attribute.to_string()); + } else { + warn!("The attributes {:?} present in attributesToHighlight parameter doesn't exist", attribute); + } + } + } + + search_builder.attributes_to_highlight(final_attributes); + } + + if let Some(filters) = &self.filters { + search_builder.filters(filters.to_string()); + } + + if let Some(matches) = self.matches { + if matches { + search_builder.get_matches(); + } + } + search_builder.search(&reader) + } +} + /// Parses the incoming string into an array of attributes for which to return a count. It returns /// a Vec of attribute names ascociated with their id. /// diff --git a/meilisearch-http/tests/common.rs b/meilisearch-http/tests/common.rs index b53b35bf1..53b58bc95 100644 --- a/meilisearch-http/tests/common.rs +++ b/meilisearch-http/tests/common.rs @@ -252,11 +252,16 @@ impl Server { self.delete_request(&url).await } - pub async fn search(&mut self, query: &str) -> (Value, StatusCode) { + pub async fn search_get(&mut self, query: &str) -> (Value, StatusCode) { let url = format!("/indexes/{}/search?{}", self.uid, query); self.get_request(&url).await } + pub async fn search_post(&mut self, body: Value) -> (Value, StatusCode) { + let url = format!("/indexes/{}/search", self.uid); + self.post_request(&url, body).await + } + pub async fn get_all_updates_status(&mut self) -> (Value, StatusCode) { let url = format!("/indexes/{}/updates", self.uid); self.get_request(&url).await diff --git a/meilisearch-http/tests/index.rs b/meilisearch-http/tests/index.rs index babe0e7aa..e4faaf0a9 100644 --- a/meilisearch-http/tests/index.rs +++ b/meilisearch-http/tests/index.rs @@ -629,7 +629,7 @@ async fn create_index_without_primary_key_and_search() { let query = "q=captain&limit=3"; - let (response, status_code) = server.search(&query).await; + let (response, status_code) = server.search_get(&query).await; assert_eq!(status_code, 200); assert_eq!(response["hits"].as_array().unwrap().len(), 0); } diff --git a/meilisearch-http/tests/search.rs b/meilisearch-http/tests/search.rs index 39bb163eb..0ced0568f 100644 --- a/meilisearch-http/tests/search.rs +++ b/meilisearch-http/tests/search.rs @@ -1,20 +1,35 @@ use std::convert::Into; +use meilisearch_http::routes::search::{SearchQuery, SearchQueryPost}; use assert_json_diff::assert_json_eq; use serde_json::json; use serde_json::Value; mod common; -// Search -// q: Captain -// limit: 3 +macro_rules! test_post_get_search { + ($server:expr, $query:expr, |$response:ident, $status_code:ident | $block:expr) => { + let post_query: SearchQueryPost = serde_json::from_str(&$query.clone().to_string()).unwrap(); + let get_query: SearchQuery = post_query.into(); + let get_query = ::serde_url_params::to_string(&get_query).unwrap(); + let ($response, $status_code) = $server.search_get(&get_query).await; + let _ =::std::panic::catch_unwind(|| $block) + .map_err(|e| panic!("panic in get route: {:?}", e.downcast_ref::<&str>().unwrap())); + let ($response, $status_code) = $server.search_post($query).await; + let _ =::std::panic::catch_unwind(|| $block) + .map_err(|e| panic!("panic in post route: {:?}", e.downcast_ref::<&str>().unwrap())); + }; +} + #[actix_rt::test] async fn search_with_limit() { let mut server = common::Server::with_uid("movies"); server.populate_movies().await; - let query = "q=captain&limit=3"; + let query = json! ({ + "q": "captain", + "limit": 3 + }); let expected = json!([ { @@ -59,8 +74,7 @@ async fn search_with_limit() { "tagline": "When patriots become heroes", "overview": "During World War II, Steve Rogers is a sickly man from Brooklyn who's transformed into super-soldier Captain America to aid in the war effort. Rogers must stop the Red Skull – Adolf Hitler's ruthless head of weaponry, and the leader of an organization that intends to use a mysterious device of untold powers for world domination.", "director": "Joe Johnston", - "producer": "Kevin Feige", - "genres": [ + "producer": "Kevin Feige", "genres": [ "Action", "Adventure", "Science Fiction" @@ -70,20 +84,21 @@ async fn search_with_limit() { } ]); - let (response, _status_code) = server.search(query).await; - assert_json_eq!(expected, response["hits"].clone(), ordered: false); + test_post_get_search!(server, query, |response, _status_code| { + assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); + }); } -// Search with offset -// q: Captain -// limit: 3 -// offset: 1 #[actix_rt::test] async fn search_with_offset() { let mut server = common::Server::with_uid("movies"); server.populate_movies().await; - let query = "q=captain&limit=3&offset=1"; + let query = json!({ + "q": "Captain", + "limit": 3, + "offset": 1 + }); let expected = json!([ { @@ -140,20 +155,21 @@ async fn search_with_offset() { } ]); - let (response, _status_code) = server.search(query).await; - assert_json_eq!(expected, response["hits"].clone(), ordered: false); + test_post_get_search!(server, query, |response, _status_code| { + assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); + }); } -// Search with attribute to highlight all -// q: Captain -// limit: 1 -// attributeToHighlight: * #[actix_rt::test] async fn search_with_attribute_to_highlight_wildcard() { let mut server = common::Server::with_uid("movies"); server.populate_movies().await; - let query = "q=captain&limit=1&attributesToHighlight=*"; + let query = json!({ + "q": "Captain", + "limit": 1, + "attributesToHighlight": ["*"] + }); let expected = json!([ { @@ -192,20 +208,21 @@ async fn search_with_attribute_to_highlight_wildcard() { } ]); - let (response, _status_code) = server.search(query).await; - assert_json_eq!(expected, response["hits"].clone(), ordered: false); + test_post_get_search!(server, query, |response, _status_code| { + assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); + }); } -// Search with attribute to highlight title -// q: Captain -// limit: 1 -// attributeToHighlight: title #[actix_rt::test] async fn search_with_attribute_to_highlight_1() { let mut server = common::Server::with_uid("movies"); server.populate_movies().await; - let query = "q=captain&limit=1&attributesToHighlight=title"; + let query = json!({ + "q": "captain", + "limit": 1, + "attributesToHighlight": ["title"] + }); let expected = json!([ { @@ -244,8 +261,9 @@ async fn search_with_attribute_to_highlight_1() { } ]); - let (response, _status_code) = server.search(query).await; - assert_json_eq!(expected, response["hits"].clone(), ordered: false); + test_post_get_search!(server, query, |response, _status_code| { + assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); + }); } // Search with attribute to highlight title and tagline @@ -257,7 +275,11 @@ async fn search_with_attribute_to_highlight_title_tagline() { let mut server = common::Server::with_uid("movies"); server.populate_movies().await; - let query = "q=captain&limit=1&attributesToHighlight=title,tagline"; + let query = json!({ + "q": "captain", + "limit": 1, + "attributesToHighlight": ["title","tagline"] + }); let expected = json!([ { @@ -296,20 +318,21 @@ async fn search_with_attribute_to_highlight_title_tagline() { } ]); - let (response, _status_code) = server.search(query).await; - assert_json_eq!(expected, response["hits"].clone(), ordered: false); + test_post_get_search!(server, query, |response, _status_code| { + assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); + }); } -// Search with attribute to highlight title and overview -// q: Captain -// limit: 1 -// attributeToHighlight: title,overview #[actix_rt::test] async fn search_with_attribute_to_highlight_title_overview() { let mut server = common::Server::with_uid("movies"); server.populate_movies().await; - let query = "q=captain&limit=1&attributesToHighlight=title,overview"; + let query = json!({ + "q": "captain", + "limit": 1, + "attributesToHighlight": ["title","overview"] + }); let expected = json!([ { @@ -348,20 +371,21 @@ async fn search_with_attribute_to_highlight_title_overview() { } ]); - let (response, _status_code) = server.search(query).await; - assert_json_eq!(expected, response["hits"].clone(), ordered: false); + test_post_get_search!(server, query, |response, _status_code| { + assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); + }); } -// Search with matches -// q: Captain -// limit: 1 -// matches: true #[actix_rt::test] async fn search_with_matches() { let mut server = common::Server::with_uid("movies"); server.populate_movies().await; - let query = "q=captain&limit=1&matches=true"; + let query = json!({ + "q": "Captain", + "limit": 1, + "matches": true + }); let expected = json!([ { @@ -397,21 +421,22 @@ async fn search_with_matches() { } ]); - let (response, _status_code) = server.search(query).await; - assert_json_eq!(expected, response["hits"].clone(), ordered: false); + test_post_get_search!(server, query, |response, _status_code| { + assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); + }); } -// Search with crop -// q: Captain -// limit: 1 -// attributesToCrop: overview -// cropLength: 20 #[actix_rt::test] async fn search_witch_crop() { let mut server = common::Server::with_uid("movies"); server.populate_movies().await; - let query = "q=captain&limit=1&attributesToCrop=overview&cropLength=20"; + let query = json!({ + "q": "Captain", + "limit": 1, + "attributesToCrop": ["overview"], + "cropLength": 20 + }); let expected = json!([ { @@ -450,20 +475,21 @@ async fn search_witch_crop() { } ]); - let (response, _status_code) = server.search(query).await; - assert_json_eq!(expected, response["hits"].clone(), ordered: false); + test_post_get_search!(server, query, |response, _status_code| { + assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); + }); } -// Search with attributes to retrieve -// q: Captain -// limit: 1 -// attributesToRetrieve: [title,tagline,overview,poster_path] #[actix_rt::test] async fn search_with_attributes_to_retrieve() { let mut server = common::Server::with_uid("movies"); server.populate_movies().await; - let query = "q=captain&limit=1&attributesToRetrieve=title,tagline,overview,poster_path"; + let query = json!({ + "q": "Captain", + "limit": 1, + "attributesToRetrieve": ["title","tagline","overview","poster_path"], + }); let expected = json!([ { @@ -474,20 +500,21 @@ async fn search_with_attributes_to_retrieve() { } ]); - let (response, _status_code) = server.search(query).await; - assert_json_eq!(expected, response["hits"].clone(), ordered: false); + test_post_get_search!(server, query, |response, _status_code| { + assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); + }); } -// Search with attributes to retrieve wildcard -// q: Captain -// limit: 1 -// attributesToRetrieve: * #[actix_rt::test] async fn search_with_attributes_to_retrieve_wildcard() { let mut server = common::Server::with_uid("movies"); server.populate_movies().await; - let query = "q=captain&limit=1&attributesToRetrieve=*"; + let query = json!({ + "q": "Captain", + "limit": 1, + "attributesToRetrieve": ["*"], + }); let expected = json!([ { @@ -509,8 +536,9 @@ async fn search_with_attributes_to_retrieve_wildcard() { } ]); - let (response, _status_code) = server.search(query).await; - assert_json_eq!(expected, response["hits"].clone(), ordered: false); + test_post_get_search!(server, query, |response, _status_code| { + assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); + }); } // Search with filter @@ -522,7 +550,12 @@ async fn search_with_filter() { let mut server = common::Server::with_uid("movies"); server.populate_movies().await; - let query = "q=captain&filters=director%20%3D%20%22Anthony%20Russo%22&limit=3"; + let query = json!({ + "q": "Captain", + "limit": 3, + "filters": "director='Anthony Russo'" + }); + let expected = json!([ { "id": 271110, @@ -576,8 +609,10 @@ async fn search_with_filter() { "vote_count": 10497 } ]); - let (response, _status_code) = server.search(query).await; - assert_json_eq!(expected, response["hits"].clone(), ordered: false); + + test_post_get_search!(server, query, |response, _status_code| { + assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); + }); let expected = json!([ { @@ -598,10 +633,15 @@ async fn search_with_filter() { } ]); - // filters: title = "american pie 2" - let query = "q=american&filters=title%20%3D%20%22american%20pie%202%22"; - let (response, _status_code) = server.search(query).await; - assert_json_eq!(expected, response["hits"].clone(), ordered: false); + let query = json!({ + "q": "a", + "limit": 3, + "filters": "title='american pie 2'" + }); + + test_post_get_search!(server, query, |response, _status_code| { + assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); + }); let expected = json!([ { @@ -639,10 +679,14 @@ async fn search_with_filter() { "vote_count": 11972 } ]); - // limit: 3, director = "anthony russo" AND (title = "captain america: civil war" OR title = "Captain America: The Winter Soldier") - let query = "q=a&limit=3&filters=director%20%3D%20%22anthony%20russo%22%20AND%20%20(title%20%3D%20%22captain%20america%3A%20civil%20war%22%20OR%20title%20%3D%20%22Captain%20America%3A%20The%20Winter%20Soldier%22)"; - let (response, _status_code) = server.search(query).await; - assert_json_eq!(expected, response["hits"].clone(), ordered: false); + let query = json!({ + "q": "a", + "limit": 3, + "filters": "director='Anthony Russo' AND (title='captain america: civil war' OR title='Captain America: The Winter Soldier')" + }); + test_post_get_search!(server, query, |response, _status_code| { + assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); + }); let expected = json!([ { @@ -698,9 +742,14 @@ async fn search_with_filter() { } ]); // director = "anthony russo" AND (title = "captain america: civil war" OR vote_average > 8.0) - let query = "q=a&limit=3&filters=director%20%3D%20%22anthony%20russo%22%20AND%20%20(title%20%3D%20%22captain%20america%3A%20civil%20war%22%20OR%20vote_average%20%3E%208.0)"; - let (response, _status_code) = server.search(query).await; - assert_json_eq!(expected, response["hits"].clone(), ordered: false); + let query = json!({ + "q": "a", + "limit": 3, + "filters": "director='anthony russo' AND (title = 'captain america: civil war' OR vote_average > 8.0)" + }); + test_post_get_search!(server, query, |response, _status_code| { + assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); + }); let expected = json!([ { @@ -754,15 +803,26 @@ async fn search_with_filter() { "poster_path": "https://image.tmdb.org/t/p/w500/fXepRAYOx1qC3wju7XdDGx60775.jpg" } ]); - // NOT director = "anthony russo" AND vote_average > 7.5 - let query = "q=a&limit=3&filters=NOT%20director%20%3D%20%22anthony%20russo%22%20AND%20vote_average%20%3E%207.5"; - let (response, _status_code) = server.search(query).await; - assert_json_eq!(expected, response["hits"].clone(), ordered: false); + + let query = json!({ + "q": "a", + "limit": 3, + "filters": "NOT director = 'anthony russo' AND vote_average > 7.5" + }); + + test_post_get_search!(server, query, |response, _status_code| { + assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); + }); let expected = json!([]); - let query = "q=a&filters=NOT%20director%20%3D%20%22anthony%20russo%22%20AND%20title%20%20%3D%20%22Avengers%3A%20Endgame%22"; - let (response, _status_code) = server.search(query).await; - assert_json_eq!(expected, response["hits"].clone(), ordered: false); + let query = json!({ + "q": "a", + "filters": "NOT director = 'anthony russo' AND title='Avengers: Endgame'" + }); + + test_post_get_search!(server, query, |response, _status_code| { + assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); + }); } // Search with attributes to highlight and matches @@ -775,7 +835,12 @@ async fn search_with_attributes_to_highlight_and_matches() { let mut server = common::Server::with_uid("movies"); server.populate_movies().await; - let query = "q=captain&limit=1&attributesToHighlight=title,overview&matches=true"; + let query = json!({ + "q": "captain", + "limit": 1, + "attributesToHighlight": ["title","overview"], + "matches": true, + }); let expected = json!( [ { @@ -828,23 +893,24 @@ async fn search_with_attributes_to_highlight_and_matches() { } ]); - let (response, _status_code) = server.search(query).await; - assert_json_eq!(expected, response["hits"].clone(), ordered: false); + test_post_get_search!(server, query, |response, _status_code| { + assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); + }); } -// Search with attributes to highlight and matches and crop -// q: Captain -// limit: 1 -// attributesToHighlight: [title,overview] -// matches: true -// cropLength: 20 -// attributesToCrop: overview #[actix_rt::test] async fn search_with_attributes_to_highlight_and_matches_and_crop() { let mut server = common::Server::with_uid("movies"); server.populate_movies().await; - let query = "q=captain&limit=1&attributesToCrop=overview&cropLength=20&attributesToHighlight=title,overview&matches=true"; + let query = json!({ + "q": "captain", + "limit": 1, + "attributesToCrop": ["overview"], + "cropLength": 20, + "attributesToHighlight": ["title","overview"], + "matches": true + }); let expected = json!([ { @@ -897,21 +963,22 @@ async fn search_with_attributes_to_highlight_and_matches_and_crop() { } ]); - let (response, _status_code) = server.search(query).await; - assert_json_eq!(expected, response["hits"].clone(), ordered: false); + test_post_get_search!(server, query, |response, _status_code| { + assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); + }); } -// Search with differents attributes -// q: Captain -// limit: 1 -// attributesToRetrieve: [title,producer,director] -// attributesToHighlight: [title] #[actix_rt::test] async fn search_with_differents_attributes() { let mut server = common::Server::with_uid("movies"); server.populate_movies().await; - let query = "q=captain&limit=1&attributesToRetrieve=title,producer,director&attributesToHighlight=title"; + let query = json!({ + "q": "captain", + "limit": 1, + "attributesToRetrieve": ["title","producer","director"], + "attributesToHighlight": ["title"], + }); let expected = json!([ { @@ -924,22 +991,23 @@ async fn search_with_differents_attributes() { } ]); - let (response, _status_code) = server.search(query).await; - assert_json_eq!(expected, response["hits"].clone(), ordered: false); + test_post_get_search!(server, query, |response, _status_code| { + assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); + }); } -// Search with attributes to highlight and matches and crop -// q: Captain -// limit: 1 -// attributesToRetrieve: [title,producer,director] -// attributesToCrop: [overview] -// cropLength: 10 #[actix_rt::test] async fn search_with_differents_attributes_2() { let mut server = common::Server::with_uid("movies"); server.populate_movies().await; - let query = "q=captain&limit=1&attributesToRetrieve=title,producer,director&attributesToCrop=overview&cropLength=10"; + let query = json!({ + "q": "captain", + "limit": 1, + "attributesToRetrieve": ["title","producer","director"], + "attributesToCrop": ["overview"], + "cropLength": 10, + }); let expected = json!([ { @@ -952,8 +1020,9 @@ async fn search_with_differents_attributes_2() { } ]); - let (response, _status_code) = server.search(query).await; - assert_json_eq!(expected, response["hits"].clone(), ordered: false); + test_post_get_search!(server, query, |response, _status_code| { + assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); + }); } // Search with attributes to highlight and matches and crop @@ -966,7 +1035,12 @@ async fn search_with_differents_attributes_3() { let mut server = common::Server::with_uid("movies"); server.populate_movies().await; - let query = "q=captain&limit=1&attributesToRetrieve=title,producer,director&attributesToCrop=overview:10"; + let query = json!({ + "q": "captain", + "limit": 1, + "attributesToRetrieve": ["title","producer","director"], + "attributesToCrop": ["overview:10"], + }); let expected = json!([ { @@ -979,8 +1053,9 @@ async fn search_with_differents_attributes_3() { } ]); - let (response, _status_code) = server.search(query).await; - assert_json_eq!(expected, response["hits"].clone(), ordered: false); + test_post_get_search!(server, query, |response, _status_code| { + assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); + }); } // Search with attributes to highlight and matches and crop @@ -993,7 +1068,12 @@ async fn search_with_differents_attributes_4() { let mut server = common::Server::with_uid("movies"); server.populate_movies().await; - let query = "q=captain&limit=1&attributesToRetrieve=title,producer,director&attributesToCrop=overview:10,title:0"; + let query = json!({ + "q": "captain", + "limit": 1, + "attributesToRetrieve": ["title","producer","director"], + "attributesToCrop": ["overview:10","title:0"], + }); let expected = json!([ { @@ -1007,8 +1087,9 @@ async fn search_with_differents_attributes_4() { } ]); - let (response, _status_code) = server.search(query).await; - assert_json_eq!(expected, response["hits"].clone(), ordered: false); + test_post_get_search!(server, query, |response, _status_code| { + assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); + }); } // Search with attributes to highlight and matches and crop @@ -1021,7 +1102,12 @@ async fn search_with_differents_attributes_5() { let mut server = common::Server::with_uid("movies"); server.populate_movies().await; - let query = "q=captain&limit=1&attributesToRetrieve=title,producer,director&attributesToCrop=*,overview:10"; + let query = json!({ + "q": "captain", + "limit": 1, + "attributesToRetrieve": ["title","producer","director"], + "attributesToCrop": ["*","overview:10"], + }); let expected = json!([ { @@ -1037,8 +1123,9 @@ async fn search_with_differents_attributes_5() { } ]); - let (response, _status_code) = server.search(query).await; - assert_json_eq!(expected, response["hits"].clone(), ordered: false); + test_post_get_search!(server, query, |response, _status_code| { + assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); + }); } // Search with attributes to highlight and matches and crop @@ -1052,7 +1139,13 @@ async fn search_with_differents_attributes_6() { let mut server = common::Server::with_uid("movies"); server.populate_movies().await; - let query = "q=captain&limit=1&attributesToRetrieve=title,producer,director&attributesToCrop=*,overview:10&attributesToHighlight=title"; + let query = json!({ + "q": "captain", + "limit": 1, + "attributesToRetrieve": ["title","producer","director"], + "attributesToCrop": ["*","overview:10"], + "attributesToHighlight": ["title"], + }); let expected = json!([ { @@ -1068,8 +1161,9 @@ async fn search_with_differents_attributes_6() { } ]); - let (response, _status_code) = server.search(query).await; - assert_json_eq!(expected, response["hits"].clone(), ordered: false); + test_post_get_search!(server, query, |response, _status_code| { + assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); + }); } // Search with attributes to highlight and matches and crop @@ -1083,7 +1177,13 @@ async fn search_with_differents_attributes_7() { let mut server = common::Server::with_uid("movies"); server.populate_movies().await; - let query = "q=captain&limit=1&attributesToRetrieve=title,producer,director&attributesToCrop=*,overview:10&attributesToHighlight=*"; + let query = json!({ + "q": "captain", + "limit": 1, + "attributesToRetrieve": ["title","producer","director"], + "attributesToCrop": ["*","overview:10"], + "attributesToHighlight": ["*"], + }); let expected = json!([ { @@ -1099,8 +1199,9 @@ async fn search_with_differents_attributes_7() { } ]); - let (response, _status_code) = server.search(query).await; - assert_json_eq!(expected, response["hits"].clone(), ordered: false); + test_post_get_search!(server, query, |response, _status_code| { + assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); + }); } // Search with attributes to highlight and matches and crop @@ -1114,7 +1215,13 @@ async fn search_with_differents_attributes_8() { let mut server = common::Server::with_uid("movies"); server.populate_movies().await; - let query = "q=captain&limit=1&attributesToRetrieve=title,producer,director&attributesToCrop=*,overview:10&attributesToHighlight=*,tagline"; + let query = json!({ + "q": "captain", + "limit": 1, + "attributesToRetrieve": ["title","producer","director"], + "attributesToCrop": ["*","overview:10"], + "attributesToHighlight": ["*","tagline"], + }); let expected = json!([ { @@ -1131,8 +1238,9 @@ async fn search_with_differents_attributes_8() { } ]); - let (response, _status_code) = server.search(query).await; - assert_json_eq!(expected, response["hits"].clone(), ordered: false); + test_post_get_search!(server, query, |response, _status_code| { + assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); + }); } #[actix_rt::test] @@ -1143,112 +1251,147 @@ async fn test_faceted_search_valid() { let body = json!({ "attributesForFaceting": ["color"] }); - server.update_all_settings(body).await; - let query = "q=a&facetFilters=%5B%22color%3Agreen%22%5D"; - let (response, _status_code) = server.search(query).await; - 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 = "q=a&facetFilters=%5B%22color%3Ablue%22%5D"; - let (response, _status_code) = server.search(query).await; - 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 case insensitive : ["color:Blue"] - let query = "q=a&facetFilters=%5B%22color%3ABlue%22%5D"; - let (response, _status_code) = server.search(query).await; - 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")); + server.update_all_settings(body).await; + + let query = json!({ + "q": "a", + "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!({ + "q": "a", + "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!({ + "q": "a", + "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 = "q=a&facetFilters=%5B%22tags%3Abug%22%5D"; - let (response, _status_code) = server.search(query).await; - 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 = "q=a&facetFilters=%5B%22color%3Ablue%22,%20%22tags%3Abug%22%20%5D"; - let (response, _status_code) = server.search(query).await; - 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 = "q=a&facetFilters=%5B%5B%22color%3Ablue%22,%20%22color%3Agreen%22%5D%5D"; - let (response, _status_code) = server.search(query).await; - 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 = "q=a&facetFilters=%5B%22color%3Ablue%22,%20%22tags%3Abug%22%20%5D"; - let (response, _status_code) = server.search(query).await; - assert!(!response.get("hits").unwrap().as_array().unwrap().is_empty()); - assert!(response - .get("hits") - .unwrap() - .as_array() - .unwrap() - .iter() - .all(|value| - value - .get("tags") + let query = json!({ + "q": "a", + "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() - .contains(&Value::String("bug".to_owned())) - && (value + .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!({ + "q": "a", + "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!({ + "q": "a", + "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"))); + .unwrap() == "green")); + }); + // test and-or: ["tags:bug", ["color:blue", "color:green"]] + let query = json!({ + "q": "a", + "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] @@ -1256,10 +1399,16 @@ async fn test_faceted_search_invalid() { let mut server = common::Server::test_server().await; //no faceted attributes set - let query = "q=a&facetFilters=%5B%22color%3Ablue%22,%20%22tags%3Abug%22%20%5D"; - let (response, status_code) = server.search(query).await; - assert_eq!(status_code, 400); - assert_eq!(response["errorCode"], "invalid_facet"); + let query = json!({ + "q": "a", + "facetFilters": ["color:blue"] + }); + + test_post_get_search!(server, query, |response, status_code| { + + assert_eq!(status_code, 400); + assert_eq!(response["errorCode"], "invalid_facet"); + }); let body = json!({ "attributesForFaceting": ["color", "tags"] @@ -1267,37 +1416,70 @@ async fn test_faceted_search_invalid() { server.update_all_settings(body).await; // empty arrays are error // [] - let query = "q=a&facetFilters=%5B%5D"; - let (_response, status_code) = server.search(query).await; - assert_eq!(status_code, 400); - assert_eq!(response["errorCode"], "invalid_facet"); + let query = json!({ + "q": "a", + "facetFilters": [] + }); + + test_post_get_search!(server, query, |response, status_code| { + assert_eq!(status_code, 400); + assert_eq!(response["errorCode"], "invalid_facet"); + }); // [[]] - let query = "q=a&facetFilters=%5B%5B%5D"; - let (_response, status_code) = server.search(query).await; - assert_eq!(status_code, 400); - assert_eq!(response["errorCode"], "invalid_facet"); + let query = json!({ + "q": "a", + "facetFilters": [[]] + }); + + test_post_get_search!(server, query, |response, status_code| { + assert_eq!(status_code, 400); + assert_eq!(response["errorCode"], "invalid_facet"); + }); + // ["color:green", []] - let query = "q=a&facetFilters=%5B%22color%3Agreen%22,%20%5B%5D"; - let (_response, status_code) = server.search(query).await; - assert_eq!(status_code, 400); - assert_eq!(response["errorCode"], "invalid_facet"); + let query = json!({ + "q": "a", + "facetFilters": ["color:green", []] + }); + + test_post_get_search!(server, query, |response, status_code| { + assert_eq!(status_code, 400); + assert_eq!(response["errorCode"], "invalid_facet"); + }); // too much depth // [[[]]] - let query = "q=a&facetFilters=%5B%5B%5B%5D%5D%5D"; - let (_response, status_code) = server.search(query).await; - assert_eq!(status_code, 400); - assert_eq!(response["errorCode"], "invalid_facet"); + let query = json!({ + "q": "a", + "facetFilters": [[[]]] + }); + + test_post_get_search!(server, query, |response, status_code| { + assert_eq!(status_code, 400); + assert_eq!(response["errorCode"], "invalid_facet"); + }); + // [["color:green", ["color:blue"]]] - let query = "q=a&facetFilters=%5B%5B%22color%3Agreen%22,%20%5B%22color%3Ablue%22%5D%5D%5D"; - let (_response, status_code) = server.search(query).await; - assert_eq!(status_code, 400); - assert_eq!(response["errorCode"], "invalid_facet"); + let query = json!({ + "q": "a", + "facetFilters": [["color:green", ["color:blue"]]] + }); + + test_post_get_search!(server, query, |response, status_code| { + assert_eq!(status_code, 400); + assert_eq!(response["errorCode"], "invalid_facet"); + }); + // "color:green" - let query = "q=a&facetFilters=%22color%3Agreen%22"; - let (_response, status_code) = server.search(query).await; - assert_eq!(status_code, 400); - assert_eq!(response["errorCode"], "invalid_facet"); + let query = json!({ + "q": "a", + "facetFilters": "color:green" + }); + + test_post_get_search!(server, query, |response, status_code| { + assert_eq!(status_code, 400); + assert_eq!(response["errorCode"], "invalid_facet"); + }); } #[actix_rt::test] @@ -1305,57 +1487,98 @@ async fn test_facet_count() { let mut server = common::Server::test_server().await; // test without facet distribution - let query = "q=a"; - let (response, _status_code) = server.search(query).await; - assert!(response.get("exhaustiveFacetsCount").is_none()); - assert!(response.get("facetsDistribution").is_none()); + let query = json!({ + "q": "a", + }); + 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 = "q=a&facetsDistribution=%5B%22color%22%5D"; - let (_response, status_code) = server.search(query).await; - assert_eq!(status_code, 400); + let query = json!({ + "q": "a", + "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: - let (response, _status_code) = server.search(query).await; - assert!(response.get("exhaustiveFacetsCount").is_some()); - assert_eq!(response.get("facetsDistribution").unwrap().as_object().unwrap().values().count(), 1); + 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 = "q=a&facetsDistribution=%5B%22color%22,%20%22tags%22%5D"; - let (response, _status_code) = server.search(query).await; - let facets = response.get("facetsDistribution").unwrap().as_object().unwrap(); - eprintln!("response: {:#?}", response); - 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); + let query = json!({ + "q": "a", + "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 = "q=a&facetsDistribution=%5B%22*%22%5D"; - let (response, _status_code) = server.search(query).await; - assert_eq!(response.get("facetsDistribution").unwrap().as_object().unwrap().values().count(), 2); + let query = json!({ + "q": "a", + "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 = "q=a&facetsDistribution=%5B%22color%22,%20%22*%22%5D"; - let (response, _status_code) = server.search(query).await; - assert_eq!(response.get("facetsDistribution").unwrap().as_object().unwrap().values().count(), 2); + let query = json!({ + "q": "a", + "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 = "q=a&facetsDistribution=%5B%5D"; - let (response, _status_code) = server.search(query).await; - assert_eq!(response.get("facetsDistribution").unwrap().as_object().unwrap().values().count(), 0); + let query = json!({ + "q": "a", + "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 = "q=a&facetsDistribution=%5B%22gender%22%5D"; - let (_response, status_code) = server.search(query).await; - assert_eq!(status_code, 400); + let query = json!({ + "q": "a", + "facetsDistribution": ["gender"] + }); + test_post_get_search!(server, query, |_response, status_code|{ + assert_eq!(status_code, 400); + }); + +} + +#[actix_rt::test] +#[should_panic] +async fn test_bad_facet_distribution() { + let mut server = common::Server::test_server().await; // string instead of array: - let query = "q=a&faceDistribution=%22gender%22"; - let (_response, status_code) = server.search(query).await; - assert_eq!(status_code, 400); + let query = json!({ + "q": "a", + "facetsDistribution": "color" + }); + test_post_get_search!(server, query, |_response, _status_code| {}); + // invalid value in array: - let query = "q=a&facetsDistribution=%5B%22color%22,%20true%5D"; - let (_response, status_code) = server.search(query).await; - assert_eq!(status_code, 400); + let query = json!({ + "q": "a", + "facetsDistribution": ["color", true] + }); + test_post_get_search!(server, query, |_response, _status_code| {}); } #[actix_rt::test] @@ -1381,52 +1604,66 @@ async fn highlight_cropped_text() { server.add_or_replace_multiple_documents(doc).await; // tests from #680 - let query = "q=insert&attributesToHighlight=*&attributesToCrop=body&cropLength=30"; - let (response, _status_code) = server.search(query).await; + //let query = "q=insert&attributesToHighlight=*&attributesToCrop=body&cropLength=30"; + let query = json!({ + "q": "insert", + "attributesToHighlight": ["*"], + "attributesToCrop": ["body"], + "cropLength": 30, + }); let expected_response = "that, try the following: \n1. insert your trip\n2. google your"; - assert_eq!(response - .get("hits") - .unwrap() - .as_array() - .unwrap() - .get(0) - .unwrap() - .as_object() - .unwrap() - .get("_formatted") - .unwrap() - .as_object() - .unwrap() - .get("body") - .unwrap() - , &Value::String(expected_response.to_owned())); + test_post_get_search!(server, query, |response, _status_code|{ + assert_eq!(response + .get("hits") + .unwrap() + .as_array() + .unwrap() + .get(0) + .unwrap() + .as_object() + .unwrap() + .get("_formatted") + .unwrap() + .as_object() + .unwrap() + .get("body") + .unwrap() + , &Value::String(expected_response.to_owned())); + }); - let query = "q=insert&attributesToHighlight=*&attributesToCrop=body&cropLength=80"; - let (response, _status_code) = server.search(query).await; + //let query = "q=insert&attributesToHighlight=*&attributesToCrop=body&cropLength=80"; + let query = json!({ + "q": "insert", + "attributesToHighlight": ["*"], + "attributesToCrop": ["body"], + "cropLength": 80, + }); let expected_response = "well, it may not work like that, try the following: \n1. insert your trip\n2. google your `searchQuery`\n3. find a solution \n> say hello"; - assert_eq!(response - .get("hits") - .unwrap() - .as_array() - .unwrap() - .get(0) - .unwrap() - .as_object() - .unwrap() - .get("_formatted") - .unwrap() - .as_object() - .unwrap() - .get("body") - .unwrap() - , &Value::String(expected_response.to_owned())); + test_post_get_search!(server, query, |response, _status_code| { + assert_eq!(response + .get("hits") + .unwrap() + .as_array() + .unwrap() + .get(0) + .unwrap() + .as_object() + .unwrap() + .get("_formatted") + .unwrap() + .as_object() + .unwrap() + .get("body") + .unwrap() + , &Value::String(expected_response.to_owned())); + }); } #[actix_rt::test] async fn well_formated_error_with_bad_request_params() { let mut server = common::Server::with_uid("test"); let query = "foo=bar"; - let (response, _status_code) = server.search(query).await; + let (response, _status_code) = server.search_get(query).await; assert!(response.get("message").is_some()); assert!(response.get("errorCode").is_some()); assert!(response.get("errorType").is_some()); diff --git a/meilisearch-http/tests/search_settings.rs b/meilisearch-http/tests/search_settings.rs index 6effbdda0..1c8237ea8 100644 --- a/meilisearch-http/tests/search_settings.rs +++ b/meilisearch-http/tests/search_settings.rs @@ -106,7 +106,7 @@ async fn search_with_settings_basic() { } ]); - let (response, _status_code) = server.search(query).await; + let (response, _status_code) = server.search_get(query).await; assert_json_eq!(expect, response["hits"].clone(), ordered: false); } @@ -212,7 +212,7 @@ async fn search_with_settings_stop_words() { } ]); - let (response, _status_code) = server.search(query).await; + let (response, _status_code) = server.search_get(query).await; assert_json_eq!(expect, response["hits"].clone(), ordered: false); } @@ -323,7 +323,7 @@ async fn search_with_settings_synonyms() { } ]); - let (response, _status_code) = server.search(query).await; + let (response, _status_code) = server.search_get(query).await; assert_json_eq!(expect, response["hits"].clone(), ordered: false); } @@ -429,7 +429,7 @@ async fn search_with_settings_ranking_rules() { } ]); - let (response, _status_code) = server.search(query).await; + let (response, _status_code) = server.search_get(query).await; assert_json_eq!(expect, response["hits"].clone(), ordered: false); } @@ -534,7 +534,7 @@ async fn search_with_settings_searchable_attributes() { } ]); - let (response, _status_code) = server.search(query).await; + let (response, _status_code) = server.search_get(query).await; assert_json_eq!(expect, response["hits"].clone(), ordered: false); } @@ -604,7 +604,7 @@ async fn search_with_settings_displayed_attributes() { } ]); - let (response, _status_code) = server.search(query).await; + let (response, _status_code) = server.search_get(query).await; assert_json_eq!(expect, response["hits"].clone(), ordered: false); } @@ -674,6 +674,6 @@ async fn search_with_settings_searchable_attributes_2() { } ]); - let (response, _status_code) = server.search(query).await; + let (response, _status_code) = server.search_get(query).await; assert_json_eq!(expect, response["hits"].clone(), ordered: false); } diff --git a/meilisearch-http/tests/settings_stop_words.rs b/meilisearch-http/tests/settings_stop_words.rs index 9204b2419..2af770975 100644 --- a/meilisearch-http/tests/settings_stop_words.rs +++ b/meilisearch-http/tests/settings_stop_words.rs @@ -45,12 +45,12 @@ async fn add_documents_and_stop_words() { // 3 - Search for a document with stop words - let (response, _status_code) = server.search("q=the%20mask").await; + let (response, _status_code) = server.search_get("q=the%20mask").await; assert!(!response["hits"].as_array().unwrap().is_empty()); // 4 - Search for documents with *only* stop words - let (response, _status_code) = server.search("q=the%20of").await; + let (response, _status_code) = server.search_get("q=the%20of").await; assert!(response["hits"].as_array().unwrap().is_empty()); // 5 - Delete all stop words