From a5accbcf10dade02fa6ae01c8e372aa32f9db64f Mon Sep 17 00:00:00 2001 From: Paulo Bressan Date: Thu, 5 Sep 2024 17:15:33 -0300 Subject: [PATCH] Implement fetch team invites (#112) * feat: implemented fetch invites domain * feat: implemented fetch invites rpc --- Cargo.lock | 2 +- src/domain/auth/mod.rs | 1 + src/domain/event/mod.rs | 1 + src/domain/metadata/command.rs | 14 +- src/domain/metadata/mod.rs | 1 + src/domain/project/cache.rs | 37 ++--- src/domain/project/cluster.rs | 16 +- src/domain/project/command.rs | 266 +++++++++++++++++---------------- src/domain/project/mod.rs | 2 + src/domain/resource/cache.rs | 20 +-- src/domain/resource/cluster.rs | 16 +- src/domain/resource/command.rs | 139 ++++++----------- src/domain/usage/cache.rs | 15 +- src/domain/usage/cluster.rs | 25 +--- src/domain/usage/command.rs | 48 +----- src/driven/cache/project.rs | 64 ++++++++ src/drivers/grpc/project.rs | 46 +++++- 17 files changed, 331 insertions(+), 382 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dd54f89..6b18138 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -821,7 +821,7 @@ dependencies = [ [[package]] name = "dmtri" version = "0.1.0" -source = "git+https://github.com/demeter-run/specs.git#a756df246247725b7f2cfd1601da77f78b7f6a5f" +source = "git+https://github.com/demeter-run/specs.git#be82e1523873246466da424b8ec3f46af3a684b7" dependencies = [ "bytes", "pbjson", diff --git a/src/domain/auth/mod.rs b/src/domain/auth/mod.rs index bd1bf26..bc76870 100644 --- a/src/domain/auth/mod.rs +++ b/src/domain/auth/mod.rs @@ -4,6 +4,7 @@ use super::{error::Error, project::cache::ProjectDrivenCache}; use crate::domain::Result; +#[cfg_attr(test, mockall::automock)] #[async_trait::async_trait] pub trait Auth0Driven: Send + Sync { fn verify(&self, token: &str) -> Result; diff --git a/src/domain/event/mod.rs b/src/domain/event/mod.rs index 9e6f0d2..f10aa6b 100644 --- a/src/domain/event/mod.rs +++ b/src/domain/event/mod.rs @@ -188,6 +188,7 @@ impl Event { } } +#[cfg_attr(test, mockall::automock)] #[async_trait::async_trait] pub trait EventDrivenBridge: Send + Sync { async fn dispatch(&self, event: Event) -> Result<()>; diff --git a/src/domain/metadata/command.rs b/src/domain/metadata/command.rs index e644489..58b0ec9 100644 --- a/src/domain/metadata/command.rs +++ b/src/domain/metadata/command.rs @@ -11,23 +11,13 @@ pub async fn fetch(metadata: Arc) -> Result Result>; - async fn find_by_kind(&self, kind: &str) -> Result>; - } - } - #[tokio::test] async fn it_should_fetch_metadata() { - let mut metadata = MockFakeMetadataDrivenCrds::new(); + let mut metadata = MockMetadataDriven::new(); metadata .expect_find() .return_once(|| Ok(vec![CustomResourceDefinition::default()])); diff --git a/src/domain/metadata/mod.rs b/src/domain/metadata/mod.rs index 12f7783..138f746 100644 --- a/src/domain/metadata/mod.rs +++ b/src/domain/metadata/mod.rs @@ -6,6 +6,7 @@ use super::{error::Error, Result}; pub mod command; +#[cfg_attr(test, mockall::automock)] #[async_trait::async_trait] pub trait MetadataDriven: Send + Sync { async fn find(&self) -> Result>; diff --git a/src/domain/project/cache.rs b/src/domain/project/cache.rs index 695dcf5..5e92ab9 100644 --- a/src/domain/project/cache.rs +++ b/src/domain/project/cache.rs @@ -9,6 +9,7 @@ use crate::domain::Result; use super::{Project, ProjectSecret, ProjectUpdate, ProjectUser, ProjectUserInvite}; +#[cfg_attr(test, mockall::automock)] #[async_trait::async_trait] pub trait ProjectDrivenCache: Send + Sync { async fn find(&self, user_id: &str, page: &u32, page_size: &u32) -> Result>; @@ -24,6 +25,12 @@ pub trait ProjectDrivenCache: Send + Sync { user_id: &str, project_id: &str, ) -> Result>; + async fn find_user_invite( + &self, + project_id: &str, + page: &u32, + page_size: &u32, + ) -> Result>; async fn find_user_invite_by_code(&self, code: &str) -> Result>; async fn create_user_invite(&self, invite: &ProjectUserInvite) -> Result<()>; async fn create_user_acceptance(&self, invite_id: &str, user: &ProjectUser) -> Result<()>; @@ -66,33 +73,11 @@ pub async fn create_user_invite_acceptance( #[cfg(test)] mod tests { - use mockall::mock; - use super::*; - mock! { - pub FakeProjectDrivenCache { } - - #[async_trait::async_trait] - impl ProjectDrivenCache for FakeProjectDrivenCache { - async fn find(&self, user_id: &str, page: &u32, page_size: &u32) -> Result>; - async fn find_by_namespace(&self, namespace: &str) -> Result>; - async fn find_by_id(&self, id: &str) -> Result>; - async fn create(&self, project: &Project) -> Result<()>; - async fn update(&self, project: &ProjectUpdate) -> Result<()>; - async fn delete(&self, id: &str, deleted_at: &DateTime) -> Result<()>; - async fn create_secret(&self, secret: &ProjectSecret) -> Result<()>; - async fn find_secret_by_project_id(&self, project_id: &str) -> Result>; - async fn find_user_permission(&self,user_id: &str, project_id: &str) -> Result>; - async fn find_user_invite_by_code(&self, code: &str) -> Result>; - async fn create_user_invite(&self, invite: &ProjectUserInvite) -> Result<()>; - async fn create_user_acceptance(&self, invite_id: &str, user: &ProjectUser) -> Result<()>; - } - } - #[tokio::test] async fn it_should_create_project_cache() { - let mut cache = MockFakeProjectDrivenCache::new(); + let mut cache = MockProjectDrivenCache::new(); cache.expect_create().return_once(|_| Ok(())); let evt = ProjectCreated::default(); @@ -103,7 +88,7 @@ mod tests { #[tokio::test] async fn it_should_create_project_secret_cache() { - let mut cache = MockFakeProjectDrivenCache::new(); + let mut cache = MockProjectDrivenCache::new(); cache.expect_create_secret().return_once(|_| Ok(())); let evt = ProjectSecretCreated::default(); @@ -114,7 +99,7 @@ mod tests { #[tokio::test] async fn it_should_create_user_invite_cache() { - let mut cache = MockFakeProjectDrivenCache::new(); + let mut cache = MockProjectDrivenCache::new(); cache.expect_create_user_invite().return_once(|_| Ok(())); let evt = ProjectUserInviteCreated::default(); @@ -125,7 +110,7 @@ mod tests { #[tokio::test] async fn it_should_create_user_invite_acceptance_cache() { - let mut cache = MockFakeProjectDrivenCache::new(); + let mut cache = MockProjectDrivenCache::new(); cache .expect_create_user_acceptance() .return_once(|_, _| Ok(())); diff --git a/src/domain/project/cluster.rs b/src/domain/project/cluster.rs index 1d03a76..9db951d 100644 --- a/src/domain/project/cluster.rs +++ b/src/domain/project/cluster.rs @@ -9,6 +9,7 @@ use crate::domain::{ Result, }; +#[cfg_attr(test, mockall::automock)] #[async_trait::async_trait] pub trait ProjectDrivenCluster: Send + Sync { async fn create(&self, namespace: &Namespace) -> Result<()>; @@ -55,24 +56,11 @@ pub async fn delete_manifest( #[cfg(test)] mod tests { - use k8s_openapi::api::core::v1::Namespace; - use mockall::mock; - use super::*; - mock! { - pub FakeProjectDrivenCluster { } - - #[async_trait::async_trait] - impl ProjectDrivenCluster for FakeProjectDrivenCluster { - async fn create(&self, namespace: &Namespace) -> Result<()>; - async fn delete(&self, namespace: &Namespace) -> Result<()>; - } - } - #[tokio::test] async fn it_should_apply_manifest() { - let mut cluster = MockFakeProjectDrivenCluster::new(); + let mut cluster = MockProjectDrivenCluster::new(); cluster.expect_create().return_once(|_| Ok(())); let project = ProjectCreated::default(); diff --git a/src/domain/project/command.rs b/src/domain/project/command.rs index 6d1e550..2fa4be5 100644 --- a/src/domain/project/command.rs +++ b/src/domain/project/command.rs @@ -22,8 +22,8 @@ use crate::domain::{ }; use super::{ - cache::ProjectDrivenCache, Project, ProjectEmailDriven, ProjectSecret, ProjectUserRole, - StripeDriven, + cache::ProjectDrivenCache, Project, ProjectEmailDriven, ProjectSecret, ProjectUserInvite, + ProjectUserRole, StripeDriven, }; pub async fn fetch(cache: Arc, cmd: FetchCmd) -> Result> { @@ -219,6 +219,18 @@ pub async fn verify_secret( Ok(secret) } +pub async fn fetch_user_invite( + cache: Arc, + cmd: FetchUserInviteCmd, +) -> Result> { + assert_credential(&cmd.credential)?; + assert_permission(cache.clone(), &cmd.credential, &cmd.project_id).await?; + + cache + .find_user_invite(&cmd.project_id, &cmd.page, &cmd.page_size) + .await +} + pub async fn create_user_invite( cache: Arc, email: Arc, @@ -433,6 +445,38 @@ pub struct VerifySecretCmd { pub key: String, } +#[derive(Debug, Clone)] +pub struct FetchUserInviteCmd { + pub credential: Credential, + pub page: u32, + pub page_size: u32, + pub project_id: String, +} +impl FetchUserInviteCmd { + pub fn new( + credential: Credential, + page: Option, + page_size: Option, + project_id: String, + ) -> Result { + let page = page.unwrap_or(1); + let page_size = page_size.unwrap_or(PAGE_SIZE_DEFAULT); + + if page_size >= PAGE_SIZE_MAX { + return Err(Error::CommandMalformed(format!( + "page_size exceeded the limit of {PAGE_SIZE_MAX}" + ))); + } + + Ok(Self { + credential, + page, + page_size, + project_id, + }) + } +} + #[derive(Debug, Clone)] pub struct CreateUserInviteCmd { pub credential: Credential, @@ -483,74 +527,19 @@ impl AcceptUserInviteCmd { #[cfg(test)] mod tests { - use chrono::DateTime; - use mockall::mock; use uuid::Uuid; use super::*; use crate::domain::{ - event::Event, - project::{ProjectUpdate, ProjectUser, ProjectUserInvite}, + auth::MockAuth0Driven, + event::MockEventDrivenBridge, + project::{ + cache::MockProjectDrivenCache, MockProjectEmailDriven, MockStripeDriven, ProjectUser, + ProjectUserInvite, + }, tests::{INVALID_HRP_KEY, INVALID_KEY, KEY, SECRET}, }; - mock! { - pub FakeProjectDrivenCache { } - - #[async_trait::async_trait] - impl ProjectDrivenCache for FakeProjectDrivenCache { - async fn find(&self, user_id: &str, page: &u32, page_size: &u32) -> Result>; - async fn find_by_namespace(&self, namespace: &str) -> Result>; - async fn find_by_id(&self, id: &str) -> Result>; - async fn create(&self, project: &Project) -> Result<()>; - async fn update(&self, project: &ProjectUpdate) -> Result<()>; - async fn delete(&self, id: &str, deleted_at: &DateTime) -> Result<()>; - async fn create_secret(&self, secret: &ProjectSecret) -> Result<()>; - async fn find_secret_by_project_id(&self, project_id: &str) -> Result>; - async fn find_user_permission(&self,user_id: &str, project_id: &str) -> Result>; - async fn find_user_invite_by_code(&self, code: &str) -> Result>; - async fn create_user_invite(&self, invite: &ProjectUserInvite) -> Result<()>; - async fn create_user_acceptance(&self, invite_id: &str, user: &ProjectUser) -> Result<()>; - } - } - - mock! { - pub FakeEventDrivenBridge { } - - #[async_trait::async_trait] - impl EventDrivenBridge for FakeEventDrivenBridge { - async fn dispatch(&self, event: Event) -> Result<()>; - } - } - - mock! { - pub FakeAuth0Driven { } - - #[async_trait::async_trait] - impl Auth0Driven for FakeAuth0Driven { - fn verify(&self, token: &str) -> Result; - async fn find_info(&self, token: &str) -> Result<(String, String)>; - } - } - - mock! { - pub FakeStripeDriven { } - - #[async_trait::async_trait] - impl StripeDriven for FakeStripeDriven { - async fn create_customer(&self, name: &str, email: &str) -> Result; - } - } - - mock! { - pub FakeProjectEmailDriven { } - - #[async_trait::async_trait] - impl ProjectEmailDriven for FakeProjectEmailDriven { - async fn send_invite(&self, project_name: &str, email: &str, code: &str, expires_in: &DateTime) -> Result<()>; - } - } - impl Default for FetchCmd { fn default() -> Self { Self { @@ -598,6 +587,16 @@ mod tests { } } } + impl Default for FetchUserInviteCmd { + fn default() -> Self { + Self { + credential: Credential::Auth0("user id".into()), + page: 1, + page_size: 12, + project_id: Uuid::new_v4().to_string(), + } + } + } impl Default for CreateUserInviteCmd { fn default() -> Self { Self { @@ -622,7 +621,7 @@ mod tests { #[tokio::test] async fn it_should_fetch_user_projects() { - let mut cache = MockFakeProjectDrivenCache::new(); + let mut cache = MockProjectDrivenCache::new(); cache .expect_find() .return_once(|_, _, _| Ok(vec![Project::default()])); @@ -635,20 +634,20 @@ mod tests { #[tokio::test] async fn it_should_create_project() { - let mut cache = MockFakeProjectDrivenCache::new(); + let mut cache = MockProjectDrivenCache::new(); cache.expect_find_by_namespace().return_once(|_| Ok(None)); - let mut auth0 = MockFakeAuth0Driven::new(); + let mut auth0 = MockAuth0Driven::new(); auth0 .expect_find_info() .return_once(|_| Ok(("user name".into(), "user email".into()))); - let mut stripe = MockFakeStripeDriven::new(); + let mut stripe = MockStripeDriven::new(); stripe .expect_create_customer() .return_once(|_, _| Ok("stripe id".into())); - let mut event = MockFakeEventDrivenBridge::new(); + let mut event = MockEventDrivenBridge::new(); event.expect_dispatch().return_once(|_| Ok(())); let cmd = CreateCmd::default(); @@ -667,14 +666,14 @@ mod tests { #[tokio::test] async fn it_should_fail_create_project_when_namespace_exists() { - let mut cache = MockFakeProjectDrivenCache::new(); + let mut cache = MockProjectDrivenCache::new(); cache .expect_find_by_namespace() .return_once(|_| Ok(Some(Project::default()))); - let auth0 = MockFakeAuth0Driven::new(); - let stripe = MockFakeStripeDriven::new(); - let event = MockFakeEventDrivenBridge::new(); + let auth0 = MockAuth0Driven::new(); + let stripe = MockStripeDriven::new(); + let event = MockEventDrivenBridge::new(); let cmd = CreateCmd::default(); @@ -691,10 +690,10 @@ mod tests { } #[tokio::test] async fn it_should_fail_create_project_when_invalid_permission() { - let cache = MockFakeProjectDrivenCache::new(); - let auth0 = MockFakeAuth0Driven::new(); - let stripe = MockFakeStripeDriven::new(); - let event = MockFakeEventDrivenBridge::new(); + let cache = MockProjectDrivenCache::new(); + let auth0 = MockAuth0Driven::new(); + let stripe = MockStripeDriven::new(); + let event = MockEventDrivenBridge::new(); let cmd = CreateCmd { credential: Credential::ApiKey("xxxx".into()), @@ -715,7 +714,7 @@ mod tests { #[tokio::test] async fn it_should_update_project() { - let mut cache = MockFakeProjectDrivenCache::new(); + let mut cache = MockProjectDrivenCache::new(); cache .expect_find_user_permission() .return_once(|_, _| Ok(Some(ProjectUser::default()))); @@ -726,7 +725,7 @@ mod tests { .expect_find_secret_by_project_id() .return_once(|_| Ok(Vec::new())); - let mut event = MockFakeEventDrivenBridge::new(); + let mut event = MockEventDrivenBridge::new(); event.expect_dispatch().return_once(|_| Ok(())); let cmd = UpdateCmd::default(); @@ -737,7 +736,7 @@ mod tests { #[tokio::test] async fn it_should_create_project_secret() { - let mut cache = MockFakeProjectDrivenCache::new(); + let mut cache = MockProjectDrivenCache::new(); cache .expect_find_user_permission() .return_once(|_, _| Ok(Some(ProjectUser::default()))); @@ -748,7 +747,7 @@ mod tests { .expect_find_secret_by_project_id() .return_once(|_| Ok(Vec::new())); - let mut event = MockFakeEventDrivenBridge::new(); + let mut event = MockEventDrivenBridge::new(); event.expect_dispatch().return_once(|_| Ok(())); let cmd = CreateSecretCmd::default(); @@ -758,13 +757,13 @@ mod tests { } #[tokio::test] async fn it_should_fail_create_project_secret_when_project_doesnt_exists() { - let mut cache = MockFakeProjectDrivenCache::new(); + let mut cache = MockProjectDrivenCache::new(); cache .expect_find_user_permission() .return_once(|_, _| Ok(Some(ProjectUser::default()))); cache.expect_find_by_id().return_once(|_| Ok(None)); - let event = MockFakeEventDrivenBridge::new(); + let event = MockEventDrivenBridge::new(); let cmd = CreateSecretCmd::default(); @@ -773,8 +772,8 @@ mod tests { } #[tokio::test] async fn it_should_fail_create_project_secret_when_invalid_credential() { - let cache = MockFakeProjectDrivenCache::new(); - let event = MockFakeEventDrivenBridge::new(); + let cache = MockProjectDrivenCache::new(); + let event = MockEventDrivenBridge::new(); let cmd = CreateSecretCmd { credential: Credential::ApiKey("xxxx".into()), @@ -786,12 +785,12 @@ mod tests { } #[tokio::test] async fn it_should_fail_create_project_secret_when_invalid_permission() { - let mut cache = MockFakeProjectDrivenCache::new(); + let mut cache = MockProjectDrivenCache::new(); cache .expect_find_user_permission() .return_once(|_, _| Ok(None)); - let event = MockFakeEventDrivenBridge::new(); + let event = MockEventDrivenBridge::new(); let cmd = CreateSecretCmd::default(); @@ -800,7 +799,7 @@ mod tests { } #[tokio::test] async fn it_should_fail_create_project_secret_when_max_secret_exceeded() { - let mut cache = MockFakeProjectDrivenCache::new(); + let mut cache = MockProjectDrivenCache::new(); cache .expect_find_user_permission() .return_once(|_, _| Ok(Some(ProjectUser::default()))); @@ -811,7 +810,7 @@ mod tests { .expect_find_secret_by_project_id() .return_once(|_| Ok(vec![ProjectSecret::default(); 3])); - let event = MockFakeEventDrivenBridge::new(); + let event = MockEventDrivenBridge::new(); let cmd = CreateSecretCmd::default(); @@ -821,7 +820,7 @@ mod tests { #[tokio::test] async fn it_should_verify_secret() { - let mut cache = MockFakeProjectDrivenCache::new(); + let mut cache = MockProjectDrivenCache::new(); cache .expect_find_secret_by_project_id() .return_once(|_| Ok(vec![ProjectSecret::default()])); @@ -833,7 +832,7 @@ mod tests { } #[tokio::test] async fn it_should_fail_verify_secret_when_invalid_key() { - let mut cache = MockFakeProjectDrivenCache::new(); + let mut cache = MockProjectDrivenCache::new(); cache .expect_find_secret_by_project_id() .return_once(|_| Ok(vec![ProjectSecret::default()])); @@ -848,7 +847,7 @@ mod tests { } #[tokio::test] async fn it_should_fail_verify_secret_when_invalid_bech32() { - let cache = MockFakeProjectDrivenCache::new(); + let cache = MockProjectDrivenCache::new(); let cmd = VerifySecretCmd { key: "invalid bech32".into(), @@ -860,7 +859,7 @@ mod tests { } #[tokio::test] async fn it_should_fail_verify_secret_when_invalid_bech32_hrp() { - let cache = MockFakeProjectDrivenCache::new(); + let cache = MockProjectDrivenCache::new(); let cmd = VerifySecretCmd { key: INVALID_HRP_KEY.into(), @@ -872,7 +871,7 @@ mod tests { } #[tokio::test] async fn it_should_fail_verify_secret_when_there_arent_secrets_storaged() { - let mut cache = MockFakeProjectDrivenCache::new(); + let mut cache = MockProjectDrivenCache::new(); cache .expect_find_secret_by_project_id() .return_once(|_| Ok(vec![])); @@ -883,9 +882,24 @@ mod tests { assert!(result.is_err()); } + #[tokio::test] + async fn it_should_fetch_project_user_invites() { + let mut cache = MockProjectDrivenCache::new(); + cache + .expect_find_user_permission() + .return_once(|_, _| Ok(Some(ProjectUser::default()))); + cache + .expect_find_user_invite() + .return_once(|_, _, _| Ok(vec![ProjectUserInvite::default()])); + + let cmd = FetchUserInviteCmd::default(); + + let result = fetch_user_invite(Arc::new(cache), cmd).await; + assert!(result.is_ok()); + } #[tokio::test] async fn it_should_create_project_user_invite() { - let mut cache = MockFakeProjectDrivenCache::new(); + let mut cache = MockProjectDrivenCache::new(); cache .expect_find_user_permission() .return_once(|_, _| Ok(Some(ProjectUser::default()))); @@ -893,10 +907,10 @@ mod tests { .expect_find_by_id() .return_once(|_| Ok(Some(Project::default()))); - let mut email = MockFakeProjectEmailDriven::new(); + let mut email = MockProjectEmailDriven::new(); email.expect_send_invite().return_once(|_, _, _, _| Ok(())); - let mut event = MockFakeEventDrivenBridge::new(); + let mut event = MockEventDrivenBridge::new(); event.expect_dispatch().return_once(|_| Ok(())); let cmd = CreateUserInviteCmd::default(); @@ -908,14 +922,14 @@ mod tests { } #[tokio::test] async fn it_should_fail_create_project_user_invite_when_project_doesnt_exists() { - let mut cache = MockFakeProjectDrivenCache::new(); + let mut cache = MockProjectDrivenCache::new(); cache .expect_find_user_permission() .return_once(|_, _| Ok(Some(ProjectUser::default()))); cache.expect_find_by_id().return_once(|_| Ok(None)); - let email = MockFakeProjectEmailDriven::new(); - let event = MockFakeEventDrivenBridge::new(); + let email = MockProjectEmailDriven::new(); + let event = MockEventDrivenBridge::new(); let cmd = CreateUserInviteCmd::default(); @@ -925,9 +939,9 @@ mod tests { } #[tokio::test] async fn it_should_fail_create_project_user_invite_when_invalid_credential() { - let cache = MockFakeProjectDrivenCache::new(); - let email = MockFakeProjectEmailDriven::new(); - let event = MockFakeEventDrivenBridge::new(); + let cache = MockProjectDrivenCache::new(); + let email = MockProjectEmailDriven::new(); + let event = MockEventDrivenBridge::new(); let cmd = CreateUserInviteCmd { credential: Credential::ApiKey("xxxx".into()), @@ -940,13 +954,13 @@ mod tests { } #[tokio::test] async fn it_should_fail_create_project_user_invite_when_invalid_permission() { - let mut cache = MockFakeProjectDrivenCache::new(); + let mut cache = MockProjectDrivenCache::new(); cache .expect_find_user_permission() .return_once(|_, _| Ok(None)); - let email = MockFakeProjectEmailDriven::new(); - let event = MockFakeEventDrivenBridge::new(); + let email = MockProjectEmailDriven::new(); + let event = MockEventDrivenBridge::new(); let cmd = CreateUserInviteCmd::default(); @@ -960,7 +974,7 @@ mod tests { let invite = ProjectUserInvite::default(); let invite_email = invite.email.clone(); - let mut cache = MockFakeProjectDrivenCache::new(); + let mut cache = MockProjectDrivenCache::new(); cache .expect_find_user_invite_by_code() .return_once(|_| Ok(Some(invite))); @@ -968,12 +982,12 @@ mod tests { .expect_find_user_permission() .return_once(|_, _| Ok(None)); - let mut auth0 = MockFakeAuth0Driven::new(); + let mut auth0 = MockAuth0Driven::new(); auth0 .expect_find_info() .return_once(|_| Ok(("user name".into(), invite_email))); - let mut event = MockFakeEventDrivenBridge::new(); + let mut event = MockEventDrivenBridge::new(); event.expect_dispatch().return_once(|_| Ok(())); let cmd = AcceptUserInviteCmd::default(); @@ -985,13 +999,13 @@ mod tests { } #[tokio::test] async fn it_should_fail_accept_project_user_invite_when_invalid_code() { - let mut cache = MockFakeProjectDrivenCache::new(); + let mut cache = MockProjectDrivenCache::new(); cache .expect_find_user_invite_by_code() .return_once(|_| Ok(None)); - let auth0 = MockFakeAuth0Driven::new(); - let event = MockFakeEventDrivenBridge::new(); + let auth0 = MockAuth0Driven::new(); + let event = MockEventDrivenBridge::new(); let cmd = AcceptUserInviteCmd::default(); @@ -1001,9 +1015,9 @@ mod tests { } #[tokio::test] async fn it_should_fail_accept_project_user_invite_when_invalid_credential() { - let cache = MockFakeProjectDrivenCache::new(); - let auth0 = MockFakeAuth0Driven::new(); - let event = MockFakeEventDrivenBridge::new(); + let cache = MockProjectDrivenCache::new(); + let auth0 = MockAuth0Driven::new(); + let event = MockEventDrivenBridge::new(); let cmd = AcceptUserInviteCmd { credential: Credential::ApiKey("xxxx".into()), @@ -1016,7 +1030,7 @@ mod tests { } #[tokio::test] async fn it_should_fail_accept_project_user_invite_when_email_doesnt_match() { - let mut cache = MockFakeProjectDrivenCache::new(); + let mut cache = MockProjectDrivenCache::new(); cache .expect_find_user_invite_by_code() .return_once(|_| Ok(Some(ProjectUserInvite::default()))); @@ -1024,12 +1038,12 @@ mod tests { .expect_find_user_permission() .return_once(|_, _| Ok(None)); - let mut auth0 = MockFakeAuth0Driven::new(); + let mut auth0 = MockAuth0Driven::new(); auth0 .expect_find_info() .return_once(|_| Ok(("user name".into(), "user email".into()))); - let event = MockFakeEventDrivenBridge::new(); + let event = MockEventDrivenBridge::new(); let cmd = AcceptUserInviteCmd::default(); @@ -1040,7 +1054,7 @@ mod tests { } #[tokio::test] async fn it_should_fail_accept_project_user_invite_when_invite_has_already_been_accepted() { - let mut cache = MockFakeProjectDrivenCache::new(); + let mut cache = MockProjectDrivenCache::new(); cache.expect_find_user_invite_by_code().return_once(|_| { Ok(Some(ProjectUserInvite { status: ProjectUserInviteStatus::Accepted, @@ -1051,8 +1065,8 @@ mod tests { .expect_find_user_permission() .return_once(|_, _| Ok(None)); - let auth0 = MockFakeAuth0Driven::new(); - let event = MockFakeEventDrivenBridge::new(); + let auth0 = MockAuth0Driven::new(); + let event = MockEventDrivenBridge::new(); let cmd = AcceptUserInviteCmd::default(); @@ -1063,7 +1077,7 @@ mod tests { } #[tokio::test] async fn it_should_fail_accept_project_user_invite_when_user_already_is_in_the_project() { - let mut cache = MockFakeProjectDrivenCache::new(); + let mut cache = MockProjectDrivenCache::new(); cache.expect_find_user_invite_by_code().return_once(|_| { Ok(Some(ProjectUserInvite { status: ProjectUserInviteStatus::Accepted, @@ -1074,8 +1088,8 @@ mod tests { .expect_find_user_permission() .return_once(|_, _| Ok(Some(ProjectUser::default()))); - let auth0 = MockFakeAuth0Driven::new(); - let event = MockFakeEventDrivenBridge::new(); + let auth0 = MockAuth0Driven::new(); + let event = MockEventDrivenBridge::new(); let cmd = AcceptUserInviteCmd::default(); @@ -1086,7 +1100,7 @@ mod tests { } #[tokio::test] async fn it_should_fail_accept_project_user_invite_when_invite_code_expired() { - let mut cache = MockFakeProjectDrivenCache::new(); + let mut cache = MockProjectDrivenCache::new(); cache.expect_find_user_invite_by_code().return_once(|_| { Ok(Some(ProjectUserInvite { expires_in: Utc::now() - Duration::from_secs(10), @@ -1094,8 +1108,8 @@ mod tests { })) }); - let auth0 = MockFakeAuth0Driven::new(); - let event = MockFakeEventDrivenBridge::new(); + let auth0 = MockAuth0Driven::new(); + let event = MockEventDrivenBridge::new(); let cmd = AcceptUserInviteCmd::default(); diff --git a/src/domain/project/mod.rs b/src/domain/project/mod.rs index 48b2754..70c87d0 100644 --- a/src/domain/project/mod.rs +++ b/src/domain/project/mod.rs @@ -15,11 +15,13 @@ pub mod cache; pub mod cluster; pub mod command; +#[cfg_attr(test, mockall::automock)] #[async_trait::async_trait] pub trait StripeDriven: Send + Sync { async fn create_customer(&self, name: &str, email: &str) -> Result; } +#[cfg_attr(test, mockall::automock)] #[async_trait::async_trait] pub trait ProjectEmailDriven: Send + Sync { async fn send_invite( diff --git a/src/domain/resource/cache.rs b/src/domain/resource/cache.rs index 638c7fc..a3d1a08 100644 --- a/src/domain/resource/cache.rs +++ b/src/domain/resource/cache.rs @@ -9,6 +9,7 @@ use super::{Resource, ResourceUpdate}; use chrono::{DateTime, Utc}; +#[cfg_attr(test, mockall::automock)] #[async_trait::async_trait] pub trait ResourceDrivenCache: Send + Sync { async fn find(&self, project_id: &str, page: &u32, page_size: &u32) -> Result>; @@ -32,26 +33,11 @@ pub async fn delete(cache: Arc, evt: ResourceDeleted) - #[cfg(test)] mod tests { - use mockall::mock; - use super::*; - mock! { - pub FakeResourceDrivenCache { } - - #[async_trait::async_trait] - impl ResourceDrivenCache for FakeResourceDrivenCache { - async fn find(&self,project_id: &str,page: &u32,page_size: &u32) -> Result>; - async fn find_by_id(&self, id: &str) -> Result>; - async fn create(&self, resource: &Resource) -> Result<()>; - async fn update(&self, resource: &ResourceUpdate) -> Result<()>; - async fn delete(&self, id: &str, deleted_at: &DateTime) -> Result<()>; - } - } - #[tokio::test] async fn it_should_create_resource_cache() { - let mut cache = MockFakeResourceDrivenCache::new(); + let mut cache = MockResourceDrivenCache::new(); cache.expect_create().return_once(|_| Ok(())); let evt = ResourceCreated::default(); @@ -62,7 +48,7 @@ mod tests { #[tokio::test] async fn it_should_delete_resource_cache() { - let mut cache = MockFakeResourceDrivenCache::new(); + let mut cache = MockResourceDrivenCache::new(); cache.expect_delete().return_once(|_, _| Ok(())); let evt = ResourceDeleted::default(); diff --git a/src/domain/resource/cluster.rs b/src/domain/resource/cluster.rs index aaf189d..14c6ba2 100644 --- a/src/domain/resource/cluster.rs +++ b/src/domain/resource/cluster.rs @@ -11,6 +11,7 @@ use crate::domain::{ Result, }; +#[cfg_attr(test, mockall::automock)] #[async_trait::async_trait] pub trait ResourceDrivenCluster: Send + Sync { async fn create(&self, obj: &DynamicObject) -> Result<()>; @@ -96,24 +97,11 @@ fn build_api_resource(kind: &str) -> ApiResource { #[cfg(test)] mod tests { - use mockall::mock; - use super::*; - mock! { - pub FakeResourceDrivenCluster { } - - #[async_trait::async_trait] - impl ResourceDrivenCluster for FakeResourceDrivenCluster { - async fn create(&self, obj: &DynamicObject) -> Result<()>; - async fn update(&self, obj: &DynamicObject) -> Result<()>; - async fn delete(&self, obj: &DynamicObject) -> Result<()>; - } - } - #[tokio::test] async fn it_should_apply_manifest() { - let mut cluster = MockFakeResourceDrivenCluster::new(); + let mut cluster = MockResourceDrivenCluster::new(); cluster.expect_create().return_once(|_| Ok(())); let evt = ResourceCreated::default(); diff --git a/src/domain/resource/command.rs b/src/domain/resource/command.rs index fa691fa..bd233e4 100644 --- a/src/domain/resource/command.rs +++ b/src/domain/resource/command.rs @@ -291,72 +291,17 @@ pub struct DeleteCmd { #[cfg(test)] mod tests { - use chrono::DateTime; - use k8s_openapi::apiextensions_apiserver::pkg::apis::apiextensions::v1::CustomResourceDefinition; - use mockall::mock; use uuid::Uuid; - use crate::domain::event::Event; + use crate::domain::event::MockEventDrivenBridge; use crate::domain::metadata::tests::mock_crd; - use crate::domain::project::{ - Project, ProjectSecret, ProjectUpdate, ProjectUser, ProjectUserInvite, - }; - use crate::domain::resource::ResourceUpdate; + use crate::domain::metadata::MockMetadataDriven; + use crate::domain::project::cache::MockProjectDrivenCache; + use crate::domain::project::{Project, ProjectUser}; + use crate::domain::resource::cache::MockResourceDrivenCache; use super::*; - mock! { - pub FakeProjectDrivenCache { } - - #[async_trait::async_trait] - impl ProjectDrivenCache for FakeProjectDrivenCache { - async fn find(&self, user_id: &str, page: &u32, page_size: &u32) -> Result>; - async fn find_by_namespace(&self, namespace: &str) -> Result>; - async fn find_by_id(&self, id: &str) -> Result>; - async fn create(&self, project: &Project) -> Result<()>; - async fn update(&self, project: &ProjectUpdate) -> Result<()>; - async fn delete(&self, id: &str, deleted_at: &DateTime) -> Result<()>; - async fn create_secret(&self, secret: &ProjectSecret) -> Result<()>; - async fn find_secret_by_project_id(&self, project_id: &str) -> Result>; - async fn find_user_permission(&self,user_id: &str, project_id: &str) -> Result>; - async fn find_user_invite_by_code(&self, code: &str) -> Result>; - async fn create_user_invite(&self, invite: &ProjectUserInvite) -> Result<()>; - async fn create_user_acceptance(&self, invite_id: &str, user: &ProjectUser) -> Result<()>; - } - } - - mock! { - pub FakeResourceDrivenCache { } - - #[async_trait::async_trait] - impl ResourceDrivenCache for FakeResourceDrivenCache { - async fn find(&self,project_id: &str,page: &u32,page_size: &u32) -> Result>; - async fn find_by_id(&self, id: &str) -> Result>; - async fn create(&self, resource: &Resource) -> Result<()>; - async fn update(&self, resource: &ResourceUpdate) -> Result<()>; - async fn delete(&self, id: &str, deleted_at: &DateTime) -> Result<()>; - } - } - - mock! { - pub FakeEventDrivenBridge { } - - #[async_trait::async_trait] - impl EventDrivenBridge for FakeEventDrivenBridge { - async fn dispatch(&self, event: Event) -> Result<()>; - } - } - - mock! { - pub FakeMetadataDrivenCrds { } - - #[async_trait::async_trait] - impl MetadataDriven for FakeMetadataDrivenCrds { - async fn find(&self) -> Result>; - async fn find_by_kind(&self, kind: &str) -> Result>; - } - } - impl Default for FetchCmd { fn default() -> Self { Self { @@ -399,12 +344,12 @@ mod tests { #[tokio::test] async fn it_should_fetch_project_resources() { - let mut project_cache = MockFakeProjectDrivenCache::new(); + let mut project_cache = MockProjectDrivenCache::new(); project_cache .expect_find_user_permission() .return_once(|_, _| Ok(Some(ProjectUser::default()))); - let mut resource_cache = MockFakeResourceDrivenCache::new(); + let mut resource_cache = MockResourceDrivenCache::new(); resource_cache .expect_find() .return_once(|_, _, _| Ok(vec![Resource::default()])); @@ -416,12 +361,12 @@ mod tests { } #[tokio::test] async fn it_should_fail_fetch_project_resources_when_user_doesnt_have_permission() { - let mut project_cache = MockFakeProjectDrivenCache::new(); + let mut project_cache = MockProjectDrivenCache::new(); project_cache .expect_find_user_permission() .return_once(|_, _| Ok(None)); - let resource_cache = MockFakeResourceDrivenCache::new(); + let resource_cache = MockResourceDrivenCache::new(); let cmd = FetchCmd::default(); @@ -430,8 +375,8 @@ mod tests { } #[tokio::test] async fn it_should_fail_fetch_project_resources_when_secret_doesnt_have_permission() { - let project_cache = MockFakeProjectDrivenCache::new(); - let resource_cache = MockFakeResourceDrivenCache::new(); + let project_cache = MockProjectDrivenCache::new(); + let resource_cache = MockResourceDrivenCache::new(); let cmd = FetchCmd { credential: Credential::ApiKey(Uuid::new_v4().to_string()), @@ -444,7 +389,7 @@ mod tests { #[tokio::test] async fn it_should_fetch_project_resources_by_id() { - let mut project_cache = MockFakeProjectDrivenCache::new(); + let mut project_cache = MockProjectDrivenCache::new(); project_cache .expect_find_user_permission() .return_once(|_, _| Ok(Some(ProjectUser::default()))); @@ -456,7 +401,7 @@ mod tests { .expect_find_by_id() .return_once(|_| Ok(Some(project_cloned))); - let mut resource_cache = MockFakeResourceDrivenCache::new(); + let mut resource_cache = MockResourceDrivenCache::new(); resource_cache.expect_find_by_id().return_once(|_| { Ok(Some(Resource { project_id: project.id, @@ -472,7 +417,7 @@ mod tests { } #[tokio::test] async fn it_should_fail_fetch_project_resources_by_id_when_resource_is_from_other_project() { - let mut project_cache = MockFakeProjectDrivenCache::new(); + let mut project_cache = MockProjectDrivenCache::new(); project_cache .expect_find_user_permission() .return_once(|_, _| Ok(Some(ProjectUser::default()))); @@ -480,7 +425,7 @@ mod tests { .expect_find_by_id() .return_once(|_| Ok(Some(Project::default()))); - let mut resource_cache = MockFakeResourceDrivenCache::new(); + let mut resource_cache = MockResourceDrivenCache::new(); resource_cache .expect_find_by_id() .return_once(|_| Ok(Some(Resource::default()))); @@ -494,7 +439,7 @@ mod tests { #[tokio::test] async fn it_should_create_resource() { - let mut project_cache = MockFakeProjectDrivenCache::new(); + let mut project_cache = MockProjectDrivenCache::new(); project_cache .expect_find_user_permission() .return_once(|_, _| Ok(Some(ProjectUser::default()))); @@ -502,12 +447,12 @@ mod tests { .expect_find_by_id() .return_once(|_| Ok(Some(Project::default()))); - let mut metadata = MockFakeMetadataDrivenCrds::new(); + let mut metadata = MockMetadataDriven::new(); metadata .expect_find_by_kind() .return_once(|_| Ok(Some(mock_crd()))); - let mut event = MockFakeEventDrivenBridge::new(); + let mut event = MockEventDrivenBridge::new(); event.expect_dispatch().return_once(|_| Ok(())); let cmd = CreateCmd::default(); @@ -524,15 +469,15 @@ mod tests { } #[tokio::test] async fn it_should_fail_create_resource_when_crd_doesnt_exist() { - let mut project_cache = MockFakeProjectDrivenCache::new(); + let mut project_cache = MockProjectDrivenCache::new(); project_cache .expect_find_user_permission() .return_once(|_, _| Ok(Some(ProjectUser::default()))); - let mut metadata = MockFakeMetadataDrivenCrds::new(); + let mut metadata = MockMetadataDriven::new(); metadata.expect_find_by_kind().return_once(|_| Ok(None)); - let event = MockFakeEventDrivenBridge::new(); + let event = MockEventDrivenBridge::new(); let cmd = CreateCmd::default(); @@ -548,18 +493,18 @@ mod tests { } #[tokio::test] async fn it_should_fail_create_resource_when_project_doesnt_exist() { - let mut project_cache = MockFakeProjectDrivenCache::new(); + let mut project_cache = MockProjectDrivenCache::new(); project_cache .expect_find_user_permission() .return_once(|_, _| Ok(Some(ProjectUser::default()))); project_cache.expect_find_by_id().return_once(|_| Ok(None)); - let mut metadata = MockFakeMetadataDrivenCrds::new(); + let mut metadata = MockMetadataDriven::new(); metadata .expect_find_by_kind() .return_once(|_| Ok(Some(mock_crd()))); - let event = MockFakeEventDrivenBridge::new(); + let event = MockEventDrivenBridge::new(); let cmd = CreateCmd::default(); @@ -575,13 +520,13 @@ mod tests { } #[tokio::test] async fn it_should_fail_create_resource_when_user_doesnt_have_permission() { - let mut project_cache = MockFakeProjectDrivenCache::new(); + let mut project_cache = MockProjectDrivenCache::new(); project_cache .expect_find_user_permission() .return_once(|_, _| Ok(None)); - let metadata = MockFakeMetadataDrivenCrds::new(); - let event = MockFakeEventDrivenBridge::new(); + let metadata = MockMetadataDriven::new(); + let event = MockEventDrivenBridge::new(); let cmd = CreateCmd::default(); @@ -596,9 +541,9 @@ mod tests { } #[tokio::test] async fn it_should_fail_create_resource_when_secret_doesnt_have_permission() { - let project_cache = MockFakeProjectDrivenCache::new(); - let metadata = MockFakeMetadataDrivenCrds::new(); - let event = MockFakeEventDrivenBridge::new(); + let project_cache = MockProjectDrivenCache::new(); + let metadata = MockMetadataDriven::new(); + let event = MockEventDrivenBridge::new(); let cmd = CreateCmd { credential: Credential::ApiKey(Uuid::new_v4().to_string()), @@ -618,7 +563,7 @@ mod tests { #[tokio::test] async fn it_should_delete_resource() { - let mut project_cache = MockFakeProjectDrivenCache::new(); + let mut project_cache = MockProjectDrivenCache::new(); project_cache .expect_find_user_permission() .return_once(|_, _| Ok(Some(ProjectUser::default()))); @@ -630,7 +575,7 @@ mod tests { .expect_find_by_id() .return_once(|_| Ok(Some(project_cloned))); - let mut resource_cache = MockFakeResourceDrivenCache::new(); + let mut resource_cache = MockResourceDrivenCache::new(); resource_cache.expect_find_by_id().return_once(|_| { Ok(Some(Resource { project_id: project.id, @@ -638,7 +583,7 @@ mod tests { })) }); - let mut event = MockFakeEventDrivenBridge::new(); + let mut event = MockEventDrivenBridge::new(); event.expect_dispatch().return_once(|_| Ok(())); let cmd = DeleteCmd::default(); @@ -655,13 +600,13 @@ mod tests { } #[tokio::test] async fn it_should_fail_delete_resource_when_user_doesnt_have_permission() { - let mut project_cache = MockFakeProjectDrivenCache::new(); + let mut project_cache = MockProjectDrivenCache::new(); project_cache .expect_find_user_permission() .return_once(|_, _| Ok(None)); - let resource_cache = MockFakeResourceDrivenCache::new(); - let event = MockFakeEventDrivenBridge::new(); + let resource_cache = MockResourceDrivenCache::new(); + let event = MockEventDrivenBridge::new(); let cmd = DeleteCmd::default(); @@ -677,9 +622,9 @@ mod tests { } #[tokio::test] async fn it_should_fail_delete_resource_when_secret_doesnt_have_permission() { - let project_cache = MockFakeProjectDrivenCache::new(); - let resource_cache = MockFakeResourceDrivenCache::new(); - let event = MockFakeEventDrivenBridge::new(); + let project_cache = MockProjectDrivenCache::new(); + let resource_cache = MockResourceDrivenCache::new(); + let event = MockEventDrivenBridge::new(); let cmd = DeleteCmd { credential: Credential::ApiKey(Uuid::new_v4().to_string()), @@ -698,7 +643,7 @@ mod tests { } #[tokio::test] async fn it_should_fail_delete_resource_when_resource_is_from_other_project() { - let mut project_cache = MockFakeProjectDrivenCache::new(); + let mut project_cache = MockProjectDrivenCache::new(); project_cache .expect_find_user_permission() .return_once(|_, _| Ok(Some(ProjectUser::default()))); @@ -706,12 +651,12 @@ mod tests { .expect_find_by_id() .return_once(|_| Ok(Some(Project::default()))); - let mut resource_cache = MockFakeResourceDrivenCache::new(); + let mut resource_cache = MockResourceDrivenCache::new(); resource_cache .expect_find_by_id() .return_once(|_| Ok(Some(Resource::default()))); - let event = MockFakeEventDrivenBridge::new(); + let event = MockEventDrivenBridge::new(); let cmd = DeleteCmd::default(); diff --git a/src/domain/usage/cache.rs b/src/domain/usage/cache.rs index 9c03890..bf4ccf5 100644 --- a/src/domain/usage/cache.rs +++ b/src/domain/usage/cache.rs @@ -4,6 +4,7 @@ use crate::domain::{event::UsageCreated, Result}; use super::{Usage, UsageReport}; +#[cfg_attr(test, mockall::automock)] #[async_trait::async_trait] pub trait UsageDrivenCache: Send + Sync { async fn find_report( @@ -21,23 +22,11 @@ pub async fn create(cache: Arc, evt: UsageCreated) -> Resu #[cfg(test)] mod tests { - use mockall::mock; - use super::*; - mock! { - pub FakeUsageDrivenCache { } - - #[async_trait::async_trait] - impl UsageDrivenCache for FakeUsageDrivenCache { - async fn find_report(&self, project_id: &str, page: &u32, page_size: &u32,) -> Result>; - async fn create(&self, usage: Vec) -> Result<()>; - } - } - #[tokio::test] async fn it_should_create_usage_cache() { - let mut cache = MockFakeUsageDrivenCache::new(); + let mut cache = MockUsageDrivenCache::new(); cache.expect_create().return_once(|_| Ok(())); let evt = UsageCreated::default(); diff --git a/src/domain/usage/cluster.rs b/src/domain/usage/cluster.rs index 10158f6..4dfc51e 100644 --- a/src/domain/usage/cluster.rs +++ b/src/domain/usage/cluster.rs @@ -11,6 +11,7 @@ use crate::domain::{ use super::UsageUnit; +#[cfg_attr(test, mockall::automock)] #[async_trait::async_trait] pub trait UsageDrivenCluster: Send + Sync { async fn find_metrics( @@ -59,36 +60,18 @@ pub async fn sync_usage( #[cfg(test)] mod tests { - use mockall::mock; + use crate::domain::event::MockEventDrivenBridge; use super::*; - use crate::domain::event::Event; - - mock! { - pub FakeUsageDriven { } - - #[async_trait::async_trait] - impl UsageDrivenCluster for FakeUsageDriven { - async fn find_metrics(&self, start: DateTime, end: DateTime) -> Result>; - } - } - mock! { - pub FakeEventDrivenBridge { } - - #[async_trait::async_trait] - impl EventDrivenBridge for FakeEventDrivenBridge { - async fn dispatch(&self, event: Event) -> Result<()>; - } - } #[tokio::test] async fn it_should_sync_usage() { - let mut usage = MockFakeUsageDriven::new(); + let mut usage = MockUsageDrivenCluster::new(); usage .expect_find_metrics() .return_once(|_, _| Ok(Default::default())); - let mut event = MockFakeEventDrivenBridge::new(); + let mut event = MockEventDrivenBridge::new(); event.expect_dispatch().return_once(|_| Ok(())); let result = sync_usage( diff --git a/src/domain/usage/command.rs b/src/domain/usage/command.rs index cb3ac14..11fec34 100644 --- a/src/domain/usage/command.rs +++ b/src/domain/usage/command.rs @@ -55,46 +55,14 @@ impl FetchCmd { #[cfg(test)] mod tests { - use chrono::{DateTime, Utc}; - use mockall::mock; use uuid::Uuid; use super::*; use crate::domain::{ - project::{Project, ProjectSecret, ProjectUpdate, ProjectUser, ProjectUserInvite}, - usage::Usage, + project::{cache::MockProjectDrivenCache, ProjectUser}, + usage::cache::MockUsageDrivenCache, }; - mock! { - pub FakeProjectDrivenCache { } - - #[async_trait::async_trait] - impl ProjectDrivenCache for FakeProjectDrivenCache { - async fn find(&self, user_id: &str, page: &u32, page_size: &u32) -> Result>; - async fn find_by_namespace(&self, namespace: &str) -> Result>; - async fn find_by_id(&self, id: &str) -> Result>; - async fn create(&self, project: &Project) -> Result<()>; - async fn update(&self, project: &ProjectUpdate) -> Result<()>; - async fn delete(&self, id: &str, deleted_at: &DateTime) -> Result<()>; - async fn create_secret(&self, secret: &ProjectSecret) -> Result<()>; - async fn find_secret_by_project_id(&self, project_id: &str) -> Result>; - async fn find_user_permission(&self,user_id: &str, project_id: &str) -> Result>; - async fn find_user_invite_by_code(&self, code: &str) -> Result>; - async fn create_user_invite(&self, invite: &ProjectUserInvite) -> Result<()>; - async fn create_user_acceptance(&self, invite_id: &str, user: &ProjectUser) -> Result<()>; - } - } - - mock! { - pub FakeUsageDrivenCache { } - - #[async_trait::async_trait] - impl UsageDrivenCache for FakeUsageDrivenCache { - async fn find_report(&self, project_id: &str, page: &u32, page_size: &u32,) -> Result>; - async fn create(&self, usage: Vec) -> Result<()>; - } - } - impl Default for FetchCmd { fn default() -> Self { Self { @@ -108,12 +76,12 @@ mod tests { #[tokio::test] async fn it_should_fetch_project_usage_report() { - let mut project_cache = MockFakeProjectDrivenCache::new(); + let mut project_cache = MockProjectDrivenCache::new(); project_cache .expect_find_user_permission() .return_once(|_, _| Ok(Some(ProjectUser::default()))); - let mut usage_cache = MockFakeUsageDrivenCache::new(); + let mut usage_cache = MockUsageDrivenCache::new(); usage_cache .expect_find_report() .return_once(|_, _, _| Ok(vec![UsageReport::default()])); @@ -125,12 +93,12 @@ mod tests { } #[tokio::test] async fn it_should_fail_fetch_project_usage_report_when_user_doesnt_have_permission() { - let mut project_cache = MockFakeProjectDrivenCache::new(); + let mut project_cache = MockProjectDrivenCache::new(); project_cache .expect_find_user_permission() .return_once(|_, _| Ok(None)); - let usage_cache = MockFakeUsageDrivenCache::new(); + let usage_cache = MockUsageDrivenCache::new(); let cmd = FetchCmd::default(); @@ -139,8 +107,8 @@ mod tests { } #[tokio::test] async fn it_should_fail_fetch_project_usage_report_when_secret_doesnt_have_permission() { - let project_cache = MockFakeProjectDrivenCache::new(); - let usage_cache = MockFakeUsageDrivenCache::new(); + let project_cache = MockProjectDrivenCache::new(); + let usage_cache = MockUsageDrivenCache::new(); let cmd = FetchCmd { credential: Credential::ApiKey(Uuid::new_v4().to_string()), diff --git a/src/driven/cache/project.rs b/src/driven/cache/project.rs index 945ca2b..99b471c 100644 --- a/src/driven/cache/project.rs +++ b/src/driven/cache/project.rs @@ -352,6 +352,42 @@ impl ProjectDrivenCache for SqliteProjectDrivenCache { Ok(invite) } + async fn find_user_invite( + &self, + project_id: &str, + page: &u32, + page_size: &u32, + ) -> Result> { + let offset = page_size * (page - 1); + + let invites = sqlx::query_as::<_, ProjectUserInvite>( + r#" + SELECT + pui.id, + pui.project_id, + pui.email, + pui."role", + pui.code, + pui.status, + pui.expires_in, + pui.created_at, + pui.updated_at + FROM project_user_invite pui + WHERE pui.project_id = $1 + ORDER BY pui.created_at DESC + LIMIT $2 + OFFSET $3; + "#, + ) + .bind(project_id) + .bind(page_size) + .bind(offset) + .fetch_all(&self.sqlite.db) + .await?; + + Ok(invites) + } + async fn create_user_invite(&self, invite: &ProjectUserInvite) -> Result<()> { let role = invite.role.to_string(); let status = invite.status.to_string(); @@ -692,6 +728,34 @@ mod tests { assert!(result.is_ok()); assert!(result.unwrap().is_none()); } + + #[tokio::test] + async fn it_should_find_user_invites() { + let cache = get_cache().await; + + let project = Project::default(); + cache.create(&project).await.unwrap(); + + let invite = ProjectUserInvite { + project_id: project.id.clone(), + ..Default::default() + }; + cache.create_user_invite(&invite).await.unwrap(); + + let result = cache.find_user_invite(&project.id, &1, &12).await; + + assert!(result.is_ok()); + assert!(result.unwrap().len() == 1); + } + #[tokio::test] + async fn it_should_return_none_find_user_invites() { + let cache = get_cache().await; + let result = cache.find_user_invite(Default::default(), &1, &12).await; + + assert!(result.is_ok()); + assert!(result.unwrap().is_empty()); + } + #[tokio::test] async fn it_should_create_user_invite() { let cache = get_cache().await; diff --git a/src/drivers/grpc/project.rs b/src/drivers/grpc/project.rs index 23394d6..d04c253 100644 --- a/src/drivers/grpc/project.rs +++ b/src/drivers/grpc/project.rs @@ -7,7 +7,10 @@ use uuid::Uuid; use crate::domain::{ auth::{Auth0Driven, Credential}, event::EventDrivenBridge, - project::{self, cache::ProjectDrivenCache, Project, ProjectEmailDriven, StripeDriven}, + project::{ + self, cache::ProjectDrivenCache, Project, ProjectEmailDriven, ProjectUserInvite, + StripeDriven, + }, }; pub struct ProjectServiceImpl { @@ -197,6 +200,32 @@ impl proto::project_service_server::ProjectService for ProjectServiceImpl { Ok(tonic::Response::new(message)) } + async fn fetch_project_user_invites( + &self, + request: tonic::Request, + ) -> Result, tonic::Status> { + let credential = match request.extensions().get::() { + Some(credential) => credential.clone(), + None => return Err(Status::unauthenticated("invalid credential")), + }; + + let req = request.into_inner(); + + let cmd = project::command::FetchUserInviteCmd::new( + credential, + req.page, + req.page_size, + req.project_id, + )?; + + let invites = project::command::fetch_user_invite(self.cache.clone(), cmd.clone()).await?; + + let records = invites.into_iter().map(|v| v.into()).collect(); + let message = proto::FetchProjectUserInvitesResponse { records }; + + Ok(tonic::Response::new(message)) + } + async fn create_project_user_invite( &self, request: tonic::Request, @@ -295,3 +324,18 @@ impl From for proto::Project { } } } + +impl From for proto::ProjectUserInvite { + fn from(value: ProjectUserInvite) -> Self { + Self { + id: value.id, + project_id: value.project_id, + email: value.email, + role: value.role.to_string(), + status: value.status.to_string(), + expires_in: value.expires_in.to_rfc3339(), + created_at: value.created_at.to_rfc3339(), + updated_at: value.updated_at.to_rfc3339(), + } + } +}