mnesis-store 0.2.1

Event store edge layer for the Mnesis event-sourcing framework
Documentation
//! Tests for `SerdeCodec`, `SerdeFormat`, `Json`, and `JsonCodec`.

#![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};

// -- Test domain --

#[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;
}

// =============================================================================
// Unit tests — SerdeFormat (Json)
// =============================================================================

#[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);
}

// =============================================================================
// Unit tests — JsonCodec (Codec<E> impl)
// =============================================================================

#[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();

    // Pass a completely wrong event_type — serde JSON dispatches via the
    // internally-tagged `"type"` field, so the env's event_type is unused.
    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);
}

// =============================================================================
// Integration test — full EventStore round-trip
// =============================================================================

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();

        // Save a "Created" event
        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();

        // Load and verify state after Created
        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()));

        // Append a Done event
        repo.save(&mut loaded, &save_events(&[TodoEvent::Done]))
            .await
            .unwrap();

        // Reload and verify full state
        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()));
    }
}

// ─── #207 test helper ──────────────────────────────────────────────────────
// `Repository::save` takes `&Events<E, N>` (non-empty, compile-time capacity).
// These tests build batches from runtime-length slices/proptest vectors, so we
// pack them into `Events<E, 32>` (capacity 33 — covers every batch built here;
// the largest strategy yields 29). Empty input is a programmer error: `save`
// makes a zero-event batch unrepresentable by construction.
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
}