reifydb_flow_operator_sdk/testing/
context.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::{
5	collections::HashMap,
6	sync::{Arc, Mutex},
7};
8
9use reifydb_core::{
10	CommitVersion,
11	value::encoded::{EncodedKey, EncodedValues},
12};
13
14use crate::OperatorContext;
15
16/// Mock implementation of OperatorContext for testing
17#[derive(Clone)]
18pub struct TestContext {
19	state_store: Arc<Mutex<HashMap<EncodedKey, EncodedValues>>>,
20	version: CommitVersion,
21	logs: Arc<Mutex<Vec<String>>>,
22}
23
24impl TestContext {
25	/// Create a new test context with the given version
26	pub fn new(version: CommitVersion) -> Self {
27		Self {
28			state_store: Arc::new(Mutex::new(HashMap::new())),
29			version,
30			logs: Arc::new(Mutex::new(Vec::new())),
31		}
32	}
33
34	/// Create a new mock context with version 1
35	pub fn default() -> Self {
36		Self::new(CommitVersion(1))
37	}
38
39	/// Get a reference to the internal state store for inspection
40	pub fn state_store(&self) -> &Arc<Mutex<HashMap<EncodedKey, EncodedValues>>> {
41		&self.state_store
42	}
43
44	/// Get all captured log messages
45	pub fn logs(&self) -> Vec<String> {
46		self.logs.lock().unwrap().clone()
47	}
48
49	/// Clear all captured logs
50	pub fn clear_logs(&self) {
51		self.logs.lock().unwrap().clear();
52	}
53
54	/// Get the current version
55	pub fn version(&self) -> CommitVersion {
56		self.version
57	}
58
59	/// Set the current version
60	pub fn set_version(&mut self, version: CommitVersion) {
61		self.version = version;
62	}
63
64	/// Get state value by key
65	pub fn get_state(&self, key: &EncodedKey) -> Option<Vec<u8>> {
66		self.state_store.lock().unwrap().get(key).map(|v| v.0.to_vec())
67	}
68
69	/// Set state value
70	pub fn set_state(&self, key: EncodedKey, value: Vec<u8>) {
71		use reifydb_core::CowVec;
72		self.state_store.lock().unwrap().insert(key, EncodedValues(CowVec::new(value)));
73	}
74
75	/// Remove state value
76	pub fn remove_state(&self, key: &EncodedKey) -> Option<Vec<u8>> {
77		self.state_store.lock().unwrap().remove(key).map(|v| v.0.to_vec())
78	}
79
80	/// Check if a key exists in state
81	pub fn has_state(&self, key: &EncodedKey) -> bool {
82		self.state_store.lock().unwrap().contains_key(key)
83	}
84
85	/// Get the number of state entries
86	pub fn state_count(&self) -> usize {
87		self.state_store.lock().unwrap().len()
88	}
89
90	/// Clear all state
91	pub fn clear_state(&self) {
92		self.state_store.lock().unwrap().clear();
93	}
94
95	/// Get all state keys
96	pub fn state_keys(&self) -> Vec<EncodedKey> {
97		self.state_store.lock().unwrap().keys().cloned().collect()
98	}
99
100	/// Create an OperatorContext from this test context
101	///
102	/// # Status: NOT IMPLEMENTED
103	///
104	/// This method is currently a placeholder and will panic if called.
105	/// To implement this, we would need to:
106	/// 1. Create FFI callbacks that bridge TestContext with OperatorContext
107	/// 2. Extend OperatorContext to support a testing/mock mode
108	///
109	/// **Note**: This functionality is not required for the current testing infrastructure.
110	/// Most testing can be done using the builders, assertions, and stateful helpers
111	/// without needing to create an OperatorContext.
112	///
113	/// # Panics
114	///
115	/// This method will always panic with "not implemented".
116	pub fn as_operator_context(&self) -> OperatorContext {
117		todo!("Implement test OperatorContext creation - requires FFI callback bridging")
118	}
119}
120
121#[cfg(test)]
122mod tests {
123	use super::*;
124	use crate::testing::helpers::encode_key;
125
126	#[test]
127	fn test_context_state_operations() {
128		let ctx = TestContext::default();
129		let key = encode_key("test_key");
130		let value = vec![1, 2, 3];
131
132		// Test set and get
133		ctx.set_state(key.clone(), value.clone());
134		assert_eq!(ctx.get_state(&key), Some(value.clone()));
135		assert!(ctx.has_state(&key));
136
137		// Test remove
138		let removed = ctx.remove_state(&key);
139		assert_eq!(removed, Some(value));
140		assert!(!ctx.has_state(&key));
141		assert_eq!(ctx.get_state(&key), None);
142	}
143
144	#[test]
145	fn test_context_logs() {
146		let ctx = TestContext::default();
147
148		// Simulate logging (would be done through callbacks in real usage)
149		ctx.logs.lock().unwrap().push("Log 1".to_string());
150		ctx.logs.lock().unwrap().push("Log 2".to_string());
151
152		let logs = ctx.logs();
153		assert_eq!(logs.len(), 2);
154		assert_eq!(logs[0], "Log 1");
155		assert_eq!(logs[1], "Log 2");
156
157		ctx.clear_logs();
158		assert_eq!(ctx.logs().len(), 0);
159	}
160
161	#[test]
162	fn test_context_state_inspection() {
163		let ctx = TestContext::default();
164
165		ctx.set_state(encode_key("key1"), vec![1]);
166		ctx.set_state(encode_key("key2"), vec![2]);
167		ctx.set_state(encode_key("key3"), vec![3]);
168
169		assert_eq!(ctx.state_count(), 3);
170
171		let keys = ctx.state_keys();
172		assert_eq!(keys.len(), 3);
173
174		ctx.clear_state();
175		assert_eq!(ctx.state_count(), 0);
176	}
177}