reifydb_flow_operator_sdk/testing/
assertions.rs1use reifydb_core::{
5 Row,
6 value::encoded::{EncodedKey, EncodedValuesLayout},
7};
8use reifydb_type::{RowNumber, Value};
9
10use crate::{FlowChange, FlowDiff, testing::TestStateStore};
11
12pub struct FlowChangeAssertion<'a> {
14 change: &'a FlowChange,
15}
16
17impl<'a> FlowChangeAssertion<'a> {
18 pub fn new(change: &'a FlowChange) -> Self {
20 Self {
21 change,
22 }
23 }
24
25 pub fn has_diffs(&self, count: usize) -> &Self {
27 assert_eq!(
28 self.change.diffs.len(),
29 count,
30 "Expected {} diffs, found {}",
31 count,
32 self.change.diffs.len()
33 );
34 self
35 }
36
37 pub fn is_empty(&self) -> &Self {
39 assert!(self.change.diffs.is_empty(), "Expected empty change, found {} diffs", self.change.diffs.len());
40 self
41 }
42
43 pub fn has_insert(&self) -> &Self {
45 let has_insert = self.change.diffs.iter().any(|d| matches!(d, FlowDiff::Insert { .. }));
46 assert!(has_insert, "Expected at least one insert diff");
47 self
48 }
49
50 pub fn has_update(&self) -> &Self {
52 let has_update = self.change.diffs.iter().any(|d| matches!(d, FlowDiff::Update { .. }));
53 assert!(has_update, "Expected at least one update diff");
54 self
55 }
56
57 pub fn has_remove(&self) -> &Self {
59 let has_remove = self.change.diffs.iter().any(|d| matches!(d, FlowDiff::Remove { .. }));
60 assert!(has_remove, "Expected at least one remove diff");
61 self
62 }
63
64 pub fn diff_at(&self, index: usize) -> DiffAssertion {
66 assert!(
67 index < self.change.diffs.len(),
68 "Diff index {} out of range (total: {})",
69 index,
70 self.change.diffs.len()
71 );
72 DiffAssertion::new(&self.change.diffs[index])
73 }
74
75 pub fn inserts(&self) -> Vec<&Row> {
77 self.change
78 .diffs
79 .iter()
80 .filter_map(|d| match d {
81 FlowDiff::Insert {
82 post,
83 } => Some(post),
84 _ => None,
85 })
86 .collect()
87 }
88
89 pub fn updates(&self) -> Vec<(&Row, &Row)> {
91 self.change
92 .diffs
93 .iter()
94 .filter_map(|d| match d {
95 FlowDiff::Update {
96 pre,
97 post,
98 } => Some((pre, post)),
99 _ => None,
100 })
101 .collect()
102 }
103
104 pub fn removes(&self) -> Vec<&Row> {
106 self.change
107 .diffs
108 .iter()
109 .filter_map(|d| match d {
110 FlowDiff::Remove {
111 pre,
112 } => Some(pre),
113 _ => None,
114 })
115 .collect()
116 }
117
118 pub fn has_inserts(&self, count: usize) -> &Self {
120 let actual = self.inserts().len();
121 assert_eq!(actual, count, "Expected {} inserts, found {}", count, actual);
122 self
123 }
124
125 pub fn has_updates(&self, count: usize) -> &Self {
127 let actual = self.updates().len();
128 assert_eq!(actual, count, "Expected {} updates, found {}", count, actual);
129 self
130 }
131
132 pub fn has_removes(&self, count: usize) -> &Self {
134 let actual = self.removes().len();
135 assert_eq!(actual, count, "Expected {} removes, found {}", count, actual);
136 self
137 }
138}
139
140pub struct DiffAssertion<'a> {
142 diff: &'a FlowDiff,
143}
144
145impl<'a> DiffAssertion<'a> {
146 pub fn new(diff: &'a FlowDiff) -> Self {
147 Self {
148 diff,
149 }
150 }
151
152 pub fn is_insert(&self) -> &Row {
154 match self.diff {
155 FlowDiff::Insert {
156 post,
157 } => post,
158 _ => panic!("Expected insert diff, found {:?}", self.diff),
159 }
160 }
161
162 pub fn is_update(&self) -> (&Row, &Row) {
164 match self.diff {
165 FlowDiff::Update {
166 pre,
167 post,
168 } => (pre, post),
169 _ => panic!("Expected update diff, found {:?}", self.diff),
170 }
171 }
172
173 pub fn is_remove(&self) -> &Row {
175 match self.diff {
176 FlowDiff::Remove {
177 pre,
178 } => pre,
179 _ => panic!("Expected remove diff, found {:?}", self.diff),
180 }
181 }
182}
183
184pub struct RowAssertion<'a> {
186 row: &'a Row,
187}
188
189impl<'a> RowAssertion<'a> {
190 pub fn new(row: &'a Row) -> Self {
192 Self {
193 row,
194 }
195 }
196
197 pub fn has_number(&self, number: impl Into<RowNumber>) -> &Self {
199 let expected = number.into();
200 assert_eq!(
201 self.row.number, expected,
202 "Expected row number {:?}, found {:?}",
203 expected, self.row.number
204 );
205 self
206 }
207
208 pub fn has_values(&self, expected: &[Value]) -> &Self {
210 let actual = super::helpers::get_values_named(&self.row.layout, &self.row.encoded);
211 assert_eq!(actual, expected, "Row values mismatch. Expected: {:?}, Actual: {:?}", expected, actual);
212 self
213 }
214
215 pub fn has_field(&self, field_name: &str, expected: Value) -> &Self {
217 let values = super::helpers::get_values_named(&self.row.layout, &self.row.encoded);
218 let field_index =
219 self.row.layout
220 .names()
221 .iter()
222 .position(|n| n.as_str() == field_name)
223 .expect(&format!("Field '{}' not found in layout", field_name));
224
225 assert_eq!(
226 values[field_index], expected,
227 "Field '{}' mismatch. Expected: {:?}, Actual: {:?}",
228 field_name, expected, values[field_index]
229 );
230 self
231 }
232
233 pub fn values(&self) -> Vec<Value> {
235 super::helpers::get_values_named(&self.row.layout, &self.row.encoded)
236 }
237}
238
239pub struct StateAssertion<'a> {
241 store: &'a TestStateStore,
242}
243
244impl<'a> StateAssertion<'a> {
245 pub fn new(store: &'a TestStateStore) -> Self {
247 Self {
248 store,
249 }
250 }
251
252 pub fn is_empty(&self) -> &Self {
254 assert!(self.store.is_empty(), "Expected empty state, found {} entries", self.store.len());
255 self
256 }
257
258 pub fn has_entries(&self, count: usize) -> &Self {
260 self.store.assert_count(count);
261 self
262 }
263
264 pub fn has_key(&self, key: &EncodedKey) -> &Self {
266 self.store.assert_exists(key);
267 self
268 }
269
270 pub fn not_has_key(&self, key: &EncodedKey) -> &Self {
272 self.store.assert_not_exists(key);
273 self
274 }
275
276 pub fn key_has_values(&self, key: &EncodedKey, expected: &[Value], layout: &EncodedValuesLayout) -> &Self {
278 self.store.assert_value(key, expected, layout);
279 self
280 }
281
282 pub fn all_keys<F>(&self, predicate: F) -> &Self
284 where
285 F: Fn(&EncodedKey) -> bool,
286 {
287 for key in self.store.keys() {
288 assert!(predicate(key), "Key {:?} did not match predicate", key);
289 }
290 self
291 }
292}
293
294pub trait Assertable {
296 type Assertion<'a>
297 where
298 Self: 'a;
299
300 fn assert(&self) -> Self::Assertion<'_>;
301}
302
303impl Assertable for FlowChange {
304 type Assertion<'a>
305 = FlowChangeAssertion<'a>
306 where
307 Self: 'a;
308
309 fn assert(&self) -> FlowChangeAssertion<'_> {
310 FlowChangeAssertion::new(self)
311 }
312}
313
314impl Assertable for Row {
315 type Assertion<'a>
316 = RowAssertion<'a>
317 where
318 Self: 'a;
319
320 fn assert(&self) -> RowAssertion<'_> {
321 RowAssertion::new(self)
322 }
323}
324
325impl Assertable for TestStateStore {
326 type Assertion<'a>
327 = StateAssertion<'a>
328 where
329 Self: 'a;
330
331 fn assert(&self) -> StateAssertion<'_> {
332 StateAssertion::new(self)
333 }
334}
335
336#[cfg(test)]
337mod tests {
338 use reifydb_type::Type;
339
340 use super::*;
341 use crate::testing::{
342 builders::{TestFlowChangeBuilder, TestRowBuilder},
343 helpers::encode_key,
344 };
345
346 #[test]
347 fn test_flow_change_assertions() {
348 let change = TestFlowChangeBuilder::new()
349 .insert_row(1, vec![Value::Int8(10i64)])
350 .update_row(2, vec![Value::Int8(20i64)], vec![Value::Int8(30i64)])
351 .remove_row(3, vec![Value::Int8(40i64)])
352 .build();
353
354 change.assert()
355 .has_diffs(3)
356 .has_insert()
357 .has_update()
358 .has_remove()
359 .has_inserts(1)
360 .has_updates(1)
361 .has_removes(1);
362
363 let change_assert = change.assert();
365 let diff_assert = change_assert.diff_at(0);
366 let insert_row = diff_assert.is_insert();
367 insert_row.assert().has_number(1).has_values(&[Value::Int8(10i64)]);
368 }
369
370 #[test]
371 fn test_row_assertions() {
372 let row = TestRowBuilder::new(42)
373 .with_values(vec![Value::Int8(100i64), Value::Utf8("test".into())])
374 .build();
375
376 row.assert().has_number(42).has_values(&[Value::Int8(100i64), Value::Utf8("test".into())]);
377
378 assert_eq!(row.assert().values().len(), 2);
379 }
380
381 #[test]
382 fn test_state_assertions() {
383 let mut store = TestStateStore::new();
384 let layout = EncodedValuesLayout::new(&[Type::Int8]);
385 let key1 = encode_key("key1");
386 let key2 = encode_key("key2");
387
388 store.set_value(key1.clone(), &[Value::Int8(10i64)], &layout);
389 store.set_value(key2.clone(), &[Value::Int8(20i64)], &layout);
390
391 store.assert()
392 .has_entries(2)
393 .has_key(&key1)
394 .has_key(&key2)
395 .key_has_values(&key1, &[Value::Int8(10i64)], &layout)
396 .all_keys(|k| k.0.len() == 6); }
398
399 #[test]
400 #[should_panic(expected = "Expected 5 diffs, found 1")]
401 fn test_assertion_failure() {
402 let change = TestFlowChangeBuilder::new().insert_row(1, vec![Value::Int8(10i64)]).build();
403
404 change.assert().has_diffs(5);
405 }
406}