diff --git a/meilisearch/tests/common/index.rs b/meilisearch/tests/common/index.rs index c127af921..96ac5ce2e 100644 --- a/meilisearch/tests/common/index.rs +++ b/meilisearch/tests/common/index.rs @@ -30,7 +30,7 @@ impl Index<'_> { .post_str( url, include_str!("../assets/test_set.json"), - ("content-type", "application/json"), + vec![("content-type", "application/json")], ) .await; assert_eq!(code, 202); @@ -46,7 +46,7 @@ impl Index<'_> { .post_str( url, include_str!("../assets/test_set.ndjson"), - ("content-type", "application/x-ndjson"), + vec![("content-type", "application/x-ndjson")], ) .await; assert_eq!(code, 202); @@ -96,6 +96,21 @@ impl Index<'_> { self.service.post_encoded(url, documents, self.encoder).await } + pub async fn raw_add_documents( + &self, + payload: &str, + content_type: Option<&str>, + query_parameter: &str, + ) -> (Value, StatusCode) { + let url = format!("/indexes/{}/documents{}", urlencode(self.uid.as_ref()), query_parameter); + + if let Some(content_type) = content_type { + self.service.post_str(url, payload, vec![("Content-Type", content_type)]).await + } else { + self.service.post_str(url, payload, Vec::new()).await + } + } + pub async fn update_documents( &self, documents: Value, @@ -110,6 +125,21 @@ impl Index<'_> { self.service.put_encoded(url, documents, self.encoder).await } + pub async fn raw_update_documents( + &self, + payload: &str, + content_type: Option<&str>, + query_parameter: &str, + ) -> (Value, StatusCode) { + let url = format!("/indexes/{}/documents{}", urlencode(self.uid.as_ref()), query_parameter); + + if let Some(content_type) = content_type { + self.service.put_str(url, payload, vec![("Content-Type", content_type)]).await + } else { + self.service.put_str(url, payload, Vec::new()).await + } + } + pub async fn wait_task(&self, update_id: u64) -> Value { // try several times to get status, or panic to not wait forever let url = format!("/tasks/{}", update_id); diff --git a/meilisearch/tests/common/service.rs b/meilisearch/tests/common/service.rs index f1b800753..c6ac65418 100644 --- a/meilisearch/tests/common/service.rs +++ b/meilisearch/tests/common/service.rs @@ -34,17 +34,18 @@ impl Service { self.request(req).await } - /// Send a test post request from a text body, with a `content-type:application/json` header. + /// Send a test post request from a text body. pub async fn post_str( &self, url: impl AsRef, body: impl AsRef, - header: (&str, &str), + headers: Vec<(&str, &str)>, ) -> (Value, StatusCode) { - let req = test::TestRequest::post() - .uri(url.as_ref()) - .set_payload(body.as_ref().to_string()) - .insert_header(header); + let mut req = + test::TestRequest::post().uri(url.as_ref()).set_payload(body.as_ref().to_string()); + for header in headers { + req = req.insert_header(header); + } self.request(req).await } @@ -57,6 +58,21 @@ impl Service { self.put_encoded(url, body, Encoder::Plain).await } + /// Send a test put request from a text body. + pub async fn put_str( + &self, + url: impl AsRef, + body: impl AsRef, + headers: Vec<(&str, &str)>, + ) -> (Value, StatusCode) { + let mut req = + test::TestRequest::put().uri(url.as_ref()).set_payload(body.as_ref().to_string()); + for header in headers { + req = req.insert_header(header); + } + self.request(req).await + } + pub async fn put_encoded( &self, url: impl AsRef, diff --git a/meilisearch/tests/documents/errors.rs b/meilisearch/tests/documents/errors.rs index ffec01062..fcccf8e15 100644 --- a/meilisearch/tests/documents/errors.rs +++ b/meilisearch/tests/documents/errors.rs @@ -1,5 +1,6 @@ use meili_snap::*; use serde_json::json; +use urlencoding::encode; use crate::common::Server; @@ -97,3 +98,317 @@ async fn delete_documents_batch() { } "###); } + +#[actix_rt::test] +async fn replace_documents_missing_payload() { + let server = Server::new().await; + let index = server.index("test"); + + let (response, code) = index.raw_add_documents("", Some("application/json"), "").await; + snapshot!(code, @"400 Bad Request"); + snapshot!(json_string!(response), @r###" + { + "message": "A json payload is missing.", + "code": "missing_payload", + "type": "invalid_request", + "link": "https://docs.meilisearch.com/errors#missing_payload" + } + "###); + + let (response, code) = index.raw_add_documents("", Some("application/x-ndjson"), "").await; + snapshot!(code, @"400 Bad Request"); + snapshot!(json_string!(response), @r###" + { + "message": "A ndjson payload is missing.", + "code": "missing_payload", + "type": "invalid_request", + "link": "https://docs.meilisearch.com/errors#missing_payload" + } + "###); + + let (response, code) = index.raw_add_documents("", Some("text/csv"), "").await; + snapshot!(code, @"400 Bad Request"); + snapshot!(json_string!(response), @r###" + { + "message": "A csv payload is missing.", + "code": "missing_payload", + "type": "invalid_request", + "link": "https://docs.meilisearch.com/errors#missing_payload" + } + "###); +} + +#[actix_rt::test] +async fn update_documents_missing_payload() { + let server = Server::new().await; + let index = server.index("test"); + + let (response, code) = index.raw_update_documents("", Some("application/json"), "").await; + snapshot!(code, @"400 Bad Request"); + snapshot!(json_string!(response), @r###" + { + "message": "A json payload is missing.", + "code": "missing_payload", + "type": "invalid_request", + "link": "https://docs.meilisearch.com/errors#missing_payload" + } + "###); + + let (response, code) = index.raw_update_documents("", Some("application/x-ndjson"), "").await; + snapshot!(code, @"400 Bad Request"); + snapshot!(json_string!(response), @r###" + { + "message": "A ndjson payload is missing.", + "code": "missing_payload", + "type": "invalid_request", + "link": "https://docs.meilisearch.com/errors#missing_payload" + } + "###); + + let (response, code) = index.raw_update_documents("", Some("text/csv"), "").await; + snapshot!(code, @"400 Bad Request"); + snapshot!(json_string!(response), @r###" + { + "message": "A csv payload is missing.", + "code": "missing_payload", + "type": "invalid_request", + "link": "https://docs.meilisearch.com/errors#missing_payload" + } + "###); +} + +#[actix_rt::test] +async fn replace_documents_missing_content_type() { + let server = Server::new().await; + let index = server.index("test"); + + let (response, code) = index.raw_add_documents("", None, "").await; + snapshot!(code, @"415 Unsupported Media Type"); + snapshot!(json_string!(response), @r###" + { + "message": "A Content-Type header is missing. Accepted values for the Content-Type header are: `application/json`, `application/x-ndjson`, `text/csv`", + "code": "missing_content_type", + "type": "invalid_request", + "link": "https://docs.meilisearch.com/errors#missing_content_type" + } + "###); + + // even with a csv delimiter specified this error is triggered first + let (response, code) = index.raw_add_documents("", None, "?csvDelimiter=;").await; + snapshot!(code, @"415 Unsupported Media Type"); + snapshot!(json_string!(response), @r###" + { + "message": "A Content-Type header is missing. Accepted values for the Content-Type header are: `application/json`, `application/x-ndjson`, `text/csv`", + "code": "missing_content_type", + "type": "invalid_request", + "link": "https://docs.meilisearch.com/errors#missing_content_type" + } + "###); +} + +#[actix_rt::test] +async fn update_documents_missing_content_type() { + let server = Server::new().await; + let index = server.index("test"); + + let (response, code) = index.raw_update_documents("", None, "").await; + snapshot!(code, @"415 Unsupported Media Type"); + snapshot!(json_string!(response), @r###" + { + "message": "A Content-Type header is missing. Accepted values for the Content-Type header are: `application/json`, `application/x-ndjson`, `text/csv`", + "code": "missing_content_type", + "type": "invalid_request", + "link": "https://docs.meilisearch.com/errors#missing_content_type" + } + "###); + + // even with a csv delimiter specified this error is triggered first + let (response, code) = index.raw_update_documents("", None, "?csvDelimiter=;").await; + snapshot!(code, @"415 Unsupported Media Type"); + snapshot!(json_string!(response), @r###" + { + "message": "A Content-Type header is missing. Accepted values for the Content-Type header are: `application/json`, `application/x-ndjson`, `text/csv`", + "code": "missing_content_type", + "type": "invalid_request", + "link": "https://docs.meilisearch.com/errors#missing_content_type" + } + "###); +} + +#[actix_rt::test] +async fn replace_documents_bad_content_type() { + let server = Server::new().await; + let index = server.index("test"); + + let (response, code) = index.raw_add_documents("", Some("doggo"), "").await; + snapshot!(code, @"415 Unsupported Media Type"); + snapshot!(json_string!(response), @r###" + { + "message": "The Content-Type `doggo` is invalid. Accepted values for the Content-Type header are: `application/json`, `application/x-ndjson`, `text/csv`", + "code": "invalid_content_type", + "type": "invalid_request", + "link": "https://docs.meilisearch.com/errors#invalid_content_type" + } + "###); +} + +#[actix_rt::test] +async fn update_documents_bad_content_type() { + let server = Server::new().await; + let index = server.index("test"); + + let (response, code) = index.raw_update_documents("", Some("doggo"), "").await; + snapshot!(code, @"415 Unsupported Media Type"); + snapshot!(json_string!(response), @r###" + { + "message": "The Content-Type `doggo` is invalid. Accepted values for the Content-Type header are: `application/json`, `application/x-ndjson`, `text/csv`", + "code": "invalid_content_type", + "type": "invalid_request", + "link": "https://docs.meilisearch.com/errors#invalid_content_type" + } + "###); +} + +#[actix_rt::test] +async fn replace_documents_bad_csv_delimiter() { + let server = Server::new().await; + let index = server.index("test"); + + let (response, code) = + index.raw_add_documents("", Some("application/json"), "?csvDelimiter").await; + snapshot!(code, @"400 Bad Request"); + snapshot!(json_string!(response), @r###" + { + "message": "Invalid value in parameter `csvDelimiter`: expected a string of one character, but found an empty string", + "code": "invalid_index_csv_delimiter", + "type": "invalid_request", + "link": "https://docs.meilisearch.com/errors#invalid_index_csv_delimiter" + } + "###); + + let (response, code) = + index.raw_add_documents("", Some("application/json"), "?csvDelimiter=doggo").await; + snapshot!(code, @"400 Bad Request"); + snapshot!(json_string!(response), @r###" + { + "message": "Invalid value in parameter `csvDelimiter`: expected a string of one character, but found the following string of 5 characters: `doggo`", + "code": "invalid_index_csv_delimiter", + "type": "invalid_request", + "link": "https://docs.meilisearch.com/errors#invalid_index_csv_delimiter" + } + "###); + + let (response, code) = + index.raw_add_documents("", Some("application/json"), &format!("?csvDelimiter={}", encode("🍰"))).await; + snapshot!(code, @"400 Bad Request"); + snapshot!(json_string!(response), @r###" + { + "message": "csv delimiter must be an ascii character. Found: `🍰`", + "code": "invalid_index_csv_delimiter", + "type": "invalid_request", + "link": "https://docs.meilisearch.com/errors#invalid_index_csv_delimiter" + } + "###); +} + +#[actix_rt::test] +async fn update_documents_bad_csv_delimiter() { + let server = Server::new().await; + let index = server.index("test"); + + let (response, code) = + index.raw_update_documents("", Some("application/json"), "?csvDelimiter").await; + snapshot!(code, @"400 Bad Request"); + snapshot!(json_string!(response), @r###" + { + "message": "Invalid value in parameter `csvDelimiter`: expected a string of one character, but found an empty string", + "code": "invalid_index_csv_delimiter", + "type": "invalid_request", + "link": "https://docs.meilisearch.com/errors#invalid_index_csv_delimiter" + } + "###); + + let (response, code) = + index.raw_update_documents("", Some("application/json"), "?csvDelimiter=doggo").await; + snapshot!(code, @"400 Bad Request"); + snapshot!(json_string!(response), @r###" + { + "message": "Invalid value in parameter `csvDelimiter`: expected a string of one character, but found the following string of 5 characters: `doggo`", + "code": "invalid_index_csv_delimiter", + "type": "invalid_request", + "link": "https://docs.meilisearch.com/errors#invalid_index_csv_delimiter" + } + "###); + + let (response, code) = + index.raw_update_documents("", Some("application/json"), &format!("?csvDelimiter={}", encode("🍰"))).await; + snapshot!(code, @"400 Bad Request"); + snapshot!(json_string!(response), @r###" + { + "message": "csv delimiter must be an ascii character. Found: `🍰`", + "code": "invalid_index_csv_delimiter", + "type": "invalid_request", + "link": "https://docs.meilisearch.com/errors#invalid_index_csv_delimiter" + } + "###); +} + +#[actix_rt::test] +async fn replace_documents_csv_delimiter_with_bad_content_type() { + let server = Server::new().await; + let index = server.index("test"); + + let (response, code) = + index.raw_add_documents("", Some("application/json"), "?csvDelimiter=a").await; + snapshot!(code, @"415 Unsupported Media Type"); + snapshot!(json_string!(response), @r###" + { + "message": "The Content-Type `application/json` does not support the use of a csv delimiter. The csv delimiter can only be used with the Content-Type `text/csv`.", + "code": "invalid_content_type", + "type": "invalid_request", + "link": "https://docs.meilisearch.com/errors#invalid_content_type" + } + "###); + + let (response, code) = + index.raw_add_documents("", Some("application/x-ndjson"), "?csvDelimiter=a").await; + snapshot!(code, @"415 Unsupported Media Type"); + snapshot!(json_string!(response), @r###" + { + "message": "The Content-Type `application/x-ndjson` does not support the use of a csv delimiter. The csv delimiter can only be used with the Content-Type `text/csv`.", + "code": "invalid_content_type", + "type": "invalid_request", + "link": "https://docs.meilisearch.com/errors#invalid_content_type" + } + "###); +} + +#[actix_rt::test] +async fn update_documents_csv_delimiter_with_bad_content_type() { + let server = Server::new().await; + let index = server.index("test"); + + let (response, code) = + index.raw_update_documents("", Some("application/json"), "?csvDelimiter=a").await; + snapshot!(code, @"415 Unsupported Media Type"); + snapshot!(json_string!(response), @r###" + { + "message": "The Content-Type `application/json` does not support the use of a csv delimiter. The csv delimiter can only be used with the Content-Type `text/csv`.", + "code": "invalid_content_type", + "type": "invalid_request", + "link": "https://docs.meilisearch.com/errors#invalid_content_type" + } + "###); + + let (response, code) = + index.raw_update_documents("", Some("application/x-ndjson"), "?csvDelimiter=a").await; + snapshot!(code, @"415 Unsupported Media Type"); + snapshot!(json_string!(response), @r###" + { + "message": "The Content-Type `application/x-ndjson` does not support the use of a csv delimiter. The csv delimiter can only be used with the Content-Type `text/csv`.", + "code": "invalid_content_type", + "type": "invalid_request", + "link": "https://docs.meilisearch.com/errors#invalid_content_type" + } + "###); +}