1910: After v0.24.0: import `stable` in `main` r=MarinPostma a=curquiza



Co-authored-by: Tamo <tamo@meilisearch.com>
Co-authored-by: many <maxime@meilisearch.com>
Co-authored-by: bors[bot] <26634292+bors[bot]@users.noreply.github.com>
Co-authored-by: Guillaume Mourier <guillaume@meilisearch.com>
Co-authored-by: Irevoire <tamo@meilisearch.com>
Co-authored-by: Clémentine Urquizar <clementine@meilisearch.com>
This commit is contained in:
bors[bot] 2021-11-17 12:48:56 +00:00 committed by GitHub
commit 8363200fd7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 320 additions and 157 deletions

View file

@ -98,5 +98,5 @@ default = ["analytics", "mini-dashboard"]
tikv-jemallocator = "0.4.1"
[package.metadata.mini-dashboard]
assets-url = "https://github.com/meilisearch/mini-dashboard/releases/download/v0.1.4/build.zip"
sha1 = "750e8a8e56cfa61fbf9ead14b08a5f17ad3f3d37"
assets-url = "https://github.com/meilisearch/mini-dashboard/releases/download/v0.1.5/build.zip"
sha1 = "1d955ea91b7691bd6fc207cb39866b82210783f0"

View file

@ -18,7 +18,7 @@ impl SearchAggregator {
Self::default()
}
pub fn finish(&mut self, _: &dyn Any) {}
pub fn succeed(&mut self, _: &dyn Any) {}
}
impl MockAnalytics {

View file

@ -1,4 +1,4 @@
use std::collections::{HashMap, HashSet};
use std::collections::{BinaryHeap, HashMap, HashSet};
use std::fs;
use std::path::Path;
use std::sync::Arc;
@ -38,7 +38,7 @@ fn write_user_id(db_path: &Path, user_id: &str) {
}
}
const SEGMENT_API_KEY: &str = "vHi89WrNDckHSQssyUJqLvIyp2QFITSC";
const SEGMENT_API_KEY: &str = "P3FWhhEsJiEDCuEHpmcN9DHcK4hVfBvb";
pub fn extract_user_agents(request: &HttpRequest) -> Vec<String> {
request
@ -187,7 +187,7 @@ impl Segment {
"kernel_version": kernel_version,
"cores": sys.processors().len(),
"ram_size": sys.total_memory(),
"disk_size": sys.disks().iter().map(|disk| disk.available_space()).max(),
"disk_size": sys.disks().iter().map(|disk| disk.total_space()).max(),
"server_provider": std::env::var("MEILI_SERVER_PROVIDER").ok(),
})
});
@ -254,9 +254,9 @@ impl Segment {
.await;
}
let get_search = std::mem::take(&mut self.get_search_aggregator)
.into_event(&self.user, "Document Searched GET");
.into_event(&self.user, "Documents Searched GET");
let post_search = std::mem::take(&mut self.post_search_aggregator)
.into_event(&self.user, "Document Searched POST");
.into_event(&self.user, "Documents Searched POST");
let add_documents = std::mem::take(&mut self.add_documents_aggregator)
.into_event(&self.user, "Documents Added");
let update_documents = std::mem::take(&mut self.update_documents_aggregator)
@ -286,7 +286,7 @@ pub struct SearchAggregator {
// requests
total_received: usize,
total_succeeded: usize,
time_spent: Vec<usize>,
time_spent: BinaryHeap<usize>,
// sort
sort_with_geo_point: bool,
@ -364,7 +364,7 @@ impl SearchAggregator {
ret
}
pub fn finish(&mut self, result: &SearchResult) {
pub fn succeed(&mut self, result: &SearchResult) {
self.total_succeeded += 1;
self.time_spent.push(result.processing_time_ms as usize);
}
@ -398,17 +398,21 @@ impl SearchAggregator {
self.max_offset = self.max_offset.max(other.max_offset);
}
pub fn into_event(mut self, user: &User, event_name: &str) -> Option<Track> {
pub fn into_event(self, user: &User, event_name: &str) -> Option<Track> {
if self.total_received == 0 {
None
} else {
// the index of the 99th percentage of value
let percentile_99th = 0.99 * (self.total_succeeded as f64 - 1.) + 1.;
self.time_spent.drain(percentile_99th as usize..);
// we get all the values in a sorted manner
let time_spent = self.time_spent.into_sorted_vec();
// We are only intersted by the slowest value of the 99th fastest results
let time_spent = time_spent[percentile_99th as usize];
let properties = json!({
"user-agent": self.user_agents,
"requests": {
"99th_response_time": format!("{:.2}", self.time_spent.iter().sum::<usize>() as f64 / self.time_spent.len() as f64),
"99th_response_time": format!("{:.2}", time_spent),
"total_succeeded": self.total_succeeded,
"total_failed": self.total_received.saturating_sub(self.total_succeeded), // just to be sure we never panics
"total_received": self.total_received,

View file

@ -2,14 +2,14 @@ use meilisearch_error::{Code, ErrorCode};
#[derive(Debug, thiserror::Error)]
pub enum AuthenticationError {
#[error("You must have an authorization token")]
#[error("The X-MEILI-API-KEY header is missing.")]
MissingAuthorizationHeader,
#[error("Invalid API key")]
#[error("The provided API key is invalid.")]
InvalidToken(String),
// Triggered on configuration error.
#[error("Irretrievable state")]
#[error("An internal error has occurred. `Irretrievable state`.")]
IrretrievableState,
#[error("Unknown authentication policy")]
#[error("An internal error has occurred. `Unknown authentication policy`.")]
UnknownPolicy,
}

View file

@ -118,17 +118,18 @@ pub async fn search_with_url_query(
let mut aggregate = SearchAggregator::from_query(&query, &req);
let search_result = meilisearch
.search(path.into_inner().index_uid, query)
.await?;
let search_result = meilisearch.search(path.into_inner().index_uid, query).await;
if let Ok(ref search_result) = search_result {
aggregate.succeed(search_result);
}
analytics.get_search(aggregate);
let search_result = search_result?;
// Tests that the nb_hits is always set to false
#[cfg(test)]
assert!(!search_result.exhaustive_nb_hits);
aggregate.finish(&search_result);
analytics.get_search(aggregate);
debug!("returns: {:?}", search_result);
Ok(HttpResponse::Ok().json(search_result))
}
@ -145,17 +146,18 @@ pub async fn search_with_post(
let mut aggregate = SearchAggregator::from_query(&query, &req);
let search_result = meilisearch
.search(path.into_inner().index_uid, query)
.await?;
let search_result = meilisearch.search(path.into_inner().index_uid, query).await;
if let Ok(ref search_result) = search_result {
aggregate.succeed(search_result);
}
analytics.post_search(aggregate);
let search_result = search_result?;
// Tests that the nb_hits is always set to false
#[cfg(test)]
assert!(!search_result.exhaustive_nb_hits);
aggregate.finish(&search_result);
analytics.post_search(aggregate);
debug!("returns: {:?}", search_result);
Ok(HttpResponse::Ok().json(search_result))
}

View file

@ -97,8 +97,7 @@ pub struct FailedUpdateResult {
pub update_id: u64,
#[serde(rename = "type")]
pub update_type: UpdateType,
#[serde(flatten)]
pub response: ResponseError,
pub error: ResponseError,
pub duration: f64, // in seconds
pub enqueued_at: DateTime<Utc>,
pub processed_at: DateTime<Utc>,
@ -190,12 +189,12 @@ impl From<UpdateStatus> for UpdateStatusResponse {
let update_id = failed.id();
let processed_at = failed.failed_at;
let enqueued_at = failed.from.from.enqueued_at;
let response = failed.into();
let error = failed.into();
let content = FailedUpdateResult {
update_id,
update_type,
response,
error,
duration,
enqueued_at,
processed_at,

View file

@ -7,6 +7,7 @@ use actix_web::http::StatusCode;
use paste::paste;
use serde_json::{json, Value};
use tokio::time::sleep;
use urlencoding::encode;
use super::service::Service;
@ -14,12 +15,12 @@ macro_rules! make_settings_test_routes {
($($name:ident),+) => {
$(paste! {
pub async fn [<update_$name>](&self, value: Value) -> (Value, StatusCode) {
let url = format!("/indexes/{}/settings/{}", self.uid, stringify!($name).replace("_", "-"));
let url = format!("/indexes/{}/settings/{}", encode(self.uid.as_ref()).to_string(), stringify!($name).replace("_", "-"));
self.service.post(url, value).await
}
pub async fn [<get_$name>](&self) -> (Value, StatusCode) {
let url = format!("/indexes/{}/settings/{}", self.uid, stringify!($name).replace("_", "-"));
let url = format!("/indexes/{}/settings/{}", encode(self.uid.as_ref()).to_string(), stringify!($name).replace("_", "-"));
self.service.get(url).await
}
})*
@ -34,12 +35,15 @@ pub struct Index<'a> {
#[allow(dead_code)]
impl Index<'_> {
pub async fn get(&self) -> (Value, StatusCode) {
let url = format!("/indexes/{}", self.uid);
let url = format!("/indexes/{}", encode(self.uid.as_ref()).to_string());
self.service.get(url).await
}
pub async fn load_test_set(&self) -> u64 {
let url = format!("/indexes/{}/documents", self.uid);
let url = format!(
"/indexes/{}/documents",
encode(self.uid.as_ref()).to_string()
);
let (response, code) = self
.service
.post_str(url, include_str!("../assets/test_set.json"))
@ -62,13 +66,13 @@ impl Index<'_> {
let body = json!({
"primaryKey": primary_key,
});
let url = format!("/indexes/{}", self.uid);
let url = format!("/indexes/{}", encode(self.uid.as_ref()).to_string());
self.service.put(url, body).await
}
pub async fn delete(&self) -> (Value, StatusCode) {
let url = format!("/indexes/{}", self.uid);
let url = format!("/indexes/{}", encode(self.uid.as_ref()).to_string());
self.service.delete(url).await
}
@ -78,8 +82,15 @@ impl Index<'_> {
primary_key: Option<&str>,
) -> (Value, StatusCode) {
let url = match primary_key {
Some(key) => format!("/indexes/{}/documents?primaryKey={}", self.uid, key),
None => format!("/indexes/{}/documents", self.uid),
Some(key) => format!(
"/indexes/{}/documents?primaryKey={}",
encode(self.uid.as_ref()).to_string(),
key
),
None => format!(
"/indexes/{}/documents",
encode(self.uid.as_ref()).to_string()
),
};
self.service.post(url, documents).await
}
@ -90,15 +101,26 @@ impl Index<'_> {
primary_key: Option<&str>,
) -> (Value, StatusCode) {
let url = match primary_key {
Some(key) => format!("/indexes/{}/documents?primaryKey={}", self.uid, key),
None => format!("/indexes/{}/documents", self.uid),
Some(key) => format!(
"/indexes/{}/documents?primaryKey={}",
encode(self.uid.as_ref()).to_string(),
key
),
None => format!(
"/indexes/{}/documents",
encode(self.uid.as_ref()).to_string()
),
};
self.service.put(url, documents).await
}
pub async fn wait_update_id(&self, update_id: u64) -> Value {
// try 10 times to get status, or panic to not wait forever
let url = format!("/indexes/{}/updates/{}", self.uid, update_id);
let url = format!(
"/indexes/{}/updates/{}",
encode(self.uid.as_ref()).to_string(),
update_id
);
for _ in 0..10 {
let (response, status_code) = self.service.get(&url).await;
assert_eq!(status_code, 200, "response: {}", response);
@ -113,12 +135,16 @@ impl Index<'_> {
}
pub async fn get_update(&self, update_id: u64) -> (Value, StatusCode) {
let url = format!("/indexes/{}/updates/{}", self.uid, update_id);
let url = format!(
"/indexes/{}/updates/{}",
encode(self.uid.as_ref()).to_string(),
update_id
);
self.service.get(url).await
}
pub async fn list_updates(&self) -> (Value, StatusCode) {
let url = format!("/indexes/{}/updates", self.uid);
let url = format!("/indexes/{}/updates", encode(self.uid.as_ref()).to_string());
self.service.get(url).await
}
@ -127,12 +153,19 @@ impl Index<'_> {
id: u64,
_options: Option<GetDocumentOptions>,
) -> (Value, StatusCode) {
let url = format!("/indexes/{}/documents/{}", self.uid, id);
let url = format!(
"/indexes/{}/documents/{}",
encode(self.uid.as_ref()).to_string(),
id
);
self.service.get(url).await
}
pub async fn get_all_documents(&self, options: GetAllDocumentsOptions) -> (Value, StatusCode) {
let mut url = format!("/indexes/{}/documents?", self.uid);
let mut url = format!(
"/indexes/{}/documents?",
encode(self.uid.as_ref()).to_string()
);
if let Some(limit) = options.limit {
url.push_str(&format!("limit={}&", limit));
}
@ -152,39 +185,58 @@ impl Index<'_> {
}
pub async fn delete_document(&self, id: u64) -> (Value, StatusCode) {
let url = format!("/indexes/{}/documents/{}", self.uid, id);
let url = format!(
"/indexes/{}/documents/{}",
encode(self.uid.as_ref()).to_string(),
id
);
self.service.delete(url).await
}
pub async fn clear_all_documents(&self) -> (Value, StatusCode) {
let url = format!("/indexes/{}/documents", self.uid);
let url = format!(
"/indexes/{}/documents",
encode(self.uid.as_ref()).to_string()
);
self.service.delete(url).await
}
pub async fn delete_batch(&self, ids: Vec<u64>) -> (Value, StatusCode) {
let url = format!("/indexes/{}/documents/delete-batch", self.uid);
let url = format!(
"/indexes/{}/documents/delete-batch",
encode(self.uid.as_ref()).to_string()
);
self.service
.post(url, serde_json::to_value(&ids).unwrap())
.await
}
pub async fn settings(&self) -> (Value, StatusCode) {
let url = format!("/indexes/{}/settings", self.uid);
let url = format!(
"/indexes/{}/settings",
encode(self.uid.as_ref()).to_string()
);
self.service.get(url).await
}
pub async fn update_settings(&self, settings: Value) -> (Value, StatusCode) {
let url = format!("/indexes/{}/settings", self.uid);
let url = format!(
"/indexes/{}/settings",
encode(self.uid.as_ref()).to_string()
);
self.service.post(url, settings).await
}
pub async fn delete_settings(&self) -> (Value, StatusCode) {
let url = format!("/indexes/{}/settings", self.uid);
let url = format!(
"/indexes/{}/settings",
encode(self.uid.as_ref()).to_string()
);
self.service.delete(url).await
}
pub async fn stats(&self) -> (Value, StatusCode) {
let url = format!("/indexes/{}/stats", self.uid);
let url = format!("/indexes/{}/stats", encode(self.uid.as_ref()).to_string());
self.service.get(url).await
}
@ -209,13 +261,17 @@ impl Index<'_> {
}
pub async fn search_post(&self, query: Value) -> (Value, StatusCode) {
let url = format!("/indexes/{}/search", self.uid);
let url = format!("/indexes/{}/search", encode(self.uid.as_ref()).to_string());
self.service.post(url, query).await
}
pub async fn search_get(&self, query: Value) -> (Value, StatusCode) {
let params = serde_url_params::to_string(&query).unwrap();
let url = format!("/indexes/{}/search?{}", self.uid, params);
let url = format!(
"/indexes/{}/search?{}",
encode(self.uid.as_ref()).to_string(),
params
);
self.service.get(url).await
}

View file

@ -7,7 +7,6 @@ use meilisearch_lib::options::{IndexerOpts, MaxMemory};
use once_cell::sync::Lazy;
use serde_json::Value;
use tempfile::TempDir;
use urlencoding::encode;
use meilisearch_http::option::Opt;
@ -62,7 +61,7 @@ impl Server {
/// Returns a view to an index. There is no guarantee that the index exists.
pub fn index(&self, uid: impl AsRef<str>) -> Index<'_> {
Index {
uid: encode(uid.as_ref()).to_string(),
uid: uid.as_ref().to_string(),
service: &self.service,
}
}

View file

@ -812,13 +812,15 @@ async fn error_add_documents_bad_document_id() {
let (response, code) = index.get_update(0).await;
assert_eq!(code, 200);
assert_eq!(response["status"], json!("failed"));
assert_eq!(response["message"], json!("Document identifier `foo & bar` is invalid. A document identifier can be of type integer or string, only composed of alphanumeric characters (a-z A-Z 0-9), hyphens (-) and underscores (_)."));
assert_eq!(response["code"], json!("invalid_document_id"));
assert_eq!(response["type"], json!("invalid_request"));
assert_eq!(
response["link"],
json!("https://docs.meilisearch.com/errors#invalid_document_id")
);
let expected_error = json!({
"message": "Document identifier `foo & bar` is invalid. A document identifier can be of type integer or string, only composed of alphanumeric characters (a-z A-Z 0-9), hyphens (-) and underscores (_).",
"code": "invalid_document_id",
"type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#invalid_document_id"
});
assert_eq!(response["error"], expected_error);
}
#[actix_rt::test]
@ -837,13 +839,15 @@ async fn error_update_documents_bad_document_id() {
let (response, code) = index.get_update(0).await;
assert_eq!(code, 200);
assert_eq!(response["status"], json!("failed"));
assert_eq!(response["message"], json!("Document identifier `foo & bar` is invalid. A document identifier can be of type integer or string, only composed of alphanumeric characters (a-z A-Z 0-9), hyphens (-) and underscores (_)."));
assert_eq!(response["code"], json!("invalid_document_id"));
assert_eq!(response["type"], json!("invalid_request"));
assert_eq!(
response["link"],
json!("https://docs.meilisearch.com/errors#invalid_document_id")
);
let expected_error = json!({
"message": "Document identifier `foo & bar` is invalid. A document identifier can be of type integer or string, only composed of alphanumeric characters (a-z A-Z 0-9), hyphens (-) and underscores (_).",
"code": "invalid_document_id",
"type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#invalid_document_id"
});
assert_eq!(response["error"], expected_error);
}
#[actix_rt::test]
@ -862,16 +866,15 @@ async fn error_add_documents_missing_document_id() {
let (response, code) = index.get_update(0).await;
assert_eq!(code, 200);
assert_eq!(response["status"], "failed");
assert_eq!(
response["message"],
json!(r#"Document doesn't have a `docid` attribute: `{"id":"11","content":"foobar"}`."#)
);
assert_eq!(response["code"], json!("missing_document_id"));
assert_eq!(response["type"], json!("invalid_request"));
assert_eq!(
response["link"],
json!("https://docs.meilisearch.com/errors#missing_document_id")
);
let expected_error = json!({
"message": r#"Document doesn't have a `docid` attribute: `{"id":"11","content":"foobar"}`."#,
"code": "missing_document_id",
"type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#missing_document_id"
});
assert_eq!(response["error"], expected_error);
}
#[actix_rt::test]
@ -890,16 +893,15 @@ async fn error_update_documents_missing_document_id() {
let (response, code) = index.get_update(0).await;
assert_eq!(code, 200);
assert_eq!(response["status"], "failed");
assert_eq!(
response["message"],
r#"Document doesn't have a `docid` attribute: `{"id":"11","content":"foobar"}`."#
);
assert_eq!(response["code"], "missing_document_id");
assert_eq!(response["type"], "invalid_request");
assert_eq!(
response["link"],
"https://docs.meilisearch.com/errors#missing_document_id"
);
let expected_error = json!({
"message": r#"Document doesn't have a `docid` attribute: `{"id":"11","content":"foobar"}`."#,
"code": "missing_document_id",
"type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#missing_document_id"
});
assert_eq!(response["error"], expected_error);
}
#[actix_rt::test]
@ -927,45 +929,47 @@ async fn error_document_field_limit_reached() {
assert_eq!(code, 200);
// Documents without a primary key are not accepted.
assert_eq!(response["status"], "failed");
assert_eq!(
response["message"],
"A document cannot contain more than 65,535 fields."
);
assert_eq!(response["code"], "document_fields_limit_reached");
assert_eq!(response["type"], "invalid_request");
assert_eq!(
response["link"],
"https://docs.meilisearch.com/errors#document_fields_limit_reached"
);
let expected_error = json!({
"message": "A document cannot contain more than 65,535 fields.",
"code": "document_fields_limit_reached",
"type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#document_fields_limit_reached"
});
assert_eq!(response["error"], expected_error);
}
#[actix_rt::test]
#[ignore] // // TODO: Fix in an other PR: this does not provoke any error.
async fn error_add_documents_invalid_geo_field() {
let server = Server::new().await;
let index = server.index("test");
index.create(Some("id")).await;
index
.update_settings(json!({"sortableAttributes": ["_geo"]}))
.await;
let documents = json!([
{
"id": "11",
"_geo": "foobar"
}
]);
index.add_documents(documents, None).await;
index.wait_update_id(0).await;
let (response, code) = index.get_update(0).await;
index.wait_update_id(1).await;
let (response, code) = index.get_update(1).await;
assert_eq!(code, 200);
assert_eq!(response["status"], "failed");
assert_eq!(
response["message"],
r#"The document with the id: `11` contains an invalid _geo field: :syntaxErrorHelper:REPLACE_ME."#
);
assert_eq!(response["code"], "invalid_geo_field");
assert_eq!(response["type"], "invalid_request");
assert_eq!(
response["link"],
"https://docs.meilisearch.com/errors#invalid_geo_field"
);
let expected_error = json!({
"message": r#"The document with the id: `11` contains an invalid _geo field: `foobar`."#,
"code": "invalid_geo_field",
"type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#invalid_geo_field"
});
assert_eq!(response["error"], expected_error);
}
#[actix_rt::test]
@ -993,3 +997,31 @@ async fn error_add_documents_payload_size() {
assert_eq!(response, expected_response);
assert_eq!(code, 413);
}
#[actix_rt::test]
async fn error_primary_key_inference() {
let server = Server::new().await;
let index = server.index("test");
let documents = json!([
{
"title": "11",
"desc": "foobar"
}
]);
index.add_documents(documents, None).await;
index.wait_update_id(0).await;
let (response, code) = index.get_update(0).await;
assert_eq!(code, 200);
assert_eq!(response["status"], "failed");
let expected_error = json!({
"message": r#"The primary key inference process failed because the engine did not find any fields containing `id` substring in their name. If your document identifier does not contain any `id` substring, you can set the primary key of the index."#,
"code": "primary_key_inference_failed",
"type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#primary_key_inference_failed"
});
assert_eq!(response["error"], expected_error);
}

View file

@ -89,7 +89,6 @@ async fn error_create_existing_index() {
}
#[actix_rt::test]
#[ignore] // TODO: Fix in an other PR: uid returned `test%20test%23%21` instead of `test test#!`
async fn error_create_with_invalid_index_uid() {
let server = Server::new().await;
let index = server.index("test test#!");

View file

@ -63,7 +63,7 @@ async fn get_settings() {
}
#[actix_rt::test]
async fn update_settings_unknown_field() {
async fn error_update_settings_unknown_field() {
let server = Server::new().await;
let index = server.index("test");
let (_response, code) = index.update_settings(json!({"foo": 12})).await;
@ -95,10 +95,19 @@ async fn test_partial_update() {
}
#[actix_rt::test]
async fn delete_settings_unexisting_index() {
async fn error_delete_settings_unexisting_index() {
let server = Server::new().await;
let index = server.index("test");
let (_response, code) = index.delete_settings().await;
let (response, code) = index.delete_settings().await;
let expected_response = json!({
"message": "Index `test` not found.",
"code": "index_not_found",
"type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#index_not_found"
});
assert_eq!(response, expected_response);
assert_eq!(code, 404);
}
@ -164,11 +173,20 @@ async fn update_setting_unexisting_index() {
}
#[actix_rt::test]
async fn update_setting_unexisting_index_invalid_uid() {
async fn error_update_setting_unexisting_index_invalid_uid() {
let server = Server::new().await;
let index = server.index("test##! ");
let (response, code) = index.update_settings(json!({})).await;
assert_eq!(code, 400, "{}", response);
let expected_response = json!({
"message": "`test##! ` is not a valid index uid. Index uid can be an integer or a string containing only alphanumeric characters, hyphens (-) and underscores (_).",
"code": "invalid_index_uid",
"type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#invalid_index_uid"
});
assert_eq!(response, expected_response);
assert_eq!(code, 400);
}
macro_rules! test_setting_routes {
@ -246,3 +264,49 @@ test_setting_routes!(
ranking_rules,
synonyms
);
#[actix_rt::test]
async fn error_set_invalid_ranking_rules() {
let server = Server::new().await;
let index = server.index("test");
index.create(None).await;
let (_response, _code) = index
.update_settings(json!({ "rankingRules": [ "manyTheFish"]}))
.await;
index.wait_update_id(0).await;
let (response, code) = index.get_update(0).await;
assert_eq!(code, 200);
assert_eq!(response["status"], "failed");
let expected_error = json!({
"message": r#"`manyTheFish` ranking rule is invalid. Valid ranking rules are Words, Typo, Sort, Proximity, Attribute, Exactness and custom ranking rules."#,
"code": "invalid_ranking_rule",
"type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#invalid_ranking_rule"
});
assert_eq!(response["error"], expected_error);
}
#[actix_rt::test]
async fn set_and_reset_distinct_attribute_with_dedicated_route() {
let server = Server::new().await;
let index = server.index("test");
let (_response, _code) = index.update_distinct_attribute(json!("test")).await;
index.wait_update_id(0).await;
let (response, _) = index.get_distinct_attribute().await;
assert_eq!(response, "test");
index.update_distinct_attribute(json!(null)).await;
index.wait_update_id(1).await;
let (response, _) = index.get_distinct_attribute().await;
assert_eq!(response, json!(null));
}