#![allow(dead_code)] use actix_web::{http::StatusCode, test}; use serde_json::{json, Value}; use std::time::Duration; use tempdir::TempDir; use tokio::time::delay_for; use meilisearch_core::DatabaseOptions; use meilisearch_http::data::Data; use meilisearch_http::helpers::NormalizePath; use meilisearch_http::option::Opt; /// Performs a search test on both post and get routes #[macro_export] macro_rules! test_post_get_search { ($server:expr, $query:expr, |$response:ident, $status_code:ident | $block:expr) => { let post_query: meilisearch_http::routes::search::SearchQueryPost = serde_json::from_str(&$query.clone().to_string()).unwrap(); let get_query: meilisearch_http::routes::search::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())); }; } pub struct Server { pub uid: String, pub data: Data, } impl Server { pub fn with_uid(uid: &str) -> Server { let tmp_dir = TempDir::new("meilisearch").unwrap(); let default_db_options = DatabaseOptions::default(); let opt = Opt { db_path: tmp_dir.path().join("db").to_str().unwrap().to_string(), dumps_folder: tmp_dir.path().join("dump"), dump_batch_size: 16, http_addr: "127.0.0.1:7700".to_owned(), master_key: None, env: "development".to_owned(), no_analytics: true, max_mdb_size: default_db_options.main_map_size, max_udb_size: default_db_options.update_map_size, http_payload_size_limit: 10000000, ..Opt::default() }; let data = Data::new(opt.clone()).unwrap(); Server { uid: uid.to_string(), data, } } pub async fn test_server() -> Self { let mut server = Self::with_uid("test"); let body = json!({ "uid": "test", "primaryKey": "id", }); server.create_index(body).await; let body = json!({ "rankingRules": [ "typo", "words", "proximity", "attribute", "wordsPosition", "exactness", ], "searchableAttributes": [ "balance", "picture", "age", "color", "name", "gender", "email", "phone", "address", "about", "registered", "latitude", "longitude", "tags", ], "displayedAttributes": [ "id", "isActive", "balance", "picture", "age", "color", "name", "gender", "email", "phone", "address", "about", "registered", "latitude", "longitude", "tags", ], }); server.update_all_settings(body).await; let dataset = include_bytes!("assets/test_set.json"); let body: Value = serde_json::from_slice(dataset).unwrap(); server.add_or_replace_multiple_documents(body).await; server } pub fn data(&self) -> &Data { &self.data } pub async fn wait_update_id(&mut self, update_id: u64) { // try 10 times to get status, or panic to not wait forever for _ in 0..10 { let (response, status_code) = self.get_update_status(update_id).await; assert_eq!(status_code, 200); if response["status"] == "processed" || response["status"] == "failed" { // eprintln!("{:#?}", response); return; } delay_for(Duration::from_secs(1)).await; } panic!("Timeout waiting for update id"); } // Global Http request GET/POST/DELETE async or sync pub async fn get_request(&mut self, url: &str) -> (Value, StatusCode) { eprintln!("get_request: {}", url); let mut app = test::init_service(meilisearch_http::create_app(&self.data).wrap(NormalizePath)).await; let req = test::TestRequest::get().uri(url).to_request(); let res = test::call_service(&mut app, req).await; let status_code = res.status().clone(); let body = test::read_body(res).await; let response = serde_json::from_slice(&body).unwrap_or_default(); (response, status_code) } pub async fn post_request(&self, url: &str, body: Value) -> (Value, StatusCode) { eprintln!("post_request: {}", url); let mut app = test::init_service(meilisearch_http::create_app(&self.data).wrap(NormalizePath)).await; let req = test::TestRequest::post() .uri(url) .set_json(&body) .to_request(); let res = test::call_service(&mut app, req).await; let status_code = res.status().clone(); let body = test::read_body(res).await; let response = serde_json::from_slice(&body).unwrap_or_default(); (response, status_code) } pub async fn post_request_async(&mut self, url: &str, body: Value) -> (Value, StatusCode) { eprintln!("post_request_async: {}", url); let (response, status_code) = self.post_request(url, body).await; // eprintln!("response: {}", response); assert_eq!(status_code, 202); assert!(response["updateId"].as_u64().is_some()); self.wait_update_id(response["updateId"].as_u64().unwrap()) .await; (response, status_code) } pub async fn put_request(&mut self, url: &str, body: Value) -> (Value, StatusCode) { eprintln!("put_request: {}", url); let mut app = test::init_service(meilisearch_http::create_app(&self.data).wrap(NormalizePath)).await; let req = test::TestRequest::put() .uri(url) .set_json(&body) .to_request(); let res = test::call_service(&mut app, req).await; let status_code = res.status().clone(); let body = test::read_body(res).await; let response = serde_json::from_slice(&body).unwrap_or_default(); (response, status_code) } pub async fn put_request_async(&mut self, url: &str, body: Value) -> (Value, StatusCode) { eprintln!("put_request_async: {}", url); let (response, status_code) = self.put_request(url, body).await; assert!(response["updateId"].as_u64().is_some()); assert_eq!(status_code, 202); self.wait_update_id(response["updateId"].as_u64().unwrap()) .await; (response, status_code) } pub async fn delete_request(&mut self, url: &str) -> (Value, StatusCode) { eprintln!("delete_request: {}", url); let mut app = test::init_service(meilisearch_http::create_app(&self.data).wrap(NormalizePath)).await; let req = test::TestRequest::delete().uri(url).to_request(); let res = test::call_service(&mut app, req).await; let status_code = res.status().clone(); let body = test::read_body(res).await; let response = serde_json::from_slice(&body).unwrap_or_default(); (response, status_code) } pub async fn delete_request_async(&mut self, url: &str) -> (Value, StatusCode) { eprintln!("delete_request_async: {}", url); let (response, status_code) = self.delete_request(url).await; assert!(response["updateId"].as_u64().is_some()); assert_eq!(status_code, 202); self.wait_update_id(response["updateId"].as_u64().unwrap()) .await; (response, status_code) } // All Routes pub async fn list_indexes(&mut self) -> (Value, StatusCode) { self.get_request("/indexes").await } pub async fn create_index(&mut self, body: Value) -> (Value, StatusCode) { self.post_request("/indexes", body).await } pub async fn search_multi_index(&mut self, query: &str) -> (Value, StatusCode) { let url = format!("/indexes/search?{}", query); self.get_request(&url).await } pub async fn get_index(&mut self) -> (Value, StatusCode) { let url = format!("/indexes/{}", self.uid); self.get_request(&url).await } pub async fn update_index(&mut self, body: Value) -> (Value, StatusCode) { let url = format!("/indexes/{}", self.uid); self.put_request(&url, body).await } pub async fn delete_index(&mut self) -> (Value, StatusCode) { let url = format!("/indexes/{}", self.uid); self.delete_request(&url).await } 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 } pub async fn get_update_status(&mut self, update_id: u64) -> (Value, StatusCode) { let url = format!("/indexes/{}/updates/{}", self.uid, update_id); self.get_request(&url).await } pub async fn get_all_documents(&mut self) -> (Value, StatusCode) { let url = format!("/indexes/{}/documents", self.uid); self.get_request(&url).await } pub async fn add_or_replace_multiple_documents(&mut self, body: Value) { let url = format!("/indexes/{}/documents", self.uid); self.post_request_async(&url, body).await; } pub async fn add_or_replace_multiple_documents_sync( &mut self, body: Value, ) -> (Value, StatusCode) { let url = format!("/indexes/{}/documents", self.uid); self.post_request(&url, body).await } pub async fn add_or_update_multiple_documents(&mut self, body: Value) { let url = format!("/indexes/{}/documents", self.uid); self.put_request_async(&url, body).await; } pub async fn clear_all_documents(&mut self) { let url = format!("/indexes/{}/documents", self.uid); self.delete_request_async(&url).await; } pub async fn get_document(&mut self, document_id: impl ToString) -> (Value, StatusCode) { let url = format!( "/indexes/{}/documents/{}", self.uid, document_id.to_string() ); self.get_request(&url).await } pub async fn delete_document(&mut self, document_id: impl ToString) -> (Value, StatusCode) { let url = format!( "/indexes/{}/documents/{}", self.uid, document_id.to_string() ); self.delete_request_async(&url).await } pub async fn delete_multiple_documents(&mut self, body: Value) { let url = format!("/indexes/{}/documents/delete-batch", self.uid); self.post_request_async(&url, body).await; } pub async fn get_all_settings(&mut self) -> (Value, StatusCode) { let url = format!("/indexes/{}/settings", self.uid); self.get_request(&url).await } pub async fn update_all_settings(&mut self, body: Value) { let url = format!("/indexes/{}/settings", self.uid); self.post_request_async(&url, body).await; } pub async fn delete_all_settings(&mut self) -> (Value, StatusCode) { let url = format!("/indexes/{}/settings", self.uid); self.delete_request_async(&url).await } pub async fn get_ranking_rules(&mut self) -> (Value, StatusCode) { let url = format!("/indexes/{}/settings/ranking-rules", self.uid); self.get_request(&url).await } pub async fn update_ranking_rules(&mut self, body: Value) { let url = format!("/indexes/{}/settings/ranking-rules", self.uid); self.post_request_async(&url, body).await; } pub async fn update_ranking_rules_sync(&mut self, body: Value) -> (Value, StatusCode) { let url = format!("/indexes/{}/settings/ranking-rules", self.uid); self.post_request(&url, body).await } pub async fn delete_ranking_rules(&mut self) -> (Value, StatusCode) { let url = format!("/indexes/{}/settings/ranking-rules", self.uid); self.delete_request_async(&url).await } pub async fn get_distinct_attribute(&mut self) -> (Value, StatusCode) { let url = format!("/indexes/{}/settings/distinct-attribute", self.uid); self.get_request(&url).await } pub async fn update_distinct_attribute(&mut self, body: Value) { let url = format!("/indexes/{}/settings/distinct-attribute", self.uid); self.post_request_async(&url, body).await; } pub async fn delete_distinct_attribute(&mut self) -> (Value, StatusCode) { let url = format!("/indexes/{}/settings/distinct-attribute", self.uid); self.delete_request_async(&url).await } pub async fn get_primary_key(&mut self) -> (Value, StatusCode) { let url = format!("/indexes/{}/settings/primary_key", self.uid); self.get_request(&url).await } pub async fn get_searchable_attributes(&mut self) -> (Value, StatusCode) { let url = format!("/indexes/{}/settings/searchable-attributes", self.uid); self.get_request(&url).await } pub async fn update_searchable_attributes(&mut self, body: Value) { let url = format!("/indexes/{}/settings/searchable-attributes", self.uid); self.post_request_async(&url, body).await; } pub async fn delete_searchable_attributes(&mut self) -> (Value, StatusCode) { let url = format!("/indexes/{}/settings/searchable-attributes", self.uid); self.delete_request_async(&url).await } pub async fn get_displayed_attributes(&mut self) -> (Value, StatusCode) { let url = format!("/indexes/{}/settings/displayed-attributes", self.uid); self.get_request(&url).await } pub async fn update_displayed_attributes(&mut self, body: Value) { let url = format!("/indexes/{}/settings/displayed-attributes", self.uid); self.post_request_async(&url, body).await; } pub async fn delete_displayed_attributes(&mut self) -> (Value, StatusCode) { let url = format!("/indexes/{}/settings/displayed-attributes", self.uid); self.delete_request_async(&url).await } pub async fn get_synonyms(&mut self) -> (Value, StatusCode) { let url = format!("/indexes/{}/settings/synonyms", self.uid); self.get_request(&url).await } pub async fn update_synonyms(&mut self, body: Value) { let url = format!("/indexes/{}/settings/synonyms", self.uid); self.post_request_async(&url, body).await; } pub async fn delete_synonyms(&mut self) -> (Value, StatusCode) { let url = format!("/indexes/{}/settings/synonyms", self.uid); self.delete_request_async(&url).await } pub async fn get_stop_words(&mut self) -> (Value, StatusCode) { let url = format!("/indexes/{}/settings/stop-words", self.uid); self.get_request(&url).await } pub async fn update_stop_words(&mut self, body: Value) { let url = format!("/indexes/{}/settings/stop-words", self.uid); self.post_request_async(&url, body).await; } pub async fn delete_stop_words(&mut self) -> (Value, StatusCode) { let url = format!("/indexes/{}/settings/stop-words", self.uid); self.delete_request_async(&url).await } pub async fn get_index_stats(&mut self) -> (Value, StatusCode) { let url = format!("/indexes/{}/stats", self.uid); self.get_request(&url).await } pub async fn list_keys(&mut self) -> (Value, StatusCode) { self.get_request("/keys").await } pub async fn get_health(&mut self) -> (Value, StatusCode) { self.get_request("/health").await } pub async fn update_health(&mut self, body: Value) -> (Value, StatusCode) { self.put_request("/health", body).await } pub async fn get_version(&mut self) -> (Value, StatusCode) { self.get_request("/version").await } pub async fn get_sys_info(&mut self) -> (Value, StatusCode) { self.get_request("/sys-info").await } pub async fn get_sys_info_pretty(&mut self) -> (Value, StatusCode) { self.get_request("/sys-info/pretty").await } pub async fn trigger_dump(&self) -> (Value, StatusCode) { self.post_request("/dumps", Value::Null).await } pub async fn get_dump_status(&mut self, dump_uid: &str) -> (Value, StatusCode) { let url = format!("/dumps/{}/status", dump_uid); self.get_request(&url).await } pub async fn trigger_dump_importation(&mut self, dump_uid: &str) -> (Value, StatusCode) { let url = format!("/dumps/{}/import", dump_uid); self.get_request(&url).await } }