diff --git a/crates/meilisearch/src/personalization/mod.rs b/crates/meilisearch/src/personalization/mod.rs index 683e337dc..9310d2679 100644 --- a/crates/meilisearch/src/personalization/mod.rs +++ b/crates/meilisearch/src/personalization/mod.rs @@ -11,23 +11,23 @@ const MAX_RETRIES: u32 = 10; #[derive(Debug, thiserror::Error)] enum PersonalizationError { - #[error("HTTP request failed: {0}")] + #[error("Personalization service: HTTP request failed: {0}")] Request(#[from] reqwest::Error), - #[error("Failed to parse response: {0}")] + #[error("Personalization service: Failed to parse response: {0}")] Parse(String), - #[error("Cohere API error: {0}")] + #[error("Personalization service: Cohere API error: {0}")] Api(String), - #[error("Unauthorized: invalid API key")] + #[error("Personalization service: Unauthorized: invalid API key")] Unauthorized, - #[error("Rate limited: too many requests")] + #[error("Personalization service: Rate limited: too many requests")] RateLimited, - #[error("Bad request: {0}")] + #[error("Personalization service: Bad request: {0}")] BadRequest(String), - #[error("Internal server error: {0}")] + #[error("Personalization service: Internal server error: {0}")] InternalServerError(String), - #[error("Network error: {0}")] + #[error("Personalization service: Network error: {0}")] Network(String), - #[error("Deadline exceeded")] + #[error("Personalization service: Deadline exceeded")] DeadlineExceeded, #[error( "{disabled_action} requires enabling the `{feature}` experimental feature. See {issue_link}" diff --git a/crates/meilisearch/tests/features/mod.rs b/crates/meilisearch/tests/features/mod.rs index e0f1afb9b..04922f4e2 100644 --- a/crates/meilisearch/tests/features/mod.rs +++ b/crates/meilisearch/tests/features/mod.rs @@ -207,3 +207,45 @@ async fn errors() { } "###); } + +#[actix_rt::test] +async fn search_with_personalization_without_enabling_the_feature() { + let server = Server::new().await; + let index = server.unique_index(); + + // Create the index and add some documents + let (task, _code) = index.create(None).await; + server.wait_task(task.uid()).await.succeeded(); + + let (task, _code) = index + .add_documents( + json!([ + {"id": 1, "title": "The Dark Knight", "genre": "Action"}, + {"id": 2, "title": "Inception", "genre": "Sci-Fi"}, + {"id": 3, "title": "The Matrix", "genre": "Sci-Fi"} + ]), + None, + ) + .await; + server.wait_task(task.uid()).await.succeeded(); + + // Try to search with personalization - should return feature_not_enabled error + let (response, code) = index + .search_post(json!({ + "q": "movie", + "personalize": { + "userContext": "I love science fiction movies" + } + })) + .await; + + meili_snap::snapshot!(code, @"400 Bad Request"); + meili_snap::snapshot!(meili_snap::json_string!(response), @r###" + { + "message": "reranking search results requires enabling the `personalization` experimental feature. See https://github.com/orgs/meilisearch/discussions/866", + "code": "feature_not_enabled", + "type": "invalid_request", + "link": "https://docs.meilisearch.com/errors#feature_not_enabled" + } + "###); +} diff --git a/crates/meilisearch/tests/search/errors.rs b/crates/meilisearch/tests/search/errors.rs index 6054a5d16..3fd4fb1d6 100644 --- a/crates/meilisearch/tests/search/errors.rs +++ b/crates/meilisearch/tests/search/errors.rs @@ -1,7 +1,11 @@ use meili_snap::*; +use meilisearch::Opt; +use tempfile::TempDir; use super::test_settings_documents_indexing_swapping_and_search; -use crate::common::{shared_does_not_exists_index, Server, DOCUMENTS, NESTED_DOCUMENTS}; +use crate::common::{ + default_settings, shared_does_not_exists_index, Server, DOCUMENTS, NESTED_DOCUMENTS, +}; use crate::json; #[actix_rt::test] @@ -1320,3 +1324,97 @@ async fn search_with_contains_without_enabling_the_feature() { } "#); } + +#[actix_rt::test] +async fn search_with_personalization_invalid_api_key() { + // Create a server with a fake personalization API key + let dir = TempDir::new().unwrap(); + let options = Opt { + experimental_personalization_api_key: Some("fake-api-key-12345".to_string()), + ..default_settings(dir.path()) + }; + let server = Server::new_with_options(options).await.unwrap(); + let index = server.unique_index(); + + // Create the index and add some documents + let (task, _code) = index.create(None).await; + server.wait_task(task.uid()).await.succeeded(); + + let (task, _code) = index + .add_documents( + json!([ + {"id": 1, "title": "The Dark Knight", "genre": "Action"}, + {"id": 2, "title": "Inception", "genre": "Sci-Fi"}, + {"id": 3, "title": "The Matrix", "genre": "Sci-Fi"} + ]), + None, + ) + .await; + server.wait_task(task.uid()).await.succeeded(); + + // Try to search with personalization - should return remote_invalid_api_key error + let (response, code) = index + .search_post(json!({ + "q": "the", + "personalize": { + "userContext": "I love science fiction movies" + } + })) + .await; + + snapshot!(code, @"403 Forbidden"); + snapshot!(json_string!(response), @r#" + { + "message": "Personalization service: Unauthorized: invalid API key", + "code": "remote_invalid_api_key", + "type": "auth", + "link": "https://docs.meilisearch.com/errors#remote_invalid_api_key" + } + "#); +} + +#[actix_rt::test] +async fn search_with_personalization_no_user_context() { + // Create a server with a fake personalization API key + let dir = TempDir::new().unwrap(); + let options = Opt { + experimental_personalization_api_key: Some("fake-api-key-12345".to_string()), + ..default_settings(dir.path()) + }; + let server = Server::new_with_options(options).await.unwrap(); + let index = server.unique_index(); + + // Create the index and add some documents + let (task, _code) = index.create(None).await; + server.wait_task(task.uid()).await.succeeded(); + + let (task, _code) = index + .add_documents( + json!([ + {"id": 1, "title": "The Dark Knight", "genre": "Action"}, + {"id": 2, "title": "Inception", "genre": "Sci-Fi"}, + {"id": 3, "title": "The Matrix", "genre": "Sci-Fi"} + ]), + None, + ) + .await; + server.wait_task(task.uid()).await.succeeded(); + + // Try to search with personalization - should return remote_invalid_api_key error + let (response, code) = index + .search_post(json!({ + "q": "the", + "personalize": {} + })) + .await; + + snapshot!(code, @"400 Bad Request"); + snapshot!(json_string!(response), @r###" + { + "message": "Missing field `userContext` inside `.personalize`", + "code": "invalid_search_personalize", + "type": "invalid_request", + "link": "https://docs.meilisearch.com/errors#invalid_search_personalize" + } + "###); +}