mirror of
https://github.com/meilisearch/MeiliSearch
synced 2024-11-26 23:04:26 +01:00
Merge #3341
3341: add functionnal + error tests on the swap_indexes route and fix a confusing error message r=loiclec a=irevoire Fix https://github.com/meilisearch/meilisearch/issues/3340 Fix part of https://github.com/meilisearch/meilisearch/issues/3325 Fix https://github.com/meilisearch/meilisearch/issues/3381 Test both the functionality and the error codes Co-authored-by: Tamo <tamo@meilisearch.com> Co-authored-by: Loïc Lecrenier <loic.lecrenier@me.com>
This commit is contained in:
commit
6f7e0c431a
@ -24,8 +24,8 @@ pub enum MeilisearchHttpError {
|
|||||||
MissingPayload(PayloadType),
|
MissingPayload(PayloadType),
|
||||||
#[error("The provided payload reached the size limit.")]
|
#[error("The provided payload reached the size limit.")]
|
||||||
PayloadTooLarge,
|
PayloadTooLarge,
|
||||||
#[error("Two indexes must be given for each swap. The list `{:?}` contains {} indexes.",
|
#[error("Two indexes must be given for each swap. The list `[{}]` contains {} indexes.",
|
||||||
.0, .0.len()
|
.0.iter().map(|uid| format!("\"{uid}\"")).collect::<Vec<_>>().join(", "), .0.len()
|
||||||
)]
|
)]
|
||||||
SwapIndexPayloadWrongLength(Vec<IndexUid>),
|
SwapIndexPayloadWrongLength(Vec<IndexUid>),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
|
@ -8,6 +8,7 @@ mod search;
|
|||||||
mod settings;
|
mod settings;
|
||||||
mod snapshot;
|
mod snapshot;
|
||||||
mod stats;
|
mod stats;
|
||||||
|
mod swap_indexes;
|
||||||
mod tasks;
|
mod tasks;
|
||||||
|
|
||||||
// Tests are isolated by features in different modules to allow better readability, test
|
// Tests are isolated by features in different modules to allow better readability, test
|
||||||
|
94
meilisearch/tests/swap_indexes/errors.rs
Normal file
94
meilisearch/tests/swap_indexes/errors.rs
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
use meili_snap::*;
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
|
use crate::common::Server;
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn swap_indexes_bad_format() {
|
||||||
|
let server = Server::new().await;
|
||||||
|
|
||||||
|
let (response, code) = server.index_swap(json!("doggo")).await;
|
||||||
|
snapshot!(code, @"400 Bad Request");
|
||||||
|
snapshot!(json_string!(response), @r###"
|
||||||
|
{
|
||||||
|
"message": "Invalid value type: expected an array, but found a string: `\"doggo\"`",
|
||||||
|
"code": "bad_request",
|
||||||
|
"type": "invalid_request",
|
||||||
|
"link": "https://docs.meilisearch.com/errors#bad-request"
|
||||||
|
}
|
||||||
|
"###);
|
||||||
|
|
||||||
|
let (response, code) = server.index_swap(json!(["doggo"])).await;
|
||||||
|
snapshot!(code, @"400 Bad Request");
|
||||||
|
snapshot!(json_string!(response), @r###"
|
||||||
|
{
|
||||||
|
"message": "Invalid value type at `[0]`: expected an object, but found a string: `\"doggo\"`",
|
||||||
|
"code": "bad_request",
|
||||||
|
"type": "invalid_request",
|
||||||
|
"link": "https://docs.meilisearch.com/errors#bad-request"
|
||||||
|
}
|
||||||
|
"###);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn swap_indexes_bad_indexes() {
|
||||||
|
let server = Server::new().await;
|
||||||
|
|
||||||
|
let (response, code) = server.index_swap(json!([{ "indexes": "doggo"}])).await;
|
||||||
|
snapshot!(code, @"400 Bad Request");
|
||||||
|
snapshot!(json_string!(response), @r###"
|
||||||
|
{
|
||||||
|
"message": "Invalid value type at `[0].indexes`: expected an array, but found a string: `\"doggo\"`",
|
||||||
|
"code": "invalid_swap_indexes",
|
||||||
|
"type": "invalid_request",
|
||||||
|
"link": "https://docs.meilisearch.com/errors#invalid-swap-indexes"
|
||||||
|
}
|
||||||
|
"###);
|
||||||
|
|
||||||
|
let (response, code) = server.index_swap(json!([{ "indexes": ["doggo"]}])).await;
|
||||||
|
snapshot!(code, @"400 Bad Request");
|
||||||
|
snapshot!(json_string!(response), @r###"
|
||||||
|
{
|
||||||
|
"message": "Two indexes must be given for each swap. The list `[\"doggo\"]` contains 1 indexes.",
|
||||||
|
"code": "invalid_swap_indexes",
|
||||||
|
"type": "invalid_request",
|
||||||
|
"link": "https://docs.meilisearch.com/errors#invalid-swap-indexes"
|
||||||
|
}
|
||||||
|
"###);
|
||||||
|
|
||||||
|
let (response, code) =
|
||||||
|
server.index_swap(json!([{ "indexes": ["doggo", "crabo", "croco"]}])).await;
|
||||||
|
snapshot!(code, @"400 Bad Request");
|
||||||
|
snapshot!(json_string!(response), @r###"
|
||||||
|
{
|
||||||
|
"message": "Two indexes must be given for each swap. The list `[\"doggo\", \"crabo\", \"croco\"]` contains 3 indexes.",
|
||||||
|
"code": "invalid_swap_indexes",
|
||||||
|
"type": "invalid_request",
|
||||||
|
"link": "https://docs.meilisearch.com/errors#invalid-swap-indexes"
|
||||||
|
}
|
||||||
|
"###);
|
||||||
|
|
||||||
|
let (response, code) = server.index_swap(json!([{ "indexes": ["doggo", "doggo"]}])).await;
|
||||||
|
snapshot!(code, @"400 Bad Request");
|
||||||
|
snapshot!(json_string!(response), @r###"
|
||||||
|
{
|
||||||
|
"message": "Indexes must be declared only once during a swap. `doggo` was specified several times.",
|
||||||
|
"code": "invalid_swap_duplicate_index_found",
|
||||||
|
"type": "invalid_request",
|
||||||
|
"link": "https://docs.meilisearch.com/errors#invalid-swap-duplicate-index-found"
|
||||||
|
}
|
||||||
|
"###);
|
||||||
|
|
||||||
|
let (response, code) = server
|
||||||
|
.index_swap(json!([{ "indexes": ["doggo", "catto"]}, { "indexes": ["girafo", "doggo"]}]))
|
||||||
|
.await;
|
||||||
|
snapshot!(code, @"400 Bad Request");
|
||||||
|
snapshot!(json_string!(response), @r###"
|
||||||
|
{
|
||||||
|
"message": "Indexes must be declared only once during a swap. `doggo` was specified several times.",
|
||||||
|
"code": "invalid_swap_duplicate_index_found",
|
||||||
|
"type": "invalid_request",
|
||||||
|
"link": "https://docs.meilisearch.com/errors#invalid-swap-duplicate-index-found"
|
||||||
|
}
|
||||||
|
"###);
|
||||||
|
}
|
357
meilisearch/tests/swap_indexes/mod.rs
Normal file
357
meilisearch/tests/swap_indexes/mod.rs
Normal file
@ -0,0 +1,357 @@
|
|||||||
|
mod errors;
|
||||||
|
|
||||||
|
use meili_snap::{json_string, snapshot};
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
|
use crate::common::{GetAllDocumentsOptions, Server};
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn swap_indexes() {
|
||||||
|
let server = Server::new().await;
|
||||||
|
let a = server.index("a");
|
||||||
|
let (_, code) = a.add_documents(json!({ "id": 1, "index": "a"}), None).await;
|
||||||
|
snapshot!(code, @"202 Accepted");
|
||||||
|
let b = server.index("b");
|
||||||
|
let (res, code) = b.add_documents(json!({ "id": 1, "index": "b"}), None).await;
|
||||||
|
snapshot!(code, @"202 Accepted");
|
||||||
|
snapshot!(res["taskUid"], @"1");
|
||||||
|
server.wait_task(1).await;
|
||||||
|
|
||||||
|
let (tasks, code) = server.tasks().await;
|
||||||
|
snapshot!(code, @"200 OK");
|
||||||
|
snapshot!(json_string!(tasks, { ".results[].duration" => "[duration]", ".results[].enqueuedAt" => "[date]", ".results[].startedAt" => "[date]", ".results[].finishedAt" => "[date]" }), @r###"
|
||||||
|
{
|
||||||
|
"results": [
|
||||||
|
{
|
||||||
|
"uid": 1,
|
||||||
|
"indexUid": "b",
|
||||||
|
"status": "succeeded",
|
||||||
|
"type": "documentAdditionOrUpdate",
|
||||||
|
"canceledBy": null,
|
||||||
|
"details": {
|
||||||
|
"receivedDocuments": 1,
|
||||||
|
"indexedDocuments": 1
|
||||||
|
},
|
||||||
|
"error": null,
|
||||||
|
"duration": "[duration]",
|
||||||
|
"enqueuedAt": "[date]",
|
||||||
|
"startedAt": "[date]",
|
||||||
|
"finishedAt": "[date]"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uid": 0,
|
||||||
|
"indexUid": "a",
|
||||||
|
"status": "succeeded",
|
||||||
|
"type": "documentAdditionOrUpdate",
|
||||||
|
"canceledBy": null,
|
||||||
|
"details": {
|
||||||
|
"receivedDocuments": 1,
|
||||||
|
"indexedDocuments": 1
|
||||||
|
},
|
||||||
|
"error": null,
|
||||||
|
"duration": "[duration]",
|
||||||
|
"enqueuedAt": "[date]",
|
||||||
|
"startedAt": "[date]",
|
||||||
|
"finishedAt": "[date]"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"limit": 20,
|
||||||
|
"from": 1,
|
||||||
|
"next": null
|
||||||
|
}
|
||||||
|
"###);
|
||||||
|
|
||||||
|
let (res, code) = server.index_swap(json!([{ "indexes": ["a", "b"] }])).await;
|
||||||
|
snapshot!(code, @"202 Accepted");
|
||||||
|
snapshot!(res["taskUid"], @"2");
|
||||||
|
server.wait_task(2).await;
|
||||||
|
|
||||||
|
let (tasks, code) = server.tasks().await;
|
||||||
|
snapshot!(code, @"200 OK");
|
||||||
|
|
||||||
|
// Notice how the task 0 which was initially representing the creation of the index `A` now represents the creation of the index `B`.
|
||||||
|
snapshot!(json_string!(tasks, { ".results[].duration" => "[duration]", ".results[].enqueuedAt" => "[date]", ".results[].startedAt" => "[date]", ".results[].finishedAt" => "[date]" }), @r###"
|
||||||
|
{
|
||||||
|
"results": [
|
||||||
|
{
|
||||||
|
"uid": 2,
|
||||||
|
"indexUid": null,
|
||||||
|
"status": "succeeded",
|
||||||
|
"type": "indexSwap",
|
||||||
|
"canceledBy": null,
|
||||||
|
"details": {
|
||||||
|
"swaps": [
|
||||||
|
{
|
||||||
|
"indexes": [
|
||||||
|
"a",
|
||||||
|
"b"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"error": null,
|
||||||
|
"duration": "[duration]",
|
||||||
|
"enqueuedAt": "[date]",
|
||||||
|
"startedAt": "[date]",
|
||||||
|
"finishedAt": "[date]"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uid": 1,
|
||||||
|
"indexUid": "a",
|
||||||
|
"status": "succeeded",
|
||||||
|
"type": "documentAdditionOrUpdate",
|
||||||
|
"canceledBy": null,
|
||||||
|
"details": {
|
||||||
|
"receivedDocuments": 1,
|
||||||
|
"indexedDocuments": 1
|
||||||
|
},
|
||||||
|
"error": null,
|
||||||
|
"duration": "[duration]",
|
||||||
|
"enqueuedAt": "[date]",
|
||||||
|
"startedAt": "[date]",
|
||||||
|
"finishedAt": "[date]"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uid": 0,
|
||||||
|
"indexUid": "b",
|
||||||
|
"status": "succeeded",
|
||||||
|
"type": "documentAdditionOrUpdate",
|
||||||
|
"canceledBy": null,
|
||||||
|
"details": {
|
||||||
|
"receivedDocuments": 1,
|
||||||
|
"indexedDocuments": 1
|
||||||
|
},
|
||||||
|
"error": null,
|
||||||
|
"duration": "[duration]",
|
||||||
|
"enqueuedAt": "[date]",
|
||||||
|
"startedAt": "[date]",
|
||||||
|
"finishedAt": "[date]"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"limit": 20,
|
||||||
|
"from": 2,
|
||||||
|
"next": null
|
||||||
|
}
|
||||||
|
"###);
|
||||||
|
|
||||||
|
// BUT, the data in `a` should now points to the data that was in `b`.
|
||||||
|
// And the opposite is true as well
|
||||||
|
let (res, _) = a.get_all_documents(GetAllDocumentsOptions::default()).await;
|
||||||
|
snapshot!(res["results"], @r###"[{"id":1,"index":"b"}]"###);
|
||||||
|
let (res, _) = b.get_all_documents(GetAllDocumentsOptions::default()).await;
|
||||||
|
snapshot!(res["results"], @r###"[{"id":1,"index":"a"}]"###);
|
||||||
|
|
||||||
|
// ================
|
||||||
|
// And now we're going to attempt the famous and dangerous DOUBLE index swap 🤞
|
||||||
|
|
||||||
|
let c = server.index("c");
|
||||||
|
let (res, code) = c.add_documents(json!({ "id": 1, "index": "c"}), None).await;
|
||||||
|
snapshot!(code, @"202 Accepted");
|
||||||
|
snapshot!(res["taskUid"], @"3");
|
||||||
|
let d = server.index("d");
|
||||||
|
let (res, code) = d.add_documents(json!({ "id": 1, "index": "d"}), None).await;
|
||||||
|
snapshot!(code, @"202 Accepted");
|
||||||
|
snapshot!(res["taskUid"], @"4");
|
||||||
|
server.wait_task(4).await;
|
||||||
|
|
||||||
|
// ensure the index creation worked properly
|
||||||
|
let (tasks, code) = server.tasks_filter("limit=2").await;
|
||||||
|
snapshot!(code, @"200 OK");
|
||||||
|
snapshot!(json_string!(tasks, { ".results[].duration" => "[duration]", ".results[].enqueuedAt" => "[date]", ".results[].startedAt" => "[date]", ".results[].finishedAt" => "[date]" }), @r###"
|
||||||
|
{
|
||||||
|
"results": [
|
||||||
|
{
|
||||||
|
"uid": 4,
|
||||||
|
"indexUid": "d",
|
||||||
|
"status": "succeeded",
|
||||||
|
"type": "documentAdditionOrUpdate",
|
||||||
|
"canceledBy": null,
|
||||||
|
"details": {
|
||||||
|
"receivedDocuments": 1,
|
||||||
|
"indexedDocuments": 1
|
||||||
|
},
|
||||||
|
"error": null,
|
||||||
|
"duration": "[duration]",
|
||||||
|
"enqueuedAt": "[date]",
|
||||||
|
"startedAt": "[date]",
|
||||||
|
"finishedAt": "[date]"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uid": 3,
|
||||||
|
"indexUid": "c",
|
||||||
|
"status": "succeeded",
|
||||||
|
"type": "documentAdditionOrUpdate",
|
||||||
|
"canceledBy": null,
|
||||||
|
"details": {
|
||||||
|
"receivedDocuments": 1,
|
||||||
|
"indexedDocuments": 1
|
||||||
|
},
|
||||||
|
"error": null,
|
||||||
|
"duration": "[duration]",
|
||||||
|
"enqueuedAt": "[date]",
|
||||||
|
"startedAt": "[date]",
|
||||||
|
"finishedAt": "[date]"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"limit": 2,
|
||||||
|
"from": 4,
|
||||||
|
"next": 2
|
||||||
|
}
|
||||||
|
"###);
|
||||||
|
|
||||||
|
// It's happening 😲
|
||||||
|
|
||||||
|
let (res, code) =
|
||||||
|
server.index_swap(json!([{ "indexes": ["a", "b"] }, { "indexes": ["c", "d"] } ])).await;
|
||||||
|
snapshot!(res["taskUid"], @"5");
|
||||||
|
snapshot!(code, @"202 Accepted");
|
||||||
|
server.wait_task(5).await;
|
||||||
|
|
||||||
|
// ensure the index creation worked properly
|
||||||
|
let (tasks, code) = server.tasks().await;
|
||||||
|
snapshot!(code, @"200 OK");
|
||||||
|
|
||||||
|
// What should we check for each tasks in this test:
|
||||||
|
// Task number;
|
||||||
|
// 0. should have the indexUid `a` again
|
||||||
|
// 1. should have the indexUid `b` again
|
||||||
|
// 2. stays unchanged
|
||||||
|
// 3. now have the indexUid `d` instead of `c`
|
||||||
|
// 4. now have the indexUid `c` instead of `d`
|
||||||
|
snapshot!(json_string!(tasks, { ".results[].duration" => "[duration]", ".results[].enqueuedAt" => "[date]", ".results[].startedAt" => "[date]", ".results[].finishedAt" => "[date]" }), @r###"
|
||||||
|
{
|
||||||
|
"results": [
|
||||||
|
{
|
||||||
|
"uid": 5,
|
||||||
|
"indexUid": null,
|
||||||
|
"status": "succeeded",
|
||||||
|
"type": "indexSwap",
|
||||||
|
"canceledBy": null,
|
||||||
|
"details": {
|
||||||
|
"swaps": [
|
||||||
|
{
|
||||||
|
"indexes": [
|
||||||
|
"a",
|
||||||
|
"b"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"indexes": [
|
||||||
|
"c",
|
||||||
|
"d"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"error": null,
|
||||||
|
"duration": "[duration]",
|
||||||
|
"enqueuedAt": "[date]",
|
||||||
|
"startedAt": "[date]",
|
||||||
|
"finishedAt": "[date]"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uid": 4,
|
||||||
|
"indexUid": "c",
|
||||||
|
"status": "succeeded",
|
||||||
|
"type": "documentAdditionOrUpdate",
|
||||||
|
"canceledBy": null,
|
||||||
|
"details": {
|
||||||
|
"receivedDocuments": 1,
|
||||||
|
"indexedDocuments": 1
|
||||||
|
},
|
||||||
|
"error": null,
|
||||||
|
"duration": "[duration]",
|
||||||
|
"enqueuedAt": "[date]",
|
||||||
|
"startedAt": "[date]",
|
||||||
|
"finishedAt": "[date]"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uid": 3,
|
||||||
|
"indexUid": "d",
|
||||||
|
"status": "succeeded",
|
||||||
|
"type": "documentAdditionOrUpdate",
|
||||||
|
"canceledBy": null,
|
||||||
|
"details": {
|
||||||
|
"receivedDocuments": 1,
|
||||||
|
"indexedDocuments": 1
|
||||||
|
},
|
||||||
|
"error": null,
|
||||||
|
"duration": "[duration]",
|
||||||
|
"enqueuedAt": "[date]",
|
||||||
|
"startedAt": "[date]",
|
||||||
|
"finishedAt": "[date]"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uid": 2,
|
||||||
|
"indexUid": null,
|
||||||
|
"status": "succeeded",
|
||||||
|
"type": "indexSwap",
|
||||||
|
"canceledBy": null,
|
||||||
|
"details": {
|
||||||
|
"swaps": [
|
||||||
|
{
|
||||||
|
"indexes": [
|
||||||
|
"b",
|
||||||
|
"a"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"error": null,
|
||||||
|
"duration": "[duration]",
|
||||||
|
"enqueuedAt": "[date]",
|
||||||
|
"startedAt": "[date]",
|
||||||
|
"finishedAt": "[date]"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uid": 1,
|
||||||
|
"indexUid": "b",
|
||||||
|
"status": "succeeded",
|
||||||
|
"type": "documentAdditionOrUpdate",
|
||||||
|
"canceledBy": null,
|
||||||
|
"details": {
|
||||||
|
"receivedDocuments": 1,
|
||||||
|
"indexedDocuments": 1
|
||||||
|
},
|
||||||
|
"error": null,
|
||||||
|
"duration": "[duration]",
|
||||||
|
"enqueuedAt": "[date]",
|
||||||
|
"startedAt": "[date]",
|
||||||
|
"finishedAt": "[date]"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uid": 0,
|
||||||
|
"indexUid": "a",
|
||||||
|
"status": "succeeded",
|
||||||
|
"type": "documentAdditionOrUpdate",
|
||||||
|
"canceledBy": null,
|
||||||
|
"details": {
|
||||||
|
"receivedDocuments": 1,
|
||||||
|
"indexedDocuments": 1
|
||||||
|
},
|
||||||
|
"error": null,
|
||||||
|
"duration": "[duration]",
|
||||||
|
"enqueuedAt": "[date]",
|
||||||
|
"startedAt": "[date]",
|
||||||
|
"finishedAt": "[date]"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"limit": 20,
|
||||||
|
"from": 5,
|
||||||
|
"next": null
|
||||||
|
}
|
||||||
|
"###);
|
||||||
|
|
||||||
|
// - The data in `a` should point to `a`.
|
||||||
|
// - The data in `b` should point to `b`.
|
||||||
|
// - The data in `c` should point to `d`.
|
||||||
|
// - The data in `d` should point to `c`.
|
||||||
|
let (res, _) = a.get_all_documents(GetAllDocumentsOptions::default()).await;
|
||||||
|
snapshot!(res["results"], @r###"[{"id":1,"index":"a"}]"###);
|
||||||
|
let (res, _) = b.get_all_documents(GetAllDocumentsOptions::default()).await;
|
||||||
|
snapshot!(res["results"], @r###"[{"id":1,"index":"b"}]"###);
|
||||||
|
let (res, _) = c.get_all_documents(GetAllDocumentsOptions::default()).await;
|
||||||
|
snapshot!(res["results"], @r###"[{"id":1,"index":"d"}]"###);
|
||||||
|
let (res, _) = d.get_all_documents(GetAllDocumentsOptions::default()).await;
|
||||||
|
snapshot!(res["results"], @r###"[{"id":1,"index":"c"}]"###);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user