#![allow(clippy::unwrap_used, reason = "tests")]
#![allow(clippy::expect_used, reason = "tests")]
#![allow(clippy::panic, reason = "tests")]
use std::sync::Arc;
use bytes::Bytes;
use futures::TryStreamExt;
use mnesis::Version;
use mnesis_inmemory::InMemoryStore;
use mnesis_store::store::RawEventStore;
use mnesis_store::{PendingBatch, PendingEnvelope, PersistedEnvelope, Store, StreamKey};
use tokio::sync::Barrier;
fn sk(id: &str) -> StreamKey {
StreamKey::from_slice(id.as_bytes())
}
fn env(version: u64) -> PendingEnvelope {
mnesis_store::pending_envelope(Version::new(version).unwrap())
.event_type("Recorded")
.payload(Bytes::from_static(b"payload"))
.build()
.expect("valid envelope")
}
async fn drain_all<S: RawEventStore>(
store: &Store<S>,
) -> Vec<(S::AllPosition, StreamKey, PersistedEnvelope)> {
store
.read_all(None)
.await
.expect("read_all")
.try_collect()
.await
.expect("drain $all")
}
#[tokio::test]
async fn append_returns_the_position_all_reports_for_that_event() {
let store = Store::new(InMemoryStore::new());
let only = env(1);
let returned = store
.append(&sk("s1"), None, PendingBatch::of(&only))
.await
.expect("append");
let rows = drain_all(&store).await;
assert_eq!(rows.len(), 1, "one append of one event is one $all row");
assert_eq!(
rows[0].0, returned,
"the returned position must be the position $all reports for that event"
);
}
#[tokio::test]
async fn multi_event_append_returns_the_last_events_position() {
let store = Store::new(InMemoryStore::new());
let envs = [env(1), env(2), env(3)];
let returned = store
.append(&sk("s1"), None, PendingBatch::new(&envs).unwrap())
.await
.expect("append");
let rows = drain_all(&store).await;
assert_eq!(rows.len(), 3, "three events are three $all rows");
assert_eq!(
rows[2].0, returned,
"the returned position is the LAST event's, so a consumer that has \
reached it has necessarily been delivered the whole run"
);
assert_ne!(
rows[0].0, returned,
"the first event's position must not be what is returned"
);
}
#[tokio::test]
async fn successive_appends_return_strictly_increasing_positions() {
let store = Store::new(InMemoryStore::new());
let (e1, e2, e3) = (env(1), env(2), env(1));
let first = store
.append(&sk("s1"), None, PendingBatch::of(&e1))
.await
.expect("append 1");
let second = store
.append(&sk("s1"), Version::new(1), PendingBatch::of(&e2))
.await
.expect("append 2");
let other_stream = store
.append(&sk("s2"), None, PendingBatch::of(&e3))
.await
.expect("append 3");
assert!(first < second, "positions advance within one stream");
assert!(
second < other_stream,
"positions advance across streams — `$all` order is store-wide"
);
}
#[tokio::test]
async fn a_rejected_append_returns_no_position_and_writes_nothing() {
let store = Store::new(InMemoryStore::new());
let first = env(1);
store
.append(&sk("s1"), None, PendingBatch::of(&first))
.await
.expect("append");
let duplicate = env(1);
let err = store
.append(&sk("s1"), None, PendingBatch::of(&duplicate))
.await
.expect_err("stale expected_version must be rejected");
assert!(
matches!(err, mnesis_store::AppendError::Conflict { .. }),
"a stale expectation is a Conflict, got {err:?}"
);
let rows = drain_all(&store).await;
assert_eq!(rows.len(), 1, "the rejected append must not have landed");
}
#[tokio::test]
async fn concurrent_appends_return_positions_ordered_as_all_delivers_them() {
let store = Arc::new(Store::new(InMemoryStore::new()));
let barrier = Arc::new(Barrier::new(2));
let mut handles = Vec::new();
for name in ["s1", "s2"] {
let task_store = Arc::clone(&store);
let task_barrier = Arc::clone(&barrier);
handles.push(tokio::spawn(async move {
let only = env(1);
task_barrier.wait().await;
let pos = task_store
.append(&sk(name), None, PendingBatch::of(&only))
.await
.expect("concurrent append");
(sk(name), pos)
}));
}
let mut claimed = Vec::new();
for h in handles {
claimed.push(h.await.expect("task panicked"));
}
assert_ne!(
claimed[0].1, claimed[1].1,
"two concurrent appends must not claim the same position"
);
claimed.sort_by_key(|&(_, pos)| pos);
let delivered = drain_all(&store).await;
assert_eq!(delivered.len(), 2, "both appends landed");
for (i, (expected_key, expected_pos)) in claimed.iter().enumerate() {
assert_eq!(
delivered[i].0, *expected_pos,
"delivery {i} must carry the position that append returned"
);
assert_eq!(
delivered[i].1, *expected_key,
"delivery {i} must be the stream whose append returned that position"
);
}
}