use core::future::Future;
use core::time::Duration;
use futures::StreamExt;
use futures::pin_mut;
use mnesis::Version;
use mnesis_store::store::RawEventStore;
use mnesis_store::wake::WakeSource;
use mnesis_store::{AppendError, Step, StreamKey, Subscription};
use tokio::time::timeout;
use crate::row::{
ConformanceRow, SubId, append_event, append_rows, assert_strictly_increasing, drain_all,
drain_stream, envelope_for,
};
const WAIT: Duration = Duration::from_secs(10);
pub async fn check_empty_read_yields_none<S, C, F, Fut>(factory: &F)
where
S: RawEventStore + WakeSource,
C: Send,
F: Fn() -> Fut + Send + Sync,
Fut: Future<Output = (S, C)> + Send,
{
let (store, _guard) = factory().await;
let got = drain_stream(&store, &StreamKey::from_slice(b"missing"), Version::INITIAL).await;
assert!(
got.is_empty(),
"reading an absent stream must yield an empty stream, got {} rows",
got.len(),
);
}
pub async fn check_append_then_read_round_trips<S, C, F, Fut>(factory: &F)
where
S: RawEventStore + WakeSource,
C: Send,
F: Fn() -> Fut + Send + Sync,
Fut: Future<Output = (S, C)> + Send,
{
let (store, _guard) = factory().await;
let id = StreamKey::from_slice(b"round-trip");
let rows = vec![
ConformanceRow::new(1, "Created", vec![]),
ConformanceRow::new(2, "user.signed_up", vec![0]).with_schema_version(7),
ConformanceRow::new(3, "ÉvénementUTF8", vec![0; 64]).with_metadata(vec![1, 2, 3]),
ConformanceRow::new(4, "with spaces 123", vec![0xff; 64]).with_schema_version(u32::MAX),
ConformanceRow::new(5, "E", (0..=255u8).collect()),
ConformanceRow::new(
6,
"E",
(0..4096u32)
.map(|i| u8::try_from(i % 256).unwrap_or(0))
.collect(),
),
];
append_rows(&store, &id, &rows).await;
let got = drain_stream(&store, &id, Version::INITIAL).await;
assert_eq!(
got, rows,
"rows must round-trip byte-for-byte in insertion order"
);
}
pub async fn check_versions_strictly_monotonic_and_fused<S, C, F, Fut>(factory: &F)
where
S: RawEventStore + WakeSource,
C: Send,
F: Fn() -> Fut + Send + Sync,
Fut: Future<Output = (S, C)> + Send,
{
let (store, _guard) = factory().await;
let id = StreamKey::from_slice(b"monotonic");
let rows: Vec<_> = (1..=64u64)
.map(|v| ConformanceRow::new(v, "E", vec![]))
.collect();
append_rows(&store, &id, &rows).await;
let stream = store
.read_stream(&id, Version::INITIAL)
.await
.unwrap_or_else(|e| panic!("read_stream failed: {e:?}"));
pin_mut!(stream);
let mut versions = Vec::new();
while let Some(item) = stream.next().await {
versions.push(
item.unwrap_or_else(|e| panic!("item errored: {e:?}"))
.version()
.as_u64(),
);
}
let want: Vec<u64> = (1..=64).collect();
assert_eq!(
versions, want,
"versions must be exactly 1..=64, strictly increasing"
);
for i in 0..8 {
assert!(
stream.next().await.is_none(),
"fused-after-None violated on repeat #{i}",
);
}
}
pub async fn check_large_stream_completes<S, C, F, Fut>(factory: &F)
where
S: RawEventStore + WakeSource,
C: Send,
F: Fn() -> Fut + Send + Sync,
Fut: Future<Output = (S, C)> + Send,
{
let (store, _guard) = factory().await;
let id = StreamKey::from_slice(b"large");
let rows: Vec<_> = (1..=1500u64)
.map(|v| ConformanceRow::new(v, "E", vec![]))
.collect();
append_rows(&store, &id, &rows).await;
let got = drain_stream(&store, &id, Version::INITIAL).await;
let versions: Vec<u64> = got.iter().map(|r| r.version).collect();
let want: Vec<u64> = (1..=1500).collect();
assert_eq!(
versions, want,
"1500-event stream must drain exactly 1..=1500"
);
}
pub async fn check_read_stream_from_is_inclusive<S, C, F, Fut>(factory: &F)
where
S: RawEventStore + WakeSource,
C: Send,
F: Fn() -> Fut + Send + Sync,
Fut: Future<Output = (S, C)> + Send,
{
let (store, _guard) = factory().await;
let id = StreamKey::from_slice(b"inclusive");
let rows: Vec<_> = (1..=5u64)
.map(|v| ConformanceRow::new(v, "E", vec![]))
.collect();
append_rows(&store, &id, &rows).await;
let got = drain_stream(&store, &id, Version::new(3).expect("v3")).await;
let versions: Vec<u64> = got.iter().map(|r| r.version).collect();
assert_eq!(
versions,
vec![3, 4, 5],
"read_stream(from=3) is inclusive: yields 3,4,5"
);
}
pub async fn check_append_conflict_is_surfaced<S, C, F, Fut>(factory: &F)
where
S: RawEventStore + WakeSource,
C: Send,
F: Fn() -> Fut + Send + Sync,
Fut: Future<Output = (S, C)> + Send,
{
let (store, _guard) = factory().await;
let id = StreamKey::from_slice(b"conflict");
append_rows(
&store,
&id,
&[
ConformanceRow::new(1, "E", vec![1]),
ConformanceRow::new(2, "E", vec![2]),
],
)
.await;
let env = envelope_for(&ConformanceRow::new(1, "E", vec![9]));
let err = store
.append(&id, None, &[env])
.await
.expect_err("appending with a stale expected_version must fail");
match err {
AppendError::Conflict { actual, .. } => {
assert_eq!(
actual,
Version::new(2),
"Conflict must carry the actual head (2)",
);
}
other => panic!("expected Conflict, got {other:?}"),
}
let got = drain_stream(&store, &id, Version::INITIAL).await;
assert_eq!(got.len(), 2, "a conflicted append must not land any event");
assert_eq!(got[0].payload, vec![1]);
assert_eq!(got[1].payload, vec![2]);
}
pub async fn check_append_retry_after_conflict_succeeds<S, C, F, Fut>(factory: &F)
where
S: RawEventStore + WakeSource,
C: Send,
F: Fn() -> Fut + Send + Sync,
Fut: Future<Output = (S, C)> + Send,
{
let (store, _guard) = factory().await;
let id = StreamKey::from_slice(b"retry");
append_rows(&store, &id, &[ConformanceRow::new(1, "E", vec![1])]).await;
let stale = envelope_for(&ConformanceRow::new(1, "E", vec![9]));
store
.append(&id, None, &[stale])
.await
.expect_err("stale append must conflict");
let retry = envelope_for(&ConformanceRow::new(2, "E", vec![2]));
store
.append(&id, Version::new(1), &[retry])
.await
.expect("retry with corrected expected_version must succeed");
let got = drain_stream(&store, &id, Version::INITIAL).await;
let versions: Vec<u64> = got.iter().map(|r| r.version).collect();
assert_eq!(versions, vec![1, 2], "retry lands exactly one new event");
}
pub async fn check_all_empty_store_yields_none<S, C, F, Fut>(factory: &F)
where
S: RawEventStore + WakeSource,
C: Send,
F: Fn() -> Fut + Send + Sync,
Fut: Future<Output = (S, C)> + Send,
{
let (store, _guard) = factory().await;
let got = drain_all(&store, None).await;
assert!(
got.is_empty(),
"empty store: read_all(None) must yield nothing"
);
}
pub async fn check_all_global_order_across_streams<S, C, F, Fut>(factory: &F)
where
S: RawEventStore + WakeSource,
C: Send,
F: Fn() -> Fut + Send + Sync,
Fut: Future<Output = (S, C)> + Send,
{
let (store, _guard) = factory().await;
let a = StreamKey::from_slice(b"a");
let b = StreamKey::from_slice(b"b");
append_event(&store, &a, 1, b"a1").await;
append_event(&store, &a, 2, b"a2").await;
append_event(&store, &b, 1, b"b1").await;
append_event(&store, &a, 3, b"a3").await;
append_event(&store, &a, 4, b"a4").await;
let got = drain_all(&store, None).await;
let payloads: Vec<Vec<u8>> = got.iter().map(|(_, p)| p.clone()).collect();
assert_eq!(
payloads,
vec![
b"a1".to_vec(),
b"a2".to_vec(),
b"b1".to_vec(),
b"a3".to_vec(),
b"a4".to_vec()
],
"read_all(None) must yield every event across streams in append order",
);
assert_strictly_increasing(&got);
}
pub async fn check_all_from_is_exclusive<S, C, F, Fut>(factory: &F)
where
S: RawEventStore + WakeSource,
C: Send,
F: Fn() -> Fut + Send + Sync,
Fut: Future<Output = (S, C)> + Send,
{
let (store, _guard) = factory().await;
let a = StreamKey::from_slice(b"a");
append_event(&store, &a, 1, b"a1").await;
append_event(&store, &a, 2, b"a2").await;
append_event(&store, &a, 3, b"a3").await;
let full = drain_all(&store, None).await;
assert_eq!(full.len(), 3);
let checkpoint = full[0].0;
let rest = drain_all(&store, Some(checkpoint)).await;
let payloads: Vec<Vec<u8>> = rest.iter().map(|(_, p)| p.clone()).collect();
assert_eq!(
payloads,
vec![b"a2".to_vec(), b"a3".to_vec()],
"read_all(Some(p)) is EXCLUSIVE",
);
assert!(
rest[0].0 > checkpoint,
"resumed position must be strictly after checkpoint"
);
}
pub async fn check_all_multi_resume_cycles<S, C, F, Fut>(factory: &F)
where
S: RawEventStore + WakeSource,
C: Send,
F: Fn() -> Fut + Send + Sync,
Fut: Future<Output = (S, C)> + Send,
{
let (store, _guard) = factory().await;
let a = StreamKey::from_slice(b"a");
let b = StreamKey::from_slice(b"b");
let mut va = 0u64;
let mut vb = 0u64;
let mut expected: Vec<Vec<u8>> = Vec::new();
for i in 0..10u64 {
if i % 2 == 0 {
va += 1;
let p = format!("a{va}").into_bytes();
append_event(&store, &a, va, &p).await;
expected.push(p);
} else {
vb += 1;
let p = format!("b{vb}").into_bytes();
append_event(&store, &b, vb, &p).await;
expected.push(p);
}
}
let full = drain_all(&store, None).await;
let full_payloads: Vec<Vec<u8>> = full.iter().map(|(_, p)| p.clone()).collect();
assert_eq!(
full_payloads, expected,
"single-shot read_all(None) must match append order"
);
let mut acc: Vec<(S::AllPosition, Vec<u8>)> = Vec::new();
let mut checkpoint: Option<S::AllPosition> = None;
loop {
let stream = store
.read_all(checkpoint)
.await
.unwrap_or_else(|e| panic!("open read_all cycle failed: {e:?}"));
pin_mut!(stream);
let mut taken = 0;
let mut advanced = false;
while let Some(item) = stream.next().await {
let (pos, env) = item.unwrap_or_else(|e| panic!("cycle item errored: {e:?}"));
acc.push((pos, env.payload().to_vec()));
checkpoint = Some(pos);
advanced = true;
taken += 1;
if taken == 3 {
break;
}
}
if !advanced {
break;
}
}
let acc_payloads: Vec<Vec<u8>> = acc.iter().map(|(_, p)| p.clone()).collect();
assert_eq!(
acc_payloads, full_payloads,
"multi-resume cycles must reconstruct the full stream exactly",
);
assert_strictly_increasing(&acc);
}
pub async fn check_all_boundary_then_new_append<S, C, F, Fut>(factory: &F)
where
S: RawEventStore + WakeSource,
C: Send,
F: Fn() -> Fut + Send + Sync,
Fut: Future<Output = (S, C)> + Send,
{
let (store, _guard) = factory().await;
let a = StreamKey::from_slice(b"a");
let b = StreamKey::from_slice(b"b");
append_event(&store, &a, 1, b"a1").await;
append_event(&store, &b, 1, b"b1").await;
let full = drain_all(&store, None).await;
assert_eq!(full.len(), 2);
let last = full.last().expect("non-empty").0;
let empty = drain_all(&store, Some(last)).await;
assert!(
empty.is_empty(),
"nothing is strictly after the last position"
);
append_event(&store, &a, 2, b"a2").await;
let after = drain_all(&store, Some(last)).await;
let payloads: Vec<Vec<u8>> = after.iter().map(|(_, p)| p.clone()).collect();
assert_eq!(
payloads,
vec![b"a2".to_vec()],
"same checkpoint surfaces exactly the new event"
);
assert!(
after[0].0 > last,
"new position must be strictly after the prior last"
);
}
pub async fn check_read_stream_inclusive_read_all_exclusive_coexist<S, C, F, Fut>(factory: &F)
where
S: RawEventStore + WakeSource,
C: Send,
F: Fn() -> Fut + Send + Sync,
Fut: Future<Output = (S, C)> + Send,
{
let (store, _guard) = factory().await;
let a = StreamKey::from_slice(b"a");
append_event(&store, &a, 1, b"a1").await;
append_event(&store, &a, 2, b"a2").await;
append_event(&store, &a, 3, b"a3").await;
let got = drain_stream(&store, &a, Version::new(2).expect("v2")).await;
let versions: Vec<u64> = got.iter().map(|r| r.version).collect();
assert_eq!(versions, vec![2, 3], "read_stream(from=2) is INCLUSIVE");
let full = drain_all(&store, None).await;
assert_eq!(full.len(), 3);
let pos_of_a2 = full[1].0;
let after = drain_all(&store, Some(pos_of_a2)).await;
let payloads: Vec<Vec<u8>> = after.iter().map(|(_, p)| p.clone()).collect();
assert_eq!(
payloads,
vec![b"a3".to_vec()],
"read_all(from=pos(a2)) is EXCLUSIVE"
);
}
async fn next_step<St, T, E>(stream: &mut core::pin::Pin<&mut St>, what: &str) -> Step<T>
where
St: futures::Stream<Item = Result<Step<T>, E>>,
E: core::fmt::Debug,
{
timeout(WAIT, stream.next())
.await
.unwrap_or_else(|_| panic!("{what}: subscription hung (lost wake?)"))
.unwrap_or_else(|| panic!("{what}: subscription ended (must never return None)"))
.unwrap_or_else(|e| panic!("{what}: subscription item errored: {e:?}"))
}
pub async fn check_subscription_backlog_then_caught_up_then_live<S, C, F, Fut>(factory: &F)
where
S: RawEventStore + WakeSource,
S::Stream: Unpin,
C: Send,
F: Fn() -> Fut + Send + Sync,
Fut: Future<Output = (S, C)> + Send,
{
let (raw, _guard) = factory().await;
let store = raw.into_store();
let id = SubId::new("sub-proto");
for v in 1..=3u64 {
append_event(&store, &id.key(), v, format!("p{v}").as_bytes()).await;
}
let sub = Subscription::new(&store);
let stream = sub
.subscribe(&id, None)
.unwrap_or_else(|e| panic!("register failed: {e:?}"));
pin_mut!(stream);
for want in 1..=3u64 {
match next_step(&mut stream, "backlog").await {
Step::Event(env) => assert_eq!(
env.version().as_u64(),
want,
"backlog must replay in version order",
),
Step::CaughtUp => panic!("CaughtUp before the backlog drained (at v{want})"),
}
}
match next_step(&mut stream, "boundary").await {
Step::CaughtUp => {}
Step::Event(env) => panic!(
"expected CaughtUp after backlog, got Event v{}",
env.version()
),
}
append_event(&store, &id.key(), 4, b"p4").await;
match next_step(&mut stream, "live").await {
Step::Event(env) => assert_eq!(env.version().as_u64(), 4, "live event must be v4"),
Step::CaughtUp => panic!("CaughtUp must be emitted exactly once"),
}
}
pub async fn check_subscription_resume_strict_after<S, C, F, Fut>(factory: &F)
where
S: RawEventStore + WakeSource,
S::Stream: Unpin,
C: Send,
F: Fn() -> Fut + Send + Sync,
Fut: Future<Output = (S, C)> + Send,
{
let (raw, _guard) = factory().await;
let store = raw.into_store();
let id = SubId::new("sub-resume");
for v in 1..=5u64 {
append_event(&store, &id.key(), v, b"p").await;
}
let sub = Subscription::new(&store);
let stream = sub
.subscribe(&id, Some(Version::new(3).expect("v3")))
.unwrap_or_else(|e| panic!("register failed: {e:?}"));
pin_mut!(stream);
match next_step(&mut stream, "resume").await {
Step::Event(env) => assert_eq!(
env.version().as_u64(),
4,
"resume from Some(3) must deliver v4 first (strict-after, no dup)",
),
Step::CaughtUp => panic!("expected v4 before CaughtUp"),
}
}
pub async fn check_subscription_all_backlog_then_caught_up_then_live<S, C, F, Fut>(factory: &F)
where
S: RawEventStore + WakeSource,
S::AllStream: Unpin,
C: Send,
F: Fn() -> Fut + Send + Sync,
Fut: Future<Output = (S, C)> + Send,
{
let (raw, _guard) = factory().await;
let store = raw.into_store();
let a = StreamKey::from_slice(b"a");
let b = StreamKey::from_slice(b"b");
append_event(&store, &a, 1, b"a1").await;
append_event(&store, &b, 1, b"b1").await;
append_event(&store, &a, 2, b"a2").await;
let sub = Subscription::new(&store);
let stream = sub
.subscribe_all(None)
.unwrap_or_else(|e| panic!("register failed: {e:?}"));
pin_mut!(stream);
let mut backlog: Vec<(S::AllPosition, Vec<u8>)> = Vec::new();
while let Step::Event((pos, env)) = next_step(&mut stream, "all backlog").await {
backlog.push((pos, env.payload().to_vec()));
}
let payloads: Vec<Vec<u8>> = backlog.iter().map(|(_, p)| p.clone()).collect();
assert_eq!(
payloads,
vec![b"a1".to_vec(), b"b1".to_vec(), b"a2".to_vec()],
"$all backlog must replay in position order",
);
assert_strictly_increasing(&backlog);
let last = backlog.last().expect("non-empty").0;
append_event(&store, &b, 2, b"b2").await;
match next_step(&mut stream, "all live").await {
Step::Event((pos, env)) => {
assert_eq!(
env.payload(),
b"b2",
"live $all event must be the new append"
);
assert!(
pos > last,
"live position must be strictly after the backlog"
);
}
Step::CaughtUp => panic!("CaughtUp must be emitted exactly once"),
}
}
pub async fn check_subscription_absent_stream_waits_then_delivers<S, C, F, Fut>(factory: &F)
where
S: RawEventStore + WakeSource,
S::Stream: Unpin,
C: Send,
F: Fn() -> Fut + Send + Sync,
Fut: Future<Output = (S, C)> + Send,
{
let (raw, _guard) = factory().await;
let store = raw.into_store();
let id = SubId::new("ghost");
let sub = Subscription::new(&store);
let stream = sub
.subscribe(&id, None)
.unwrap_or_else(|e| panic!("register failed: {e:?}"));
pin_mut!(stream);
match next_step(&mut stream, "absent-stream boundary").await {
Step::CaughtUp => {}
Step::Event(env) => panic!("absent stream must have no backlog, got v{}", env.version()),
}
append_event(&store, &id.key(), 1, b"first").await;
match next_step(&mut stream, "absent-stream first event").await {
Step::Event(env) => {
assert_eq!(env.version().as_u64(), 1, "the first event must be v1");
assert_eq!(env.payload(), b"first");
}
Step::CaughtUp => panic!("CaughtUp must be emitted exactly once"),
}
}
pub async fn check_two_subscribers_same_stream_both_receive<S, C, F, Fut>(factory: &F)
where
S: RawEventStore + WakeSource,
S::Stream: Unpin,
C: Send,
F: Fn() -> Fut + Send + Sync,
Fut: Future<Output = (S, C)> + Send,
{
let (raw, _guard) = factory().await;
let store = raw.into_store();
let id = SubId::new("fanout");
append_event(&store, &id.key(), 1, b"p1").await;
let sub = Subscription::new(&store);
let stream_a = sub
.subscribe(&id, None)
.unwrap_or_else(|e| panic!("register a failed: {e:?}"));
let stream_b = sub
.subscribe(&id, None)
.unwrap_or_else(|e| panic!("register b failed: {e:?}"));
pin_mut!(stream_a);
pin_mut!(stream_b);
match next_step(&mut stream_a, "fanout backlog a").await {
Step::Event(env) => assert_eq!(env.version().as_u64(), 1, "subscriber a backlog"),
Step::CaughtUp => panic!("subscriber a: CaughtUp before backlog"),
}
match next_step(&mut stream_a, "fanout boundary a").await {
Step::CaughtUp => {}
Step::Event(env) => panic!("subscriber a: expected CaughtUp, got v{}", env.version()),
}
match next_step(&mut stream_b, "fanout backlog b").await {
Step::Event(env) => assert_eq!(env.version().as_u64(), 1, "subscriber b backlog"),
Step::CaughtUp => panic!("subscriber b: CaughtUp before backlog"),
}
match next_step(&mut stream_b, "fanout boundary b").await {
Step::CaughtUp => {}
Step::Event(env) => panic!("subscriber b: expected CaughtUp, got v{}", env.version()),
}
append_event(&store, &id.key(), 2, b"p2").await;
match next_step(&mut stream_a, "fanout live a").await {
Step::Event(env) => assert_eq!(
env.version().as_u64(),
2,
"subscriber a must receive the live event — fan-out, not a queue",
),
Step::CaughtUp => panic!("subscriber a: CaughtUp must be emitted exactly once"),
}
match next_step(&mut stream_b, "fanout live b").await {
Step::Event(env) => assert_eq!(
env.version().as_u64(),
2,
"subscriber b must receive the live event — fan-out, not a queue",
),
Step::CaughtUp => panic!("subscriber b: CaughtUp must be emitted exactly once"),
}
}
pub async fn check_subscription_large_backlog_crosses_chunk_seam<S, C, F, Fut>(factory: &F)
where
S: RawEventStore + WakeSource,
S::Stream: Unpin,
C: Send,
F: Fn() -> Fut + Send + Sync,
Fut: Future<Output = (S, C)> + Send,
{
const N: u64 = 2500; let (raw, _guard) = factory().await;
let store = raw.into_store();
let id = SubId::new("sub-chunk");
let rows: Vec<_> = (1..=N)
.map(|v| ConformanceRow::new(v, "E", vec![]))
.collect();
append_rows(&store, &id.key(), &rows).await;
let sub = Subscription::new(&store);
let stream = sub
.subscribe(&id, None)
.unwrap_or_else(|e| panic!("register failed: {e:?}"));
pin_mut!(stream);
let mut versions = Vec::with_capacity(usize::try_from(N).unwrap_or(usize::MAX));
while let Step::Event(env) = next_step(&mut stream, "chunk backlog").await {
versions.push(env.version().as_u64());
}
let want: Vec<u64> = (1..=N).collect();
assert_eq!(
versions, want,
"backlog across chunk seams must be exactly 1..=N — no gap, no duplicate",
);
}
pub async fn check_subscription_beyond_head_filters_below_bound<S, C, F, Fut>(factory: &F)
where
S: RawEventStore + WakeSource,
S::Stream: Unpin,
C: Send,
F: Fn() -> Fut + Send + Sync,
Fut: Future<Output = (S, C)> + Send,
{
let (raw, _guard) = factory().await;
let store = raw.into_store();
let id = SubId::new("beyond-head");
append_event(&store, &id.key(), 1, b"p1").await;
append_event(&store, &id.key(), 2, b"p2").await;
let sub = Subscription::new(&store);
let stream = sub
.subscribe(&id, Some(Version::new(5).expect("v5")))
.unwrap_or_else(|e| panic!("register failed: {e:?}"));
pin_mut!(stream);
match next_step(&mut stream, "beyond-head boundary").await {
Step::CaughtUp => {}
Step::Event(env) => panic!(
"subscribing beyond the head must have an empty backlog, got v{}",
env.version(),
),
}
for v in 3..=6u64 {
append_event(&store, &id.key(), v, format!("p{v}").as_bytes()).await;
}
match next_step(&mut stream, "beyond-head first delivery").await {
Step::Event(env) => assert_eq!(
env.version().as_u64(),
6,
"the first delivered event must be from+1 (6) — below-bound live appends must be filtered, never delivered",
),
Step::CaughtUp => panic!("CaughtUp must be emitted exactly once"),
}
}