mod common;
use common::helpers::{get_repository, get_side_effect};
use common::test_aggregate::{Account, AccountEvent, SideEffects};
use eventastic::aggregate::Root;
use eventastic_postgres::NoEncryption;
use uuid::Uuid;
#[tokio::test]
async fn side_effect_is_correctly_stored() {
let repository = get_repository().await;
let account_id = Uuid::new_v4();
let event_id = Uuid::new_v4();
let email = "test@example.com".to_string();
let starting_balance = 100;
let open_event = AccountEvent::Open {
account_id,
event_id,
email: email.clone(),
starting_balance,
};
let mut account = Account::record_new(open_event).expect("Failed to create account");
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");
let (side_effect, retries, requeue) = get_side_effect(event_id, NoEncryption)
.await
.expect("Side effect should be stored in outbox table");
match side_effect {
SideEffects::SendEmail {
id: side_effect_id,
address,
content,
} => {
assert_eq!(side_effect_id, event_id);
assert_eq!(address, email);
assert!(content.contains(&account_id.to_string()));
assert!(content.contains(&starting_balance.to_string()));
}
_ => panic!("Expected SendEmail side effect"),
}
assert_eq!(retries, 0, "Initial retries should be 0");
assert!(requeue, "Side effect should be requeued by default");
}
#[tokio::test]
async fn multiple_side_effects_are_stored_correctly() {
let repository = get_repository().await;
let account_id = Uuid::new_v4();
let open_event_id = Uuid::new_v4();
let add_event_id = Uuid::new_v4();
let open_event = AccountEvent::Open {
account_id,
event_id: open_event_id,
email: "test@example.com".to_string(),
starting_balance: 200,
};
let mut account = Account::record_new(open_event).expect("Failed to create account");
let add_amount = 50;
let add_event = AccountEvent::Add {
event_id: add_event_id,
amount: add_amount,
};
account
.record_that(add_event)
.expect("Failed to apply Add event");
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");
get_side_effect(open_event_id, NoEncryption)
.await
.expect("Open event side effect should be stored");
let (side_effect, _, _) = get_side_effect(add_event_id, NoEncryption)
.await
.expect("Add event side effect should be stored");
match side_effect {
SideEffects::PublishMessage { id, message } => {
assert_eq!(id, add_event_id);
assert_eq!(message, add_amount.to_string());
}
_ => panic!("Expected PublishMessage side effect"),
}
}
#[tokio::test]
async fn side_effect_regeneration_works_correctly() {
use eventastic::aggregate::Context;
let repository = get_repository().await;
let account_id = Uuid::new_v4();
let open_event_id = Uuid::new_v4();
let add_event_id = Uuid::new_v4();
let remove_event_id = Uuid::new_v4();
let open_event = AccountEvent::Open {
account_id,
event_id: open_event_id,
email: "test@example.com".to_string(),
starting_balance: 100,
};
let add_event = AccountEvent::Add {
event_id: add_event_id,
amount: 50,
};
let remove_event = AccountEvent::Remove {
event_id: remove_event_id,
amount: 25,
};
let mut account = Account::record_new(open_event.clone()).expect("Failed to create account");
account
.record_that(add_event.clone())
.expect("Failed to apply Add event");
account
.record_that(remove_event.clone())
.expect("Failed to apply Remove event");
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");
let mut repo_clone = repository.clone();
let regenerated_open_effects =
Context::<Account>::regenerate_side_effects(&mut repo_clone, &account_id, &open_event_id)
.await
.expect("Failed to regenerate side effects for Open event");
assert!(
regenerated_open_effects.is_some(),
"Open event should generate side effects"
);
let open_effects = regenerated_open_effects.unwrap();
assert_eq!(
open_effects.len(),
1,
"Open event should generate exactly one side effect"
);
match &open_effects[0] {
SideEffects::SendEmail {
id,
address,
content,
} => {
assert_eq!(*id, open_event_id, "Side effect ID should match event ID");
assert_eq!(address, "test@example.com", "Email address should match");
assert!(
content.contains(&account_id.to_string()),
"Content should contain account ID"
);
assert!(
content.contains("100"),
"Content should contain starting balance"
);
}
_ => panic!("Expected SendEmail side effect for Open event"),
}
let mut repo_clone = repository.clone();
let regenerated_add_effects =
Context::<Account>::regenerate_side_effects(&mut repo_clone, &account_id, &add_event_id)
.await
.expect("Failed to regenerate side effects for Add event");
assert!(
regenerated_add_effects.is_some(),
"Add event should generate side effects"
);
let add_effects = regenerated_add_effects.unwrap();
assert_eq!(
add_effects.len(),
1,
"Add event should generate exactly one side effect"
);
match &add_effects[0] {
SideEffects::PublishMessage { id, message } => {
assert_eq!(*id, add_event_id, "Side effect ID should match event ID");
assert_eq!(message, "50", "Message should contain the amount");
}
_ => panic!("Expected PublishMessage side effect for Add event"),
}
let mut repo_clone = repository.clone();
let regenerated_remove_effects =
Context::<Account>::regenerate_side_effects(&mut repo_clone, &account_id, &remove_event_id)
.await
.expect("Failed to regenerate side effects for Remove event");
assert!(
regenerated_remove_effects.is_none(),
"Remove event should not generate side effects"
);
let non_existent_event_id = Uuid::new_v4();
let mut repo_clone = repository.clone();
let non_existent_result = Context::<Account>::regenerate_side_effects(
&mut repo_clone,
&account_id,
&non_existent_event_id,
)
.await
.expect("Should not error for non-existent event ID");
assert!(
non_existent_result.is_none(),
"Non-existent event should return None"
);
let non_existent_aggregate_id = Uuid::new_v4();
let mut repo_clone = repository.clone();
let non_existent_aggregate_result = Context::<Account>::regenerate_side_effects(
&mut repo_clone,
&non_existent_aggregate_id,
&open_event_id,
)
.await
.expect("Should not error for non-existent aggregate ID");
assert!(
non_existent_aggregate_result.is_none(),
"Non-existent aggregate should return None"
);
}