From 06e91ace25dcae0a5abfd0114941e3bfd681e208 Mon Sep 17 00:00:00 2001 From: Kent Tristan Yves Sarmiento Date: Tue, 26 Dec 2023 14:11:19 +0800 Subject: [PATCH] refactor: inmemory repository (#35) --- .github/workflows/development.yml | 2 +- Cargo.lock | 1 - link-for-later/Cargo.toml | 1 - link-for-later/src/repository/inmemory.rs | 199 +++++++++++++++++++--- 4 files changed, 179 insertions(+), 24 deletions(-) diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml index 0850241..0f9af12 100644 --- a/.github/workflows/development.yml +++ b/.github/workflows/development.yml @@ -116,7 +116,7 @@ jobs: - name: Generate and fix code coverage run: | - cargo llvm-cov --ignore-filename-regex "main|inmemory" --lcov --output-path lcov.info + cargo llvm-cov --ignore-filename-regex "main" --lcov --output-path lcov.info ./rust-covfix lcov.info -o lcov.info env: RUST_TEST_THREADS: 1 diff --git a/Cargo.lock b/Cargo.lock index fff9350..f8b80e6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1324,7 +1324,6 @@ dependencies = [ "jsonwebtoken", "mockall", "mongodb", - "once_cell", "rand", "rstest", "serde", diff --git a/link-for-later/Cargo.toml b/link-for-later/Cargo.toml index d57c91c..b55b6ee 100644 --- a/link-for-later/Cargo.toml +++ b/link-for-later/Cargo.toml @@ -15,7 +15,6 @@ futures = "0.3.29" http-body-util = "0.1.0" jsonwebtoken = "9.2.0" mongodb = "2.8.0" -once_cell = "1.19.0" serde = { version = "1.0.193", features = ["derive"] } serde_json = "1.0.108" tokio = { version = "1", features = ["macros"] } diff --git a/link-for-later/src/repository/inmemory.rs b/link-for-later/src/repository/inmemory.rs index cb66fab..b47ec0e 100644 --- a/link-for-later/src/repository/inmemory.rs +++ b/link-for-later/src/repository/inmemory.rs @@ -1,7 +1,6 @@ use std::sync::Mutex; use axum::async_trait; -use once_cell::sync::Lazy; use crate::types::{ dto::{LinkQuery, LinkQueryBuilder, UserQuery}, @@ -11,22 +10,39 @@ use crate::types::{ use super::{Links as LinksRepository, Users as UsersRepository}; -#[derive(Default)] -pub struct LinksRepositoryProvider {} +pub struct LinksRepositoryProvider { + links_data: Mutex>, + links_data_counter: Mutex>, +} -static INMEMORY_LINKS_DATA: Lazy>> = Lazy::new(|| Mutex::new(Vec::new())); -static INMEMORY_LINKS_DATA_COUNTER: Lazy>> = Lazy::new(|| Mutex::new(Vec::new())); +pub struct UsersRepositoryProvider { + users_data: Mutex>, + users_data_counter: Mutex>, +} -#[derive(Default)] -pub struct UsersRepositoryProvider {} +impl Default for LinksRepositoryProvider { + fn default() -> Self { + Self { + links_data: Mutex::new(Vec::new()), + links_data_counter: Mutex::new(Vec::new()), + } + } +} -static INMEMORY_USERS_DATA: Lazy>> = Lazy::new(|| Mutex::new(Vec::new())); -static INMEMORY_USERS_DATA_COUNTER: Lazy>> = Lazy::new(|| Mutex::new(Vec::new())); +impl Default for UsersRepositoryProvider { + fn default() -> Self { + Self { + users_data: Mutex::new(Vec::new()), + users_data_counter: Mutex::new(Vec::new()), + } + } +} #[async_trait] impl LinksRepository for LinksRepositoryProvider { async fn find(&self, query: &LinkQuery) -> Result> { - let filtered_links: Vec = INMEMORY_LINKS_DATA + let filtered_links: Vec = self + .links_data .lock() .unwrap() .iter() @@ -40,7 +56,7 @@ impl LinksRepository for LinksRepositoryProvider { } async fn get(&self, query: &LinkQuery) -> Result { - INMEMORY_LINKS_DATA + self.links_data .lock() .unwrap() .iter() @@ -50,17 +66,17 @@ impl LinksRepository for LinksRepositoryProvider { } async fn create(&self, item: &LinkItem) -> Result { - let id = INMEMORY_LINKS_DATA_COUNTER.lock().unwrap().len() + 1; + let id = self.links_data_counter.lock().unwrap().len() + 1; let link = LinkItemBuilder::from(item.clone()) .id(&id.to_string()) .build(); - INMEMORY_LINKS_DATA.lock().unwrap().push(link.clone()); - INMEMORY_LINKS_DATA_COUNTER.lock().unwrap().push(id); + self.links_data.lock().unwrap().push(link.clone()); + self.links_data_counter.lock().unwrap().push(id); Ok(link) } async fn update(&self, id: &str, item: &LinkItem) -> Result { - INMEMORY_LINKS_DATA + self.links_data .lock() .unwrap() .iter() @@ -68,14 +84,14 @@ impl LinksRepository for LinksRepositoryProvider { .cloned() .ok_or_else(|| AppError::LinkNotFound(id.to_owned()))?; self.delete(item).await?; - INMEMORY_LINKS_DATA.lock().unwrap().push(item.clone()); + self.links_data.lock().unwrap().push(item.clone()); Ok(item.clone()) } async fn delete(&self, item: &LinkItem) -> Result<()> { let query = LinkQueryBuilder::new(item.id(), item.owner()).build(); self.get(&query).await?; - INMEMORY_LINKS_DATA + self.links_data .lock() .unwrap() .retain(|link| link.id() != query.id()); @@ -86,7 +102,7 @@ impl LinksRepository for LinksRepositoryProvider { #[async_trait] impl UsersRepository for UsersRepositoryProvider { async fn get(&self, query: &UserQuery) -> Result { - INMEMORY_USERS_DATA + self.users_data .lock() .unwrap() .iter() @@ -96,12 +112,153 @@ impl UsersRepository for UsersRepositoryProvider { } async fn create(&self, info: &UserInfo) -> Result { - let id = INMEMORY_USERS_DATA_COUNTER.lock().unwrap().len() + 1; + let id = self.users_data_counter.lock().unwrap().len() + 1; let user = UserInfoBuilder::from(info.clone()) .id(&id.to_string()) .build(); - INMEMORY_USERS_DATA.lock().unwrap().push(user.clone()); - INMEMORY_USERS_DATA_COUNTER.lock().unwrap().push(id); + self.users_data.lock().unwrap().push(user.clone()); + self.users_data_counter.lock().unwrap().push(id); Ok(user) } } + +#[cfg(test)] +mod tests { + + use crate::types::dto::UserQueryBuilder; + + use super::*; + + #[tokio::test] + async fn test_search_links_empty() { + let repo_query = LinkQueryBuilder::default().owner("user-id").build(); + let links_repository = LinksRepositoryProvider::default(); + + let retrieved_items = links_repository.find(&repo_query).await.unwrap(); + assert!(retrieved_items.is_empty()); + } + + #[tokio::test] + async fn test_search_created_links() { + let item = LinkItemBuilder::new("http://link").owner("user-id").build(); + + let links_repository = LinksRepositoryProvider::default(); + let created_item = links_repository.create(&item).await.unwrap(); + let expected_items = vec![created_item.clone()]; + + let repo_query = LinkQueryBuilder::default().owner("user-id").build(); + let retrieved_items = links_repository.find(&repo_query).await.unwrap(); + assert!(!retrieved_items.is_empty()); + assert!(retrieved_items + .iter() + .all(|item| expected_items.contains(item))); + } + + #[tokio::test] + async fn test_get_link_not_found() { + let repo_query = LinkQueryBuilder::new("1", "user-id").build(); + + let links_repository = LinksRepositoryProvider::default(); + let response = links_repository.get(&repo_query).await; + + assert_eq!(response, Err(AppError::LinkNotFound("1".into()))); + } + + #[tokio::test] + async fn test_get_created_link() { + let item = LinkItemBuilder::new("http://link").owner("user-id").build(); + + let links_repository = LinksRepositoryProvider::default(); + let created_item = links_repository.create(&item).await.unwrap(); + + let repo_query = LinkQueryBuilder::new(created_item.id(), "user-id").build(); + let retrieved_item = links_repository.get(&repo_query).await.unwrap(); + + assert_eq!(created_item, retrieved_item); + } + + #[tokio::test] + async fn test_update_link_not_found() { + let item = LinkItemBuilder::new("http://link").owner("user-id").build(); + + let links_repository = LinksRepositoryProvider::default(); + let response = links_repository.update("1", &item).await; + + assert_eq!(response, Err(AppError::LinkNotFound("1".into()))); + } + + #[tokio::test] + async fn test_update_created_link() { + let item = LinkItemBuilder::new("http://link").owner("user-id").build(); + + let links_repository = LinksRepositoryProvider::default(); + let created_item = links_repository.create(&item).await.unwrap(); + + let item = LinkItemBuilder::from(created_item.clone()) + .title("title") + .build(); + let updated_item = links_repository + .update(created_item.id(), &item) + .await + .unwrap(); + + let repo_query = LinkQueryBuilder::new(updated_item.id(), "user-id").build(); + let retrieved_item = links_repository.get(&repo_query).await.unwrap(); + + assert_eq!(updated_item, retrieved_item); + } + + #[tokio::test] + async fn test_delete_link_not_found() { + let item = LinkItemBuilder::new("http://link") + .id("1") + .owner("user-id") + .build(); + + let links_repository = LinksRepositoryProvider::default(); + let response = links_repository.delete(&item).await; + + assert_eq!(response, Err(AppError::LinkNotFound("1".into()))); + } + + #[tokio::test] + async fn test_delete_created_link() { + let item = LinkItemBuilder::new("http://link").owner("user-id").build(); + + let links_repository = LinksRepositoryProvider::default(); + let created_item = links_repository.create(&item).await.unwrap(); + + links_repository.delete(&created_item).await.unwrap(); + + let repo_query = LinkQueryBuilder::new(created_item.id(), "user-id").build(); + let response = links_repository.get(&repo_query).await; + + assert_eq!(response, Err(AppError::LinkNotFound("1".into()))); + } + + #[tokio::test] + async fn test_get_user_not_found() { + let repo_query = UserQueryBuilder::new("user@test.com").build(); + + let users_repository = UsersRepositoryProvider::default(); + let response = users_repository.get(&repo_query).await; + + assert_eq!( + response, + Err(AppError::UserNotFound("user@test.com".into())) + ); + } + + #[tokio::test] + async fn test_get_created_user() { + let user = UserInfoBuilder::new("user@test.com", "test").build(); + + let users_repository = UsersRepositoryProvider::default(); + let created_user = users_repository.create(&user).await.unwrap(); + + let repo_query = UserQueryBuilder::new("user@test.com").build(); + let retrieved_user = users_repository.get(&repo_query).await.unwrap(); + + assert_eq!(created_user, retrieved_user); + } +}