#![cfg(feature = "json")]
#![allow(clippy::unwrap_used, reason = "tests")]
#![allow(clippy::expect_used, reason = "tests")]
use std::fmt;
use mnesis::*;
use mnesis_store::{Decode, Encode, Json, JsonCodec, SerdeCodec, SerdeFormat};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type")]
enum TodoEvent {
Created { title: String },
Done,
}
impl Message for TodoEvent {}
impl DomainEvent for TodoEvent {
fn name(&self) -> &'static str {
match self {
Self::Created { .. } => "Created",
Self::Done => "Done",
}
}
}
#[derive(Default, Debug, Clone, PartialEq)]
struct TodoState {
title: String,
done: bool,
}
impl AggregateState for TodoState {
type Event = TodoEvent;
fn initial() -> Self {
Self::default()
}
fn apply(mut self, event: &TodoEvent) -> Self {
match event {
TodoEvent::Created { title } => self.title.clone_from(title),
TodoEvent::Done => self.done = true,
}
self
}
}
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
struct TodoId(String);
impl fmt::Display for TodoId {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(&self.0)
}
}
impl AsRef<[u8]> for TodoId {
fn as_ref(&self) -> &[u8] {
self.0.as_bytes()
}
}
#[derive(Debug, thiserror::Error)]
#[error("todo error")]
struct TodoError;
struct TodoAggregate;
impl Aggregate for TodoAggregate {
type State = TodoState;
type Error = TodoError;
type Id = TodoId;
}
#[test]
fn json_format_roundtrip() {
let format = Json;
let event = TodoEvent::Created {
title: "Buy milk".to_owned(),
};
let bytes = format.serialize(&event).unwrap();
let decoded: TodoEvent = format.deserialize(&bytes).unwrap();
assert_eq!(decoded, event);
}
#[test]
fn json_format_unit_variant_roundtrip() {
let format = Json;
let event = TodoEvent::Done;
let bytes = format.serialize(&event).unwrap();
let decoded: TodoEvent = format.deserialize(&bytes).unwrap();
assert_eq!(decoded, event);
}
#[test]
fn json_codec_encode_decode_roundtrip() {
let codec = JsonCodec::default();
let event = TodoEvent::Created {
title: "Walk the dog".to_owned(),
};
let bytes = codec.encode(&event).unwrap();
let env = mnesis_store::PersistedEnvelope::for_decode("Created", &bytes).unwrap();
let decoded: TodoEvent = codec.decode(&env).unwrap();
assert_eq!(decoded, event);
}
#[test]
fn json_codec_ignores_event_type_parameter() {
let codec = JsonCodec::default();
let event = TodoEvent::Created {
title: "Ignored type test".to_owned(),
};
let bytes = codec.encode(&event).unwrap();
let env = mnesis_store::PersistedEnvelope::for_decode("TotallyWrongType", &bytes).unwrap();
let decoded: TodoEvent = codec.decode(&env).unwrap();
assert_eq!(decoded, event);
}
#[test]
fn json_codec_invalid_payload_returns_error() {
let codec = JsonCodec::default();
let garbage: &[u8] = b"\xff\xfe not json at all";
let env = mnesis_store::PersistedEnvelope::for_decode("Created", garbage).unwrap();
let result: Result<TodoEvent, _> = codec.decode(&env);
assert!(result.is_err(), "decoding garbage bytes must fail");
}
#[test]
fn json_codec_is_constructible_via_default() {
let _codec: JsonCodec = JsonCodec::default();
}
#[test]
fn json_codec_is_constructible_via_new() {
let _codec: SerdeCodec<Json> = SerdeCodec::new(Json);
}
mod integration {
use super::*;
use mnesis_inmemory::InMemoryStore;
use mnesis_store::Repository;
use mnesis_store::Store;
#[tokio::test]
async fn json_codec_works_with_event_store() {
let store = Store::new(InMemoryStore::new());
let repo = store.repository().json().build();
let mut agg = AggregateRoot::<TodoAggregate>::new(TodoId("todo-1".into()));
repo.save(
&mut agg,
&save_events(&[TodoEvent::Created {
title: "Write tests".to_owned(),
}]),
)
.await
.unwrap();
let mut loaded: AggregateRoot<TodoAggregate> =
repo.load(TodoId("todo-1".into())).await.unwrap();
assert_eq!(loaded.state().title, "Write tests");
assert!(!loaded.state().done);
assert_eq!(loaded.version(), Some(Version::new(1).unwrap()));
repo.save(&mut loaded, &save_events(&[TodoEvent::Done]))
.await
.unwrap();
let final_agg: AggregateRoot<TodoAggregate> =
repo.load(TodoId("todo-1".into())).await.unwrap();
assert_eq!(final_agg.state().title, "Write tests");
assert!(final_agg.state().done);
assert_eq!(final_agg.version(), Some(Version::new(2).unwrap()));
}
}
fn save_events<E: mnesis::DomainEvent + Clone>(slice: &[E]) -> mnesis::Events<E, 32> {
let (first, rest) = slice
.split_first()
.expect("save requires at least one event");
let mut events = mnesis::Events::new(first.clone());
for event in rest {
events.add(event.clone());
}
events
}