use mnesis::testing::SagaFixture;
use mnesis::{Aggregate, AggregateState, DomainEvent, Events, Message, React, Saga, events};
#[derive(Debug, Clone, Hash, PartialEq, Eq, Default)]
struct OrderId([u8; 8]);
impl OrderId {
const fn new(n: u64) -> Self {
Self(n.to_le_bytes())
}
}
impl core::fmt::Display for OrderId {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}", u64::from_le_bytes(self.0))
}
}
impl AsRef<[u8]> for OrderId {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum SagaEvent {
PaymentRequested,
OrderCompleted,
}
impl Message for SagaEvent {}
impl DomainEvent for SagaEvent {
fn name(&self) -> &'static str {
match self {
Self::PaymentRequested => "PaymentRequested",
Self::OrderCompleted => "OrderCompleted",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum Intent {
TakePayment,
}
impl Message for Intent {}
#[derive(Debug)]
struct OrderSagaState {
payment_requested: bool,
completed: bool,
}
impl AggregateState for OrderSagaState {
type Event = SagaEvent;
fn initial() -> Self {
Self {
payment_requested: false,
completed: false,
}
}
fn apply(mut self, event: &SagaEvent) -> Self {
match event {
SagaEvent::PaymentRequested => self.payment_requested = true,
SagaEvent::OrderCompleted => self.completed = true,
}
self
}
}
#[derive(Debug, thiserror::Error, PartialEq)]
#[error("order saga error")]
enum OrderSagaError {
#[allow(
dead_code,
reason = "satisfies the Aggregate::Error bound; not raised by these tests"
)]
EmptyOrder,
}
struct OrderSaga;
impl Aggregate for OrderSaga {
type State = OrderSagaState;
type Error = OrderSagaError;
type Id = OrderId;
}
impl Saga for OrderSaga {
type CorrelationKey = u64;
type Command = Intent;
fn intent_for(event: &SagaEvent) -> Option<Intent> {
match event {
SagaEvent::PaymentRequested => Some(Intent::TakePayment),
SagaEvent::OrderCompleted => None,
}
}
}
#[derive(Debug)]
struct UnrelatedEvent;
impl Message for UnrelatedEvent {}
impl DomainEvent for UnrelatedEvent {
fn name(&self) -> &'static str {
"UnrelatedEvent"
}
}
impl React<UnrelatedEvent> for OrderSaga {
fn correlate(_event: &UnrelatedEvent) -> Option<u64> {
None
}
fn react(
_state: &OrderSagaState,
_event: &UnrelatedEvent,
) -> Result<Option<Events<SagaEvent>>, OrderSagaError> {
Ok(None)
}
}
#[derive(Debug)]
struct OrderExpedited {
id: u64,
}
impl Message for OrderExpedited {}
impl DomainEvent for OrderExpedited {
fn name(&self) -> &'static str {
"OrderExpedited"
}
}
impl React<OrderExpedited, 1> for OrderSaga {
fn correlate(event: &OrderExpedited) -> Option<u64> {
Some(event.id)
}
fn react(
_state: &OrderSagaState,
_event: &OrderExpedited,
) -> Result<Option<Events<SagaEvent, 1>>, OrderSagaError> {
Ok(Some(events![
SagaEvent::PaymentRequested,
SagaEvent::OrderCompleted
]))
}
}
#[test]
fn correlate_returns_none_for_unrouted_event() {
assert_eq!(
<OrderSaga as React<UnrelatedEvent>>::correlate(&UnrelatedEvent),
None
);
}
#[test]
fn react_produces_multiple_events_when_capacity_allows() {
SagaFixture::<OrderSaga>::with_id(OrderId::new(1))
.given([])
.when(&OrderExpedited { id: 1 })
.then_expect_events([SagaEvent::PaymentRequested, SagaEvent::OrderCompleted])
.then_expect_commands([Intent::TakePayment]);
}
#[test]
fn react_returns_none_for_unrouted_event() {
SagaFixture::<OrderSaga>::with_id(OrderId::new(1))
.given([])
.when(&UnrelatedEvent)
.then_expect_ignored();
}
#[test]
fn react_multi_event_after_replay() {
SagaFixture::<OrderSaga>::with_id(OrderId::new(2))
.given([SagaEvent::PaymentRequested])
.when(&OrderExpedited { id: 2 })
.then_expect_events([SagaEvent::PaymentRequested, SagaEvent::OrderCompleted]);
}