diff --git a/meilisearch-lib/src/index_resolver/mod.rs b/meilisearch-lib/src/index_resolver/mod.rs index abbfdfd4c..686a549b9 100644 --- a/meilisearch-lib/src/index_resolver/mod.rs +++ b/meilisearch-lib/src/index_resolver/mod.rs @@ -386,8 +386,14 @@ mod real { #[cfg(test)] mod test { + use crate::index::IndexStats; + + use super::index_store::MockIndexStore; + use super::meta_store::MockIndexMetaStore; use super::*; + use futures::future::ok; + use milli::FieldDistribution; use nelson::Mocker; pub enum MockIndexResolver { @@ -493,174 +499,178 @@ mod test { } } - // TODO: ignoring this test, it has become too complex to maintain, and rather implement - // handler logic test. - // proptest! { - // #[test] - // #[ignore] - // fn test_process_task( - // task in any::().prop_filter("IndexUid should be Some", |s| s.index_uid.is_some()), - // index_exists in any::(), - // index_op_fails in any::(), - // any_int in any::(), - // ) { - // actix_rt::System::new().block_on(async move { - // let uuid = Uuid::new_v4(); - // let mut index_store = MockIndexStore::new(); - // - // let mocker = Mocker::default(); - // - // // Return arbitrary data from index call. - // match &task.content { - // TaskContent::DocumentAddition{primary_key, ..} => { - // let result = move || if !index_op_fails { - // Ok(DocumentAdditionResult { indexed_documents: any_int, number_of_documents: any_int }) - // } else { - // // return this error because it's easy to generate... - // Err(IndexError::DocumentNotFound("a doc".into())) - // }; - // if primary_key.is_some() { - // mocker.when::>("update_primary_key") - // .then(move |_| Ok(IndexMeta{ created_at: OffsetDateTime::now_utc(), updated_at: OffsetDateTime::now_utc(), primary_key: None })); - // } - // mocker.when::<(IndexDocumentsMethod, Option, UpdateFileStore, IntoIter), IndexResult>("update_documents") - // .then(move |(_, _, _, _)| result()); - // } - // TaskContent::SettingsUpdate{..} => { - // let result = move || if !index_op_fails { - // Ok(()) - // } else { - // // return this error because it's easy to generate... - // Err(IndexError::DocumentNotFound("a doc".into())) - // }; - // mocker.when::<&Settings, IndexResult<()>>("update_settings") - // .then(move |_| result()); - // } - // TaskContent::DocumentDeletion(DocumentDeletion::Ids(_ids)) => { - // let result = move || if !index_op_fails { - // Ok(DocumentDeletionResult { deleted_documents: any_int as u64, remaining_documents: any_int as u64 }) - // } else { - // // return this error because it's easy to generate... - // Err(IndexError::DocumentNotFound("a doc".into())) - // }; - // - // mocker.when::<&[String], IndexResult>("delete_documents") - // .then(move |_| result()); - // }, - // TaskContent::DocumentDeletion(DocumentDeletion::Clear) => { - // let result = move || if !index_op_fails { - // Ok(()) - // } else { - // // return this error because it's easy to generate... - // Err(IndexError::DocumentNotFound("a doc".into())) - // }; - // mocker.when::<(), IndexResult<()>>("clear_documents") - // .then(move |_| result()); - // }, - // TaskContent::IndexDeletion => { - // mocker.when::<(), ()>("close") - // .times(index_exists as usize) - // .then(move |_| ()); - // } - // TaskContent::IndexUpdate { primary_key } - // | TaskContent::IndexCreation { primary_key } => { - // if primary_key.is_some() { - // let result = move || if !index_op_fails { - // Ok(IndexMeta{ created_at: OffsetDateTime::now_utc(), updated_at: OffsetDateTime::now_utc(), primary_key: None }) - // } else { - // // return this error because it's easy to generate... - // Err(IndexError::DocumentNotFound("a doc".into())) - // }; - // mocker.when::>("update_primary_key") - // .then(move |_| result()); - // } - // } - // TaskContent::Dump { .. } => { } - // } - // - // mocker.when::<(), IndexResult>("stats") - // .then(|()| Ok(IndexStats { size: 0, number_of_documents: 0, is_indexing: Some(false), field_distribution: BTreeMap::new() })); - // - // let index = Index::mock(mocker); - // - // match &task.content { - // // an unexisting index should trigger an index creation in the folllowing cases: - // TaskContent::DocumentAddition { allow_index_creation: true, .. } - // | TaskContent::SettingsUpdate { allow_index_creation: true, is_deletion: false, .. } - // | TaskContent::IndexCreation { .. } if !index_exists => { - // index_store - // .expect_create() - // .once() - // .withf(move |&found| !index_exists || found == uuid) - // .returning(move |_| Box::pin(ok(index.clone()))); - // }, - // TaskContent::IndexDeletion => { - // index_store - // .expect_delete() - // // this is called only if the index.exists - // .times(index_exists as usize) - // .withf(move |&found| !index_exists || found == uuid) - // .returning(move |_| Box::pin(ok(Some(index.clone())))); - // } - // // if index already exists, create index will return an error - // TaskContent::IndexCreation { .. } if index_exists => (), - // TaskContent::Dump { .. } => (), - // // The index exists and get should be called - // _ if index_exists => { - // index_store - // .expect_get() - // .once() - // .withf(move |&found| found == uuid) - // .returning(move |_| Box::pin(ok(Some(index.clone())))); - // }, - // // the index doesn't exist and shouldn't be created, the uuidstore will return an error, and get_index will never be called. - // _ => (), - // } - // - // let mut uuid_store = MockIndexMetaStore::new(); - // uuid_store - // .expect_get() - // .returning(move |uid| { - // Box::pin(ok((uid, index_exists.then(|| crate::index_resolver::meta_store::IndexMeta {uuid, creation_task_id: 0 })))) - // }); - // - // // we sould only be creating an index if the index doesn't alredy exist - // uuid_store - // .expect_insert() - // .withf(move |_, _| !index_exists) - // .returning(|_, _| Box::pin(ok(()))); - // - // uuid_store - // .expect_delete() - // .times(matches!(task.content, TaskContent::IndexDeletion) as usize) - // .returning(move |_| Box::pin(ok(index_exists.then(|| crate::index_resolver::meta_store::IndexMeta { uuid, creation_task_id: 0})))); - // - // let mocker = Mocker::default(); - // let update_file_store = UpdateFileStore::mock(mocker); - // let index_resolver = IndexResolver::new(uuid_store, index_store, update_file_store); - // - // let batch = Batch { id: Some(1), created_at: OffsetDateTime::now_utc(), content: crate::tasks::batch::BatchContent::IndexUpdate(task.clone()) }; - // if index_resolver.accept(&batch) { - // let result = index_resolver.process_batch(batch).await; - // - // // Test for some expected output scenarios: - // // Index creation and deletion cannot fail because of a failed index op, since they - // // don't perform index ops. - // if index_op_fails && !matches!(task.content, TaskContent::IndexDeletion | TaskContent::IndexCreation { primary_key: None } | TaskContent::IndexUpdate { primary_key: None } | TaskContent::Dump { .. }) - // || (index_exists && matches!(task.content, TaskContent::IndexCreation { .. })) - // || (!index_exists && matches!(task.content, TaskContent::IndexDeletion - // | TaskContent::DocumentDeletion(_) - // | TaskContent::SettingsUpdate { is_deletion: true, ..} - // | TaskContent::SettingsUpdate { allow_index_creation: false, ..} - // | TaskContent::DocumentAddition { allow_index_creation: false, ..} - // | TaskContent::IndexUpdate { .. } )) - // { - // assert!(matches!(result.content.first().unwrap().events.last().unwrap(), TaskEvent::Failed { .. }), "{:?}", result); - // } else { - // assert!(matches!(result.content.first().unwrap().events.last().unwrap(), TaskEvent::Succeeded { .. }), "{:?}", result); - // } - // } - // }); - // } - // } + #[actix_rt::test] + async fn test_remove_unknown_index() { + let mut meta_store = MockIndexMetaStore::new(); + meta_store + .expect_delete() + .once() + .returning(|_| Box::pin(ok(None))); + + let index_store = MockIndexStore::new(); + + let mocker = Mocker::default(); + let file_store = UpdateFileStore::mock(mocker); + + let index_resolver = IndexResolver::new(meta_store, index_store, file_store); + + let mut task = Task { + id: 1, + content: TaskContent::IndexDeletion { + index_uid: IndexUid::new_unchecked("test"), + }, + events: Vec::new(), + }; + + index_resolver.process_task(&mut task).await; + + assert!(matches!(task.events[0], TaskEvent::Failed { .. })); + } + + #[actix_rt::test] + async fn test_remove_index() { + let mut meta_store = MockIndexMetaStore::new(); + meta_store.expect_delete().once().returning(|_| { + Box::pin(ok(Some(IndexMeta { + uuid: Uuid::new_v4(), + creation_task_id: 1, + }))) + }); + + let mut index_store = MockIndexStore::new(); + index_store.expect_delete().once().returning(|_| { + let mocker = Mocker::default(); + mocker.when::<(), ()>("close").then(|_| ()); + mocker + .when::<(), IndexResult>("stats") + .then(|_| { + Ok(IndexStats { + size: 10, + number_of_documents: 10, + is_indexing: None, + field_distribution: FieldDistribution::default(), + }) + }); + Box::pin(ok(Some(Index::mock(mocker)))) + }); + + let mocker = Mocker::default(); + let file_store = UpdateFileStore::mock(mocker); + + let index_resolver = IndexResolver::new(meta_store, index_store, file_store); + + let mut task = Task { + id: 1, + content: TaskContent::IndexDeletion { + index_uid: IndexUid::new_unchecked("test"), + }, + events: Vec::new(), + }; + + index_resolver.process_task(&mut task).await; + + assert!(matches!(task.events[0], TaskEvent::Succeeded { .. })); + } + + #[actix_rt::test] + async fn test_delete_documents() { + let mut meta_store = MockIndexMetaStore::new(); + meta_store.expect_get().once().returning(|_| { + Box::pin(ok(( + "test".to_string(), + Some(IndexMeta { + uuid: Uuid::new_v4(), + creation_task_id: 1, + }), + ))) + }); + + let mut index_store = MockIndexStore::new(); + index_store.expect_get().once().returning(|_| { + let mocker = Mocker::default(); + mocker + .when::<(), IndexResult<()>>("clear_documents") + .once() + .then(|_| Ok(())); + mocker + .when::<(), IndexResult>("stats") + .once() + .then(|_| { + Ok(IndexStats { + size: 10, + number_of_documents: 10, + is_indexing: None, + field_distribution: FieldDistribution::default(), + }) + }); + Box::pin(ok(Some(Index::mock(mocker)))) + }); + + let mocker = Mocker::default(); + let file_store = UpdateFileStore::mock(mocker); + + let index_resolver = IndexResolver::new(meta_store, index_store, file_store); + + let mut task = Task { + id: 1, + content: TaskContent::DocumentDeletion { + deletion: DocumentDeletion::Clear, + index_uid: IndexUid::new_unchecked("test"), + }, + events: Vec::new(), + }; + + index_resolver.process_task(&mut task).await; + + assert!(matches!(task.events[0], TaskEvent::Succeeded { .. })); + } + + #[actix_rt::test] + async fn test_index_update() { + let mut meta_store = MockIndexMetaStore::new(); + meta_store.expect_get().once().returning(|_| { + Box::pin(ok(( + "test".to_string(), + Some(IndexMeta { + uuid: Uuid::new_v4(), + creation_task_id: 1, + }), + ))) + }); + + let mut index_store = MockIndexStore::new(); + index_store.expect_get().once().returning(|_| { + let mocker = Mocker::default(); + + mocker + .when::>("update_primary_key") + .once() + .then(|_| { + Ok(crate::index::IndexMeta { + created_at: OffsetDateTime::now_utc(), + updated_at: OffsetDateTime::now_utc(), + primary_key: Some("key".to_string()), + }) + }); + Box::pin(ok(Some(Index::mock(mocker)))) + }); + + let mocker = Mocker::default(); + let file_store = UpdateFileStore::mock(mocker); + + let index_resolver = IndexResolver::new(meta_store, index_store, file_store); + + let mut task = Task { + id: 1, + content: TaskContent::IndexUpdate { + primary_key: Some("key".to_string()), + index_uid: IndexUid::new_unchecked("test"), + }, + events: Vec::new(), + }; + + index_resolver.process_task(&mut task).await; + + assert!(matches!(task.events[0], TaskEvent::Succeeded { .. })); + } }