use mnesis::testing::AggregateFixture;
use mnesis::*;
use std::fmt;
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
struct UserId(String);
impl UserId {
fn new(v: u64) -> Self {
Self(format!("user-{v}"))
}
}
impl fmt::Display for UserId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl AsRef<[u8]> for UserId {
fn as_ref(&self) -> &[u8] {
self.0.as_bytes()
}
}
#[derive(Debug, Clone, PartialEq)]
struct UserCreated {
name: String,
}
#[derive(Debug, Clone, PartialEq)]
struct UserActivated;
#[derive(Debug, Clone, PartialEq, mnesis_macros::DomainEvent)]
enum UserEvent {
Created(UserCreated),
Activated(UserActivated),
}
#[derive(Default, Debug, Clone)]
struct UserState {
name: String,
active: bool,
}
impl AggregateState for UserState {
type Event = UserEvent;
fn initial() -> Self {
Self::default()
}
fn apply(mut self, event: &UserEvent) -> Self {
match event {
UserEvent::Created(e) => self.name.clone_from(&e.name),
UserEvent::Activated(_) => self.active = true,
}
self
}
}
struct User;
#[derive(Debug, thiserror::Error)]
enum UserError {
#[error("user already exists")]
AlreadyExists,
#[error("user already active")]
AlreadyActive,
}
impl Aggregate for User {
type State = UserState;
type Error = UserError;
type Id = UserId;
}
struct CreateUser {
name: String,
}
struct ActivateUser;
impl Handle<CreateUser> for User {
fn handle(state: &UserState, cmd: CreateUser) -> Result<Events<UserEvent>, UserError> {
if !state.name.is_empty() {
return Err(UserError::AlreadyExists);
}
Ok(events![UserEvent::Created(UserCreated { name: cmd.name })])
}
}
impl Handle<ActivateUser> for User {
fn handle(state: &UserState, _cmd: ActivateUser) -> Result<Events<UserEvent>, UserError> {
if state.active {
return Err(UserError::AlreadyActive);
}
Ok(events![UserEvent::Activated(UserActivated)])
}
}
#[test]
fn full_aggregate_lifecycle_with_handle() {
let mut user = AggregateRoot::<User>::new(UserId::new(1));
let create_events = user
.handle(CreateUser {
name: "Alice".into(),
})
.unwrap();
assert_eq!(create_events.len(), 1);
assert_eq!(create_events.iter().next().unwrap().name(), "Created");
let v1 = Version::new(1).unwrap();
user.commit_persisted(v1, &create_events);
assert_eq!(user.state().name, "Alice");
assert_eq!(user.version(), Some(v1));
let activate_events = user.handle(ActivateUser).unwrap();
let v2 = Version::new(2).unwrap();
user.commit_persisted(v2, &activate_events);
assert!(user.state().active);
assert_eq!(user.version(), Some(v2));
}
#[test]
fn rehydrate_then_decide() {
AggregateFixture::<User>::with_id(UserId::new(2))
.given([UserEvent::Created(UserCreated { name: "Bob".into() })])
.then_expect_state(|s| assert_eq!(s.name, "Bob"))
.when(ActivateUser)
.then_expect_events([UserEvent::Activated(UserActivated)])
.then_expect_state(|s| assert!(s.active));
}
#[test]
fn duplicate_create_is_rejected() {
AggregateFixture::<User>::with_id(UserId::new(3))
.given([UserEvent::Created(UserCreated {
name: "Charlie".into(),
})])
.when(CreateUser {
name: "David".into(),
})
.then_expect_error_matching(|e| matches!(e, UserError::AlreadyExists));
}
#[test]
fn duplicate_activate_is_rejected() {
AggregateFixture::<User>::with_id(UserId::new(3))
.given([
UserEvent::Created(UserCreated {
name: "Charlie".into(),
}),
UserEvent::Activated(UserActivated),
])
.when(ActivateUser)
.then_expect_error_matching(|e| matches!(e, UserError::AlreadyActive));
}
#[test]
fn derive_macro_generates_correct_event_names() {
let created = UserEvent::Created(UserCreated {
name: "test".into(),
});
let activated = UserEvent::Activated(UserActivated);
assert_eq!(created.name(), "Created");
assert_eq!(activated.name(), "Activated");
}