diff --git a/Cargo.lock b/Cargo.lock index 511b832..6205eb1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -255,6 +255,12 @@ dependencies = [ "libc", ] +[[package]] +name = "anstyle" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" + [[package]] name = "async-trait" version = "0.1.80" @@ -686,6 +692,12 @@ version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" +[[package]] +name = "downcast" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" + [[package]] name = "either" version = "1.11.0" @@ -816,6 +828,7 @@ dependencies = [ "derive_more", "lazy_static", "log", + "mockall", "pretty_env_logger", "rand", "reqwest", @@ -838,6 +851,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fragile" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa" + [[package]] name = "futures-channel" version = "0.3.30" @@ -1452,6 +1471,33 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "mockall" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43766c2b5203b10de348ffe19f7e54564b64f3d6018ff7648d1e2d6d3a0f0a48" +dependencies = [ + "cfg-if", + "downcast", + "fragile", + "lazy_static", + "mockall_derive", + "predicates", + "predicates-tree", +] + +[[package]] +name = "mockall_derive" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af7cbce79ec385a1d4f54baa90a76401eb15d9cab93685f62e7e9f942aa00ae2" +dependencies = [ + "cfg-if", + "proc-macro2", + "quote", + "syn 2.0.60", +] + [[package]] name = "mutually_exclusive_features" version = "0.0.3" @@ -1821,6 +1867,32 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "predicates" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68b87bfd4605926cdfefc1c3b5f8fe560e3feca9d5552cf68c466d3d8236c7e8" +dependencies = [ + "anstyle", + "predicates-core", +] + +[[package]] +name = "predicates-core" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b794032607612e7abeb4db69adb4e33590fa6cf1149e95fd7cb00e634b92f174" + +[[package]] +name = "predicates-tree" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368ba315fb8c5052ab692e68a0eefec6ec57b23a36959c14496f0b0df2c0cecf" +dependencies = [ + "predicates-core", + "termtree", +] + [[package]] name = "pretty_env_logger" version = "0.5.0" @@ -2683,6 +2755,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "termtree" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" + [[package]] name = "thiserror" version = "1.0.59" diff --git a/Cargo.toml b/Cargo.toml index 5063237..8e5c497 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,9 @@ tera = "1.19.1" tracing = { version = "0.1.40", features = ["log"] } tracing-actix-web = "0.7.10" url = { version = "2.5.0", features = ["serde"] } +mockall = "0.12" [dev-dependencies] actix-rt= "2.9" +mockall = { version = "0.12", features = ["nightly"] } diff --git a/src/auth/adapter/input/web/login/adapter.rs b/src/auth/adapter/input/web/login/adapter.rs index 0c8ae0e..78dd662 100644 --- a/src/auth/adapter/input/web/login/adapter.rs +++ b/src/auth/adapter/input/web/login/adapter.rs @@ -55,3 +55,64 @@ impl RequestAuthorizationInterface for RequestAuthorizationHandler { .finish()) } } + +#[cfg(test)] +mod tests { + use actix_web::http::{header, StatusCode}; + + use super::*; + use crate::auth::application::port::out::{ + db::save_oauth_state::MockSaveOAuthState, forge::oauth_auth_req_uri::MockOAuthAuthReqUri, + }; + use crate::utils::random_string::*; + + #[actix_web::test] + async fn test_adapter() { + let random_string = "foorand"; + + let url = Url::parse("http://test_ui_req_auth_interface_adapter").unwrap(); + let oauth_provider = "test_ui_req_auth_interface_adapter"; + let mut redirect_uri = url.clone(); + redirect_uri.set_query(Some(&format!("state={random_string}"))); + + let mut mock_random_generate_string = MockGenerateRandomStringInterface::new(); + mock_random_generate_string + .expect_get_random() + .times(1) + .return_const(random_string.to_string()); + + let r = redirect_uri.clone(); + let mut mock_oauth_req_uri = MockOAuthAuthReqUri::new(); + mock_oauth_req_uri + .expect_oauth_auth_req_uri() + .times(1) + .returning(move |_, _| Ok(r.clone())); + + let mut mock_save_oauth_state = MockSaveOAuthState::new(); + mock_save_oauth_state + .expect_save_oauth_state() + .times(1) + .returning(|_, _, _| Ok(())); + + let adapter = RequestAuthorizationHandler::new( + Arc::new(mock_save_oauth_state), + Arc::new(mock_oauth_req_uri), + Arc::new(mock_random_generate_string), + url.clone(), + ); + + let res = adapter + .request_oauth_authorization(oauth_provider.into()) + .await + .unwrap(); + assert_eq!(res.status(), StatusCode::FOUND); + assert_eq!( + res.headers() + .get(header::LOCATION) + .unwrap() + .to_str() + .unwrap(), + redirect_uri.as_str() + ); + } +} diff --git a/src/auth/adapter/out/db/mod.rs b/src/auth/adapter/out/db/mod.rs index 2788cf4..0f50f3a 100644 --- a/src/auth/adapter/out/db/mod.rs +++ b/src/auth/adapter/out/db/mod.rs @@ -2,8 +2,8 @@ use std::sync::Arc; use sqlx::PgPool; -use crate::db::migrate::RunMigrations; use crate::auth::application::port::out::db::save_oauth_state::SaveOAuthState; +use crate::db::migrate::RunMigrations; pub mod postgres; diff --git a/src/auth/application/port/out/db/mod.rs b/src/auth/application/port/out/db/mod.rs index b8701a7..0047e6d 100644 --- a/src/auth/application/port/out/db/mod.rs +++ b/src/auth/application/port/out/db/mod.rs @@ -2,46 +2,3 @@ pub mod delete_oauth_state; pub mod errors; pub mod oauth_state_exists; pub mod save_oauth_state; - -#[cfg(test)] -pub mod tests { - use std::sync::{Arc, RwLock}; - - use url::Url; - - use super::*; - - use errors::*; - use save_oauth_state::SaveOAuthState; - - #[derive(Clone, Default)] - pub struct MockDB { - pub calls: Arc>>, - } - - #[derive(Clone, Default)] - pub struct Call { - pub state: String, - pub oauth_provider: String, - pub redirect_uri: String, - } - - #[async_trait::async_trait] - impl SaveOAuthState for MockDB { - async fn save_oauth_state( - &self, - state: &str, - oauth_provider: &str, - redirect_uri: &Url, - ) -> OutDBPortResult<()> { - let mut calls = self.calls.write().unwrap(); - calls.push(Call { - state: state.to_string(), - oauth_provider: oauth_provider.to_string(), - redirect_uri: redirect_uri.to_string(), - }); - - Ok(()) - } - } -} diff --git a/src/auth/application/port/out/db/save_oauth_state.rs b/src/auth/application/port/out/db/save_oauth_state.rs index 2a1e05c..6e41f8c 100644 --- a/src/auth/application/port/out/db/save_oauth_state.rs +++ b/src/auth/application/port/out/db/save_oauth_state.rs @@ -1,7 +1,10 @@ +use mockall::predicate::*; +use mockall::*; use url::Url; use super::errors::*; +#[automock] #[async_trait::async_trait] pub trait SaveOAuthState: Send + Sync { /// Save OAuth state code generated during authorization request, which will later be used to diff --git a/src/auth/application/port/out/forge/mod.rs b/src/auth/application/port/out/forge/mod.rs index e1ddea5..05bf0bd 100644 --- a/src/auth/application/port/out/forge/mod.rs +++ b/src/auth/application/port/out/forge/mod.rs @@ -2,29 +2,3 @@ pub mod errors; pub mod oauth_auth_req_uri; pub mod refresh_access_token; pub mod request_access_token; - -#[cfg(test)] -pub mod tests { - use url::Url; - - use super::*; - - use errors::*; - use oauth_auth_req_uri::OAuthAuthReqUri; - - #[derive(Clone, Default)] - pub struct MockForge; // { - - #[async_trait::async_trait] - impl OAuthAuthReqUri for MockForge { - fn oauth_auth_req_uri( - &self, - state: &str, - process_authorization_response_uri: &Url, - ) -> OutForgePortResult { - let mut u = process_authorization_response_uri.clone(); - u.set_query(Some(&format!("state={state}"))); - Ok(u) - } - } -} diff --git a/src/auth/application/port/out/forge/oauth_auth_req_uri.rs b/src/auth/application/port/out/forge/oauth_auth_req_uri.rs index f35632d..21ed3b1 100644 --- a/src/auth/application/port/out/forge/oauth_auth_req_uri.rs +++ b/src/auth/application/port/out/forge/oauth_auth_req_uri.rs @@ -1,6 +1,9 @@ -use super::errors::*; +use mockall::*; use url::Url; +use super::errors::*; + +#[automock] pub trait OAuthAuthReqUri: Send + Sync { fn oauth_auth_req_uri( &self, diff --git a/src/auth/application/port/out/forge/refresh_access_token.rs b/src/auth/application/port/out/forge/refresh_access_token.rs new file mode 100644 index 0000000..ff4eb90 --- /dev/null +++ b/src/auth/application/port/out/forge/refresh_access_token.rs @@ -0,0 +1,8 @@ +use super::errors::*; +use crate::auth::domain::AuthCode; + +#[async_trait::async_trait] +pub trait RefreshAccessToken { + // returns ID of newly created user user + async fn refresh_access_token(&self, auth_code: &AuthCode) -> OutForgePortResult; +} diff --git a/src/auth/application/port/out/forge/request_access_token.rs b/src/auth/application/port/out/forge/request_access_token.rs new file mode 100644 index 0000000..0281815 --- /dev/null +++ b/src/auth/application/port/out/forge/request_access_token.rs @@ -0,0 +1,8 @@ +use super::errors::*; +use crate::auth::domain::AuthCode; + +#[async_trait::async_trait] +pub trait RequestAccessToken: Send + Sync { + // returns ID of newly created user user + async fn request_access_token(&self, code: &str) -> OutForgePortResult; +} diff --git a/src/auth/application/services/request_authorization/service.rs b/src/auth/application/services/request_authorization/service.rs index fe2ea15..6240e58 100644 --- a/src/auth/application/services/request_authorization/service.rs +++ b/src/auth/application/services/request_authorization/service.rs @@ -75,8 +75,8 @@ mod tests { use crate::auth::application::{ port::out::{ - db::{save_oauth_state::MockSaveOAuthState, tests::MockDB}, - forge::tests::MockForge, + db::save_oauth_state::MockSaveOAuthState, + forge::oauth_auth_req_uri::MockOAuthAuthReqUri, }, services::request_authorization::command::RequestAuthorizationCommand, }; @@ -87,33 +87,40 @@ mod tests { #[actix_rt::test] async fn test_service() { let random_string = "foorand"; - let save_oauth_state = MockDB::default(); - let oauth_auth_req_uri = MockForge; + let url = Url::parse("http://test_service_request_auth").unwrap(); let oauth_provider = "test_service_request_auth_oauth_provider"; + let mut redirect_uri = url.clone(); + redirect_uri.set_query(Some(&format!("state={random_string}"))); + let mut mock_random_generate_string = MockGenerateRandomStringInterface::new(); mock_random_generate_string .expect_get_random() + .times(1) .return_const(random_string.to_string()); + let r = redirect_uri.clone(); + let mut mock_oauth_req_uri = MockOAuthAuthReqUri::new(); + mock_oauth_req_uri + .expect_oauth_auth_req_uri() + .times(1) + .returning(move |_, _| Ok(r.clone())); + + let mut mock_save_oauth_state = MockSaveOAuthState::new(); + mock_save_oauth_state + .expect_save_oauth_state() + .times(1) + .returning(|_, _, _| Ok(())); + let s = RequestAuthorizationService::new( - Arc::new(save_oauth_state.clone()), - Arc::new(oauth_auth_req_uri), + Arc::new(mock_save_oauth_state), + Arc::new(mock_oauth_req_uri), url.clone(), Arc::new(mock_random_generate_string), ); let cmd = RequestAuthorizationCommand::new_command(oauth_provider.to_owned()).unwrap(); - let res = s.request_authorization(cmd).await.unwrap().to_string(); - { - let save_oauth_state_ctx = save_oauth_state.calls.read().unwrap(); - let call = save_oauth_state_ctx.first().unwrap(); - assert_eq!( - res, - format!("http://test_service_request_auth/?state={}", &call.state) - ); - assert_eq!(call.oauth_provider, oauth_provider); - assert_eq!(call.redirect_uri, url.to_string()); - } + let res = s.request_authorization(cmd).await.unwrap(); + assert_eq!(res, redirect_uri); } } diff --git a/src/utils/random_string.rs b/src/utils/random_string.rs index c2b831a..cc5bf93 100644 --- a/src/utils/random_string.rs +++ b/src/utils/random_string.rs @@ -1,7 +1,10 @@ use std::sync::Arc; use actix_web::web; +use mockall::predicate::*; +use mockall::*; +#[automock] pub trait GenerateRandomStringInterface: Send + Sync { fn get_random(&self, len: usize) -> String; }