All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
325 lines
12 KiB
Rust
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()
|
|
// }
|
|
// }
|
|
//}
|
|
//
|