1use 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
21const WAIT: Duration = Duration::from_secs(10);
24
25pub 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
42pub 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
76pub 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
117pub 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
141pub 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
164pub 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 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
208pub 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 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 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
240pub 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
256pub 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
290pub 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_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
337pub 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 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
382pub 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
422pub 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
455pub 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
486pub 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
555pub 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
594pub 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
625async 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
639pub 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 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
688pub 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
721pub 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
788pub 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 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 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
826pub 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 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 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
889pub 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; 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
925pub 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 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 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}