mirror of
https://github.com/meilisearch/MeiliSearch
synced 2024-12-27 23:11:39 +01:00
524 lines
17 KiB
Rust
524 lines
17 KiB
Rust
use crate::common::Server;
|
|
use crate::json;
|
|
|
|
macro_rules! test_setting_routes {
|
|
($({setting: $setting:ident, update_verb: $update_verb:ident, default_value: $default_value:tt},) *) => {
|
|
$(
|
|
mod $setting {
|
|
use crate::common::Server;
|
|
|
|
#[actix_rt::test]
|
|
async fn get_unexisting_index() {
|
|
let server = Server::new().await;
|
|
let url = format!("/indexes/test/settings/{}",
|
|
stringify!($setting)
|
|
.chars()
|
|
.map(|c| if c == '_' { '-' } else { c })
|
|
.collect::<String>());
|
|
let (_response, code) = server.service.get(url).await;
|
|
assert_eq!(code, 404);
|
|
}
|
|
|
|
#[actix_rt::test]
|
|
async fn update_unexisting_index() {
|
|
let server = Server::new().await;
|
|
let url = format!("/indexes/test/settings/{}",
|
|
stringify!($setting)
|
|
.chars()
|
|
.map(|c| if c == '_' { '-' } else { c })
|
|
.collect::<String>());
|
|
let (response, code) = server.service.$update_verb(url, serde_json::Value::Null.into()).await;
|
|
assert_eq!(code, 202, "{}", response);
|
|
server.index("").wait_task(0).await;
|
|
let (response, code) = server.index("test").get().await;
|
|
assert_eq!(code, 200, "{}", response);
|
|
}
|
|
|
|
#[actix_rt::test]
|
|
async fn delete_unexisting_index() {
|
|
let server = Server::new().await;
|
|
let url = format!("/indexes/test/settings/{}",
|
|
stringify!($setting)
|
|
.chars()
|
|
.map(|c| if c == '_' { '-' } else { c })
|
|
.collect::<String>());
|
|
let (_, code) = server.service.delete(url).await;
|
|
assert_eq!(code, 202);
|
|
let response = server.index("").wait_task(0).await;
|
|
assert_eq!(response["status"], "failed");
|
|
}
|
|
|
|
#[actix_rt::test]
|
|
async fn get_default() {
|
|
let server = Server::new().await;
|
|
let index = server.index("test");
|
|
let (response, code) = index.create(None).await;
|
|
assert_eq!(code, 202, "{}", response);
|
|
index.wait_task(0).await;
|
|
let url = format!("/indexes/test/settings/{}",
|
|
stringify!($setting)
|
|
.chars()
|
|
.map(|c| if c == '_' { '-' } else { c })
|
|
.collect::<String>());
|
|
let (response, code) = server.service.get(url).await;
|
|
assert_eq!(code, 200, "{}", response);
|
|
let expected = crate::json!($default_value);
|
|
assert_eq!(expected, response);
|
|
}
|
|
}
|
|
)*
|
|
|
|
#[actix_rt::test]
|
|
async fn all_setting_tested() {
|
|
let expected = std::collections::BTreeSet::from_iter(meilisearch::routes::indexes::settings::ALL_SETTINGS_NAMES.iter());
|
|
let tested = std::collections::BTreeSet::from_iter([$(stringify!($setting)),*].iter());
|
|
let diff: Vec<_> = expected.difference(&tested).collect();
|
|
assert!(diff.is_empty(), "Not all settings were tested, please add the following settings to the `test_setting_routes!` macro: {:?}", diff);
|
|
}
|
|
};
|
|
}
|
|
|
|
test_setting_routes!(
|
|
{
|
|
setting: filterable_attributes,
|
|
update_verb: put,
|
|
default_value: []
|
|
},
|
|
{
|
|
setting: displayed_attributes,
|
|
update_verb: put,
|
|
default_value: ["*"]
|
|
},
|
|
{
|
|
setting: localized_attributes,
|
|
update_verb: put,
|
|
default_value: null
|
|
},
|
|
{
|
|
setting: searchable_attributes,
|
|
update_verb: put,
|
|
default_value: ["*"]
|
|
},
|
|
{
|
|
setting: distinct_attribute,
|
|
update_verb: put,
|
|
default_value: null
|
|
},
|
|
{
|
|
setting: stop_words,
|
|
update_verb: put,
|
|
default_value: []
|
|
},
|
|
{
|
|
setting: separator_tokens,
|
|
update_verb: put,
|
|
default_value: []
|
|
},
|
|
{
|
|
setting: non_separator_tokens,
|
|
update_verb: put,
|
|
default_value: []
|
|
},
|
|
{
|
|
setting: dictionary,
|
|
update_verb: put,
|
|
default_value: []
|
|
},
|
|
{
|
|
setting: ranking_rules,
|
|
update_verb: put,
|
|
default_value: ["words", "typo", "proximity", "attribute", "sort", "exactness"]
|
|
},
|
|
{
|
|
setting: synonyms,
|
|
update_verb: put,
|
|
default_value: {}
|
|
},
|
|
{
|
|
setting: pagination,
|
|
update_verb: patch,
|
|
default_value: {"maxTotalHits": 1000}
|
|
},
|
|
{
|
|
setting: faceting,
|
|
update_verb: patch,
|
|
default_value: {"maxValuesPerFacet": 100, "sortFacetValuesBy": {"*": "alpha"}}
|
|
},
|
|
{
|
|
setting: search_cutoff_ms,
|
|
update_verb: put,
|
|
default_value: null
|
|
},
|
|
{
|
|
setting: embedders,
|
|
update_verb: patch,
|
|
default_value: null
|
|
},
|
|
{
|
|
setting: facet_search,
|
|
update_verb: put,
|
|
default_value: true
|
|
},
|
|
{
|
|
setting: prefix_search,
|
|
update_verb: put,
|
|
default_value: "indexingTime"
|
|
},
|
|
{
|
|
setting: proximity_precision,
|
|
update_verb: put,
|
|
default_value: "byWord"
|
|
},
|
|
{
|
|
setting: sortable_attributes,
|
|
update_verb: put,
|
|
default_value: []
|
|
},
|
|
{
|
|
setting: typo_tolerance,
|
|
update_verb: patch,
|
|
default_value: {"enabled": true, "minWordSizeForTypos": {"oneTypo": 5, "twoTypos": 9}, "disableOnWords": [], "disableOnAttributes": []}
|
|
},
|
|
);
|
|
|
|
#[actix_rt::test]
|
|
async fn get_settings_unexisting_index() {
|
|
let server = Server::new().await;
|
|
let (response, code) = server.index("test").settings().await;
|
|
assert_eq!(code, 404, "{}", response)
|
|
}
|
|
|
|
#[actix_rt::test]
|
|
async fn get_settings() {
|
|
let server = Server::new().await;
|
|
let index = server.index("test");
|
|
let (response, _code) = index.create(None).await;
|
|
index.wait_task(response.uid()).await;
|
|
let (response, code) = index.settings().await;
|
|
assert_eq!(code, 200);
|
|
let settings = response.as_object().unwrap();
|
|
assert_eq!(settings.keys().len(), 19);
|
|
assert_eq!(settings["displayedAttributes"], json!(["*"]));
|
|
assert_eq!(settings["searchableAttributes"], json!(["*"]));
|
|
assert_eq!(settings["filterableAttributes"], json!([]));
|
|
assert_eq!(settings["sortableAttributes"], json!([]));
|
|
assert_eq!(settings["distinctAttribute"], json!(null));
|
|
assert_eq!(
|
|
settings["rankingRules"],
|
|
json!(["words", "typo", "proximity", "attribute", "sort", "exactness"])
|
|
);
|
|
assert_eq!(settings["stopWords"], json!([]));
|
|
assert_eq!(settings["nonSeparatorTokens"], json!([]));
|
|
assert_eq!(settings["separatorTokens"], json!([]));
|
|
assert_eq!(settings["dictionary"], json!([]));
|
|
assert_eq!(
|
|
settings["faceting"],
|
|
json!({
|
|
"maxValuesPerFacet": 100,
|
|
"sortFacetValuesBy": {
|
|
"*": "alpha"
|
|
}
|
|
})
|
|
);
|
|
assert_eq!(
|
|
settings["pagination"],
|
|
json!({
|
|
"maxTotalHits": 1000,
|
|
})
|
|
);
|
|
assert_eq!(settings["proximityPrecision"], json!("byWord"));
|
|
assert_eq!(settings["searchCutoffMs"], json!(null));
|
|
assert_eq!(settings["prefixSearch"], json!("indexingTime"));
|
|
assert_eq!(settings["facetSearch"], json!(true));
|
|
}
|
|
|
|
#[actix_rt::test]
|
|
async fn secrets_are_hidden_in_settings() {
|
|
let server = Server::new().await;
|
|
let (response, code) = server.set_features(json!({"vectorStore": true})).await;
|
|
|
|
meili_snap::snapshot!(code, @"200 OK");
|
|
meili_snap::snapshot!(meili_snap::json_string!(response), @r###"
|
|
{
|
|
"vectorStore": true,
|
|
"metrics": false,
|
|
"logsRoute": false,
|
|
"editDocumentsByFunction": false,
|
|
"containsFilter": false
|
|
}
|
|
"###);
|
|
|
|
let index = server.index("test");
|
|
let (response, _code) = index.create(None).await;
|
|
index.wait_task(response.uid()).await;
|
|
|
|
let (response, code) = index
|
|
.update_settings(json!({
|
|
"embedders": {
|
|
"default": {
|
|
"source": "rest",
|
|
"url": "https://localhost:7777",
|
|
"apiKey": "My super secret value you will never guess",
|
|
"dimensions": 4,
|
|
"request": "{{text}}",
|
|
"response": "{{embedding}}"
|
|
}
|
|
}
|
|
}))
|
|
.await;
|
|
meili_snap::snapshot!(code, @"202 Accepted");
|
|
|
|
meili_snap::snapshot!(meili_snap::json_string!(response, { ".duration" => "[duration]", ".enqueuedAt" => "[date]", ".startedAt" => "[date]", ".finishedAt" => "[date]" }),
|
|
@r###"
|
|
{
|
|
"taskUid": 1,
|
|
"indexUid": "test",
|
|
"status": "enqueued",
|
|
"type": "settingsUpdate",
|
|
"enqueuedAt": "[date]"
|
|
}
|
|
"###);
|
|
|
|
let settings_update_uid = response.uid();
|
|
|
|
index.wait_task(settings_update_uid).await;
|
|
|
|
let (response, code) = index.settings().await;
|
|
meili_snap::snapshot!(code, @"200 OK");
|
|
meili_snap::snapshot!(meili_snap::json_string!(response), @r###"
|
|
{
|
|
"displayedAttributes": [
|
|
"*"
|
|
],
|
|
"searchableAttributes": [
|
|
"*"
|
|
],
|
|
"filterableAttributes": [],
|
|
"sortableAttributes": [],
|
|
"rankingRules": [
|
|
"words",
|
|
"typo",
|
|
"proximity",
|
|
"attribute",
|
|
"sort",
|
|
"exactness"
|
|
],
|
|
"stopWords": [],
|
|
"nonSeparatorTokens": [],
|
|
"separatorTokens": [],
|
|
"dictionary": [],
|
|
"synonyms": {},
|
|
"distinctAttribute": null,
|
|
"proximityPrecision": "byWord",
|
|
"typoTolerance": {
|
|
"enabled": true,
|
|
"minWordSizeForTypos": {
|
|
"oneTypo": 5,
|
|
"twoTypos": 9
|
|
},
|
|
"disableOnWords": [],
|
|
"disableOnAttributes": []
|
|
},
|
|
"faceting": {
|
|
"maxValuesPerFacet": 100,
|
|
"sortFacetValuesBy": {
|
|
"*": "alpha"
|
|
}
|
|
},
|
|
"pagination": {
|
|
"maxTotalHits": 1000
|
|
},
|
|
"embedders": {
|
|
"default": {
|
|
"source": "rest",
|
|
"apiKey": "My suXXXXXX...",
|
|
"dimensions": 4,
|
|
"documentTemplate": "{% for field in fields %}{% if field.is_searchable and field.value != nil %}{{ field.name }}: {{ field.value }}\n{% endif %}{% endfor %}",
|
|
"documentTemplateMaxBytes": 400,
|
|
"url": "https://localhost:7777",
|
|
"request": "{{text}}",
|
|
"response": "{{embedding}}",
|
|
"headers": {}
|
|
}
|
|
},
|
|
"searchCutoffMs": null,
|
|
"localizedAttributes": null,
|
|
"facetSearch": true,
|
|
"prefixSearch": "indexingTime"
|
|
}
|
|
"###);
|
|
|
|
let (response, code) = server.get_task(settings_update_uid).await;
|
|
meili_snap::snapshot!(code, @"200 OK");
|
|
meili_snap::snapshot!(meili_snap::json_string!(response["details"]), @r###"
|
|
{
|
|
"embedders": {
|
|
"default": {
|
|
"source": "rest",
|
|
"apiKey": "My suXXXXXX...",
|
|
"dimensions": 4,
|
|
"url": "https://localhost:7777",
|
|
"request": "{{text}}",
|
|
"response": "{{embedding}}"
|
|
}
|
|
}
|
|
}
|
|
"###);
|
|
}
|
|
|
|
#[actix_rt::test]
|
|
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;
|
|
assert_eq!(code, 400);
|
|
}
|
|
|
|
#[actix_rt::test]
|
|
async fn test_partial_update() {
|
|
let server = Server::new().await;
|
|
let index = server.index("test");
|
|
let (_response, _code) = index.update_settings(json!({"displayedAttributes": ["foo"]})).await;
|
|
index.wait_task(0).await;
|
|
let (response, code) = index.settings().await;
|
|
assert_eq!(code, 200);
|
|
assert_eq!(response["displayedAttributes"], json!(["foo"]));
|
|
assert_eq!(response["searchableAttributes"], json!(["*"]));
|
|
|
|
let (_response, _) = index.update_settings(json!({"searchableAttributes": ["bar"]})).await;
|
|
index.wait_task(1).await;
|
|
|
|
let (response, code) = index.settings().await;
|
|
assert_eq!(code, 200);
|
|
assert_eq!(response["displayedAttributes"], json!(["foo"]));
|
|
assert_eq!(response["searchableAttributes"], json!(["bar"]));
|
|
}
|
|
|
|
#[actix_rt::test]
|
|
async fn error_delete_settings_unexisting_index() {
|
|
let server = Server::new().await;
|
|
let index = server.index("test");
|
|
let (_response, code) = index.delete_settings().await;
|
|
assert_eq!(code, 202);
|
|
|
|
let response = index.wait_task(0).await;
|
|
|
|
assert_eq!(response["status"], "failed");
|
|
}
|
|
|
|
#[actix_rt::test]
|
|
async fn reset_all_settings() {
|
|
let server = Server::new().await;
|
|
let index = server.index("test");
|
|
|
|
let documents = json!([
|
|
{
|
|
"id": 1,
|
|
"name": "curqui",
|
|
"age": 99
|
|
}
|
|
]);
|
|
|
|
let (response, code) = index.add_documents(documents, None).await;
|
|
assert_eq!(code, 202);
|
|
assert_eq!(response["taskUid"], 0);
|
|
index.wait_task(0).await;
|
|
|
|
index
|
|
.update_settings(json!({"displayedAttributes": ["name", "age"], "searchableAttributes": ["name"], "stopWords": ["the"], "filterableAttributes": ["age"], "synonyms": {"puppy": ["dog", "doggo", "potat"] }}))
|
|
.await;
|
|
index.wait_task(1).await;
|
|
let (response, code) = index.settings().await;
|
|
assert_eq!(code, 200);
|
|
assert_eq!(response["displayedAttributes"], json!(["name", "age"]));
|
|
assert_eq!(response["searchableAttributes"], json!(["name"]));
|
|
assert_eq!(response["stopWords"], json!(["the"]));
|
|
assert_eq!(response["synonyms"], json!({"puppy": ["dog", "doggo", "potat"] }));
|
|
assert_eq!(response["filterableAttributes"], json!(["age"]));
|
|
|
|
index.delete_settings().await;
|
|
index.wait_task(2).await;
|
|
|
|
let (response, code) = index.settings().await;
|
|
assert_eq!(code, 200);
|
|
assert_eq!(response["displayedAttributes"], json!(["*"]));
|
|
assert_eq!(response["searchableAttributes"], json!(["*"]));
|
|
assert_eq!(response["stopWords"], json!([]));
|
|
assert_eq!(response["filterableAttributes"], json!([]));
|
|
assert_eq!(response["synonyms"], json!({}));
|
|
|
|
let (response, code) = index.get_document(1, None).await;
|
|
assert_eq!(code, 200);
|
|
assert!(response.as_object().unwrap().get("age").is_some());
|
|
}
|
|
|
|
#[actix_rt::test]
|
|
async fn update_setting_unexisting_index() {
|
|
let server = Server::new().await;
|
|
let index = server.index("test");
|
|
let (_response, code) = index.update_settings(json!({})).await;
|
|
assert_eq!(code, 202);
|
|
let response = index.wait_task(0).await;
|
|
assert_eq!(response["status"], "succeeded");
|
|
let (_response, code) = index.get().await;
|
|
assert_eq!(code, 200);
|
|
index.delete_settings().await;
|
|
let response = index.wait_task(1).await;
|
|
assert_eq!(response["status"], "succeeded");
|
|
}
|
|
|
|
#[actix_rt::test]
|
|
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;
|
|
meili_snap::snapshot!(code, @"400 Bad Request");
|
|
meili_snap::snapshot!(meili_snap::json_string!(response), @r###"
|
|
{
|
|
"message": "`test##! ` is not a valid index uid. Index uid can be an integer or a string containing only alphanumeric characters, hyphens (-) and underscores (_), and can not be more than 512 bytes.",
|
|
"code": "invalid_index_uid",
|
|
"type": "invalid_request",
|
|
"link": "https://docs.meilisearch.com/errors#invalid_index_uid"
|
|
}
|
|
"###);
|
|
}
|
|
|
|
#[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;
|
|
meili_snap::snapshot!(code, @"400 Bad Request");
|
|
meili_snap::snapshot!(meili_snap::json_string!(response), @r###"
|
|
{
|
|
"message": "Invalid value at `.rankingRules[0]`: `manyTheFish` ranking rule is invalid. Valid ranking rules are words, typo, sort, proximity, attribute, exactness and custom ranking rules.",
|
|
"code": "invalid_settings_ranking_rules",
|
|
"type": "invalid_request",
|
|
"link": "https://docs.meilisearch.com/errors#invalid_settings_ranking_rules"
|
|
}
|
|
"###);
|
|
}
|
|
|
|
#[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_task(0).await;
|
|
|
|
let (response, _) = index.get_distinct_attribute().await;
|
|
|
|
assert_eq!(response, "test");
|
|
|
|
index.update_distinct_attribute(json!(null)).await;
|
|
|
|
index.wait_task(1).await;
|
|
|
|
let (response, _) = index.get_distinct_attribute().await;
|
|
|
|
assert_eq!(response, json!(null));
|
|
}
|