Skip to main content

mnesis_store_testing/
row.rs

1//! Shared test-data row, id type, and drive/drain helpers used by every
2//! conformance module.
3
4use core::fmt;
5
6use bytes::Bytes;
7use futures::StreamExt;
8use futures::pin_mut;
9use mnesis::Version;
10use mnesis_store::StreamKey;
11use mnesis_store::envelope::{PendingEnvelope, PersistedEnvelope, pending_envelope};
12use mnesis_store::store::RawEventStore;
13use mnesis_store::value::SchemaVersion;
14
15/// One row of test data fed into an adapter for the conformance suite to
16/// observe back out. All fields must round-trip byte-for-byte.
17#[derive(Debug, Clone, PartialEq, Eq)]
18pub struct ConformanceRow {
19    pub version: u64,
20    pub event_type: String,
21    pub schema_version: u32,
22    pub payload: Vec<u8>,
23    pub metadata: Option<Vec<u8>>,
24}
25
26impl ConformanceRow {
27    /// Convenience constructor: `schema_version = 1`, no metadata.
28    #[must_use]
29    pub fn new(version: u64, event_type: &str, payload: Vec<u8>) -> Self {
30        Self {
31            version,
32            event_type: event_type.to_owned(),
33            schema_version: 1,
34            payload,
35            metadata: None,
36        }
37    }
38
39    /// Set the schema version (defaults to 1).
40    #[must_use]
41    pub const fn with_schema_version(mut self, schema_version: u32) -> Self {
42        self.schema_version = schema_version;
43        self
44    }
45
46    /// Attach metadata (defaults to absent).
47    ///
48    /// `metadata` must be non-empty — empty metadata is unrepresentable
49    /// (`ValueError::MetadataEmpty`); use absent instead.
50    #[must_use]
51    pub fn with_metadata(mut self, metadata: Vec<u8>) -> Self {
52        self.metadata = Some(metadata);
53        self
54    }
55}
56
57/// Subscription/snapshot id: satisfies the `Id` blanket bounds.
58#[derive(Debug, Clone, Hash, PartialEq, Eq)]
59pub struct SubId(String);
60
61impl SubId {
62    #[must_use]
63    pub fn new(s: &str) -> Self {
64        Self(s.to_owned())
65    }
66
67    /// The `StreamKey` carrying the same bytes — for driving `append` on the
68    /// stream this id subscribes to.
69    #[must_use]
70    pub fn key(&self) -> StreamKey {
71        StreamKey::from_slice(self.0.as_bytes())
72    }
73}
74
75impl fmt::Display for SubId {
76    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
77        f.write_str(&self.0)
78    }
79}
80
81impl AsRef<[u8]> for SubId {
82    fn as_ref(&self) -> &[u8] {
83        self.0.as_bytes()
84    }
85}
86
87/// Build the `PendingEnvelope` a row describes. Panics on invalid rows — the
88/// suite only constructs valid ones.
89#[must_use]
90pub fn envelope_for(row: &ConformanceRow) -> PendingEnvelope {
91    let version = Version::new(row.version).expect("row version must be >= 1");
92    let mut staged = pending_envelope(version)
93        .event_type_bytes(Bytes::from(row.event_type.clone().into_bytes()))
94        .expect("valid event type")
95        .payload(row.payload.clone())
96        .schema_version(
97            SchemaVersion::from_u32(row.schema_version).expect("schema_version must be >= 1"),
98        );
99    if let Some(m) = &row.metadata {
100        staged = staged.metadata(m.clone());
101    }
102    staged.build().expect("valid envelope")
103}
104
105/// Read a `PersistedEnvelope` back into row form.
106#[must_use]
107pub fn row_of(env: &PersistedEnvelope) -> ConformanceRow {
108    ConformanceRow {
109        version: env.version().as_u64(),
110        event_type: env.event_type().to_owned(),
111        schema_version: env.schema_version(),
112        payload: env.payload().to_vec(),
113        metadata: env.metadata().map(<[u8]>::to_vec),
114    }
115}
116
117/// Append `rows` to `id` as one batch on a fresh stream (`expected = None`).
118pub async fn append_rows<S: RawEventStore>(store: &S, id: &StreamKey, rows: &[ConformanceRow]) {
119    if rows.is_empty() {
120        return;
121    }
122    let envs: Vec<PendingEnvelope> = rows.iter().map(envelope_for).collect();
123    store
124        .append(id, None, &envs)
125        .await
126        .unwrap_or_else(|e| panic!("append of {} rows failed: {e:?}", rows.len()));
127}
128
129/// Append one event at `version` with the matching optimistic expectation
130/// (`None` for version 1). Panics on failure — callers drive clean sequences.
131pub async fn append_event<S: RawEventStore>(
132    store: &S,
133    id: &StreamKey,
134    version: u64,
135    payload: &[u8],
136) {
137    let expected = Version::new(version.saturating_sub(1));
138    let env = envelope_for(&ConformanceRow::new(version, "E", payload.to_vec()));
139    store
140        .append(id, expected, &[env])
141        .await
142        .unwrap_or_else(|e| panic!("append v{version} failed: {e:?}"));
143}
144
145/// Drain `read_stream(id, from)` fully into rows.
146pub async fn drain_stream<S: RawEventStore>(
147    store: &S,
148    id: &StreamKey,
149    from: Version,
150) -> Vec<ConformanceRow> {
151    let stream = store
152        .read_stream(id, from)
153        .await
154        .unwrap_or_else(|e| panic!("read_stream failed: {e:?}"));
155    pin_mut!(stream);
156    let mut out = Vec::new();
157    while let Some(item) = stream.next().await {
158        let env = item.unwrap_or_else(|e| panic!("read_stream item errored: {e:?}"));
159        out.push(row_of(&env));
160    }
161    out
162}
163
164/// Drain `read_all(from)` fully into `(position, payload)` pairs.
165pub async fn drain_all<S: RawEventStore>(
166    store: &S,
167    from: Option<S::AllPosition>,
168) -> Vec<(S::AllPosition, Vec<u8>)> {
169    let stream = store
170        .read_all(from)
171        .await
172        .unwrap_or_else(|e| panic!("read_all failed: {e:?}"));
173    pin_mut!(stream);
174    let mut out = Vec::new();
175    while let Some(item) = stream.next().await {
176        let (pos, env) = item.unwrap_or_else(|e| panic!("read_all item errored: {e:?}"));
177        out.push((pos, env.payload().to_vec()));
178    }
179    out
180}
181
182/// Assert positions strictly increase (monotonic, no duplicate).
183pub fn assert_strictly_increasing<P: Copy + Ord + fmt::Debug>(positions: &[(P, Vec<u8>)]) {
184    for w in positions.windows(2) {
185        assert!(
186            w[1].0 > w[0].0,
187            "$all positions must be strictly increasing: {:?} then {:?}",
188            w[0].0,
189            w[1].0,
190        );
191    }
192}