mod aggregates;
use aggregates::*;
use distributed::{
Aggregate, AggregateBuilder, InMemoryRepository, OutboxMessage, OutboxStore, SnapshotStore,
Snapshottable, StreamIdentity,
};
#[test]
fn default_snapshot_has_id_and_all_fields() {
let mut todo = Todo::new();
todo.initialize("t1".into(), "alice".into(), "Buy milk".into())
.unwrap();
todo.complete().unwrap();
let snap = todo.snapshot();
assert_eq!(snap.id, "t1");
assert_eq!(snap.user_id, "alice");
assert_eq!(snap.task, "Buy milk");
assert!(snap.completed);
}
#[tokio::test]
async fn default_snapshot_roundtrip_via_snapshottable() {
let repo = InMemoryRepository::new()
.aggregate::<Todo>()
.with_snapshots(1);
let mut todo = Todo::new();
todo.initialize("t1".into(), "alice".into(), "Buy milk".into())
.unwrap();
repo.commit(&mut todo).await.unwrap();
let loaded = repo.get("t1").await.unwrap().unwrap();
let snap = loaded.snapshot();
assert_eq!(snap.id, "t1");
assert_eq!(snap.user_id, "alice");
assert_eq!(snap.task, "Buy milk");
assert!(!snap.completed);
}
#[test]
fn default_restore_from_snapshot() {
let mut todo = Todo::new();
let snap = TodoSnapshot {
id: "restored".into(),
user_id: "bob".into(),
task: "Walk dog".into(),
completed: true,
};
todo.restore_from_snapshot(snap);
assert_eq!(todo.entity.id(), "restored");
assert_eq!(todo.user_id, "bob");
assert_eq!(todo.task, "Walk dog");
assert!(todo.completed);
}
#[test]
fn custom_id_snapshot_uses_field_as_key() {
let mut inv = Inventory::new();
inv.create("inv-1".into(), "WIDGET-42".into(), 100).unwrap();
let snap = inv.snapshot();
assert_eq!(snap.sku, "WIDGET-42");
assert_eq!(snap.available, 100);
}
#[test]
fn custom_id_restore_sets_entity_id_from_field() {
let mut inv = Inventory::new();
let snap = InventorySnapshot {
sku: "GADGET-99".into(),
available: 50,
};
inv.restore_from_snapshot(snap);
assert_eq!(inv.entity.id(), "GADGET-99");
assert_eq!(inv.sku, "GADGET-99");
assert_eq!(inv.available, 50);
}
#[tokio::test]
async fn custom_id_roundtrip_via_repo() {
let repo = InMemoryRepository::new()
.aggregate::<Inventory>()
.with_snapshots(1);
let mut inv = Inventory::new();
inv.create("inv-1".into(), "SKU-A".into(), 10).unwrap();
repo.commit(&mut inv).await.unwrap();
let loaded = repo.get("inv-1").await.unwrap().unwrap();
let snap = loaded.snapshot();
assert_eq!(snap.sku, "SKU-A");
assert_eq!(snap.available, 10);
}
#[test]
fn serde_skip_fields_excluded_from_snapshot() {
let mut order = Order::new();
order.place("o1".into(), "alice".into(), 999).unwrap();
let snap = order.snapshot();
assert_eq!(snap.id, "o1");
assert_eq!(snap.customer, "alice");
assert_eq!(snap.total, 999);
}
#[test]
fn serde_skip_default_excluded_from_snapshot() {
let mut notifier = Notifier::new();
notifier.send("n1".into(), "hello".into()).unwrap();
let snap = notifier.snapshot();
assert_eq!(snap.id, "n1");
assert_eq!(snap.message, "hello");
}
#[tokio::test]
async fn serde_skip_restore_roundtrip() {
let repo = InMemoryRepository::new()
.aggregate::<Order>()
.with_snapshots(1);
let mut order = Order::new();
order.place("o1".into(), "alice".into(), 500).unwrap();
repo.commit(&mut order).await.unwrap();
let loaded = repo.get("o1").await.unwrap().unwrap();
assert_eq!(loaded.snapshot().customer, "alice");
assert_eq!(loaded.snapshot().total, 500);
assert_eq!(loaded.cached_label, "");
}
#[test]
fn sourced_attr_with_snapshot_derive() {
let mut counter = Counter::new();
counter.initialize("c1".into()).unwrap();
counter.increment(5).unwrap();
counter.increment(3).unwrap();
let snap = counter.snapshot();
assert_eq!(snap.id, "c1");
assert_eq!(snap.count, 8);
}
#[tokio::test]
async fn sourced_attr_snapshot_roundtrip_via_repo() {
let repo = InMemoryRepository::new()
.aggregate::<Counter>()
.with_snapshots(2);
let mut counter = Counter::new();
counter.initialize("c1".into()).unwrap();
counter.increment(10).unwrap();
repo.commit(&mut counter).await.unwrap();
let identity = StreamIdentity::new(Counter::aggregate_type(), "c1").unwrap();
let snap_record = repo.repo().get_snapshot(&identity).await.unwrap();
assert!(snap_record.is_some());
let loaded = repo.get("c1").await.unwrap().unwrap();
assert_eq!(loaded.snapshot().count, 10);
}
#[test]
fn custom_entity_field_snapshot() {
let mut widget = Widget::new();
widget.create("w1".into(), "Sprocket".into(), 2.5).unwrap();
let snap = widget.snapshot();
assert_eq!(snap.id, "w1");
assert_eq!(snap.name, "Sprocket");
assert_eq!(snap.weight, 2.5);
}
#[test]
fn custom_entity_field_restore() {
let mut widget = Widget::new();
let snap = WidgetSnapshot {
id: "w2".into(),
name: "Gear".into(),
weight: 1.0,
};
widget.restore_from_snapshot(snap);
assert_eq!(widget.my_entity.id(), "w2");
assert_eq!(widget.name, "Gear");
assert_eq!(widget.weight, 1.0);
}
#[test]
fn domain_event_derives_id_and_payload() {
let mut todo = Todo::new();
todo.initialize("t1".into(), "alice".into(), "Buy milk".into())
.unwrap();
let outbox = OutboxMessage::domain_event("todo.initialized", &todo).unwrap();
assert_eq!(outbox.id(), "t1:todo.initialized:1");
assert_eq!(outbox.event_type, "todo.initialized");
let decoded: TodoSnapshot = outbox.decode().unwrap();
assert_eq!(decoded.id, "t1");
assert_eq!(decoded.user_id, "alice");
assert_eq!(decoded.task, "Buy milk");
}
#[test]
fn domain_event_propagates_metadata() {
let mut todo = Todo::new();
todo.entity.set_correlation_id("req-abc");
todo.entity.set_causation_id("cmd-create");
todo.entity.set_meta("user_id", "u-42");
todo.initialize("t1".into(), "alice".into(), "Buy milk".into())
.unwrap();
let outbox = OutboxMessage::domain_event("todo.initialized", &todo).unwrap();
assert_eq!(outbox.correlation_id(), Some("req-abc"));
assert_eq!(outbox.causation_id(), Some("cmd-create"));
assert_eq!(outbox.meta("user_id"), Some("u-42"));
}
#[tokio::test]
async fn domain_event_commits_with_outbox() {
let repo = InMemoryRepository::new().aggregate::<Todo>();
let mut todo = Todo::new();
todo.initialize("t1".into(), "alice".into(), "Ship it".into())
.unwrap();
let outbox = OutboxMessage::domain_event("todo.initialized", &todo).unwrap();
repo.outbox(outbox).commit(&mut todo).await.unwrap();
let loaded = repo.get("t1").await.unwrap().unwrap();
assert_eq!(loaded.snapshot().task, "Ship it");
let pending = repo
.repo()
.outbox_store()
.pending(usize::MAX)
.await
.unwrap();
assert_eq!(pending.len(), 1);
assert!(pending[0].is_pending());
}