From d82f8fd904bc291689673685a7b38bd749b9af01 Mon Sep 17 00:00:00 2001 From: ManyTheFish Date: Tue, 23 Jul 2024 13:33:57 +0200 Subject: [PATCH] Add tests --- meilisearch/tests/search/locales.rs | 662 ++++++++++++++++++++++++++++ meilisearch/tests/search/mod.rs | 1 + 2 files changed, 663 insertions(+) create mode 100644 meilisearch/tests/search/locales.rs diff --git a/meilisearch/tests/search/locales.rs b/meilisearch/tests/search/locales.rs new file mode 100644 index 000000000..722694ba3 --- /dev/null +++ b/meilisearch/tests/search/locales.rs @@ -0,0 +1,662 @@ +use meili_snap::*; +use once_cell::sync::Lazy; + +use crate::common::{Server, Value}; +use crate::json; + +static NESTED_DOCUMENTS: Lazy = Lazy::new(|| { + json!([ + { + "id": 852, + "document_en": { + "name": "Attack on Titan", + "description": "Attack on Titan is a Japanese manga series written and illustrated by Hajime Isayama", + "author": "Hajime Isayama", + }, + "document_ja": { + "name": "進撃の巨人", + "description": "進撃の巨人は、日本の漫画シリーズであり、諫山 創によって作画されている。", + "author": "諫山 創", + }, + "document_zh": { + "name": "进击的巨人", + "description": "进击的巨人是日本的漫画系列,由諫山 創作画。", + "author": "諫山創", + }, + "_vectors": { "manual": [1, 2, 3]}, + }, + { + "id": 654, + "document_en": + { + "name": "One Piece", + "description": "One Piece is a Japanese manga series written and illustrated by Eiichiro Oda", + "author": "Eiichiro Oda", + }, + "document_ja": { + "name": "ワンピース", + "description": "ワンピースは、日本の漫画シリーズであり、尾田 栄一郎によって作画されている。", + "author": "尾田 栄一郎", + }, + "document_zh": { + "name": "ONE PIECE", + "description": "海贼王》是尾田荣一郎创作的日本漫画系列。", + "author": "尾田 栄一郎", + }, + "_vectors": { "manual": [1, 2, 54] }, + } + ]) +}); + +static DOCUMENTS: Lazy = Lazy::new(|| { + json!([ + { + "id": 852, + "name_en": "Attack on Titan", + "description_en": "Attack on Titan is a Japanese manga series written and illustrated by Hajime Isayama", + "author_en": "Hajime Isayama", + "name_ja": "進撃の巨人", + "description_ja": "進撃の巨人は、日本の漫画シリーズであり、諫山 創によって作画されている。", + "author_ja": "諫山 創", + "_vectors": { "manual": [1, 2, 3]}, + }, + { + "id": 853, + "name_zh": "进击的巨人", + "description_zh": "进击的巨人是日本的漫画系列,由諫山 創作画。", + "author_zh": "諫山創", + "_vectors": { "manual": [1, 2, 3]}, + }, + { + "id": 654, + "name_en": "One Piece", + "description_en": "One Piece is a Japanese manga series written and illustrated by Eiichiro Oda", + "author_en": "Eiichiro Oda", + "name_ja": "ワンピース", + "description_ja": "ワンピースは、日本の漫画シリーズであり、尾田 栄一郎によって作画されている。", + "author_ja": "尾田 栄一郎", + "_vectors": { "manual": [1, 2, 54] }, + }, + { + "id": 655, + "name_zh": "ONE PIECE", + "description_zh": "海贼王》是尾田荣一郎创作的日本漫画系列。", + "author_zh": "尾田 栄一郎", + "_vectors": { "manual": [1, 2, 54] }, + } + ]) +}); + +#[actix_rt::test] +async fn simple_search() { + let server = Server::new().await; + + let index = server.index("test"); + let documents = DOCUMENTS.clone(); + index + .update_settings( + json!({"searchableAttributes": ["name_en", "name_ja", "name_zh", "author_en", "author_ja", "author_zh", "description_en", "description_ja", "description_zh"]}), + ) + .await; + index.add_documents(documents, None).await; + index.wait_task(1).await; + + // english + index + .search(json!({"q": "Atta", "attributesToRetrieve": ["id"]}), |response, code| { + snapshot!(response, @r###" + { + "hits": [ + { + "id": 852 + } + ], + "query": "Atta", + "processingTimeMs": "[duration]", + "limit": 20, + "offset": 0, + "estimatedTotalHits": 1 + } + "###); + snapshot!(code, @"200 OK"); + }) + .await; + + // japanese + index + .search(json!({"q": "進撃", "attributesToRetrieve": ["id"]}), |response, code| { + snapshot!(response, @r###" + { + "hits": [ + { + "id": 853 + } + ], + "query": "進撃", + "processingTimeMs": "[duration]", + "limit": 20, + "offset": 0, + "estimatedTotalHits": 1 + } + "###); + snapshot!(code, @"200 OK"); + }) + .await; + + index + .search( + json!({"q": "進撃", "attributesToRetrieve": ["id"], "locales": ["jpn"]}), + |response, code| { + snapshot!(response, @r###" + { + "hits": [ + { + "id": 852 + }, + { + "id": 853 + } + ], + "query": "進撃", + "processingTimeMs": "[duration]", + "limit": 20, + "offset": 0, + "estimatedTotalHits": 2 + } + "###); + snapshot!(code, @"200 OK"); + }, + ) + .await; + + // chinese + index + .search(json!({"q": "进击", "attributesToRetrieve": ["id"]}), |response, code| { + snapshot!(response, @r###" + { + "hits": [ + { + "id": 853 + }, + { + "id": 852 + } + ], + "query": "进击", + "processingTimeMs": "[duration]", + "limit": 20, + "offset": 0, + "estimatedTotalHits": 2 + } + "###); + snapshot!(code, @"200 OK"); + }) + .await; +} + +#[actix_rt::test] +async fn force_locales() { + let server = Server::new().await; + + let index = server.index("test"); + let documents = DOCUMENTS.clone(); + let (response, _) = index + .update_settings( + json!({ + "searchableAttributes": ["name_en", "name_ja", "name_zh", "author_en", "author_ja", "author_zh", "description_en", "description_ja", "description_zh"], + "localizedAttributes": [ + // force japanese + {"attributePatterns": ["name_ja", "name_zh", "author_ja", "author_zh", "description_ja", "description_zh"], "locales": ["jpn"]} + ] + }), + ) + .await; + snapshot!(response, @r###" + { + "taskUid": 0, + "indexUid": "test", + "status": "enqueued", + "type": "settingsUpdate", + "enqueuedAt": "[date]" + } + "###); + index.add_documents(documents, None).await; + index.wait_task(1).await; + + // chinese detection + index + .search( + json!({"q": "\"进击的巨人\"", "attributesToRetrieve": ["id"]}), + |response, code| { + snapshot!(response, @r###" + { + "hits": [], + "query": "\"进击的巨人\"", + "processingTimeMs": "[duration]", + "limit": 20, + "offset": 0, + "estimatedTotalHits": 0 + } + "###); + snapshot!(code, @"200 OK"); + }, + ) + .await; + + // force japanese + index + .search( + json!({"q": "\"进击的巨人\"", "attributesToRetrieve": ["id"], "locales": ["jpn"]}), + |response, code| { + snapshot!(response, @r###" + { + "hits": [ + { + "id": 853 + } + ], + "query": "\"进击的巨人\"", + "processingTimeMs": "[duration]", + "limit": 20, + "offset": 0, + "estimatedTotalHits": 1 + } + "###); + snapshot!(code, @"200 OK"); + }, + ) + .await; +} + +#[actix_rt::test] +async fn force_locales_with_pattern() { + let server = Server::new().await; + + let index = server.index("test"); + let documents = DOCUMENTS.clone(); + let (response, _) = index + .update_settings( + json!({ + "searchableAttributes": ["name_en", "name_ja", "name_zh", "author_en", "author_ja", "author_zh", "description_en", "description_ja", "description_zh"], + "localizedAttributes": [ + // force japanese + {"attributePatterns": ["*_ja", "*_zh"], "locales": ["jpn"]} + ] + }), + ) + .await; + snapshot!(response, @r###" + { + "taskUid": 0, + "indexUid": "test", + "status": "enqueued", + "type": "settingsUpdate", + "enqueuedAt": "[date]" + } + "###); + index.add_documents(documents, None).await; + index.wait_task(1).await; + + // chinese detection + index + .search( + json!({"q": "\"进击的巨人\"", "attributesToRetrieve": ["id"]}), + |response, code| { + snapshot!(response, @r###" + { + "hits": [], + "query": "\"进击的巨人\"", + "processingTimeMs": "[duration]", + "limit": 20, + "offset": 0, + "estimatedTotalHits": 0 + } + "###); + snapshot!(code, @"200 OK"); + }, + ) + .await; + + // force japanese + index + .search( + json!({"q": "\"进击的巨人\"", "attributesToRetrieve": ["id"], "locales": ["jpn"]}), + |response, code| { + snapshot!(response, @r###" + { + "hits": [ + { + "id": 853 + } + ], + "query": "\"进击的巨人\"", + "processingTimeMs": "[duration]", + "limit": 20, + "offset": 0, + "estimatedTotalHits": 1 + } + "###); + snapshot!(code, @"200 OK"); + }, + ) + .await; +} + +#[actix_rt::test] +async fn force_locales_with_pattern_nested() { + let server = Server::new().await; + + let index = server.index("test"); + let documents = NESTED_DOCUMENTS.clone(); + let (response, _) = index + .update_settings(json!({ + "searchableAttributes": ["document_en", "document_ja", "document_zh"], + "localizedAttributes": [ + // force japanese + {"attributePatterns": ["document_ja.*", "*_zh.*"], "locales": ["jpn"]} + ] + })) + .await; + snapshot!(response, @r###" + { + "taskUid": 0, + "indexUid": "test", + "status": "enqueued", + "type": "settingsUpdate", + "enqueuedAt": "[date]" + } + "###); + index.add_documents(documents, None).await; + index.wait_task(1).await; + + // chinese + index + .search( + json!({"q": "\"进击的巨人\"", "attributesToRetrieve": ["id"], "locales": ["cmn"]}), + |response, code| { + snapshot!(response, @r###" + { + "hits": [], + "query": "\"进击的巨人\"", + "processingTimeMs": "[duration]", + "limit": 20, + "offset": 0, + "estimatedTotalHits": 0 + } + "###); + snapshot!(code, @"200 OK"); + }, + ) + .await; + + // force japanese + index + .search( + json!({"q": "\"进击的巨人\"", "attributesToRetrieve": ["id"], "locales": ["jpn"]}), + |response, code| { + snapshot!(response, @r###" + { + "hits": [ + { + "id": 852 + } + ], + "query": "\"进击的巨人\"", + "processingTimeMs": "[duration]", + "limit": 20, + "offset": 0, + "estimatedTotalHits": 1 + } + "###); + snapshot!(code, @"200 OK"); + }, + ) + .await; +} + +#[actix_rt::test] +async fn invalid_locales() { + let server = Server::new().await; + + let index = server.index("test"); + let documents = DOCUMENTS.clone(); + index + .update_settings( + json!({"searchableAttributes": ["name_en", "name_ja", "name_zh", "author_en", "author_ja", "author_zh", "description_en", "description_ja", "description_zh"]}), + ) + .await; + index.add_documents(documents, None).await; + index.wait_task(1).await; + + let (response, code) = index + .search_post(json!({"q": "Atta", "attributesToRetrieve": ["id"], "locales": ["invalid"]})) + .await; + snapshot!(code, @"400 Bad Request"); + snapshot!(json_string!(response), @r###" + { + "message": "Unknown value `invalid` at `.locales[0]`: expected one of `epo`, `eng`, `rus`, `cmn`, `spa`, `por`, `ita`, `ben`, `fra`, `deu`, `ukr`, `kat`, `ara`, `hin`, `jpn`, `heb`, `yid`, `pol`, `amh`, `jav`, `kor`, `nob`, `dan`, `swe`, `fin`, `tur`, `nld`, `hun`, `ces`, `ell`, `bul`, `bel`, `mar`, `kan`, `ron`, `slv`, `hrv`, `srp`, `mkd`, `lit`, `lav`, `est`, `tam`, `vie`, `urd`, `tha`, `guj`, `uzb`, `pan`, `aze`, `ind`, `tel`, `pes`, `mal`, `ori`, `mya`, `nep`, `sin`, `khm`, `tuk`, `aka`, `zul`, `sna`, `afr`, `lat`, `slk`, `cat`, `tgl`, `hye`", + "code": "invalid_search_locales", + "type": "invalid_request", + "link": "https://docs.meilisearch.com/errors#invalid_search_locales" + } + "###); + + let (response, code) = index + .search_get( + &yaup::to_string( + &json!({"q": "Atta", "attributesToRetrieve": ["id"], "locales": ["invalid"]}), + ) + .unwrap(), + ) + .await; + snapshot!(code, @"400 Bad Request"); + snapshot!(json_string!(response), @r###" + { + "message": "Invalid value in parameter `locales`: Unknown value `invalid`, expected one of `epo`, `eng`, `rus`, `cmn`, `spa`, `por`, `ita`, `ben`, `fra`, `deu`, `ukr`, `kat`, `ara`, `hin`, `jpn`, `heb`, `yid`, `pol`, `amh`, `jav`, `kor`, `nob`, `dan`, `swe`, `fin`, `tur`, `nld`, `hun`, `ces`, `ell`, `bul`, `bel`, `mar`, `kan`, `ron`, `slv`, `hrv`, `srp`, `mkd`, `lit`, `lav`, `est`, `tam`, `vie`, `urd`, `tha`, `guj`, `uzb`, `pan`, `aze`, `ind`, `tel`, `pes`, `mal`, `ori`, `mya`, `nep`, `sin`, `khm`, `tuk`, `aka`, `zul`, `sna`, `afr`, `lat`, `slk`, `cat`, `tgl`, `hye`", + "code": "invalid_search_locales", + "type": "invalid_request", + "link": "https://docs.meilisearch.com/errors#invalid_search_locales" + } + "###); +} + +#[actix_rt::test] +async fn invalid_localized_attributes_rules() { + let server = Server::new().await; + + let index = server.index("test"); + let (response, _) = index + .update_settings(json!({ + "localizedAttributes": [ + {"attributePatterns": ["*_ja", "*_zh"], "locales": ["japan"]} + ] + })) + .await; + snapshot!(response, @r###" + { + "message": "Unknown value `japan` at `.localizedAttributes[0].locales[0]`: expected one of `epo`, `eng`, `rus`, `cmn`, `spa`, `por`, `ita`, `ben`, `fra`, `deu`, `ukr`, `kat`, `ara`, `hin`, `jpn`, `heb`, `yid`, `pol`, `amh`, `jav`, `kor`, `nob`, `dan`, `swe`, `fin`, `tur`, `nld`, `hun`, `ces`, `ell`, `bul`, `bel`, `mar`, `kan`, `ron`, `slv`, `hrv`, `srp`, `mkd`, `lit`, `lav`, `est`, `tam`, `vie`, `urd`, `tha`, `guj`, `uzb`, `pan`, `aze`, `ind`, `tel`, `pes`, `mal`, `ori`, `mya`, `nep`, `sin`, `khm`, `tuk`, `aka`, `zul`, `sna`, `afr`, `lat`, `slk`, `cat`, `tgl`, `hye`", + "code": "invalid_settings_localized_attributes", + "type": "invalid_request", + "link": "https://docs.meilisearch.com/errors#invalid_settings_localized_attributes" + } + "###); + + let (response, _) = index + .update_settings(json!({ + "localizedAttributes": [ + {"attributePatterns": ["*_ja", "*_zh"], "locales": "jpn"} + ] + })) + .await; + snapshot!(response, @r###" + { + "message": "Invalid value type at `.localizedAttributes[0].locales`: expected an array, but found a string: `\"jpn\"`", + "code": "invalid_settings_localized_attributes", + "type": "invalid_request", + "link": "https://docs.meilisearch.com/errors#invalid_settings_localized_attributes" + } + "###); + + let (response, _) = index + .update_settings(json!({ + "localizedAttributes": [ + {"attributePatterns": "*_ja", "locales": ["jpn"]} + ] + })) + .await; + snapshot!(response, @r###" + { + "message": "Invalid value type at `.localizedAttributes[0].attributePatterns`: expected an array, but found a string: `\"*_ja\"`", + "code": "invalid_settings_localized_attributes", + "type": "invalid_request", + "link": "https://docs.meilisearch.com/errors#invalid_settings_localized_attributes" + } + "###); + + let (response, _) = index + .update_settings(json!({ + "localizedAttributes": [ + {"locales": ["jpn"]} + ] + })) + .await; + snapshot!(response, @r###" + { + "message": "Missing field `attributePatterns` inside `.localizedAttributes[0]`", + "code": "invalid_settings_localized_attributes", + "type": "invalid_request", + "link": "https://docs.meilisearch.com/errors#invalid_settings_localized_attributes" + } + "###); +} + +#[actix_rt::test] +async fn simple_facet_search() { + let server = Server::new().await; + + let index = server.index("test"); + let documents = DOCUMENTS.clone(); + let (response, _) = index + .update_settings(json!({ + "filterableAttributes": ["name_en", "name_ja", "name_zh"], + })) + .await; + snapshot!(response, @r###" + { + "taskUid": 0, + "indexUid": "test", + "status": "enqueued", + "type": "settingsUpdate", + "enqueuedAt": "[date]" + } + "###); + index.add_documents(documents, None).await; + index.wait_task(1).await; + + let (response, _) = index + .facet_search(json!({"facetName": "name_zh", "facetQuery": "進撃", "locales": ["cmn"]})) + .await; + + snapshot!(response, @r###" + { + "facetHits": [ + { + "value": "进击的巨人", + "count": 1 + } + ], + "facetQuery": "進撃", + "processingTimeMs": "[duration]" + } + "###); + + let (response, _) = index + .facet_search(json!({"facetName": "name_zh", "facetQuery": "進撃", "locales": ["jpn"]})) + .await; + + snapshot!(response, @r###" + { + "facetHits": [ + { + "value": "进击的巨人", + "count": 1 + } + ], + "facetQuery": "進撃", + "processingTimeMs": "[duration]" + } + "###); +} + +#[actix_rt::test] +async fn facet_search_with_localized_attributes() { + let server = Server::new().await; + + let index = server.index("test"); + let documents = DOCUMENTS.clone(); + let (response, _) = index + .update_settings(json!({ + "filterableAttributes": ["name_ja", "name_zh"], + "localizedAttributes": [ + // force japanese + {"attributePatterns": ["*_ja", "*_zh"], "locales": ["jpn"]} + ] + })) + .await; + snapshot!(response, @r###" + { + "taskUid": 0, + "indexUid": "test", + "status": "enqueued", + "type": "settingsUpdate", + "enqueuedAt": "[date]" + } + "###); + index.add_documents(documents, None).await; + index.wait_task(1).await; + + let (response, _) = index + .facet_search(json!({"facetName": "name_zh", "facetQuery": "进击", "locales": ["cmn"]})) + .await; + + snapshot!(response, @r###" + { + "facetHits": [], + "facetQuery": "进击", + "processingTimeMs": "[duration]" + } + "###); + + let (response, _) = index + .facet_search(json!({"facetName": "name_zh", "facetQuery": "进击", "locales": ["jpn"]})) + .await; + + snapshot!(response, @r###" + { + "facetHits": [ + { + "value": "进击的巨人", + "count": 1 + } + ], + "facetQuery": "进击", + "processingTimeMs": "[duration]" + } + "###); + + let (response, _) = + index.facet_search(json!({"facetName": "name_zh", "facetQuery": "进击"})).await; + + snapshot!(response, @r###" + { + "facetHits": [ + { + "value": "进击的巨人", + "count": 1 + } + ], + "facetQuery": "进击", + "processingTimeMs": "[duration]" + } + "###); +} diff --git a/meilisearch/tests/search/mod.rs b/meilisearch/tests/search/mod.rs index 7f4648e57..301ef9aa2 100644 --- a/meilisearch/tests/search/mod.rs +++ b/meilisearch/tests/search/mod.rs @@ -7,6 +7,7 @@ mod facet_search; mod formatted; mod geo; mod hybrid; +mod locales; mod matching_strategy; mod multi; mod pagination;