Skip to main content

mnesis_store_testing/
sequence.rs

1//! Sequence/Protocol conformance: multi-step interactions on one store —
2//! append→read round-trips, optimistic-conflict protocol, `$all` ordering and
3//! resume, and the subscription catch-up→live protocol.
4
5use core::future::Future;
6use core::time::Duration;
7
8use futures::StreamExt;
9use futures::pin_mut;
10use mnesis::Version;
11use mnesis_store::store::RawEventStore;
12use mnesis_store::wake::WakeSource;
13use mnesis_store::{AppendError, PendingBatch, Step, StreamKey, Subscription};
14use tokio::time::timeout;
15
16use crate::row::{
17    ConformanceRow, SubId, append_event, append_event_at, append_rows, assert_strictly_increasing,
18    drain_all, drain_all_attributed, drain_all_with_metadata, drain_stream, envelope_for,
19};
20
21/// Upper bound on any single subscription wait — a hang here means a lost
22/// wake, which is exactly what the check exists to catch.
23const WAIT: Duration = Duration::from_secs(10);
24
25/// A fresh, empty stream reads back empty (absent stream = empty, not error).
26pub async fn check_empty_read_yields_none<S, C, F, Fut>(factory: &F)
27where
28    S: RawEventStore + WakeSource,
29    C: Send,
30    F: Fn() -> Fut + Send + Sync,
31    Fut: Future<Output = (S, C)> + Send,
32{
33    let (store, _guard) = factory().await;
34    let got = drain_stream(&store, &StreamKey::from_slice(b"missing"), Version::INITIAL).await;
35    assert!(
36        got.is_empty(),
37        "reading an absent stream must yield an empty stream, got {} rows",
38        got.len(),
39    );
40}
41
42/// Mixed-shape rows round-trip byte-for-byte in insertion order: Unicode and
43/// dotted event types, schema versions across the u32 range, payloads from
44/// empty through 4 KiB, metadata absent and present.
45pub async fn check_append_then_read_round_trips<S, C, F, Fut>(factory: &F)
46where
47    S: RawEventStore + WakeSource,
48    C: Send,
49    F: Fn() -> Fut + Send + Sync,
50    Fut: Future<Output = (S, C)> + Send,
51{
52    let (store, _guard) = factory().await;
53    let id = StreamKey::from_slice(b"round-trip");
54    let rows = vec![
55        ConformanceRow::new(1, "Created", vec![]),
56        ConformanceRow::new(2, "user.signed_up", vec![0]).with_schema_version(7),
57        ConformanceRow::new(3, "ÉvénementUTF8", vec![0; 64]).with_metadata(vec![1, 2, 3]),
58        ConformanceRow::new(4, "with spaces 123", vec![0xff; 64]).with_schema_version(u32::MAX),
59        ConformanceRow::new(5, "E", (0..=255u8).collect()),
60        ConformanceRow::new(
61            6,
62            "E",
63            (0..4096u32)
64                .map(|i| u8::try_from(i % 256).unwrap_or(0))
65                .collect(),
66        ),
67    ];
68    append_rows(&store, &id, &rows).await;
69    let got = drain_stream(&store, &id, Version::INITIAL).await;
70    assert_eq!(
71        got, rows,
72        "rows must round-trip byte-for-byte in insertion order"
73    );
74}
75
76/// Versions read back strictly monotonic and the stream is fused after `None`.
77pub async fn check_versions_strictly_monotonic_and_fused<S, C, F, Fut>(factory: &F)
78where
79    S: RawEventStore + WakeSource,
80    C: Send,
81    F: Fn() -> Fut + Send + Sync,
82    Fut: Future<Output = (S, C)> + Send,
83{
84    let (store, _guard) = factory().await;
85    let id = StreamKey::from_slice(b"monotonic");
86    let rows: Vec<_> = (1..=64u64)
87        .map(|v| ConformanceRow::new(v, "E", vec![]))
88        .collect();
89    append_rows(&store, &id, &rows).await;
90
91    let stream = store
92        .read_stream(&id, Version::INITIAL)
93        .await
94        .unwrap_or_else(|e| panic!("read_stream failed: {e:?}"));
95    pin_mut!(stream);
96    let mut versions = Vec::new();
97    while let Some(item) = stream.next().await {
98        versions.push(
99            item.unwrap_or_else(|e| panic!("item errored: {e:?}"))
100                .version()
101                .as_u64(),
102        );
103    }
104    let want: Vec<u64> = (1..=64).collect();
105    assert_eq!(
106        versions, want,
107        "versions must be exactly 1..=64, strictly increasing"
108    );
109    for i in 0..8 {
110        assert!(
111            stream.next().await.is_none(),
112            "fused-after-None violated on repeat #{i}",
113        );
114    }
115}
116
117/// A stream larger than any internal batch/refill size (1500 events) drains
118/// completely with no gap or duplicate across the seams.
119pub async fn check_large_stream_completes<S, C, F, Fut>(factory: &F)
120where
121    S: RawEventStore + WakeSource,
122    C: Send,
123    F: Fn() -> Fut + Send + Sync,
124    Fut: Future<Output = (S, C)> + Send,
125{
126    let (store, _guard) = factory().await;
127    let id = StreamKey::from_slice(b"large");
128    let rows: Vec<_> = (1..=1500u64)
129        .map(|v| ConformanceRow::new(v, "E", vec![]))
130        .collect();
131    append_rows(&store, &id, &rows).await;
132    let got = drain_stream(&store, &id, Version::INITIAL).await;
133    let versions: Vec<u64> = got.iter().map(|r| r.version).collect();
134    let want: Vec<u64> = (1..=1500).collect();
135    assert_eq!(
136        versions, want,
137        "1500-event stream must drain exactly 1..=1500"
138    );
139}
140
141/// `read_stream(from)` is INCLUSIVE: from=3 on a 5-event stream yields 3,4,5.
142pub async fn check_read_stream_from_is_inclusive<S, C, F, Fut>(factory: &F)
143where
144    S: RawEventStore + WakeSource,
145    C: Send,
146    F: Fn() -> Fut + Send + Sync,
147    Fut: Future<Output = (S, C)> + Send,
148{
149    let (store, _guard) = factory().await;
150    let id = StreamKey::from_slice(b"inclusive");
151    let rows: Vec<_> = (1..=5u64)
152        .map(|v| ConformanceRow::new(v, "E", vec![]))
153        .collect();
154    append_rows(&store, &id, &rows).await;
155    let got = drain_stream(&store, &id, Version::new(3).expect("v3")).await;
156    let versions: Vec<u64> = got.iter().map(|r| r.version).collect();
157    assert_eq!(
158        versions,
159        vec![3, 4, 5],
160        "read_stream(from=3) is inclusive: yields 3,4,5"
161    );
162}
163
164/// A mismatched `expected_version` surfaces `AppendError::Conflict` carrying
165/// the store's actual head, and the store is untouched.
166pub async fn check_append_conflict_is_surfaced<S, C, F, Fut>(factory: &F)
167where
168    S: RawEventStore + WakeSource,
169    C: Send,
170    F: Fn() -> Fut + Send + Sync,
171    Fut: Future<Output = (S, C)> + Send,
172{
173    let (store, _guard) = factory().await;
174    let id = StreamKey::from_slice(b"conflict");
175    append_rows(
176        &store,
177        &id,
178        &[
179            ConformanceRow::new(1, "E", vec![1]),
180            ConformanceRow::new(2, "E", vec![2]),
181        ],
182    )
183    .await;
184
185    // Stale expectation: stream head is 2, we claim it's still fresh.
186    let env = envelope_for(&ConformanceRow::new(1, "E", vec![9]));
187    let err = store
188        .append(&id, None, PendingBatch::of(&env))
189        .await
190        .expect_err("appending with a stale expected_version must fail");
191    match err {
192        AppendError::Conflict { actual, .. } => {
193            assert_eq!(
194                actual,
195                Version::new(2),
196                "Conflict must carry the actual head (2)",
197            );
198        }
199        other => panic!("expected Conflict, got {other:?}"),
200    }
201
202    let got = drain_stream(&store, &id, Version::INITIAL).await;
203    assert_eq!(got.len(), 2, "a conflicted append must not land any event");
204    assert_eq!(got[0].payload, vec![1]);
205    assert_eq!(got[1].payload, vec![2]);
206}
207
208/// After a conflict, retrying with the corrected expectation succeeds — the
209/// standard optimistic-concurrency protocol completes.
210pub async fn check_append_retry_after_conflict_succeeds<S, C, F, Fut>(factory: &F)
211where
212    S: RawEventStore + WakeSource,
213    C: Send,
214    F: Fn() -> Fut + Send + Sync,
215    Fut: Future<Output = (S, C)> + Send,
216{
217    let (store, _guard) = factory().await;
218    let id = StreamKey::from_slice(b"retry");
219    append_rows(&store, &id, &[ConformanceRow::new(1, "E", vec![1])]).await;
220
221    // Conflict first…
222    let stale = envelope_for(&ConformanceRow::new(1, "E", vec![9]));
223    store
224        .append(&id, None, PendingBatch::of(&stale))
225        .await
226        .expect_err("stale append must conflict");
227
228    // …then the corrected retry (head is 1, next event is v2).
229    let retry = envelope_for(&ConformanceRow::new(2, "E", vec![2]));
230    store
231        .append(&id, Version::new(1), PendingBatch::of(&retry))
232        .await
233        .expect("retry with corrected expected_version must succeed");
234
235    let got = drain_stream(&store, &id, Version::INITIAL).await;
236    let versions: Vec<u64> = got.iter().map(|r| r.version).collect();
237    assert_eq!(versions, vec![1, 2], "retry lands exactly one new event");
238}
239
240/// Empty store: `read_all(None)` yields nothing.
241pub async fn check_all_empty_store_yields_none<S, C, F, Fut>(factory: &F)
242where
243    S: RawEventStore + WakeSource,
244    C: Send,
245    F: Fn() -> Fut + Send + Sync,
246    Fut: Future<Output = (S, C)> + Send,
247{
248    let (store, _guard) = factory().await;
249    let got = drain_all(&store, None).await;
250    assert!(
251        got.is_empty(),
252        "empty store: read_all(None) must yield nothing"
253    );
254}
255
256/// `read_all(None)` yields every event across streams in append (position)
257/// order, positions strictly increasing.
258pub async fn check_all_global_order_across_streams<S, C, F, Fut>(factory: &F)
259where
260    S: RawEventStore + WakeSource,
261    C: Send,
262    F: Fn() -> Fut + Send + Sync,
263    Fut: Future<Output = (S, C)> + Send,
264{
265    let (store, _guard) = factory().await;
266    let a = StreamKey::from_slice(b"a");
267    let b = StreamKey::from_slice(b"b");
268    append_event(&store, &a, 1, b"a1").await;
269    append_event(&store, &a, 2, b"a2").await;
270    append_event(&store, &b, 1, b"b1").await;
271    append_event(&store, &a, 3, b"a3").await;
272    append_event(&store, &a, 4, b"a4").await;
273
274    let got = drain_all(&store, None).await;
275    let payloads: Vec<Vec<u8>> = got.iter().map(|(_, p)| p.clone()).collect();
276    assert_eq!(
277        payloads,
278        vec![
279            b"a1".to_vec(),
280            b"a2".to_vec(),
281            b"b1".to_vec(),
282            b"a3".to_vec(),
283            b"a4".to_vec()
284        ],
285        "read_all(None) must yield every event across streams in append order",
286    );
287    assert_strictly_increasing(&got);
288}
289
290/// Metadata round-trips byte-for-byte on the `$all` read path.
291///
292/// The per-stream path is checked by [`check_metadata_absent_vs_present_distinct`];
293/// this check pins the same guarantee for the attributed `(position, key, envelope)`
294/// read used by projections and subscriptions.
295pub async fn check_all_metadata_round_trips<S, C, F, Fut>(factory: &F)
296where
297    S: RawEventStore + WakeSource,
298    C: Send,
299    F: Fn() -> Fut + Send + Sync,
300    Fut: Future<Output = (S, C)> + Send,
301{
302    let (store, _guard) = factory().await;
303    let a = StreamKey::from_slice(b"a");
304    let b = StreamKey::from_slice(b"b");
305    let rows = [
306        ConformanceRow::new(1, "A", b"a1".to_vec()),
307        ConformanceRow::new(1, "B", b"b1".to_vec()).with_metadata(vec![1, 2, 3]),
308        ConformanceRow::new(2, "A", b"a2".to_vec()).with_metadata(vec![4, 5]),
309    ];
310    // Append to distinct streams: stream `a` gets two events in one batch,
311    // then stream `b` gets one event. `$all` order is the commit order, so
312    // stream `a`'s run lands first (versions 1 and 2) and stream `b` follows.
313    append_rows(&store, &a, &[rows[0].clone(), rows[2].clone()]).await;
314    append_rows(&store, &b, &[rows[1].clone()]).await;
315
316    let got = drain_all_with_metadata(&store, None).await;
317    let payloads: Vec<Vec<u8>> = got.iter().map(|(_, _, p)| p.clone()).collect();
318    assert_eq!(
319        payloads,
320        vec![b"a1".to_vec(), b"a2".to_vec(), b"b1".to_vec()],
321        "read_all(None) must yield events across streams in append order"
322    );
323
324    let metadata: Vec<Option<Vec<u8>>> = got.iter().map(|(_, m, _)| m.clone()).collect();
325    assert_eq!(
326        metadata,
327        vec![None, Some(vec![4, 5]), Some(vec![1, 2, 3])],
328        "metadata must round-trip byte-for-byte on the $all path"
329    );
330    assert_strictly_increasing(
331        &got.iter()
332            .map(|(pos, _, payload)| (*pos, payload.clone()))
333            .collect::<Vec<_>>(),
334    );
335}
336
337/// #330: `append` returns the `$all` position it assigned to the run's last
338/// event — the read-your-writes token.
339///
340/// The returned position must be the exact position `$all` reports for that
341/// event, and successive appends (within a stream and across streams) must
342/// return strictly increasing positions. Without this an application cannot
343/// await its own write over an `$all` projection without dropping to a raw scan.
344pub async fn check_append_returns_assigned_all_position<S, C, F, Fut>(factory: &F)
345where
346    S: RawEventStore + WakeSource,
347    C: Send,
348    F: Fn() -> Fut + Send + Sync,
349    Fut: Future<Output = (S, C)> + Send,
350{
351    let a = StreamKey::from_slice(b"a");
352    let b = StreamKey::from_slice(b"b");
353    let (store, _guard) = factory().await;
354
355    let a1 = append_event_at(&store, &a, 1, b"a1").await;
356    let a2 = append_event_at(&store, &a, 2, b"a2").await;
357    let b1 = append_event_at(&store, &b, 1, b"b1").await;
358
359    assert!(
360        a1 < a2,
361        "successive appends to one stream must return strictly increasing positions",
362    );
363    assert!(
364        a2 < b1,
365        "positions are store-wide: an append to another stream advances past the last",
366    );
367
368    // Each returned position must be the exact position `$all` reports for that
369    // event — proven by pairing position to payload over the full `$all` read.
370    let all = drain_all(&store, None).await;
371    let at = |p: S::AllPosition| {
372        all.iter().find(|(pos, _)| *pos == p).map_or_else(
373            || panic!("returned position {p:?} is absent from $all"),
374            |(_, payload)| payload.clone(),
375        )
376    };
377    assert_eq!(at(a1), b"a1".to_vec(), "a1's position must name a1 in $all");
378    assert_eq!(at(a2), b"a2".to_vec(), "a2's position must name a2 in $all");
379    assert_eq!(at(b1), b"b1".to_vec(), "b1's position must name b1 in $all");
380}
381
382/// #330: a multi-event append returns the LAST event's position, so a consumer
383/// that has reached it has necessarily been delivered the whole run.
384pub async fn check_multi_event_append_returns_last_position<S, C, F, Fut>(factory: &F)
385where
386    S: RawEventStore + WakeSource,
387    C: Send,
388    F: Fn() -> Fut + Send + Sync,
389    Fut: Future<Output = (S, C)> + Send,
390{
391    let id = StreamKey::from_slice(b"multi");
392    let (store, _guard) = factory().await;
393
394    let rows = [
395        ConformanceRow::new(1, "E", b"v1".to_vec()),
396        ConformanceRow::new(2, "E", b"v2".to_vec()),
397        ConformanceRow::new(3, "E", b"v3".to_vec()),
398    ];
399    let envs: Vec<_> = rows.iter().map(envelope_for).collect();
400    let returned = store
401        .append(
402            &id,
403            None,
404            PendingBatch::new(&envs).expect("three rows are non-empty"),
405        )
406        .await
407        .unwrap_or_else(|e| panic!("multi-event append failed: {e:?}"));
408
409    let all = drain_all(&store, None).await;
410    let last = all.last().expect("three events landed");
411    assert_eq!(
412        returned, last.0,
413        "the returned position must be the LAST event's, not the first",
414    );
415    assert_eq!(last.1, b"v3".to_vec(), "and the last event is v3");
416    assert_ne!(
417        returned, all[0].0,
418        "the first event's position must not be what append returned",
419    );
420}
421
422/// #333: every `$all` item carries the origin [`StreamKey`](mnesis_store::StreamKey).
423///
424/// Attribution is a store guarantee, not a payload convention. Interleaves two
425/// streams and asserts each item's key matches its append target, in position
426/// order.
427pub async fn check_all_items_carry_their_stream_key<S, C, F, Fut>(factory: &F)
428where
429    S: RawEventStore + WakeSource,
430    C: Send,
431    F: Fn() -> Fut + Send + Sync,
432    Fut: Future<Output = (S, C)> + Send,
433{
434    let (store, _guard) = factory().await;
435    let alpha = StreamKey::from_slice(b"alpha");
436    let beta = StreamKey::from_slice(b"beta");
437    append_event(&store, &alpha, 1, b"a1").await;
438    append_event(&store, &beta, 1, b"b1").await;
439    append_event(&store, &alpha, 2, b"a2").await;
440
441    let got = drain_all_attributed(&store, None).await;
442    let attributed: Vec<(Vec<u8>, Vec<u8>)> =
443        got.iter().map(|(_, k, p)| (k.clone(), p.clone())).collect();
444    assert_eq!(
445        attributed,
446        vec![
447            (b"alpha".to_vec(), b"a1".to_vec()),
448            (b"beta".to_vec(), b"b1".to_vec()),
449            (b"alpha".to_vec(), b"a2".to_vec()),
450        ],
451        "each $all item must carry the StreamKey of the stream it was appended to, in position order",
452    );
453}
454
455/// `read_all(Some(p))` is EXCLUSIVE: strictly after `p`.
456pub async fn check_all_from_is_exclusive<S, C, F, Fut>(factory: &F)
457where
458    S: RawEventStore + WakeSource,
459    C: Send,
460    F: Fn() -> Fut + Send + Sync,
461    Fut: Future<Output = (S, C)> + Send,
462{
463    let (store, _guard) = factory().await;
464    let a = StreamKey::from_slice(b"a");
465    append_event(&store, &a, 1, b"a1").await;
466    append_event(&store, &a, 2, b"a2").await;
467    append_event(&store, &a, 3, b"a3").await;
468
469    let full = drain_all(&store, None).await;
470    assert_eq!(full.len(), 3);
471    let checkpoint = full[0].0;
472
473    let rest = drain_all(&store, Some(checkpoint)).await;
474    let payloads: Vec<Vec<u8>> = rest.iter().map(|(_, p)| p.clone()).collect();
475    assert_eq!(
476        payloads,
477        vec![b"a2".to_vec(), b"a3".to_vec()],
478        "read_all(Some(p)) is EXCLUSIVE",
479    );
480    assert!(
481        rest[0].0 > checkpoint,
482        "resumed position must be strictly after checkpoint"
483    );
484}
485
486/// Multi-resume cycles reconstruct the single-shot read exactly — no gap,
487/// duplicate, or skip across the seams.
488pub async fn check_all_multi_resume_cycles<S, C, F, Fut>(factory: &F)
489where
490    S: RawEventStore + WakeSource,
491    C: Send,
492    F: Fn() -> Fut + Send + Sync,
493    Fut: Future<Output = (S, C)> + Send,
494{
495    let (store, _guard) = factory().await;
496    let a = StreamKey::from_slice(b"a");
497    let b = StreamKey::from_slice(b"b");
498    let mut va = 0u64;
499    let mut vb = 0u64;
500    let mut expected: Vec<Vec<u8>> = Vec::new();
501    for i in 0..10u64 {
502        if i % 2 == 0 {
503            va += 1;
504            let p = format!("a{va}").into_bytes();
505            append_event(&store, &a, va, &p).await;
506            expected.push(p);
507        } else {
508            vb += 1;
509            let p = format!("b{vb}").into_bytes();
510            append_event(&store, &b, vb, &p).await;
511            expected.push(p);
512        }
513    }
514
515    let full = drain_all(&store, None).await;
516    let full_payloads: Vec<Vec<u8>> = full.iter().map(|(_, p)| p.clone()).collect();
517    assert_eq!(
518        full_payloads, expected,
519        "single-shot read_all(None) must match append order"
520    );
521
522    let mut acc: Vec<(S::AllPosition, Vec<u8>)> = Vec::new();
523    let mut checkpoint: Option<S::AllPosition> = None;
524    loop {
525        let stream = store
526            .read_all(checkpoint)
527            .await
528            .unwrap_or_else(|e| panic!("open read_all cycle failed: {e:?}"));
529        pin_mut!(stream);
530        let mut taken = 0;
531        let mut advanced = false;
532        while let Some(item) = stream.next().await {
533            let (pos, _key, env) = item.unwrap_or_else(|e| panic!("cycle item errored: {e:?}"));
534            acc.push((pos, env.payload().to_vec()));
535            checkpoint = Some(pos);
536            advanced = true;
537            taken += 1;
538            if taken == 3 {
539                break;
540            }
541        }
542        if !advanced {
543            break;
544        }
545    }
546
547    let acc_payloads: Vec<Vec<u8>> = acc.iter().map(|(_, p)| p.clone()).collect();
548    assert_eq!(
549        acc_payloads, full_payloads,
550        "multi-resume cycles must reconstruct the full stream exactly",
551    );
552    assert_strictly_increasing(&acc);
553}
554
555/// `read_all(Some(last))` is empty at the boundary; a later append surfaces
556/// exactly the new event from the same checkpoint.
557pub async fn check_all_boundary_then_new_append<S, C, F, Fut>(factory: &F)
558where
559    S: RawEventStore + WakeSource,
560    C: Send,
561    F: Fn() -> Fut + Send + Sync,
562    Fut: Future<Output = (S, C)> + Send,
563{
564    let (store, _guard) = factory().await;
565    let a = StreamKey::from_slice(b"a");
566    let b = StreamKey::from_slice(b"b");
567    append_event(&store, &a, 1, b"a1").await;
568    append_event(&store, &b, 1, b"b1").await;
569
570    let full = drain_all(&store, None).await;
571    assert_eq!(full.len(), 2);
572    let last = full.last().expect("non-empty").0;
573
574    let empty = drain_all(&store, Some(last)).await;
575    assert!(
576        empty.is_empty(),
577        "nothing is strictly after the last position"
578    );
579
580    append_event(&store, &a, 2, b"a2").await;
581    let after = drain_all(&store, Some(last)).await;
582    let payloads: Vec<Vec<u8>> = after.iter().map(|(_, p)| p.clone()).collect();
583    assert_eq!(
584        payloads,
585        vec![b"a2".to_vec()],
586        "same checkpoint surfaces exactly the new event"
587    );
588    assert!(
589        after[0].0 > last,
590        "new position must be strictly after the prior last"
591    );
592}
593
594/// Inclusive `read_stream` and exclusive `read_all` coexist on one store —
595/// the intentional asymmetry (CLAUDE rule 4).
596pub async fn check_read_stream_inclusive_read_all_exclusive_coexist<S, C, F, Fut>(factory: &F)
597where
598    S: RawEventStore + WakeSource,
599    C: Send,
600    F: Fn() -> Fut + Send + Sync,
601    Fut: Future<Output = (S, C)> + Send,
602{
603    let (store, _guard) = factory().await;
604    let a = StreamKey::from_slice(b"a");
605    append_event(&store, &a, 1, b"a1").await;
606    append_event(&store, &a, 2, b"a2").await;
607    append_event(&store, &a, 3, b"a3").await;
608
609    let got = drain_stream(&store, &a, Version::new(2).expect("v2")).await;
610    let versions: Vec<u64> = got.iter().map(|r| r.version).collect();
611    assert_eq!(versions, vec![2, 3], "read_stream(from=2) is INCLUSIVE");
612
613    let full = drain_all(&store, None).await;
614    assert_eq!(full.len(), 3);
615    let pos_of_a2 = full[1].0;
616    let after = drain_all(&store, Some(pos_of_a2)).await;
617    let payloads: Vec<Vec<u8>> = after.iter().map(|(_, p)| p.clone()).collect();
618    assert_eq!(
619        payloads,
620        vec![b"a3".to_vec()],
621        "read_all(from=pos(a2)) is EXCLUSIVE"
622    );
623}
624
625/// Take the next subscription item within `WAIT`, panicking on hang, stream
626/// end, or read error. Returns the `Step`.
627async fn next_step<St, T, E>(stream: &mut core::pin::Pin<&mut St>, what: &str) -> Step<T>
628where
629    St: futures::Stream<Item = Result<Step<T>, E>>,
630    E: core::fmt::Debug,
631{
632    timeout(WAIT, stream.next())
633        .await
634        .unwrap_or_else(|_| panic!("{what}: subscription hung (lost wake?)"))
635        .unwrap_or_else(|| panic!("{what}: subscription ended (must never return None)"))
636        .unwrap_or_else(|e| panic!("{what}: subscription item errored: {e:?}"))
637}
638
639/// Per-stream subscription protocol: backlog in order, then `CaughtUp`
640/// exactly once, then live events.
641pub async fn check_subscription_backlog_then_caught_up_then_live<S, C, F, Fut>(factory: &F)
642where
643    S: RawEventStore + WakeSource,
644    S::Stream: Unpin,
645    C: Send,
646    F: Fn() -> Fut + Send + Sync,
647    Fut: Future<Output = (S, C)> + Send,
648{
649    let (raw, _guard) = factory().await;
650    let store = raw.into_store();
651    let id = SubId::new("sub-proto");
652    for v in 1..=3u64 {
653        append_event(&store, &id.key(), v, format!("p{v}").as_bytes()).await;
654    }
655
656    let sub = Subscription::new(&store);
657    let stream = sub
658        .subscribe(&id, None)
659        .unwrap_or_else(|e| panic!("register failed: {e:?}"));
660    pin_mut!(stream);
661
662    for want in 1..=3u64 {
663        match next_step(&mut stream, "backlog").await {
664            Step::Event(env) => assert_eq!(
665                env.version().as_u64(),
666                want,
667                "backlog must replay in version order",
668            ),
669            Step::CaughtUp => panic!("CaughtUp before the backlog drained (at v{want})"),
670        }
671    }
672    match next_step(&mut stream, "boundary").await {
673        Step::CaughtUp => {}
674        Step::Event(env) => panic!(
675            "expected CaughtUp after backlog, got Event v{}",
676            env.version()
677        ),
678    }
679
680    // Live phase: an append after CaughtUp is delivered.
681    append_event(&store, &id.key(), 4, b"p4").await;
682    match next_step(&mut stream, "live").await {
683        Step::Event(env) => assert_eq!(env.version().as_u64(), 4, "live event must be v4"),
684        Step::CaughtUp => panic!("CaughtUp must be emitted exactly once"),
685    }
686}
687
688/// `subscribe(Some(v))` resumes STRICTLY AFTER `v` — no duplicate of the
689/// checkpointed event.
690pub async fn check_subscription_resume_strict_after<S, C, F, Fut>(factory: &F)
691where
692    S: RawEventStore + WakeSource,
693    S::Stream: Unpin,
694    C: Send,
695    F: Fn() -> Fut + Send + Sync,
696    Fut: Future<Output = (S, C)> + Send,
697{
698    let (raw, _guard) = factory().await;
699    let store = raw.into_store();
700    let id = SubId::new("sub-resume");
701    for v in 1..=5u64 {
702        append_event(&store, &id.key(), v, b"p").await;
703    }
704
705    let sub = Subscription::new(&store);
706    let stream = sub
707        .subscribe(&id, Some(Version::new(3).expect("v3")))
708        .unwrap_or_else(|e| panic!("register failed: {e:?}"));
709    pin_mut!(stream);
710
711    match next_step(&mut stream, "resume").await {
712        Step::Event(env) => assert_eq!(
713            env.version().as_u64(),
714            4,
715            "resume from Some(3) must deliver v4 first (strict-after, no dup)",
716        ),
717        Step::CaughtUp => panic!("expected v4 before CaughtUp"),
718    }
719}
720
721/// `$all` subscription protocol: cross-stream backlog in position order, then
722/// `CaughtUp` exactly once, then live events with strictly increasing tags.
723pub async fn check_subscription_all_backlog_then_caught_up_then_live<S, C, F, Fut>(factory: &F)
724where
725    S: RawEventStore + WakeSource,
726    S::AllStream: Unpin,
727    C: Send,
728    F: Fn() -> Fut + Send + Sync,
729    Fut: Future<Output = (S, C)> + Send,
730{
731    let (raw, _guard) = factory().await;
732    let store = raw.into_store();
733    let a = StreamKey::from_slice(b"a");
734    let b = StreamKey::from_slice(b"b");
735    append_event(&store, &a, 1, b"a1").await;
736    append_event(&store, &b, 1, b"b1").await;
737    append_event(&store, &a, 2, b"a2").await;
738
739    let sub = Subscription::new(&store);
740    let stream = sub
741        .subscribe_all(None)
742        .unwrap_or_else(|e| panic!("register failed: {e:?}"));
743    pin_mut!(stream);
744
745    let mut backlog: Vec<(S::AllPosition, Vec<u8>, Vec<u8>)> = Vec::new();
746    while let Step::Event((pos, key, env)) = next_step(&mut stream, "all backlog").await {
747        backlog.push((pos, key.as_bytes().to_vec(), env.payload().to_vec()));
748    }
749    let payloads: Vec<Vec<u8>> = backlog.iter().map(|(_, _, p)| p.clone()).collect();
750    assert_eq!(
751        payloads,
752        vec![b"a1".to_vec(), b"b1".to_vec(), b"a2".to_vec()],
753        "$all backlog must replay in position order",
754    );
755    let keys: Vec<Vec<u8>> = backlog.iter().map(|(_, k, _)| k.clone()).collect();
756    assert_eq!(
757        keys,
758        vec![b"a".to_vec(), b"b".to_vec(), b"a".to_vec()],
759        "$all backlog items must carry the StreamKey of their append target",
760    );
761    let positions: Vec<(S::AllPosition, Vec<u8>)> =
762        backlog.iter().map(|(p, _, _)| (*p, Vec::new())).collect();
763    assert_strictly_increasing(&positions);
764    let last = backlog.last().expect("non-empty").0;
765
766    append_event(&store, &b, 2, b"b2").await;
767    match next_step(&mut stream, "all live").await {
768        Step::Event((pos, key, env)) => {
769            assert_eq!(
770                env.payload(),
771                b"b2",
772                "live $all event must be the new append"
773            );
774            assert_eq!(
775                key.as_bytes(),
776                b"b",
777                "live $all item must carry the StreamKey of its append target",
778            );
779            assert!(
780                pos > last,
781                "live position must be strictly after the backlog"
782            );
783        }
784        Step::CaughtUp => panic!("CaughtUp must be emitted exactly once"),
785    }
786}
787
788/// Subscribing to a stream that does not exist yet parks (after `CaughtUp`)
789/// and is woken by the stream's FIRST event — the producer-after-consumer
790/// startup order must work.
791pub async fn check_subscription_absent_stream_waits_then_delivers<S, C, F, Fut>(factory: &F)
792where
793    S: RawEventStore + WakeSource,
794    S::Stream: Unpin,
795    C: Send,
796    F: Fn() -> Fut + Send + Sync,
797    Fut: Future<Output = (S, C)> + Send,
798{
799    let (raw, _guard) = factory().await;
800    let store = raw.into_store();
801    let id = SubId::new("ghost");
802
803    let sub = Subscription::new(&store);
804    let stream = sub
805        .subscribe(&id, None)
806        .unwrap_or_else(|e| panic!("register failed: {e:?}"));
807    pin_mut!(stream);
808
809    // An absent stream has an empty backlog: CaughtUp arrives first.
810    match next_step(&mut stream, "absent-stream boundary").await {
811        Step::CaughtUp => {}
812        Step::Event(env) => panic!("absent stream must have no backlog, got v{}", env.version()),
813    }
814
815    // The FIRST event ever written to the stream wakes the parked subscriber.
816    append_event(&store, &id.key(), 1, b"first").await;
817    match next_step(&mut stream, "absent-stream first event").await {
818        Step::Event(env) => {
819            assert_eq!(env.version().as_u64(), 1, "the first event must be v1");
820            assert_eq!(env.payload(), b"first");
821        }
822        Step::CaughtUp => panic!("CaughtUp must be emitted exactly once"),
823    }
824}
825
826/// Two simultaneous subscribers on ONE stream each receive the full event
827/// sequence — subscriptions are fan-out, never competing-consumer queues.
828pub async fn check_two_subscribers_same_stream_both_receive<S, C, F, Fut>(factory: &F)
829where
830    S: RawEventStore + WakeSource,
831    S::Stream: Unpin,
832    C: Send,
833    F: Fn() -> Fut + Send + Sync,
834    Fut: Future<Output = (S, C)> + Send,
835{
836    let (raw, _guard) = factory().await;
837    let store = raw.into_store();
838    let id = SubId::new("fanout");
839    append_event(&store, &id.key(), 1, b"p1").await;
840
841    let sub = Subscription::new(&store);
842    let stream_a = sub
843        .subscribe(&id, None)
844        .unwrap_or_else(|e| panic!("register a failed: {e:?}"));
845    let stream_b = sub
846        .subscribe(&id, None)
847        .unwrap_or_else(|e| panic!("register b failed: {e:?}"));
848    pin_mut!(stream_a);
849    pin_mut!(stream_b);
850
851    // Both drain the backlog and reach CaughtUp independently.
852    match next_step(&mut stream_a, "fanout backlog a").await {
853        Step::Event(env) => assert_eq!(env.version().as_u64(), 1, "subscriber a backlog"),
854        Step::CaughtUp => panic!("subscriber a: CaughtUp before backlog"),
855    }
856    match next_step(&mut stream_a, "fanout boundary a").await {
857        Step::CaughtUp => {}
858        Step::Event(env) => panic!("subscriber a: expected CaughtUp, got v{}", env.version()),
859    }
860    match next_step(&mut stream_b, "fanout backlog b").await {
861        Step::Event(env) => assert_eq!(env.version().as_u64(), 1, "subscriber b backlog"),
862        Step::CaughtUp => panic!("subscriber b: CaughtUp before backlog"),
863    }
864    match next_step(&mut stream_b, "fanout boundary b").await {
865        Step::CaughtUp => {}
866        Step::Event(env) => panic!("subscriber b: expected CaughtUp, got v{}", env.version()),
867    }
868
869    // One live append reaches BOTH subscribers.
870    append_event(&store, &id.key(), 2, b"p2").await;
871    match next_step(&mut stream_a, "fanout live a").await {
872        Step::Event(env) => assert_eq!(
873            env.version().as_u64(),
874            2,
875            "subscriber a must receive the live event — fan-out, not a queue",
876        ),
877        Step::CaughtUp => panic!("subscriber a: CaughtUp must be emitted exactly once"),
878    }
879    match next_step(&mut stream_b, "fanout live b").await {
880        Step::Event(env) => assert_eq!(
881            env.version().as_u64(),
882            2,
883            "subscriber b must receive the live event — fan-out, not a queue",
884        ),
885        Step::CaughtUp => panic!("subscriber b: CaughtUp must be emitted exactly once"),
886    }
887}
888
889/// A backlog larger than the catch-up chunk (1024) crosses the internal
890/// rescan seams with no gap or duplicate, and `CaughtUp` still arrives.
891pub async fn check_subscription_large_backlog_crosses_chunk_seam<S, C, F, Fut>(factory: &F)
892where
893    S: RawEventStore + WakeSource,
894    S::Stream: Unpin,
895    C: Send,
896    F: Fn() -> Fut + Send + Sync,
897    Fut: Future<Output = (S, C)> + Send,
898{
899    const N: u64 = 2500; // > 2 × CATCHUP_CHUNK (1024)
900    let (raw, _guard) = factory().await;
901    let store = raw.into_store();
902    let id = SubId::new("sub-chunk");
903    let rows: Vec<_> = (1..=N)
904        .map(|v| ConformanceRow::new(v, "E", vec![]))
905        .collect();
906    append_rows(&store, &id.key(), &rows).await;
907
908    let sub = Subscription::new(&store);
909    let stream = sub
910        .subscribe(&id, None)
911        .unwrap_or_else(|e| panic!("register failed: {e:?}"));
912    pin_mut!(stream);
913
914    let mut versions = Vec::with_capacity(usize::try_from(N).unwrap_or(usize::MAX));
915    while let Step::Event(env) = next_step(&mut stream, "chunk backlog").await {
916        versions.push(env.version().as_u64());
917    }
918    let want: Vec<u64> = (1..=N).collect();
919    assert_eq!(
920        versions, want,
921        "backlog across chunk seams must be exactly 1..=N — no gap, no duplicate",
922    );
923}
924
925/// A subscription opened beyond the head filters below-bound live appends.
926///
927/// `subscribe(Some(v))` with `v` past the current head parks after an empty
928/// backlog; live appends at versions **at or below** `v` wake the loop but
929/// must never be delivered — the first delivered event is `v + 1`. A loop or
930/// adapter that rescans from the wrong position after a below-bound wake
931/// would surface one of the filtered events here.
932pub async fn check_subscription_beyond_head_filters_below_bound<S, C, F, Fut>(factory: &F)
933where
934    S: RawEventStore + WakeSource,
935    S::Stream: Unpin,
936    C: Send,
937    F: Fn() -> Fut + Send + Sync,
938    Fut: Future<Output = (S, C)> + Send,
939{
940    let (raw, _guard) = factory().await;
941    let store = raw.into_store();
942    let id = SubId::new("beyond-head");
943    append_event(&store, &id.key(), 1, b"p1").await;
944    append_event(&store, &id.key(), 2, b"p2").await;
945
946    // Head is 2; subscribe strictly after 5 — the backlog is empty.
947    let sub = Subscription::new(&store);
948    let stream = sub
949        .subscribe(&id, Some(Version::new(5).expect("v5")))
950        .unwrap_or_else(|e| panic!("register failed: {e:?}"));
951    pin_mut!(stream);
952    match next_step(&mut stream, "beyond-head boundary").await {
953        Step::CaughtUp => {}
954        Step::Event(env) => panic!(
955            "subscribing beyond the head must have an empty backlog, got v{}",
956            env.version(),
957        ),
958    }
959
960    // Live appends at v3..=v5 are all <= from: each wakes the loop, none may
961    // be delivered. v6 is the first version strictly after `from`.
962    for v in 3..=6u64 {
963        append_event(&store, &id.key(), v, format!("p{v}").as_bytes()).await;
964    }
965    match next_step(&mut stream, "beyond-head first delivery").await {
966        Step::Event(env) => assert_eq!(
967            env.version().as_u64(),
968            6,
969            "the first delivered event must be from+1 (6) — below-bound live appends must be filtered, never delivered",
970        ),
971        Step::CaughtUp => panic!("CaughtUp must be emitted exactly once"),
972    }
973}