1986 lines
64 KiB
Rust
Raw Normal View History

2021-06-29 11:57:47 +02:00
use actix_web::test;
use meili_snap::{json_string, snapshot};
2022-10-20 18:00:07 +02:00
use serde_json::{json, Value};
use time::format_description::well_known::Rfc3339;
use time::OffsetDateTime;
2022-08-16 15:14:01 +02:00
use crate::common::encoder::Encoder;
2022-10-20 18:00:07 +02:00
use crate::common::{GetAllDocumentsOptions, Server};
2021-02-19 19:43:32 +01:00
2021-06-29 11:57:47 +02:00
/// This is the basic usage of our API and every other tests uses the content-type application/json
#[actix_rt::test]
async fn add_documents_test_json_content_types() {
let document = json!([
{
"id": 1,
"content": "Bouvier Bernois",
}
]);
// this is a what is expected and should work
let server = Server::new().await;
2022-10-18 15:14:18 +02:00
let app = server.init_web_app().await;
// post
2021-06-29 11:57:47 +02:00
let req = test::TestRequest::post()
.uri("/indexes/dog/documents")
.set_payload(document.to_string())
.insert_header(("content-type", "application/json"))
.to_request();
let res = test::call_service(&app, req).await;
let status_code = res.status();
let body = test::read_body(res).await;
let response: Value = serde_json::from_slice(&body).unwrap_or_default();
snapshot!(status_code, @"202 Accepted");
snapshot!(json_string!(response, { ".duration" => "[duration]", ".enqueuedAt" => "[date]", ".startedAt" => "[date]", ".finishedAt" => "[date]" }),
@r###"
{
"taskUid": 0,
"indexUid": "dog",
"status": "enqueued",
"type": "documentAdditionOrUpdate",
"enqueuedAt": "[date]"
}
"###);
2022-08-18 11:33:22 +02:00
// put
let req = test::TestRequest::put()
.uri("/indexes/dog/documents")
.set_payload(document.to_string())
.insert_header(("content-type", "application/json"))
.to_request();
let res = test::call_service(&app, req).await;
let status_code = res.status();
let body = test::read_body(res).await;
let response: Value = serde_json::from_slice(&body).unwrap_or_default();
snapshot!(status_code, @"202 Accepted");
snapshot!(json_string!(response, { ".duration" => "[duration]", ".enqueuedAt" => "[date]", ".startedAt" => "[date]", ".finishedAt" => "[date]" }),
@r###"
{
"taskUid": 1,
"indexUid": "dog",
"status": "enqueued",
"type": "documentAdditionOrUpdate",
"enqueuedAt": "[date]"
}
"###);
2022-08-18 11:33:22 +02:00
}
/// Here we try to send a single document instead of an array with a single document inside.
#[actix_rt::test]
async fn add_single_document_test_json_content_types() {
let document = json!({
"id": 1,
"content": "Bouvier Bernois",
});
// this is a what is expected and should work
let server = Server::new().await;
2022-10-18 15:14:18 +02:00
let app = server.init_web_app().await;
2022-08-18 11:33:22 +02:00
// post
let req = test::TestRequest::post()
.uri("/indexes/dog/documents")
.set_payload(document.to_string())
.insert_header(("content-type", "application/json"))
.to_request();
let res = test::call_service(&app, req).await;
let status_code = res.status();
let body = test::read_body(res).await;
let response: Value = serde_json::from_slice(&body).unwrap_or_default();
snapshot!(status_code, @"202 Accepted");
snapshot!(json_string!(response, { ".duration" => "[duration]", ".enqueuedAt" => "[date]", ".startedAt" => "[date]", ".finishedAt" => "[date]" }),
@r###"
{
"taskUid": 0,
"indexUid": "dog",
"status": "enqueued",
"type": "documentAdditionOrUpdate",
"enqueuedAt": "[date]"
}
"###);
// put
let req = test::TestRequest::put()
.uri("/indexes/dog/documents")
.set_payload(document.to_string())
.insert_header(("content-type", "application/json"))
.to_request();
let res = test::call_service(&app, req).await;
let status_code = res.status();
let body = test::read_body(res).await;
let response: Value = serde_json::from_slice(&body).unwrap_or_default();
snapshot!(status_code, @"202 Accepted");
snapshot!(json_string!(response, { ".duration" => "[duration]", ".enqueuedAt" => "[date]", ".startedAt" => "[date]", ".finishedAt" => "[date]" }),
@r###"
{
"taskUid": 1,
"indexUid": "dog",
"status": "enqueued",
"type": "documentAdditionOrUpdate",
"enqueuedAt": "[date]"
}
"###);
2021-06-29 11:57:47 +02:00
}
/// Here we try sending encoded (compressed) document request
#[actix_rt::test]
async fn add_single_document_gzip_encoded() {
let document = json!({
"id": 1,
"content": "Bouvier Bernois",
});
// this is a what is expected and should work
let server = Server::new().await;
2022-10-20 17:08:52 +02:00
let app = server.init_web_app().await;
// post
let document = serde_json::to_string(&document).unwrap();
let encoder = Encoder::Gzip;
let req = test::TestRequest::post()
.uri("/indexes/dog/documents")
.set_payload(encoder.encode(document.clone()))
.insert_header(("content-type", "application/json"))
.insert_header(encoder.header().unwrap())
.to_request();
let res = test::call_service(&app, req).await;
let status_code = res.status();
let body = test::read_body(res).await;
let response: Value = serde_json::from_slice(&body).unwrap_or_default();
snapshot!(status_code, @"202 Accepted");
snapshot!(json_string!(response, { ".duration" => "[duration]", ".enqueuedAt" => "[date]", ".startedAt" => "[date]", ".finishedAt" => "[date]" }),
@r###"
{
"taskUid": 0,
"indexUid": "dog",
"status": "enqueued",
"type": "documentAdditionOrUpdate",
"enqueuedAt": "[date]"
}
"###);
// put
let req = test::TestRequest::put()
.uri("/indexes/dog/documents")
.set_payload(encoder.encode(document))
.insert_header(("content-type", "application/json"))
.insert_header(encoder.header().unwrap())
.to_request();
let res = test::call_service(&app, req).await;
let status_code = res.status();
let body = test::read_body(res).await;
let response: Value = serde_json::from_slice(&body).unwrap_or_default();
snapshot!(status_code, @"202 Accepted");
snapshot!(json_string!(response, { ".duration" => "[duration]", ".enqueuedAt" => "[date]", ".startedAt" => "[date]", ".finishedAt" => "[date]" }),
@r###"
{
"taskUid": 1,
"indexUid": "dog",
"status": "enqueued",
"type": "documentAdditionOrUpdate",
"enqueuedAt": "[date]"
}
"###);
}
/// Here we try document request with every encoding
#[actix_rt::test]
async fn add_single_document_with_every_encoding() {
let document = json!({
"id": 1,
"content": "Bouvier Bernois",
});
// this is a what is expected and should work
let server = Server::new().await;
2022-10-20 17:08:52 +02:00
let app = server.init_web_app().await;
// post
let document = serde_json::to_string(&document).unwrap();
for (task_uid, encoder) in Encoder::iterator().enumerate() {
let mut req = test::TestRequest::post()
.uri("/indexes/dog/documents")
.set_payload(encoder.encode(document.clone()))
.insert_header(("content-type", "application/json"));
req = match encoder.header() {
Some(header) => req.insert_header(header),
None => req,
};
let req = req.to_request();
let res = test::call_service(&app, req).await;
let status_code = res.status();
let body = test::read_body(res).await;
let response: Value = serde_json::from_slice(&body).unwrap_or_default();
assert_eq!(status_code, 202);
assert_eq!(response["taskUid"], task_uid);
}
}
2023-02-16 14:38:03 +01:00
#[actix_rt::test]
async fn add_csv_document() {
let server = Server::new().await;
let index = server.index("pets");
let document = "#id,name,race
0,jean,bernese mountain
1,jorts,orange cat";
let (response, code) = index.raw_update_documents(document, Some("text/csv"), "").await;
snapshot!(code, @"202 Accepted");
snapshot!(json_string!(response, { ".enqueuedAt" => "[date]" }), @r###"
{
"taskUid": 0,
"indexUid": "pets",
"status": "enqueued",
"type": "documentAdditionOrUpdate",
"enqueuedAt": "[date]"
}
"###);
let response = index.wait_task(response["taskUid"].as_u64().unwrap()).await;
snapshot!(json_string!(response, { ".enqueuedAt" => "[date]", ".startedAt" => "[date]", ".finishedAt" => "[date]", ".duration" => "[duration]" }), @r###"
{
"uid": 0,
"indexUid": "pets",
"status": "succeeded",
"type": "documentAdditionOrUpdate",
"canceledBy": null,
"details": {
"receivedDocuments": 2,
"indexedDocuments": 2
},
"error": null,
"duration": "[duration]",
"enqueuedAt": "[date]",
"startedAt": "[date]",
"finishedAt": "[date]"
}
"###);
let (documents, code) = index.get_all_documents(GetAllDocumentsOptions::default()).await;
snapshot!(code, @"200 OK");
snapshot!(json_string!(documents), @r###"
{
"results": [
{
"#id": "0",
"name": "jean",
"race": "bernese mountain"
},
{
"#id": "1",
"name": "jorts",
"race": "orange cat"
}
],
"offset": 0,
"limit": 20,
"total": 2
}
"###);
}
2023-03-09 11:12:49 +01:00
#[actix_rt::test]
async fn add_csv_document_with_types() {
let server = Server::new().await;
let index = server.index("pets");
let document = "#id:number,name:string,race:string,age:number,cute:boolean
0,jean,bernese mountain,2.5,true
1,,,,
2,lilou,pug,-2,false";
let (response, code) = index.raw_update_documents(document, Some("text/csv"), "").await;
snapshot!(code, @"202 Accepted");
snapshot!(json_string!(response, { ".enqueuedAt" => "[date]" }), @r###"
{
"taskUid": 0,
"indexUid": "pets",
"status": "enqueued",
"type": "documentAdditionOrUpdate",
"enqueuedAt": "[date]"
}
"###);
let response = index.wait_task(response["taskUid"].as_u64().unwrap()).await;
snapshot!(json_string!(response, { ".enqueuedAt" => "[date]", ".startedAt" => "[date]", ".finishedAt" => "[date]", ".duration" => "[duration]" }), @r###"
{
"uid": 0,
"indexUid": "pets",
"status": "succeeded",
"type": "documentAdditionOrUpdate",
"canceledBy": null,
"details": {
"receivedDocuments": 3,
"indexedDocuments": 3
},
"error": null,
"duration": "[duration]",
"enqueuedAt": "[date]",
"startedAt": "[date]",
"finishedAt": "[date]"
}
"###);
let (documents, code) = index.get_all_documents(GetAllDocumentsOptions::default()).await;
snapshot!(code, @"200 OK");
snapshot!(json_string!(documents), @r###"
{
"results": [
{
"#id": 0,
"name": "jean",
"race": "bernese mountain",
"age": 2.5,
"cute": true
},
{
"#id": 1,
"name": null,
"race": null,
"age": null,
"cute": null
},
{
"#id": 2,
"name": "lilou",
"race": "pug",
"age": -2,
"cute": false
}
],
"offset": 0,
"limit": 20,
"total": 3
}
"###);
}
2023-02-16 14:38:03 +01:00
#[actix_rt::test]
async fn add_csv_document_with_custom_delimiter() {
let server = Server::new().await;
let index = server.index("pets");
let document = "#id|name|race
0|jean|bernese mountain
1|jorts|orange cat";
let (response, code) =
index.raw_update_documents(document, Some("text/csv"), "?csvDelimiter=|").await;
snapshot!(code, @"202 Accepted");
snapshot!(json_string!(response, { ".enqueuedAt" => "[date]" }), @r###"
{
"taskUid": 0,
"indexUid": "pets",
"status": "enqueued",
"type": "documentAdditionOrUpdate",
"enqueuedAt": "[date]"
}
"###);
let response = index.wait_task(response["taskUid"].as_u64().unwrap()).await;
snapshot!(json_string!(response, { ".enqueuedAt" => "[date]", ".startedAt" => "[date]", ".finishedAt" => "[date]", ".duration" => "[duration]" }), @r###"
{
"uid": 0,
"indexUid": "pets",
"status": "succeeded",
"type": "documentAdditionOrUpdate",
"canceledBy": null,
"details": {
"receivedDocuments": 2,
"indexedDocuments": 2
},
"error": null,
"duration": "[duration]",
"enqueuedAt": "[date]",
"startedAt": "[date]",
"finishedAt": "[date]"
}
"###);
let (documents, code) = index.get_all_documents(GetAllDocumentsOptions::default()).await;
snapshot!(code, @"200 OK");
snapshot!(json_string!(documents), @r###"
{
"results": [
{
"#id": "0",
"name": "jean",
"race": "bernese mountain"
},
{
"#id": "1",
"name": "jorts",
"race": "orange cat"
}
],
"offset": 0,
"limit": 20,
"total": 2
}
"###);
}
2023-03-09 11:12:49 +01:00
#[actix_rt::test]
async fn add_csv_document_with_types_error() {
let server = Server::new().await;
let index = server.index("pets");
let document = "#id:number,a:boolean,b:number
0,doggo,1";
let (response, code) = index.raw_update_documents(document, Some("text/csv"), "").await;
snapshot!(code, @"400 Bad Request");
snapshot!(json_string!(response, { ".enqueuedAt" => "[date]" }), @r###"
{
"message": "The `csv` payload provided is malformed: `Error parsing boolean \"doggo\" at line 1: provided string was not `true` or `false``.",
"code": "malformed_payload",
"type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#malformed_payload"
}
"###);
let document = "#id:number,a:boolean,b:number
0,true,doggo";
let (response, code) = index.raw_update_documents(document, Some("text/csv"), "").await;
snapshot!(code, @"400 Bad Request");
snapshot!(json_string!(response, { ".enqueuedAt" => "[date]" }), @r###"
{
"message": "The `csv` payload provided is malformed: `Error parsing number \"doggo\" at line 1: invalid float literal`.",
"code": "malformed_payload",
"type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#malformed_payload"
}
"###);
}
2021-10-21 14:42:01 +02:00
/// any other content-type is must be refused
2021-06-29 11:57:47 +02:00
#[actix_rt::test]
2021-10-21 14:42:01 +02:00
async fn error_add_documents_test_bad_content_types() {
2021-06-29 11:57:47 +02:00
let document = json!([
{
"id": 1,
2021-10-21 14:42:01 +02:00
"content": "Leonberg",
2021-06-29 11:57:47 +02:00
}
]);
let server = Server::new().await;
2022-10-18 15:14:18 +02:00
let app = server.init_web_app().await;
// post
2021-06-29 11:57:47 +02:00
let req = test::TestRequest::post()
.uri("/indexes/dog/documents")
.set_payload(document.to_string())
2021-10-21 14:42:01 +02:00
.insert_header(("content-type", "text/plain"))
2021-06-29 11:57:47 +02:00
.to_request();
let res = test::call_service(&app, req).await;
let status_code = res.status();
let body = test::read_body(res).await;
let response: Value = serde_json::from_slice(&body).unwrap_or_default();
snapshot!(status_code, @"415 Unsupported Media Type");
snapshot!(json_string!(response),
@r###"
{
"message": "The Content-Type `text/plain` 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"
}
"###);
// put
let req = test::TestRequest::put()
.uri("/indexes/dog/documents")
.set_payload(document.to_string())
2021-10-21 14:42:01 +02:00
.insert_header(("content-type", "text/plain"))
.to_request();
let res = test::call_service(&app, req).await;
let status_code = res.status();
let body = test::read_body(res).await;
let response: Value = serde_json::from_slice(&body).unwrap_or_default();
snapshot!(status_code, @"415 Unsupported Media Type");
snapshot!(json_string!(response),
@r###"
{
"message": "The Content-Type `text/plain` 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"
}
"###);
2021-06-29 11:57:47 +02:00
}
2021-10-21 14:42:01 +02:00
/// missing content-type must be refused
2021-06-29 11:57:47 +02:00
#[actix_rt::test]
2021-10-21 14:42:01 +02:00
async fn error_add_documents_test_no_content_type() {
2021-06-29 11:57:47 +02:00
let document = json!([
{
"id": 1,
"content": "Leonberg",
}
]);
let server = Server::new().await;
2022-10-18 15:14:18 +02:00
let app = server.init_web_app().await;
// post
2021-06-29 11:57:47 +02:00
let req = test::TestRequest::post()
.uri("/indexes/dog/documents")
.set_payload(document.to_string())
.to_request();
let res = test::call_service(&app, req).await;
let status_code = res.status();
let body = test::read_body(res).await;
let response: Value = serde_json::from_slice(&body).unwrap_or_default();
snapshot!(status_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"
}
"###);
// put
let req = test::TestRequest::put()
.uri("/indexes/dog/documents")
.set_payload(document.to_string())
.to_request();
let res = test::call_service(&app, req).await;
let status_code = res.status();
let body = test::read_body(res).await;
let response: Value = serde_json::from_slice(&body).unwrap_or_default();
snapshot!(status_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"
}
"###);
2021-10-21 14:42:01 +02:00
}
#[actix_rt::test]
async fn error_add_malformed_csv_documents() {
let document = "id, content\n1234, hello, world\n12, hello world";
let server = Server::new().await;
2022-10-18 15:14:18 +02:00
let app = server.init_web_app().await;
2021-10-21 14:42:01 +02:00
// post
let req = test::TestRequest::post()
.uri("/indexes/dog/documents")
.set_payload(document.to_string())
2021-10-25 14:09:24 +02:00
.insert_header(("content-type", "text/csv"))
2021-10-21 14:42:01 +02:00
.to_request();
let res = test::call_service(&app, req).await;
let status_code = res.status();
let body = test::read_body(res).await;
let response: Value = serde_json::from_slice(&body).unwrap_or_default();
snapshot!(status_code, @"400 Bad Request");
snapshot!(json_string!(response),
@r###"
{
"message": "The `csv` payload provided is malformed: `CSV error: record 1 (line: 2, byte: 12): found record with 3 fields, but the previous record has 2 fields`.",
"code": "malformed_payload",
"type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#malformed_payload"
}
"###);
2021-10-21 14:42:01 +02:00
// put
let req = test::TestRequest::put()
.uri("/indexes/dog/documents")
.set_payload(document.to_string())
2021-10-25 14:09:24 +02:00
.insert_header(("content-type", "text/csv"))
2021-10-21 14:42:01 +02:00
.to_request();
let res = test::call_service(&app, req).await;
let status_code = res.status();
let body = test::read_body(res).await;
let response: Value = serde_json::from_slice(&body).unwrap_or_default();
snapshot!(status_code, @"400 Bad Request");
snapshot!(json_string!(response),
@r###"
{
"message": "The `csv` payload provided is malformed: `CSV error: record 1 (line: 2, byte: 12): found record with 3 fields, but the previous record has 2 fields`.",
"code": "malformed_payload",
"type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#malformed_payload"
}
"###);
2021-10-21 14:42:01 +02:00
}
#[actix_rt::test]
async fn error_add_malformed_json_documents() {
let document = r#"[{"id": 1}, {id: 2}]"#;
let server = Server::new().await;
2022-10-18 15:14:18 +02:00
let app = server.init_web_app().await;
2021-10-21 14:42:01 +02:00
// post
let req = test::TestRequest::post()
.uri("/indexes/dog/documents")
.set_payload(document.to_string())
2021-10-25 14:09:24 +02:00
.insert_header(("content-type", "application/json"))
2021-10-21 14:42:01 +02:00
.to_request();
let res = test::call_service(&app, req).await;
let status_code = res.status();
let body = test::read_body(res).await;
let response: Value = serde_json::from_slice(&body).unwrap_or_default();
snapshot!(status_code, @"400 Bad Request");
snapshot!(json_string!(response),
@r###"
{
"message": "The `json` payload provided is malformed. `Couldn't serialize document value: key must be a string at line 1 column 14`.",
"code": "malformed_payload",
"type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#malformed_payload"
}
"###);
2021-10-21 14:42:01 +02:00
// put
let req = test::TestRequest::put()
.uri("/indexes/dog/documents")
.set_payload(document.to_string())
2021-10-25 14:09:24 +02:00
.insert_header(("content-type", "application/json"))
2021-10-21 14:42:01 +02:00
.to_request();
let res = test::call_service(&app, req).await;
let status_code = res.status();
let body = test::read_body(res).await;
let response: Value = serde_json::from_slice(&body).unwrap_or_default();
snapshot!(status_code, @"400 Bad Request");
snapshot!(json_string!(response),
@r###"
{
"message": "The `json` payload provided is malformed. `Couldn't serialize document value: key must be a string at line 1 column 14`.",
"code": "malformed_payload",
"type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#malformed_payload"
}
"###);
2022-03-25 21:31:23 +08:00
// truncate
// length = 100
let long = "0123456789".repeat(10);
2022-03-25 21:31:23 +08:00
let document = format!("\"{}\"", long);
let req = test::TestRequest::put()
.uri("/indexes/dog/documents")
.set_payload(document)
.insert_header(("content-type", "application/json"))
.to_request();
let res = test::call_service(&app, req).await;
let body = test::read_body(res).await;
let response: Value = serde_json::from_slice(&body).unwrap_or_default();
snapshot!(status_code, @"400 Bad Request");
snapshot!(json_string!(response),
@r###"
{
"message": "The `json` payload provided is malformed. `Couldn't serialize document value: data are neither an object nor a list of objects`.",
"code": "malformed_payload",
"type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#malformed_payload"
}
"###);
2022-03-25 21:31:23 +08:00
2022-03-25 21:33:47 +08:00
// add one more char to the long string to test if the truncating works.
2022-03-25 21:31:23 +08:00
let document = format!("\"{}m\"", long);
let req = test::TestRequest::put()
.uri("/indexes/dog/documents")
.set_payload(document)
.insert_header(("content-type", "application/json"))
.to_request();
let res = test::call_service(&app, req).await;
let body = test::read_body(res).await;
let response: Value = serde_json::from_slice(&body).unwrap_or_default();
snapshot!(status_code, @"400 Bad Request");
snapshot!(json_string!(response),
@r###"
{
"message": "The `json` payload provided is malformed. `Couldn't serialize document value: data are neither an object nor a list of objects`.",
"code": "malformed_payload",
"type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#malformed_payload"
}
"###);
2021-10-21 14:42:01 +02:00
}
#[actix_rt::test]
async fn error_add_malformed_ndjson_documents() {
let document = "{\"id\": 1}\n{id: 2}";
let server = Server::new().await;
2022-10-18 15:14:18 +02:00
let app = server.init_web_app().await;
2021-10-21 14:42:01 +02:00
// post
let req = test::TestRequest::post()
.uri("/indexes/dog/documents")
.set_payload(document.to_string())
2021-10-25 14:09:24 +02:00
.insert_header(("content-type", "application/x-ndjson"))
2021-10-21 14:42:01 +02:00
.to_request();
let res = test::call_service(&app, req).await;
let status_code = res.status();
let body = test::read_body(res).await;
let response: Value = serde_json::from_slice(&body).unwrap_or_default();
snapshot!(status_code, @"400 Bad Request");
snapshot!(json_string!(response),
@r###"
{
"message": "The `ndjson` payload provided is malformed. `Couldn't serialize document value: key must be a string at line 2 column 2`.",
"code": "malformed_payload",
"type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#malformed_payload"
}
"###);
2021-10-21 14:42:01 +02:00
// put
let req = test::TestRequest::put()
.uri("/indexes/dog/documents")
.set_payload(document.to_string())
2021-10-25 14:09:24 +02:00
.insert_header(("content-type", "application/x-ndjson"))
2021-10-21 14:42:01 +02:00
.to_request();
let res = test::call_service(&app, req).await;
let status_code = res.status();
let body = test::read_body(res).await;
let response: Value = serde_json::from_slice(&body).unwrap_or_default();
snapshot!(status_code, @"400 Bad Request");
snapshot!(json_string!(response),
@r###"
{
"message": "The `ndjson` payload provided is malformed. `Couldn't serialize document value: key must be a string at line 2 column 2`.",
"code": "malformed_payload",
"type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#malformed_payload"
}
"###);
2021-06-29 11:57:47 +02:00
}
2021-02-19 19:43:32 +01:00
2021-10-26 19:36:48 +02:00
#[actix_rt::test]
async fn error_add_missing_payload_csv_documents() {
let document = "";
let server = Server::new().await;
2022-10-18 15:14:18 +02:00
let app = server.init_web_app().await;
2021-10-26 19:36:48 +02:00
// post
let req = test::TestRequest::post()
.uri("/indexes/dog/documents")
.set_payload(document.to_string())
.insert_header(("content-type", "text/csv"))
.to_request();
let res = test::call_service(&app, req).await;
let status_code = res.status();
let body = test::read_body(res).await;
let response: Value = serde_json::from_slice(&body).unwrap_or_default();
snapshot!(status_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"
}
"###);
2021-10-26 19:36:48 +02:00
// put
let req = test::TestRequest::put()
.uri("/indexes/dog/documents")
.set_payload(document.to_string())
.insert_header(("content-type", "text/csv"))
.to_request();
let res = test::call_service(&app, req).await;
let status_code = res.status();
let body = test::read_body(res).await;
let response: Value = serde_json::from_slice(&body).unwrap_or_default();
snapshot!(status_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"
}
"###);
2021-10-26 19:36:48 +02:00
}
#[actix_rt::test]
async fn error_add_missing_payload_json_documents() {
let document = "";
let server = Server::new().await;
2022-10-18 15:14:18 +02:00
let app = server.init_web_app().await;
2021-10-26 19:36:48 +02:00
// post
let req = test::TestRequest::post()
.uri("/indexes/dog/documents")
.set_payload(document.to_string())
.insert_header(("content-type", "application/json"))
.to_request();
let res = test::call_service(&app, req).await;
let status_code = res.status();
let body = test::read_body(res).await;
let response: Value = serde_json::from_slice(&body).unwrap_or_default();
snapshot!(status_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"
}
"###);
2021-10-26 19:36:48 +02:00
// put
let req = test::TestRequest::put()
.uri("/indexes/dog/documents")
.set_payload(document.to_string())
.insert_header(("content-type", "application/json"))
.to_request();
let res = test::call_service(&app, req).await;
let status_code = res.status();
let body = test::read_body(res).await;
let response: Value = serde_json::from_slice(&body).unwrap_or_default();
snapshot!(status_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"
}
"###);
2021-10-26 19:36:48 +02:00
}
#[actix_rt::test]
async fn error_add_missing_payload_ndjson_documents() {
let document = "";
let server = Server::new().await;
2022-10-18 15:14:18 +02:00
let app = server.init_web_app().await;
2021-10-26 19:36:48 +02:00
// post
let req = test::TestRequest::post()
.uri("/indexes/dog/documents")
.set_payload(document.to_string())
.insert_header(("content-type", "application/x-ndjson"))
.to_request();
let res = test::call_service(&app, req).await;
let status_code = res.status();
let body = test::read_body(res).await;
let response: Value = serde_json::from_slice(&body).unwrap_or_default();
snapshot!(status_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"
}
"###);
2021-10-26 19:36:48 +02:00
// put
let req = test::TestRequest::put()
.uri("/indexes/dog/documents")
.set_payload(document.to_string())
.insert_header(("content-type", "application/x-ndjson"))
.to_request();
let res = test::call_service(&app, req).await;
let status_code = res.status();
let body = test::read_body(res).await;
let response: Value = serde_json::from_slice(&body).unwrap_or_default();
snapshot!(status_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"
}
"###);
2021-10-26 19:36:48 +02:00
}
2021-02-19 19:43:32 +01:00
#[actix_rt::test]
async fn add_documents_no_index_creation() {
let server = Server::new().await;
let index = server.index("test");
let documents = json!([
{
"id": 1,
"content": "foo",
}
]);
let (response, code) = index.add_documents(documents, None).await;
snapshot!(code, @"202 Accepted");
assert_eq!(response["taskUid"], 0);
2021-02-19 19:43:32 +01:00
index.wait_task(0).await;
2021-02-19 19:43:32 +01:00
let (response, code) = index.get_task(0).await;
snapshot!(code, @"200 OK");
snapshot!(json_string!(response, { ".duration" => "[duration]", ".enqueuedAt" => "[date]", ".startedAt" => "[date]", ".finishedAt" => "[date]" }),
@r###"
{
"uid": 0,
"indexUid": "test",
"status": "succeeded",
"type": "documentAdditionOrUpdate",
"canceledBy": null,
"details": {
"receivedDocuments": 1,
"indexedDocuments": 1
},
"error": null,
"duration": "[duration]",
"enqueuedAt": "[date]",
"startedAt": "[date]",
"finishedAt": "[date]"
}
"###);
2021-02-19 19:43:32 +01:00
2021-03-15 18:11:10 +01:00
let processed_at =
OffsetDateTime::parse(response["finishedAt"].as_str().unwrap(), &Rfc3339).unwrap();
2021-03-15 18:11:10 +01:00
let enqueued_at =
OffsetDateTime::parse(response["enqueuedAt"].as_str().unwrap(), &Rfc3339).unwrap();
2021-05-03 14:21:09 +02:00
assert!(processed_at > enqueued_at);
2021-02-19 19:43:32 +01:00
// index was created, and primary key was inferred.
2021-02-19 19:43:32 +01:00
let (response, code) = index.get().await;
snapshot!(code, @"200 OK");
2021-02-19 19:43:32 +01:00
assert_eq!(response["primaryKey"], "id");
}
2021-03-10 14:43:10 +01:00
#[actix_rt::test]
2021-10-21 14:42:01 +02:00
async fn error_document_add_create_index_bad_uid() {
2021-03-10 14:43:10 +01:00
let server = Server::new().await;
let index = server.index("883 fj!");
let (response, code) = index.add_documents(json!([{"id": 1}]), None).await;
2021-10-21 14:42:01 +02:00
snapshot!(code, @"400 Bad Request");
snapshot!(json_string!(response),
@r###"
{
"message": "`883 fj!` 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"
}
"###);
2021-03-10 14:43:10 +01:00
}
2021-02-19 19:43:32 +01:00
#[actix_rt::test]
async fn document_addition_with_primary_key() {
let server = Server::new().await;
let index = server.index("test");
let documents = json!([
{
"primary": 1,
"content": "foo",
}
]);
2021-04-22 10:14:29 +02:00
let (response, code) = index.add_documents(documents, Some("primary")).await;
snapshot!(code, @"202 Accepted");
snapshot!(json_string!(response, { ".duration" => "[duration]", ".enqueuedAt" => "[date]", ".startedAt" => "[date]", ".finishedAt" => "[date]" }),
@r###"
{
"taskUid": 0,
"indexUid": "test",
"status": "enqueued",
"type": "documentAdditionOrUpdate",
"enqueuedAt": "[date]"
}
"###);
2021-02-22 15:13:10 +01:00
index.wait_task(0).await;
2021-02-22 15:13:10 +01:00
let (response, code) = index.get_task(0).await;
snapshot!(code, @"200 OK");
snapshot!(json_string!(response, { ".duration" => "[duration]", ".enqueuedAt" => "[date]", ".startedAt" => "[date]", ".finishedAt" => "[date]" }),
@r###"
{
"uid": 0,
"indexUid": "test",
"status": "succeeded",
"type": "documentAdditionOrUpdate",
"canceledBy": null,
"details": {
"receivedDocuments": 1,
"indexedDocuments": 1
},
"error": null,
"duration": "[duration]",
"enqueuedAt": "[date]",
"startedAt": "[date]",
"finishedAt": "[date]"
}
"###);
2021-02-22 15:13:10 +01:00
let (response, code) = index.get().await;
snapshot!(code, @"200 OK");
snapshot!(json_string!(response, { ".createdAt" => "[date]", ".updatedAt" => "[date]" }),
@r###"
{
"uid": "test",
"createdAt": "[date]",
"updatedAt": "[date]",
"primaryKey": "primary"
}
"###);
2021-02-22 15:13:10 +01:00
}
2021-02-19 19:43:32 +01:00
#[actix_rt::test]
async fn replace_document() {
let server = Server::new().await;
let index = server.index("test");
let documents = json!([
{
"doc_id": 1,
"content": "foo",
}
]);
2021-04-22 10:14:29 +02:00
let (response, code) = index.add_documents(documents, None).await;
snapshot!(code,@"202 Accepted");
snapshot!(json_string!(response, { ".duration" => "[duration]", ".enqueuedAt" => "[date]", ".startedAt" => "[date]", ".finishedAt" => "[date]" }),
@r###"
{
"taskUid": 0,
"indexUid": "test",
"status": "enqueued",
"type": "documentAdditionOrUpdate",
"enqueuedAt": "[date]"
}
"###);
2021-02-19 19:43:32 +01:00
index.wait_task(0).await;
2021-02-19 19:43:32 +01:00
let documents = json!([
{
"doc_id": 1,
"other": "bar",
}
]);
let (_response, code) = index.add_documents(documents, None).await;
snapshot!(code,@"202 Accepted");
2021-02-19 19:43:32 +01:00
index.wait_task(1).await;
2021-02-19 19:43:32 +01:00
let (response, code) = index.get_task(1).await;
snapshot!(code, @"200 OK");
snapshot!(json_string!(response, { ".duration" => "[duration]", ".enqueuedAt" => "[date]", ".startedAt" => "[date]", ".finishedAt" => "[date]" }),
@r###"
{
"uid": 1,
"indexUid": "test",
"status": "succeeded",
"type": "documentAdditionOrUpdate",
"canceledBy": null,
"details": {
"receivedDocuments": 1,
"indexedDocuments": 1
},
"error": null,
"duration": "[duration]",
"enqueuedAt": "[date]",
"startedAt": "[date]",
"finishedAt": "[date]"
}
"###);
2021-02-19 19:43:32 +01:00
let (response, code) = index.get_document(1, None).await;
snapshot!(code, @"200 OK");
snapshot!(json_string!(response),
@r###"
{
"doc_id": 1,
"other": "bar"
}
"###);
2021-02-19 19:43:32 +01:00
}
#[actix_rt::test]
async fn add_no_documents() {
2021-02-19 19:43:32 +01:00
let server = Server::new().await;
let index = server.index("test");
let (_response, code) = index.add_documents(json!([]), None).await;
snapshot!(code, @"202 Accepted");
2021-02-19 19:43:32 +01:00
}
#[actix_rt::test]
async fn add_larger_dataset() {
let server = Server::new().await;
let index = server.index("test");
let update_id = index.load_test_set().await;
let (response, code) = index.get_task(update_id).await;
2021-02-19 19:43:32 +01:00
assert_eq!(code, 200);
assert_eq!(response["status"], "succeeded");
2022-05-25 12:05:24 +02:00
assert_eq!(response["type"], "documentAdditionOrUpdate");
assert_eq!(response["details"]["indexedDocuments"], 77);
assert_eq!(response["details"]["receivedDocuments"], 77);
2021-03-15 18:11:10 +01:00
let (response, code) = index
2022-10-20 18:00:07 +02:00
.get_all_documents(GetAllDocumentsOptions { limit: Some(1000), ..Default::default() })
2021-03-15 18:11:10 +01:00
.await;
assert_eq!(code, 200, "failed with `{}`", response);
assert_eq!(response["results"].as_array().unwrap().len(), 77);
2022-12-21 11:27:15 +08:00
// x-ndjson add large test
let server = Server::new().await;
let index = server.index("test");
let update_id = index.load_test_set_ndjson().await;
2022-12-21 11:27:15 +08:00
let (response, code) = index.get_task(update_id).await;
assert_eq!(code, 200);
assert_eq!(response["status"], "succeeded");
2022-05-25 12:05:24 +02:00
assert_eq!(response["type"], "documentAdditionOrUpdate");
assert_eq!(response["details"]["indexedDocuments"], 77);
assert_eq!(response["details"]["receivedDocuments"], 77);
2021-03-15 18:11:10 +01:00
let (response, code) = index
2022-10-20 18:00:07 +02:00
.get_all_documents(GetAllDocumentsOptions { limit: Some(1000), ..Default::default() })
2021-03-15 18:11:10 +01:00
.await;
assert_eq!(code, 200, "failed with `{}`", response);
assert_eq!(response["results"].as_array().unwrap().len(), 77);
2021-02-22 15:13:10 +01:00
}
2021-02-22 14:55:40 +01:00
#[actix_rt::test]
2021-10-21 14:42:01 +02:00
async fn error_add_documents_bad_document_id() {
2021-02-22 14:55:40 +01:00
let server = Server::new().await;
let index = server.index("test");
index.create(Some("docid")).await;
let documents = json!([
{
"docid": "foo & bar",
"content": "foobar"
}
]);
index.add_documents(documents, None).await;
index.wait_task(1).await;
let (response, code) = index.get_task(1).await;
snapshot!(code, @"200 OK");
snapshot!(json_string!(response, { ".duration" => "[duration]", ".enqueuedAt" => "[date]", ".startedAt" => "[date]", ".finishedAt" => "[date]" }),
@r###"
{
"uid": 1,
"indexUid": "test",
"status": "failed",
"type": "documentAdditionOrUpdate",
"canceledBy": null,
"details": {
"receivedDocuments": 1,
2023-01-25 16:52:54 +01:00
"indexedDocuments": 0
},
"error": {
"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"
},
"duration": "[duration]",
"enqueuedAt": "[date]",
"startedAt": "[date]",
"finishedAt": "[date]"
}
"###);
2021-02-22 14:55:40 +01:00
}
2021-02-22 15:13:10 +01:00
2021-10-21 14:42:01 +02:00
#[actix_rt::test]
async fn error_add_documents_missing_document_id() {
let server = Server::new().await;
let index = server.index("test");
index.create(Some("docid")).await;
let documents = json!([
{
"id": "11",
"content": "foobar"
}
]);
index.add_documents(documents, None).await;
index.wait_task(1).await;
let (response, code) = index.get_task(1).await;
snapshot!(code, @"200 OK");
snapshot!(json_string!(response, { ".duration" => "[duration]", ".enqueuedAt" => "[date]", ".startedAt" => "[date]", ".finishedAt" => "[date]" }),
@r###"
{
"uid": 1,
"indexUid": "test",
"status": "failed",
"type": "documentAdditionOrUpdate",
"canceledBy": null,
"details": {
"receivedDocuments": 1,
2023-01-25 16:52:54 +01:00
"indexedDocuments": 0
},
"error": {
"message": "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"
},
"duration": "[duration]",
"enqueuedAt": "[date]",
"startedAt": "[date]",
"finishedAt": "[date]"
}
"###);
2021-10-21 14:42:01 +02:00
}
#[actix_rt::test]
#[ignore] // // TODO: Fix in an other PR: this does not provoke any error.
async fn error_document_field_limit_reached() {
let server = Server::new().await;
let index = server.index("test");
index.create(Some("id")).await;
let mut big_object = std::collections::HashMap::new();
big_object.insert("id".to_owned(), "wow");
for i in 0..65535 {
let key = i.to_string();
big_object.insert(key, "I am a text!");
}
let documents = json!([big_object]);
let (_response, code) = index.update_documents(documents, Some("id")).await;
snapshot!(code, @"202");
index.wait_task(0).await;
let (response, code) = index.get_task(0).await;
snapshot!(code, @"200");
// Documents without a primary key are not accepted.
snapshot!(json_string!(response, { ".duration" => "[duration]", ".enqueuedAt" => "[date]", ".startedAt" => "[date]", ".finishedAt" => "[date]" }),
@"");
}
2021-10-21 14:42:01 +02:00
2023-02-20 13:45:51 +01:00
#[actix_rt::test]
async fn add_documents_with_geo_field() {
let server = Server::new().await;
let index = server.index("doggo");
index.update_settings(json!({"sortableAttributes": ["_geo"]})).await;
let documents = json!([
{
"id": "1",
},
{
"id": "2",
"_geo": null,
},
{
"id": "3",
"_geo": { "lat": 1, "lng": 1 },
},
{
"id": "4",
"_geo": { "lat": "1", "lng": "1" },
},
]);
index.add_documents(documents, None).await;
let response = index.wait_task(1).await;
snapshot!(json_string!(response, { ".duration" => "[duration]", ".enqueuedAt" => "[date]", ".startedAt" => "[date]", ".finishedAt" => "[date]" }),
@r###"
{
"uid": 1,
"indexUid": "doggo",
"status": "succeeded",
"type": "documentAdditionOrUpdate",
"canceledBy": null,
"details": {
"receivedDocuments": 4,
"indexedDocuments": 4
},
"error": null,
"duration": "[duration]",
"enqueuedAt": "[date]",
"startedAt": "[date]",
"finishedAt": "[date]"
}
"###);
}
2021-10-21 14:42:01 +02:00
#[actix_rt::test]
2022-05-16 17:16:53 +02:00
async fn add_documents_invalid_geo_field() {
2021-10-21 14:42:01 +02:00
let server = Server::new().await;
let index = server.index("test");
index.create(Some("id")).await;
2022-10-20 18:00:07 +02:00
index.update_settings(json!({"sortableAttributes": ["_geo"]})).await;
2021-11-03 14:25:49 +01:00
// _geo is not an object
2021-10-21 14:42:01 +02:00
let documents = json!([
{
"id": "11",
"_geo": "foobar"
}
]);
2021-11-03 14:25:49 +01:00
2021-10-21 14:42:01 +02:00
index.add_documents(documents, None).await;
index.wait_task(2).await;
let (response, code) = index.get_task(2).await;
snapshot!(code, @"200 OK");
snapshot!(json_string!(response, { ".duration" => "[duration]", ".enqueuedAt" => "[date]", ".startedAt" => "[date]", ".finishedAt" => "[date]" }),
@r###"
{
"uid": 2,
"indexUid": "test",
"status": "failed",
"type": "documentAdditionOrUpdate",
"canceledBy": null,
"details": {
"receivedDocuments": 1,
2023-01-25 16:52:54 +01:00
"indexedDocuments": 0
},
"error": {
"message": "The `_geo` field in the document with the id: `11` is not an object. Was expecting an object with the `_geo.lat` and `_geo.lng` fields but instead got `\"foobar\"`.",
"code": "invalid_document_geo_field",
"type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#invalid_document_geo_field"
},
"duration": "[duration]",
"enqueuedAt": "[date]",
"startedAt": "[date]",
"finishedAt": "[date]"
}
"###);
// _geo is an object but is missing both the lat and lng
let documents = json!([
{
"id": "11",
"_geo": {}
}
]);
index.add_documents(documents, None).await;
index.wait_task(3).await;
let (response, code) = index.get_task(3).await;
snapshot!(code, @"200 OK");
snapshot!(json_string!(response, { ".duration" => "[duration]", ".enqueuedAt" => "[date]", ".startedAt" => "[date]", ".finishedAt" => "[date]" }),
@r###"
{
"uid": 3,
"indexUid": "test",
"status": "failed",
"type": "documentAdditionOrUpdate",
"canceledBy": null,
"details": {
"receivedDocuments": 1,
2023-01-25 16:52:54 +01:00
"indexedDocuments": 0
},
"error": {
"message": "Could not find latitude nor longitude in the document with the id: `11`. Was expecting `_geo.lat` and `_geo.lng` fields.",
"code": "invalid_document_geo_field",
"type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#invalid_document_geo_field"
},
"duration": "[duration]",
"enqueuedAt": "[date]",
"startedAt": "[date]",
"finishedAt": "[date]"
}
"###);
// _geo is an object but is missing both the lat and lng and contains an unexpected field
let documents = json!([
{
"id": "11",
"_geo": { "doggos": "are good" }
}
]);
index.add_documents(documents, None).await;
index.wait_task(4).await;
let (response, code) = index.get_task(4).await;
snapshot!(code, @"200 OK");
snapshot!(json_string!(response, { ".duration" => "[duration]", ".enqueuedAt" => "[date]", ".startedAt" => "[date]", ".finishedAt" => "[date]" }),
@r###"
{
"uid": 4,
"indexUid": "test",
"status": "failed",
"type": "documentAdditionOrUpdate",
"canceledBy": null,
"details": {
"receivedDocuments": 1,
2023-01-25 16:52:54 +01:00
"indexedDocuments": 0
},
"error": {
"message": "Could not find latitude nor longitude in the document with the id: `11`. Was expecting `_geo.lat` and `_geo.lng` fields.",
"code": "invalid_document_geo_field",
"type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#invalid_document_geo_field"
},
"duration": "[duration]",
"enqueuedAt": "[date]",
"startedAt": "[date]",
"finishedAt": "[date]"
}
"###);
// _geo is an object but only contains the lat
let documents = json!([
{
"id": "11",
"_geo": { "lat": 12 }
}
]);
index.add_documents(documents, None).await;
index.wait_task(5).await;
let (response, code) = index.get_task(5).await;
snapshot!(code, @"200 OK");
snapshot!(json_string!(response, { ".duration" => "[duration]", ".enqueuedAt" => "[date]", ".startedAt" => "[date]", ".finishedAt" => "[date]" }),
@r###"
{
"uid": 5,
"indexUid": "test",
"status": "failed",
"type": "documentAdditionOrUpdate",
"canceledBy": null,
"details": {
"receivedDocuments": 1,
2023-01-25 16:52:54 +01:00
"indexedDocuments": 0
},
"error": {
"message": "Could not find longitude in the document with the id: `11`. Was expecting a `_geo.lng` field.",
"code": "invalid_document_geo_field",
"type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#invalid_document_geo_field"
},
"duration": "[duration]",
"enqueuedAt": "[date]",
"startedAt": "[date]",
"finishedAt": "[date]"
}
"###);
// _geo is an object but only contains the lng
let documents = json!([
{
"id": "11",
"_geo": { "lng": 12 }
}
]);
index.add_documents(documents, None).await;
index.wait_task(6).await;
let (response, code) = index.get_task(6).await;
snapshot!(code, @"200 OK");
snapshot!(json_string!(response, { ".duration" => "[duration]", ".enqueuedAt" => "[date]", ".startedAt" => "[date]", ".finishedAt" => "[date]" }),
@r###"
{
"uid": 6,
"indexUid": "test",
"status": "failed",
"type": "documentAdditionOrUpdate",
"canceledBy": null,
"details": {
"receivedDocuments": 1,
2023-01-25 16:52:54 +01:00
"indexedDocuments": 0
},
"error": {
"message": "Could not find latitude in the document with the id: `11`. Was expecting a `_geo.lat` field.",
"code": "invalid_document_geo_field",
"type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#invalid_document_geo_field"
},
"duration": "[duration]",
"enqueuedAt": "[date]",
"startedAt": "[date]",
"finishedAt": "[date]"
}
"###);
// _geo is an object but the lat has a wrong type
let documents = json!([
{
"id": "11",
"_geo": { "lat": true }
}
]);
index.add_documents(documents, None).await;
index.wait_task(7).await;
let (response, code) = index.get_task(7).await;
snapshot!(code, @"200 OK");
snapshot!(json_string!(response, { ".duration" => "[duration]", ".enqueuedAt" => "[date]", ".startedAt" => "[date]", ".finishedAt" => "[date]" }),
@r###"
{
"uid": 7,
"indexUid": "test",
"status": "failed",
"type": "documentAdditionOrUpdate",
"canceledBy": null,
"details": {
"receivedDocuments": 1,
2023-01-25 16:52:54 +01:00
"indexedDocuments": 0
},
"error": {
"message": "Could not find longitude in the document with the id: `11`. Was expecting a `_geo.lng` field.",
"code": "invalid_document_geo_field",
"type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#invalid_document_geo_field"
},
"duration": "[duration]",
"enqueuedAt": "[date]",
"startedAt": "[date]",
"finishedAt": "[date]"
}
"###);
// _geo is an object but the lng has a wrong type
let documents = json!([
{
"id": "11",
"_geo": { "lng": true }
}
]);
index.add_documents(documents, None).await;
index.wait_task(8).await;
let (response, code) = index.get_task(8).await;
snapshot!(code, @"200 OK");
snapshot!(json_string!(response, { ".duration" => "[duration]", ".enqueuedAt" => "[date]", ".startedAt" => "[date]", ".finishedAt" => "[date]" }),
@r###"
{
"uid": 8,
"indexUid": "test",
"status": "failed",
"type": "documentAdditionOrUpdate",
"canceledBy": null,
"details": {
"receivedDocuments": 1,
2023-01-25 16:52:54 +01:00
"indexedDocuments": 0
},
"error": {
"message": "Could not find latitude in the document with the id: `11`. Was expecting a `_geo.lat` field.",
"code": "invalid_document_geo_field",
"type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#invalid_document_geo_field"
},
"duration": "[duration]",
"enqueuedAt": "[date]",
"startedAt": "[date]",
"finishedAt": "[date]"
}
"###);
// _geo is an object but the lat and lng have a wrong type
let documents = json!([
{
"id": "11",
"_geo": { "lat": false, "lng": true }
}
]);
index.add_documents(documents, None).await;
index.wait_task(9).await;
let (response, code) = index.get_task(9).await;
snapshot!(code, @"200 OK");
snapshot!(json_string!(response, { ".duration" => "[duration]", ".enqueuedAt" => "[date]", ".startedAt" => "[date]", ".finishedAt" => "[date]" }),
@r###"
{
"uid": 9,
"indexUid": "test",
"status": "failed",
"type": "documentAdditionOrUpdate",
"canceledBy": null,
"details": {
"receivedDocuments": 1,
2023-01-25 16:52:54 +01:00
"indexedDocuments": 0
},
"error": {
"message": "Could not parse latitude nor longitude in the document with the id: `11`. Was expecting finite numbers but instead got `false` and `true`.",
"code": "invalid_document_geo_field",
"type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#invalid_document_geo_field"
},
"duration": "[duration]",
"enqueuedAt": "[date]",
"startedAt": "[date]",
"finishedAt": "[date]"
}
"###);
// _geo is an object but the lat can't be parsed as a float
let documents = json!([
{
"id": "11",
"_geo": { "lat": "doggo" }
}
]);
index.add_documents(documents, None).await;
index.wait_task(10).await;
let (response, code) = index.get_task(10).await;
snapshot!(code, @"200 OK");
snapshot!(json_string!(response, { ".duration" => "[duration]", ".enqueuedAt" => "[date]", ".startedAt" => "[date]", ".finishedAt" => "[date]" }),
@r###"
{
"uid": 10,
"indexUid": "test",
"status": "failed",
"type": "documentAdditionOrUpdate",
"canceledBy": null,
"details": {
"receivedDocuments": 1,
2023-01-25 16:52:54 +01:00
"indexedDocuments": 0
},
"error": {
"message": "Could not find longitude in the document with the id: `11`. Was expecting a `_geo.lng` field.",
"code": "invalid_document_geo_field",
"type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#invalid_document_geo_field"
},
"duration": "[duration]",
"enqueuedAt": "[date]",
"startedAt": "[date]",
"finishedAt": "[date]"
}
"###);
// _geo is an object but the lng can't be parsed as a float
let documents = json!([
{
"id": "11",
"_geo": { "lng": "doggo" }
}
]);
index.add_documents(documents, None).await;
index.wait_task(11).await;
let (response, code) = index.get_task(11).await;
snapshot!(code, @"200 OK");
snapshot!(json_string!(response, { ".duration" => "[duration]", ".enqueuedAt" => "[date]", ".startedAt" => "[date]", ".finishedAt" => "[date]" }),
@r###"
{
"uid": 11,
"indexUid": "test",
"status": "failed",
"type": "documentAdditionOrUpdate",
"canceledBy": null,
"details": {
"receivedDocuments": 1,
2023-01-25 16:52:54 +01:00
"indexedDocuments": 0
},
"error": {
"message": "Could not find latitude in the document with the id: `11`. Was expecting a `_geo.lat` field.",
"code": "invalid_document_geo_field",
"type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#invalid_document_geo_field"
},
"duration": "[duration]",
"enqueuedAt": "[date]",
"startedAt": "[date]",
"finishedAt": "[date]"
}
"###);
// _geo is an object but the lat and lng can't be parsed as a float
let documents = json!([
{
"id": "11",
"_geo": { "lat": "doggo", "lng": "doggo" }
}
]);
index.add_documents(documents, None).await;
index.wait_task(12).await;
let (response, code) = index.get_task(12).await;
snapshot!(code, @"200 OK");
snapshot!(json_string!(response, { ".duration" => "[duration]", ".enqueuedAt" => "[date]", ".startedAt" => "[date]", ".finishedAt" => "[date]" }),
@r###"
{
"uid": 12,
"indexUid": "test",
"status": "failed",
"type": "documentAdditionOrUpdate",
"canceledBy": null,
"details": {
"receivedDocuments": 1,
2023-01-25 16:52:54 +01:00
"indexedDocuments": 0
},
"error": {
"message": "Could not parse latitude nor longitude in the document with the id: `11`. Was expecting finite numbers but instead got `\"doggo\"` and `\"doggo\"`.",
"code": "invalid_document_geo_field",
"type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#invalid_document_geo_field"
},
"duration": "[duration]",
"enqueuedAt": "[date]",
"startedAt": "[date]",
"finishedAt": "[date]"
}
"###);
// _geo is a valid object but contains one extra unknown field
let documents = json!([
{
"id": "11",
"_geo": { "lat": 1, "lng": 2, "doggo": "are the best" }
}
]);
index.add_documents(documents, None).await;
index.wait_task(13).await;
let (response, code) = index.get_task(13).await;
snapshot!(code, @"200 OK");
snapshot!(json_string!(response, { ".duration" => "[duration]", ".enqueuedAt" => "[date]", ".startedAt" => "[date]", ".finishedAt" => "[date]" }),
@r###"
{
"uid": 13,
"indexUid": "test",
2023-01-24 13:25:31 +01:00
"status": "failed",
"type": "documentAdditionOrUpdate",
"canceledBy": null,
"details": {
"receivedDocuments": 1,
2023-01-25 16:52:54 +01:00
"indexedDocuments": 0
},
2023-01-24 13:25:31 +01:00
"error": {
"message": "The `_geo` field in the document with the id: `11` contains the following unexpected fields: `{\"doggo\":\"are the best\"}`.",
"code": "invalid_document_geo_field",
"type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#invalid_document_geo_field"
},
"duration": "[duration]",
"enqueuedAt": "[date]",
"startedAt": "[date]",
"finishedAt": "[date]"
}
"###);
2021-10-21 14:42:01 +02:00
}
#[actix_rt::test]
async fn error_add_documents_payload_size() {
let server = Server::new().await;
let index = server.index("test");
index.create(Some("id")).await;
let document = json!(
{
"id": "11",
"content": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec metus erat, consequat in blandit venenatis, ultricies eu ipsum. Etiam luctus elit et mollis ultrices. Nam turpis risus, dictum non eros in, eleifend feugiat elit. Morbi non dolor pulvinar, sagittis mi sed, ultricies lorem. Nulla ultricies sem metus. Donec at suscipit quam, sed elementum mi. Suspendisse potenti. Fusce pharetra turpis tortor, sed eleifend odio dapibus ut. Nulla facilisi. Suspendisse elementum, dui eget aliquet dignissim, ex tellus aliquam nisl, at eleifend nisl metus tempus diam. Mauris fermentum sollicitudin efficitur. Donec dignissim est vitae elit finibus faucibus"
}
);
let documents: Vec<_> = (0..16000).into_iter().map(|_| document.clone()).collect();
let documents = json!(documents);
let (response, code) = index.add_documents(documents, None).await;
snapshot!(code, @"413 Payload Too Large");
snapshot!(json_string!(response, { ".duration" => "[duration]", ".enqueuedAt" => "[date]", ".startedAt" => "[date]", ".finishedAt" => "[date]" }),
@r###"
{
"message": "The provided payload reached the size limit.",
"code": "payload_too_large",
"type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#payload_too_large"
}
"###);
2021-02-22 15:13:10 +01:00
}
2021-11-04 13:38:44 +01:00
#[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_task(0).await;
let (response, code) = index.get_task(0).await;
2021-11-04 13:38:44 +01:00
assert_eq!(code, 200);
2021-11-04 17:03:13 +01:00
snapshot!(json_string!(response, { ".duration" => "[duration]", ".enqueuedAt" => "[date]", ".startedAt" => "[date]", ".finishedAt" => "[date]" }),
2022-12-21 12:03:06 +01:00
@r###"
{
"uid": 0,
"indexUid": "test",
"status": "failed",
"type": "documentAdditionOrUpdate",
"canceledBy": null,
"details": {
"receivedDocuments": 1,
"indexedDocuments": 0
2022-12-21 12:03:06 +01:00
},
"error": {
2023-01-11 17:20:26 +01:00
"message": "The primary key inference failed as the engine did not find any field ending with `id` in its name. Please specify the primary key manually using the `primaryKey` query parameter.",
2022-12-21 12:03:06 +01:00
"code": "index_primary_key_no_candidate_found",
2021-11-04 17:03:13 +01:00
"type": "invalid_request",
2023-01-19 15:48:20 +01:00
"link": "https://docs.meilisearch.com/errors#index_primary_key_no_candidate_found"
2022-12-21 12:03:06 +01:00
},
"duration": "[duration]",
"enqueuedAt": "[date]",
"startedAt": "[date]",
"finishedAt": "[date]"
}
"###);
2021-11-04 17:03:13 +01:00
2022-12-21 12:03:06 +01:00
let documents = json!([
{
"primary_id": "12",
"object_id": "42",
"id": "124",
"title": "11",
"desc": "foobar"
}
]);
index.add_documents(documents, None).await;
index.wait_task(1).await;
let (response, code) = index.get_task(1).await;
assert_eq!(code, 200);
snapshot!(json_string!(response, { ".duration" => "[duration]", ".enqueuedAt" => "[date]", ".startedAt" => "[date]", ".finishedAt" => "[date]" }),
2022-12-21 12:03:06 +01:00
@r###"
{
"uid": 1,
"indexUid": "test",
"status": "failed",
"type": "documentAdditionOrUpdate",
"canceledBy": null,
"details": {
"receivedDocuments": 1,
"indexedDocuments": 0
2022-12-21 12:03:06 +01:00
},
"error": {
2023-01-11 17:20:26 +01:00
"message": "The primary key inference failed as the engine found 3 fields ending with `id` in their names: 'id' and 'object_id'. Please specify the primary key manually using the `primaryKey` query parameter.",
2022-12-21 12:03:06 +01:00
"code": "index_primary_key_multiple_candidates_found",
"type": "invalid_request",
2023-01-19 15:48:20 +01:00
"link": "https://docs.meilisearch.com/errors#index_primary_key_multiple_candidates_found"
2022-12-21 12:03:06 +01:00
},
"duration": "[duration]",
"enqueuedAt": "[date]",
"startedAt": "[date]",
"finishedAt": "[date]"
}
"###);
let documents = json!([
{
"primary_id": "12",
"title": "11",
"desc": "foobar"
}
]);
index.add_documents(documents, None).await;
index.wait_task(2).await;
let (response, code) = index.get_task(2).await;
assert_eq!(code, 200);
snapshot!(json_string!(response, { ".duration" => "[duration]", ".enqueuedAt" => "[date]", ".startedAt" => "[date]", ".finishedAt" => "[date]" }),
2022-12-21 12:03:06 +01:00
@r###"
{
"uid": 2,
"indexUid": "test",
"status": "succeeded",
"type": "documentAdditionOrUpdate",
"canceledBy": null,
"details": {
"receivedDocuments": 1,
"indexedDocuments": 1
},
"error": null,
"duration": "[duration]",
"enqueuedAt": "[date]",
"startedAt": "[date]",
"finishedAt": "[date]"
}
"###);
2021-11-04 13:38:44 +01:00
}
#[actix_rt::test]
async fn add_documents_with_primary_key_twice() {
let server = Server::new().await;
let index = server.index("test");
let documents = json!([
{
"title": "11",
"desc": "foobar"
}
]);
index.add_documents(documents.clone(), Some("title")).await;
index.wait_task(0).await;
let (response, _code) = index.get_task(0).await;
assert_eq!(response["status"], "succeeded");
index.add_documents(documents, Some("title")).await;
index.wait_task(1).await;
let (response, _code) = index.get_task(1).await;
assert_eq!(response["status"], "succeeded");
}
2022-08-10 16:42:26 +02:00
#[actix_rt::test]
async fn batch_several_documents_addition() {
let server = Server::new().await;
let index = server.index("test");
let mut documents: Vec<_> = (0..150usize)
.into_iter()
.map(|id| {
json!(
{
"id": id,
"title": "foo",
"desc": "bar"
}
)
})
.collect();
documents[100] = json!({"title": "error", "desc": "error"});
// enqueue batch of documents
2022-08-16 15:14:01 +02:00
let mut waiter = Vec::new();
2022-08-10 16:42:26 +02:00
for chunk in documents.chunks(30) {
2022-08-16 15:14:01 +02:00
waiter.push(index.add_documents(json!(chunk), Some("id")));
2022-08-10 16:42:26 +02:00
}
// wait first batch of documents to finish
2022-08-16 15:14:01 +02:00
futures::future::join_all(waiter).await;
2022-08-10 16:42:26 +02:00
index.wait_task(4).await;
// run a second completely failing batch
2022-08-11 13:35:35 +02:00
documents[40] = json!({"title": "error", "desc": "error"});
documents[70] = json!({"title": "error", "desc": "error"});
documents[130] = json!({"title": "error", "desc": "error"});
2022-08-16 15:14:01 +02:00
let mut waiter = Vec::new();
2022-08-10 16:42:26 +02:00
for chunk in documents.chunks(30) {
2022-08-16 15:14:01 +02:00
waiter.push(index.add_documents(json!(chunk), Some("id")));
2022-08-10 16:42:26 +02:00
}
// wait second batch of documents to finish
2022-08-16 15:14:01 +02:00
futures::future::join_all(waiter).await;
2022-08-10 16:42:26 +02:00
index.wait_task(9).await;
let (response, _code) = index.filtered_tasks(&[], &["failed"], &[]).await;
2022-08-10 16:42:26 +02:00
// Check if only the 6th task failed
2022-08-16 15:14:01 +02:00
println!("{}", &response);
assert_eq!(response["results"].as_array().unwrap().len(), 5);
2022-08-10 16:42:26 +02:00
// Check if there are exactly 120 documents (150 - 30) in the index;
let (response, code) = index
2022-10-20 18:00:07 +02:00
.get_all_documents(GetAllDocumentsOptions { limit: Some(200), ..Default::default() })
2022-08-10 16:42:26 +02:00
.await;
assert_eq!(code, 200, "failed with `{}`", response);
assert_eq!(response["results"].as_array().unwrap().len(), 120);
}