vanikam/src/identity/application/aggregate.rs
Aravinth Manivannan b037cf2b0f
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
feat: add license headers
2024-07-12 19:35:35 +05:30

325 lines
12 KiB
Rust

// SPDX-FileCopyrightText: 2024 Aravinth Manivannan <realaravinth@batsense.net>
//
// SPDX-License-Identifier: AGPL-3.0-or-later
use async_trait::async_trait;
use cqrs_es::Aggregate;
use crate::identity::application::services::errors::*;
use crate::identity::application::services::events::UserEvent;
use crate::identity::application::services::UserCommand;
use crate::identity::application::services::UserServicesInterface;
use crate::identity::domain::aggregate::User;
use crate::identity::domain::aggregate::UserBuilder;
#[async_trait]
impl Aggregate for User {
type Command = UserCommand;
type Event = UserEvent;
type Error = IdentityError;
type Services = std::sync::Arc<dyn UserServicesInterface>;
// This identifier should be unique to the system.
fn aggregate_type() -> String {
"account".to_string()
}
// The aggregate logic goes here. Note that this will be the _bulk_ of a CQRS system
// so expect to use helper functions elsewhere to keep the code clean.
async fn handle(
&self,
command: Self::Command,
services: &Self::Services,
) -> Result<Vec<Self::Event>, Self::Error> {
match command {
UserCommand::RegisterUser(cmd) => {
let res = services.register_user().register_user(cmd).await?;
Ok(vec![UserEvent::UserRegistered(res)])
}
UserCommand::DeleteUser(cmd) => {
services.delete_user().delete_user(cmd).await;
Ok(vec![UserEvent::UserDeleted])
}
UserCommand::Login(cmd) => {
let res = services.login().login(cmd).await;
Ok(vec![UserEvent::Loggedin(res)])
}
UserCommand::UpdatePassword(cmd) => {
let res = services.update_password().update_password(cmd).await;
Ok(vec![UserEvent::PasswordUpdated(res)])
}
UserCommand::UpdateEmail(cmd) => {
let res = services.update_email().update_email(cmd).await?;
Ok(vec![UserEvent::EmailUpdated(res)])
}
UserCommand::MarkUserVerified(cmd) => {
services
.mark_user_verified()
.mark_user_verified(cmd)
.await?;
Ok(vec![UserEvent::UserVerified])
}
UserCommand::SetAdmin(cmd) => {
let res = services.set_user_admin().set_user_admin(cmd).await;
Ok(vec![UserEvent::UserPromotedToAdmin(res)])
}
UserCommand::ResendVerificationEmail(cmd) => {
services
.resend_verification_email()
.resend_verification_email(cmd)
.await?;
Ok(vec![UserEvent::VerificationEmailResent])
}
}
}
fn apply(&mut self, event: Self::Event) {
match event {
UserEvent::UserRegistered(e) => {
UserBuilder::default()
.username(e.username().into())
.email(e.email().into())
.hashed_password(e.hashed_password().into())
.is_admin(e.is_admin().to_owned())
.email_verified(e.email_verified().to_owned())
.is_verified(e.is_verified().to_owned())
.deleted(false)
.build()
.unwrap();
}
UserEvent::UserDeleted => {
self.set_deleted(true);
}
UserEvent::Loggedin(_) => (),
UserEvent::PasswordUpdated(_) => (),
UserEvent::EmailUpdated(e) => {
self.set_email(e.new_email().into());
self.set_email_verified(false);
}
UserEvent::UserVerified => {
self.set_is_verified(true);
self.set_email_verified(true);
}
UserEvent::UserPromotedToAdmin(_) => {
self.set_is_admin(true);
}
UserEvent::VerificationEmailResent => (),
}
}
}
//// The aggregate tests are the most important part of a CQRS system.
//// The simplicity and flexibility of these tests are a good part of what
//// makes an event sourced system so friendly to changing business requirements.
//#[cfg(test)]
//mod aggregate_tests {
// use async_trait::async_trait;
// use std::sync::Mutex;
//
// use cqrs_es::test::TestFramework;
//
// use crate::domain::aggregate::User;
// use crate::domain::commands::UserCommand;
// use crate::domain::events::UserEvent;
// use crate::services::{AtmError, UserApi, UserServices, CheckingError};
//
// // A test framework that will apply our events and command
// // and verify that the logic works as expected.
// type AccountTestFramework = TestFramework<User>;
//
// #[test]
// fn test_deposit_money() {
// let expected = UserEvent::CustomerDepositedMoney {
// amount: 200.0,
// balance: 200.0,
// };
// let command = UserCommand::DepositMoney { amount: 200.0 };
// let services = UserServices::new(Box::new(MockUserServices::default()));
// // Obtain a new test framework
// AccountTestFramework::with(services)
// // In a test case with no previous events
// .given_no_previous_events()
// // Wnen we fire this command
// .when(command)
// // then we expect these results
// .then_expect_events(vec![expected]);
// }
//
// #[test]
// fn test_deposit_money_with_balance() {
// let previous = UserEvent::CustomerDepositedMoney {
// amount: 200.0,
// balance: 200.0,
// };
// let expected = UserEvent::CustomerDepositedMoney {
// amount: 200.0,
// balance: 400.0,
// };
// let command = UserCommand::DepositMoney { amount: 200.0 };
// let services = UserServices::new(Box::new(MockUserServices::default()));
//
// AccountTestFramework::with(services)
// // Given this previously applied event
// .given(vec![previous])
// // When we fire this command
// .when(command)
// // Then we expect this resultant event
// .then_expect_events(vec![expected]);
// }
//
// #[test]
// fn test_withdraw_money() {
// let previous = UserEvent::CustomerDepositedMoney {
// amount: 200.0,
// balance: 200.0,
// };
// let expected = UserEvent::CustomerWithdrewCash {
// amount: 100.0,
// balance: 100.0,
// };
// let services = MockUserServices::default();
// services.set_atm_withdrawal_response(Ok(()));
// let command = UserCommand::WithdrawMoney {
// amount: 100.0,
// atm_id: "ATM34f1ba3c".to_string(),
// };
//
// AccountTestFramework::with(UserServices::new(Box::new(services)))
// .given(vec![previous])
// .when(command)
// .then_expect_events(vec![expected]);
// }
//
// #[test]
// fn test_withdraw_money_client_error() {
// let previous = UserEvent::CustomerDepositedMoney {
// amount: 200.0,
// balance: 200.0,
// };
// let services = MockUserServices::default();
// services.set_atm_withdrawal_response(Err(AtmError));
// let command = UserCommand::WithdrawMoney {
// amount: 100.0,
// atm_id: "ATM34f1ba3c".to_string(),
// };
//
// let services = UserServices::new(Box::new(services));
// AccountTestFramework::with(services)
// .given(vec![previous])
// .when(command)
// .then_expect_error_message("atm rule violation");
// }
//
// #[test]
// fn test_withdraw_money_funds_not_available() {
// let command = UserCommand::WithdrawMoney {
// amount: 200.0,
// atm_id: "ATM34f1ba3c".to_string(),
// };
//
// let services = UserServices::new(Box::new(MockUserServices::default()));
// AccountTestFramework::with(services)
// .given_no_previous_events()
// .when(command)
// // Here we expect an error rather than any events
// .then_expect_error_message("funds not available")
// }
//
// #[test]
// fn test_wrote_check() {
// let previous = UserEvent::CustomerDepositedMoney {
// amount: 200.0,
// balance: 200.0,
// };
// let expected = UserEvent::CustomerWroteCheck {
// check_number: "1170".to_string(),
// amount: 100.0,
// balance: 100.0,
// };
// let services = MockUserServices::default();
// services.set_validate_check_response(Ok(()));
// let services = UserServices::new(Box::new(services));
// let command = UserCommand::WriteCheck {
// check_number: "1170".to_string(),
// amount: 100.0,
// };
//
// AccountTestFramework::with(services)
// .given(vec![previous])
// .when(command)
// .then_expect_events(vec![expected]);
// }
//
// #[test]
// fn test_wrote_check_bad_check() {
// let previous = UserEvent::CustomerDepositedMoney {
// amount: 200.0,
// balance: 200.0,
// };
// let services = MockUserServices::default();
// services.set_validate_check_response(Err(CheckingError));
// let services = UserServices::new(Box::new(services));
// let command = UserCommand::WriteCheck {
// check_number: "1170".to_string(),
// amount: 100.0,
// };
//
// AccountTestFramework::with(services)
// .given(vec![previous])
// .when(command)
// .then_expect_error_message("check invalid");
// }
//
// #[test]
// fn test_wrote_check_funds_not_available() {
// let command = UserCommand::WriteCheck {
// check_number: "1170".to_string(),
// amount: 100.0,
// };
//
// let services = UserServices::new(Box::new(MockUserServices::default()));
// AccountTestFramework::with(services)
// .given_no_previous_events()
// .when(command)
// .then_expect_error_message("funds not available")
// }
//
// pub struct MockUserServices {
// atm_withdrawal_response: Mutex<Option<Result<(), AtmError>>>,
// validate_check_response: Mutex<Option<Result<(), CheckingError>>>,
// }
//
// impl Default for MockUserServices {
// fn default() -> Self {
// Self {
// atm_withdrawal_response: Mutex::new(None),
// validate_check_response: Mutex::new(None),
// }
// }
// }
//
// impl MockUserServices {
// fn set_atm_withdrawal_response(&self, response: Result<(), AtmError>) {
// *self.atm_withdrawal_response.lock().unwrap() = Some(response);
// }
// fn set_validate_check_response(&self, response: Result<(), CheckingError>) {
// *self.validate_check_response.lock().unwrap() = Some(response);
// }
// }
//
// #[async_trait]
// impl UserApi for MockUserServices {
// async fn atm_withdrawal(&self, _atm_id: &str, _amount: f64) -> Result<(), AtmError> {
// self.atm_withdrawal_response.lock().unwrap().take().unwrap()
// }
//
// async fn validate_check(
// &self,
// _account_id: &str,
// _check_number: &str,
// ) -> Result<(), CheckingError> {
// self.validate_check_response.lock().unwrap().take().unwrap()
// }
// }
//}
//