mod common;
use common::helpers::{AccountBuilder, get_latest_event_timestamp, get_repository, load_account};
use common::test_aggregate::AccountEvent;
use eventastic::aggregate::Context;
use eventastic::repository::RepositoryReader;
use futures::StreamExt;
use uuid::Uuid;
use crate::common::helpers::create_account_with_many_events;
use crate::common::test_aggregate::Account;
#[tokio::test]
pub async fn aggregate_is_successfully_saved_and_loaded() {
let repository = get_repository().await;
let mut account = AccountBuilder::new().build();
let account_id = account.state().account_id;
let mut transaction = repository
.begin_transaction()
.await
.expect("Failed to begin transaction");
account
.save(&mut transaction)
.await
.expect("Failed to save account");
let created_account = account.state();
transaction
.commit()
.await
.expect("Failed to commit transaction");
let loaded_account = load_account(account_id).await;
let loaded_account = loaded_account.state();
assert_eq!(created_account, loaded_account);
}
#[tokio::test]
pub async fn aggregate_is_not_saved_if_no_events_are_applied() {
let repository = get_repository().await;
let mut account = AccountBuilder::new().save().await;
let account_id = account.state().account_id;
let event_time_stamp = get_latest_event_timestamp(account_id).await;
let mut transaction = repository
.begin_transaction()
.await
.expect("Failed to begin transaction");
account
.save(&mut transaction)
.await
.expect("Failed to save account");
transaction
.commit()
.await
.expect("Failed to commit transaction");
assert_eq!(
event_time_stamp,
get_latest_event_timestamp(account_id).await
);
}
#[tokio::test]
pub async fn transaction_rollback_discards_changes() {
let repository = get_repository().await;
let mut account = AccountBuilder::new().build();
let account_id = account.state().account_id;
let mut transaction = repository
.begin_transaction()
.await
.expect("Failed to begin transaction");
account
.record_that(AccountEvent::Add {
event_id: Uuid::new_v4(),
amount: 100,
})
.expect("Failed to apply event");
account
.save(&mut transaction)
.await
.expect("Failed to save account");
transaction
.rollback()
.await
.expect("Failed to rollback transaction");
let mut load_transaction = repository
.begin_transaction()
.await
.expect("Failed to begin transaction");
let load_result =
Context::<common::test_aggregate::Account>::load(&mut load_transaction, &account_id).await;
assert!(
load_result.is_err(),
"Account should not exist after rollback"
);
load_transaction
.commit()
.await
.expect("Failed to commit transaction");
}
#[tokio::test]
pub async fn transaction_isolates_changes_until_commit() {
let repository = get_repository().await;
let mut account = AccountBuilder::new().build();
let account_id = account.state().account_id;
account
.record_that(AccountEvent::Add {
event_id: Uuid::new_v4(),
amount: 50,
})
.expect("Failed to apply event");
let mut transaction = repository
.begin_transaction()
.await
.expect("Failed to begin transaction");
account
.save(&mut transaction)
.await
.expect("Failed to save account");
let mut concurrent_transaction = repository
.begin_transaction()
.await
.expect("Failed to begin transaction");
let load_result =
Context::<common::test_aggregate::Account>::load(&mut concurrent_transaction, &account_id)
.await;
assert!(
load_result.is_err(),
"Account should not be visible in another transaction before commit"
);
concurrent_transaction
.commit()
.await
.expect("Failed to commit concurrent transaction");
transaction
.commit()
.await
.expect("Failed to commit transaction");
let mut verification_transaction = repository
.begin_transaction()
.await
.expect("Failed to begin transaction");
let loaded_account = Context::<common::test_aggregate::Account>::load(
&mut verification_transaction,
&account_id,
)
.await
.expect("Failed to load account after commit");
assert_eq!(loaded_account.state().balance, 50);
verification_transaction
.commit()
.await
.expect("Failed to commit verification transaction");
}
#[tokio::test]
pub async fn transaction_handles_invalid_data_gracefully() {
let repository = get_repository().await;
let mut account = AccountBuilder::new().save().await;
let account_id = account.state().account_id;
let mut transaction = repository
.begin_transaction()
.await
.expect("Failed to begin transaction");
account
.record_that(AccountEvent::Open {
account_id,
event_id: Uuid::new_v4(),
email: "test@example.com".to_string(),
starting_balance: 50,
})
.expect_err("Should fail to apply Open event on existing account");
account
.save(&mut transaction)
.await
.expect("Failed to save account");
transaction
.commit()
.await
.expect("Failed to commit transaction");
let loaded_account = load_account(account_id).await;
assert_eq!(account.state().balance, loaded_account.state().balance);
}
#[tokio::test]
pub async fn transaction_handles_multiple_operations_correctly() {
let repository = get_repository().await;
let mut account = AccountBuilder::new().with_add_event(100).save().await;
let account_id = account.state().account_id;
let initial_balance = account.state().balance;
let mut transaction = repository
.begin_transaction()
.await
.expect("Failed to begin transaction");
account
.record_that(AccountEvent::Add {
event_id: Uuid::new_v4(),
amount: 50,
})
.expect("Failed to apply Add event");
account
.save(&mut transaction)
.await
.expect("Failed to save account after first operation");
account
.record_that(AccountEvent::Remove {
event_id: Uuid::new_v4(),
amount: 25,
})
.expect("Failed to apply Remove event");
account
.save(&mut transaction)
.await
.expect("Failed to save account after second operation");
transaction
.commit()
.await
.expect("Failed to commit transaction");
let loaded_account = load_account(account_id).await;
assert_eq!(
initial_balance + 50 - 25,
loaded_account.state().balance,
"Account balance should reflect all operations in the transaction"
);
}
#[tokio::test]
pub async fn transaction_handles_multiple_aggregates_correctly() {
let repository = get_repository().await;
let mut account1 = AccountBuilder::new().save().await;
let account_id1 = account1.state().account_id;
let mut account2 = AccountBuilder::new().save().await;
let account_id2 = account2.state().account_id;
let mut transaction = repository
.begin_transaction()
.await
.expect("Failed to begin transaction");
account1
.record_that(AccountEvent::Add {
event_id: Uuid::new_v4(),
amount: 100,
})
.expect("Failed to apply event to first account");
account1
.save(&mut transaction)
.await
.expect("Failed to save first account");
account2
.record_that(AccountEvent::Add {
event_id: Uuid::new_v4(),
amount: 200,
})
.expect("Failed to apply event to second account");
account2
.save(&mut transaction)
.await
.expect("Failed to save second account");
transaction
.commit()
.await
.expect("Failed to commit transaction");
let loaded_account1 = load_account(account_id1).await;
let loaded_account2 = load_account(account_id2).await;
assert_eq!(loaded_account1.state().balance, account1.state().balance);
assert_eq!(loaded_account2.state().balance, account2.state().balance);
}
#[tokio::test]
pub async fn repository_error_handling_and_recovery() {
let repository = get_repository().await;
let mut account = AccountBuilder::new().save().await;
let account_id = account.state().account_id;
let initial_balance = account.state().balance;
let mut transaction = repository
.begin_transaction()
.await
.expect("Failed to begin transaction");
account
.record_that(AccountEvent::Add {
event_id: Uuid::new_v4(),
amount: 50,
})
.expect("Failed to apply Add event");
account
.save(&mut transaction)
.await
.expect("Failed to save account");
transaction
.rollback()
.await
.expect("Failed to rollback transaction");
let mut recovery_transaction = repository
.begin_transaction()
.await
.expect("Failed to begin recovery transaction");
let mut reloaded_account = load_account(account_id).await;
assert_eq!(
reloaded_account.state().balance,
initial_balance,
"Account should be in original state after rollback"
);
reloaded_account
.record_that(AccountEvent::Add {
event_id: Uuid::new_v4(),
amount: 75, })
.expect("Failed to apply event during recovery");
reloaded_account
.save(&mut recovery_transaction)
.await
.expect("Failed to save account during recovery");
recovery_transaction
.commit()
.await
.expect("Failed to commit recovery transaction");
let final_account = load_account(account_id).await;
assert_eq!(
final_account.state().balance,
initial_balance + 75,
"Account balance should reflect recovery operation"
);
}
#[tokio::test]
pub async fn repository_load_works_without_transaction() {
use common::test_aggregate::Account;
use eventastic::repository::Repository;
let repository = get_repository().await;
let account = AccountBuilder::new()
.with_add_event(100)
.with_remove_event(20)
.save()
.await;
let account_id = account.state().account_id;
let expected_balance = account.state().balance;
let loaded_account: Context<Account> = repository
.load(&account_id)
.await
.expect("Failed to load account using Repository::load");
assert_eq!(loaded_account.state().account_id, account_id);
assert_eq!(loaded_account.state().balance, expected_balance);
let transaction_loaded_account = load_account(account_id).await;
assert_eq!(loaded_account.state(), transaction_loaded_account.state());
}
#[tokio::test]
async fn streaming_returns_events_in_version_order() {
let repository = get_repository().await;
let account_id = Uuid::new_v4();
let mut account = create_account_with_many_events(account_id, 150_000).await;
let mut transaction = repository
.begin_transaction()
.await
.expect("Failed to begin transaction");
transaction
.store(&mut account)
.await
.expect("Failed to save account");
transaction
.commit()
.await
.expect("Failed to commit transaction");
let mut transaction = repository
.begin_transaction()
.await
.expect("Failed to begin transaction");
let mut event_count = 0;
let mut events_stream =
RepositoryReader::<Account>::stream_from(&mut transaction, &account_id, 0);
while let Some(event_result) = events_stream.next().await {
let event = event_result.expect("Failed to get event from stream");
event_count += 1;
assert_eq!(
event.version as usize,
event_count - 1,
"Events should be in order"
);
}
}