reifydb_flow_operator_sdk/testing/
state.rs

1// Copyright (c) reifydb.com 2025
2// This file is licensed under the AGPL-3.0-or-later, see license.md file
3
4use std::collections::HashMap;
5
6use reifydb_core::value::encoded::{EncodedKey, EncodedValues, EncodedValuesLayout, EncodedValuesNamedLayout};
7use reifydb_type::Value;
8
9/// Mock state store for testing operators
10#[derive(Debug, Clone, Default)]
11pub struct TestStateStore {
12	data: HashMap<EncodedKey, EncodedValues>,
13}
14
15impl TestStateStore {
16	/// Create a new empty mock state store
17	pub fn new() -> Self {
18		Self {
19			data: HashMap::new(),
20		}
21	}
22
23	/// Get a value from the store
24	pub fn get(&self, key: &EncodedKey) -> Option<&EncodedValues> {
25		self.data.get(key)
26	}
27
28	/// Set a value in the store
29	pub fn set(&mut self, key: EncodedKey, value: EncodedValues) {
30		self.data.insert(key, value);
31	}
32
33	/// Remove a value from the store
34	pub fn remove(&mut self, key: &EncodedKey) -> Option<EncodedValues> {
35		self.data.remove(key)
36	}
37
38	/// Check if a key exists
39	pub fn contains(&self, key: &EncodedKey) -> bool {
40		self.data.contains_key(key)
41	}
42
43	/// Get the number of entries
44	pub fn len(&self) -> usize {
45		self.data.len()
46	}
47
48	/// Check if the store is empty
49	pub fn is_empty(&self) -> bool {
50		self.data.is_empty()
51	}
52
53	/// Clear all entries
54	pub fn clear(&mut self) {
55		self.data.clear();
56	}
57
58	/// Get all keys
59	pub fn keys(&self) -> Vec<&EncodedKey> {
60		self.data.keys().collect()
61	}
62
63	/// Get all key-value pairs
64	pub fn entries(&self) -> Vec<(&EncodedKey, &EncodedValues)> {
65		self.data.iter().map(|(k, v)| (k, v)).collect()
66	}
67
68	/// Decode a value using a layout
69	pub fn decode_value(&self, key: &EncodedKey, layout: &EncodedValuesLayout) -> Option<Vec<Value>> {
70		self.get(key).map(|encoded| super::helpers::get_values(layout, encoded))
71	}
72
73	/// Decode a value using a named layout
74	pub fn decode_named_value(
75		&self,
76		key: &EncodedKey,
77		layout: &EncodedValuesNamedLayout,
78	) -> Option<HashMap<String, Value>> {
79		self.get(key).map(|encoded| {
80			let values = super::helpers::get_values(layout.layout(), encoded);
81			layout.names().iter().map(|n| n.as_str().to_string()).zip(values).collect()
82		})
83	}
84
85	/// Set a value using a layout
86	pub fn set_value(&mut self, key: EncodedKey, values: &[Value], layout: &EncodedValuesLayout) {
87		let mut encoded = layout.allocate();
88		layout.set_values(&mut encoded, values);
89		self.set(key, encoded);
90	}
91
92	/// Set a value using a named layout
93	pub fn set_named_value(
94		&mut self,
95		key: EncodedKey,
96		values: &HashMap<String, Value>,
97		layout: &EncodedValuesNamedLayout,
98	) {
99		let mut encoded = layout.layout().allocate();
100
101		// Convert HashMap to ordered values based on layout names
102		let ordered_values: Vec<Value> = layout
103			.names()
104			.iter()
105			.map(|name| values.get(name.as_str()).cloned().unwrap_or(Value::Undefined))
106			.collect();
107
108		layout.layout().set_values(&mut encoded, &ordered_values);
109		self.set(key, encoded);
110	}
111
112	/// Create a snapshot of the current state
113	pub fn snapshot(&self) -> HashMap<EncodedKey, EncodedValues> {
114		self.data.clone()
115	}
116
117	/// Restore from a snapshot
118	pub fn restore(&mut self, snapshot: HashMap<EncodedKey, EncodedValues>) {
119		self.data = snapshot;
120	}
121
122	/// Assert that a key has a specific value
123	pub fn assert_value(&self, key: &EncodedKey, expected: &[Value], layout: &EncodedValuesLayout) {
124		let actual = self.decode_value(key, layout).expect(&format!("Key {:?} not found in state", key));
125		assert_eq!(actual, expected, "State value mismatch for key {:?}", key);
126	}
127
128	/// Assert that a key exists
129	pub fn assert_exists(&self, key: &EncodedKey) {
130		assert!(self.contains(key), "Expected key {:?} to exist in state", key);
131	}
132
133	/// Assert that a key does not exist
134	pub fn assert_not_exists(&self, key: &EncodedKey) {
135		assert!(!self.contains(key), "Expected key {:?} to not exist in state", key);
136	}
137
138	/// Assert the state has a specific number of entries
139	pub fn assert_count(&self, expected: usize) {
140		assert_eq!(self.len(), expected, "Expected {} entries in state, found {}", expected, self.len());
141	}
142}
143
144#[cfg(test)]
145mod tests {
146	use reifydb_type::Type;
147
148	use super::*;
149	use crate::testing::helpers::encode_key;
150
151	#[test]
152	fn test_state_store_basic_operations() {
153		use reifydb_core::CowVec;
154		let mut store = TestStateStore::new();
155		let key = encode_key("test_key");
156		let value = EncodedValues(CowVec::new(vec![1, 2, 3, 4]));
157
158		assert!(store.is_empty());
159
160		store.set(key.clone(), value.clone());
161		assert_eq!(store.get(&key), Some(&value));
162		assert!(store.contains(&key));
163		assert_eq!(store.len(), 1);
164
165		let removed = store.remove(&key);
166		assert_eq!(removed, Some(value));
167		assert!(store.is_empty());
168	}
169
170	#[test]
171	fn test_state_store_with_layout() {
172		let mut store = TestStateStore::new();
173		let layout = EncodedValuesLayout::new(&[Type::Int8, Type::Utf8]);
174		let key = encode_key("test_key");
175		let values = vec![Value::Int8(42i64), Value::Utf8("hello".into())];
176
177		store.set_value(key.clone(), &values, &layout);
178
179		let decoded = store.decode_value(&key, &layout).unwrap();
180		assert_eq!(decoded, values);
181	}
182
183	#[test]
184	fn test_state_store_with_named_layout() {
185		let mut store = TestStateStore::new();
186		let layout = EncodedValuesNamedLayout::new(vec![
187			("count".to_string(), Type::Int8),
188			("name".to_string(), Type::Utf8),
189		]);
190		let key = encode_key("test_key");
191
192		let mut values = HashMap::new();
193		values.insert("count".to_string(), Value::Int8(10i64));
194		values.insert("name".to_string(), Value::Utf8("test".into()));
195
196		store.set_named_value(key.clone(), &values, &layout);
197
198		let decoded = store.decode_named_value(&key, &layout).unwrap();
199		assert_eq!(decoded, values);
200	}
201
202	#[test]
203	fn test_state_store_snapshot_and_restore() {
204		use reifydb_core::CowVec;
205		let mut store = TestStateStore::new();
206		let key1 = encode_key("key1");
207		let key2 = encode_key("key2");
208
209		store.set(key1.clone(), EncodedValues(CowVec::new(vec![1])));
210		store.set(key2.clone(), EncodedValues(CowVec::new(vec![2])));
211
212		let snapshot = store.snapshot();
213		assert_eq!(snapshot.len(), 2);
214
215		store.clear();
216		assert!(store.is_empty());
217
218		store.restore(snapshot);
219		assert_eq!(store.len(), 2);
220		assert_eq!(store.get(&key1), Some(&EncodedValues(CowVec::new(vec![1]))));
221		assert_eq!(store.get(&key2), Some(&EncodedValues(CowVec::new(vec![2]))));
222	}
223
224	#[test]
225	fn test_state_store_assertions() {
226		let mut store = TestStateStore::new();
227		let layout = EncodedValuesLayout::new(&[Type::Int8]);
228		let key = encode_key("test_key");
229		let values = vec![Value::Int8(100i64)];
230
231		store.set_value(key.clone(), &values, &layout);
232
233		store.assert_exists(&key);
234		store.assert_value(&key, &values, &layout);
235		store.assert_count(1);
236
237		let missing_key = encode_key("missing");
238		store.assert_not_exists(&missing_key);
239	}
240}