1use core::future::Future;
5
6use mnesis::Version;
7use mnesis_store::StreamKey;
8use mnesis_store::import::{AtomicAppend, AtomicAppendError, PlannedAppend};
9use mnesis_store::wake::WakeSource;
10use crate::row::{
14 ConformanceRow, append_rows, assert_strictly_increasing, drain_all, drain_stream, envelope_for,
15};
16
17pub async fn check_atomic_multi_stream_commits_all<S, C, F, Fut>(factory: &F)
19where
20 S: AtomicAppend + WakeSource,
21 C: Send,
22 F: Fn() -> Fut + Send + Sync,
23 Fut: Future<Output = (S, C)> + Send,
24{
25 let (store, _guard) = factory().await;
26 let existing = StreamKey::from_slice(b"existing");
27 append_rows(&store, &existing, &[ConformanceRow::new(1, "E", vec![0])]).await;
28
29 let writes = vec![
30 PlannedAppend {
31 target: StreamKey::from_slice(b"fresh-a"),
32 expected_version: None,
33 head: envelope_for(&ConformanceRow::new(1, "E", vec![1])),
34 tail: Vec::new(),
35 },
36 PlannedAppend {
37 target: StreamKey::from_slice(b"fresh-b"),
38 expected_version: None,
39 head: envelope_for(&ConformanceRow::new(1, "E", vec![2])),
40 tail: vec![envelope_for(&ConformanceRow::new(2, "E", vec![3]))],
41 },
42 PlannedAppend {
43 target: existing.clone(),
44 expected_version: Version::new(1),
45 head: envelope_for(&ConformanceRow::new(2, "E", vec![4])),
46 tail: Vec::new(),
47 },
48 ];
49 let committed = store
50 .atomic_append_many(&writes)
51 .await
52 .unwrap_or_else(|e| panic!("atomic append must succeed: {e:?}"));
53
54 assert_eq!(
55 drain_stream(&store, &StreamKey::from_slice(b"fresh-a"), Version::INITIAL)
56 .await
57 .len(),
58 1
59 );
60 assert_eq!(
61 drain_stream(&store, &StreamKey::from_slice(b"fresh-b"), Version::INITIAL)
62 .await
63 .len(),
64 2
65 );
66 assert_eq!(
67 drain_stream(&store, &existing, Version::INITIAL)
68 .await
69 .len(),
70 2
71 );
72 let all = drain_all(&store, None).await;
73 assert_eq!(all.len(), 5, "$all must hold every committed event");
74 assert_strictly_increasing(&all);
75
76 let highest = all
80 .iter()
81 .map(|(pos, _)| *pos)
82 .max()
83 .expect("the batch committed events");
84 assert_eq!(
85 committed,
86 Some(highest),
87 "atomic_append_many must return the highest position it committed"
88 );
89}
90
91pub async fn check_atomic_conflict_aborts_all<S, C, F, Fut>(factory: &F)
94where
95 S: AtomicAppend + WakeSource,
96 C: Send,
97 F: Fn() -> Fut + Send + Sync,
98 Fut: Future<Output = (S, C)> + Send,
99{
100 let (store, _guard) = factory().await;
101 let existing = StreamKey::from_slice(b"existing");
102 append_rows(&store, &existing, &[ConformanceRow::new(1, "E", vec![0])]).await;
103 let all_before = drain_all(&store, None).await;
104
105 let writes = vec![
106 PlannedAppend {
107 target: StreamKey::from_slice(b"fresh-a"),
108 expected_version: None,
109 head: envelope_for(&ConformanceRow::new(1, "E", vec![1])),
110 tail: Vec::new(),
111 },
112 PlannedAppend {
113 target: existing.clone(),
115 expected_version: None,
116 head: envelope_for(&ConformanceRow::new(1, "E", vec![9])),
117 tail: Vec::new(),
118 },
119 ];
120 let err = store
121 .atomic_append_many(&writes)
122 .await
123 .expect_err("a conflicting run must abort the batch");
124 match err {
125 AtomicAppendError::Conflict { index, actual } => {
126 assert_eq!(index, 1, "the error must name the offending write");
127 assert_eq!(
128 actual,
129 Version::new(1),
130 "the error must carry the actual head"
131 );
132 }
133 other => panic!("expected Conflict, got {other:?}"),
134 }
135
136 let fresh = drain_stream(&store, &StreamKey::from_slice(b"fresh-a"), Version::INITIAL).await;
137 assert!(
138 fresh.is_empty(),
139 "NOTHING may land on any stream of an aborted batch"
140 );
141 let all_after = drain_all(&store, None).await;
142 assert_eq!(
143 all_after.len(),
144 all_before.len(),
145 "$all must be untouched by an aborted batch"
146 );
147}
148
149pub async fn check_atomic_empty_batch_is_noop<S, C, F, Fut>(factory: &F)
151where
152 S: AtomicAppend + WakeSource,
153 C: Send,
154 F: Fn() -> Fut + Send + Sync,
155 Fut: Future<Output = (S, C)> + Send,
156{
157 let (store, _guard) = factory().await;
158 let committed = store
159 .atomic_append_many(&[])
160 .await
161 .unwrap_or_else(|e| panic!("empty atomic batch must be Ok: {e:?}"));
162 assert_eq!(
163 committed, None,
164 "an empty batch commits nothing, so there is no position to return"
165 );
166 assert!(
167 drain_all(&store, None).await.is_empty(),
168 "empty batch must write nothing"
169 );
170}