openzeppelin_monitor/utils/tests/builders/stellar/
monitor.rs

1//! Test helper utilities for the EVM Monitor
2//!
3//! - `MonitorBuilder`: Builder for creating test Monitor instances
4
5use crate::models::{
6	AddressWithSpec, ContractSpec, EventCondition, FunctionCondition, MatchConditions, Monitor,
7	ScriptLanguage, TransactionCondition, TransactionStatus, TriggerConditions,
8};
9
10/// Builder for creating test Monitor instances
11pub struct MonitorBuilder {
12	name: String,
13	networks: Vec<String>,
14	paused: bool,
15	addresses: Vec<AddressWithSpec>,
16	match_conditions: MatchConditions,
17	trigger_conditions: Vec<TriggerConditions>,
18	triggers: Vec<String>,
19}
20
21impl Default for MonitorBuilder {
22	fn default() -> Self {
23		Self {
24			name: "TestMonitor".to_string(),
25			networks: vec!["stellar_mainnet".to_string()],
26			paused: false,
27			addresses: vec![AddressWithSpec {
28				address: "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF".to_string(),
29				contract_spec: None,
30			}],
31			match_conditions: MatchConditions {
32				functions: vec![],
33				events: vec![],
34				transactions: vec![],
35			},
36			trigger_conditions: vec![],
37			triggers: vec![],
38		}
39	}
40}
41
42impl MonitorBuilder {
43	pub fn new() -> Self {
44		Self::default()
45	}
46
47	pub fn name(mut self, name: &str) -> Self {
48		self.name = name.to_string();
49		self
50	}
51
52	pub fn networks(mut self, networks: Vec<String>) -> Self {
53		self.networks = networks;
54		self
55	}
56
57	pub fn paused(mut self, paused: bool) -> Self {
58		self.paused = paused;
59		self
60	}
61
62	pub fn address(mut self, address: &str) -> Self {
63		self.addresses = vec![AddressWithSpec {
64			address: address.to_string(),
65			contract_spec: None,
66		}];
67		self
68	}
69
70	pub fn addresses(mut self, addresses: Vec<String>) -> Self {
71		self.addresses = addresses
72			.into_iter()
73			.map(|addr| AddressWithSpec {
74				address: addr,
75				contract_spec: None,
76			})
77			.collect();
78		self
79	}
80
81	pub fn add_address(mut self, address: &str) -> Self {
82		self.addresses.push(AddressWithSpec {
83			address: address.to_string(),
84			contract_spec: None,
85		});
86		self
87	}
88
89	pub fn address_with_spec(mut self, address: &str, spec: ContractSpec) -> Self {
90		self.addresses = vec![AddressWithSpec {
91			address: address.to_string(),
92			contract_spec: Some(spec),
93		}];
94		self
95	}
96
97	pub fn addresses_with_spec(mut self, addresses: Vec<(String, Option<ContractSpec>)>) -> Self {
98		self.addresses = addresses
99			.into_iter()
100			.map(|(addr, spec)| AddressWithSpec {
101				address: addr.to_string(),
102				contract_spec: spec,
103			})
104			.collect();
105		self
106	}
107
108	pub fn function(mut self, signature: &str, expression: Option<String>) -> Self {
109		self.match_conditions.functions.push(FunctionCondition {
110			signature: signature.to_string(),
111			expression,
112		});
113		self
114	}
115
116	pub fn event(mut self, signature: &str, expression: Option<String>) -> Self {
117		self.match_conditions.events.push(EventCondition {
118			signature: signature.to_string(),
119			expression,
120		});
121		self
122	}
123
124	pub fn transaction(mut self, status: TransactionStatus, expression: Option<String>) -> Self {
125		self.match_conditions
126			.transactions
127			.push(TransactionCondition { status, expression });
128		self
129	}
130
131	pub fn trigger_condition(
132		mut self,
133		script_path: &str,
134		timeout_ms: u32,
135		language: ScriptLanguage,
136		arguments: Option<Vec<String>>,
137	) -> Self {
138		self.trigger_conditions.push(TriggerConditions {
139			script_path: script_path.to_string(),
140			timeout_ms,
141			arguments,
142			language,
143		});
144		self
145	}
146
147	pub fn triggers(mut self, triggers: Vec<String>) -> Self {
148		self.triggers = triggers;
149		self
150	}
151
152	pub fn match_conditions(mut self, match_conditions: MatchConditions) -> Self {
153		self.match_conditions = match_conditions;
154		self
155	}
156
157	pub fn build(self) -> Monitor {
158		Monitor {
159			name: self.name,
160			networks: self.networks,
161			paused: self.paused,
162			addresses: self.addresses,
163			match_conditions: self.match_conditions,
164			trigger_conditions: self.trigger_conditions,
165			triggers: self.triggers,
166		}
167	}
168}
169
170#[cfg(test)]
171mod tests {
172	use crate::models::StellarContractSpec;
173
174	use super::*;
175	use serde_json::json;
176
177	#[test]
178	fn test_default_monitor() {
179		let monitor = MonitorBuilder::new().build();
180
181		assert_eq!(monitor.name, "TestMonitor");
182		assert_eq!(monitor.networks, vec!["stellar_mainnet"]);
183		assert!(!monitor.paused);
184		assert_eq!(monitor.addresses.len(), 1);
185		assert_eq!(
186			monitor.addresses[0].address,
187			"GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF"
188		);
189		assert!(monitor.addresses[0].contract_spec.is_none());
190		assert!(monitor.match_conditions.functions.is_empty());
191		assert!(monitor.match_conditions.events.is_empty());
192		assert!(monitor.match_conditions.transactions.is_empty());
193		assert!(monitor.trigger_conditions.is_empty());
194		assert!(monitor.triggers.is_empty());
195	}
196
197	#[test]
198	fn test_basic_builder_methods() {
199		let monitor = MonitorBuilder::new()
200			.name("MyMonitor")
201			.networks(vec!["stellar_testnet".to_string()])
202			.paused(true)
203			.address("GBXGQJWVLWOYHFLVTKWV5FGHA3LNYY2JQKM7OAJAUEQFU6LPCSEFVXON")
204			.build();
205
206		assert_eq!(monitor.name, "MyMonitor");
207		assert_eq!(monitor.networks, vec!["stellar_testnet"]);
208		assert!(monitor.paused);
209		assert_eq!(monitor.addresses.len(), 1);
210		assert_eq!(
211			monitor.addresses[0].address,
212			"GBXGQJWVLWOYHFLVTKWV5FGHA3LNYY2JQKM7OAJAUEQFU6LPCSEFVXON"
213		);
214	}
215
216	#[test]
217	fn test_address_methods() {
218		let monitor = MonitorBuilder::new()
219			.addresses(vec![
220				"GBXGQJWVLWOYHFLVTKWV5FGHA3LNYY2JQKM7OAJAUEQFU6LPCSEFVXON".to_string(),
221				"GCXKMKSWKQYYGTJA7KKTHG3ICTYR7VCXKCO7SBTBHADS45TVCJ4O6NA".to_string(),
222			])
223			.add_address("GC5SXLNAM3C4NMGK2PXK4R34B5GNZ47FYQ24ZIBFDFOCU6D4KBN4POAE")
224			.build();
225
226		assert_eq!(monitor.addresses.len(), 3);
227		assert_eq!(
228			monitor.addresses[0].address,
229			"GBXGQJWVLWOYHFLVTKWV5FGHA3LNYY2JQKM7OAJAUEQFU6LPCSEFVXON"
230		);
231		assert_eq!(
232			monitor.addresses[1].address,
233			"GCXKMKSWKQYYGTJA7KKTHG3ICTYR7VCXKCO7SBTBHADS45TVCJ4O6NA"
234		);
235		assert_eq!(
236			monitor.addresses[2].address,
237			"GC5SXLNAM3C4NMGK2PXK4R34B5GNZ47FYQ24ZIBFDFOCU6D4KBN4POAE"
238		);
239	}
240
241	#[test]
242	fn test_address_with_abi() {
243		let abi = json!({"some": "abi"});
244		let monitor = MonitorBuilder::new()
245			.address_with_spec(
246				"GBXGQJWVLWOYHFLVTKWV5FGHA3LNYY2JQKM7OAJAUEQFU6LPCSEFVXON",
247				ContractSpec::Stellar(StellarContractSpec::from(abi.clone())),
248			)
249			.build();
250
251		assert_eq!(monitor.addresses.len(), 1);
252		assert_eq!(
253			monitor.addresses[0].address,
254			"GBXGQJWVLWOYHFLVTKWV5FGHA3LNYY2JQKM7OAJAUEQFU6LPCSEFVXON"
255		);
256		assert_eq!(
257			monitor.addresses[0].contract_spec,
258			Some(ContractSpec::Stellar(StellarContractSpec::from(abi)))
259		);
260	}
261
262	#[test]
263	fn test_addresses_with_abi() {
264		let abi1 = json!({"contract_spec": "1"});
265		let abi2 = json!({"contract_spec": "2"});
266		let monitor = MonitorBuilder::new()
267			.addresses_with_spec(vec![
268				(
269					"GBXGQJWVLWOYHFLVTKWV5FGHA3LNYY2JQKM7OAJAUEQFU6LPCSEFVXON".to_string(),
270					Some(ContractSpec::Stellar(StellarContractSpec::from(
271						abi1.clone(),
272					))),
273				),
274				(
275					"GCXKMKSWKQYYGTJA7KKTHG3ICTYR7VCXKCO7SBTBHADS45TVCJ4O6NA".to_string(),
276					None,
277				),
278				(
279					"GC5SXLNAM3C4NMGK2PXK4R34B5GNZ47FYQ24ZIBFDFOCU6D4KBN4POAE".to_string(),
280					Some(ContractSpec::Stellar(StellarContractSpec::from(
281						abi2.clone(),
282					))),
283				),
284			])
285			.build();
286
287		assert_eq!(monitor.addresses.len(), 3);
288		assert_eq!(
289			monitor.addresses[0].address,
290			"GBXGQJWVLWOYHFLVTKWV5FGHA3LNYY2JQKM7OAJAUEQFU6LPCSEFVXON"
291		);
292		assert_eq!(
293			monitor.addresses[0].contract_spec,
294			Some(ContractSpec::Stellar(StellarContractSpec::from(abi1)))
295		);
296		assert_eq!(
297			monitor.addresses[1].address,
298			"GCXKMKSWKQYYGTJA7KKTHG3ICTYR7VCXKCO7SBTBHADS45TVCJ4O6NA"
299		);
300		assert_eq!(monitor.addresses[1].contract_spec, None);
301		assert_eq!(
302			monitor.addresses[2].address,
303			"GC5SXLNAM3C4NMGK2PXK4R34B5GNZ47FYQ24ZIBFDFOCU6D4KBN4POAE"
304		);
305		assert_eq!(
306			monitor.addresses[2].contract_spec,
307			Some(ContractSpec::Stellar(StellarContractSpec::from(abi2)))
308		);
309	}
310
311	#[test]
312	fn test_match_conditions() {
313		let monitor = MonitorBuilder::new()
314			.function(
315				"transfer(to:address,amount:i128)",
316				Some("amount >= 0".to_string()),
317			)
318			.event("transfer_event(from:address,to:address,amount:i128)", None)
319			.transaction(TransactionStatus::Success, None)
320			.build();
321
322		assert_eq!(monitor.match_conditions.functions.len(), 1);
323		assert_eq!(
324			monitor.match_conditions.functions[0].signature,
325			"transfer(to:address,amount:i128)"
326		);
327		assert_eq!(
328			monitor.match_conditions.functions[0].expression,
329			Some("amount >= 0".to_string())
330		);
331		assert_eq!(monitor.match_conditions.events.len(), 1);
332		assert_eq!(
333			monitor.match_conditions.events[0].signature,
334			"transfer_event(from:address,to:address,amount:i128)"
335		);
336		assert_eq!(monitor.match_conditions.transactions.len(), 1);
337		assert_eq!(
338			monitor.match_conditions.transactions[0].status,
339			TransactionStatus::Success
340		);
341	}
342
343	#[test]
344	fn test_match_condition() {
345		let monitor = MonitorBuilder::new()
346			.match_conditions(MatchConditions {
347				functions: vec![FunctionCondition {
348					signature: "transfer(to:address,amount:i128)".to_string(),
349					expression: None,
350				}],
351				events: vec![],
352				transactions: vec![],
353			})
354			.build();
355		assert_eq!(monitor.match_conditions.functions.len(), 1);
356		assert_eq!(
357			monitor.match_conditions.functions[0].signature,
358			"transfer(to:address,amount:i128)"
359		);
360		assert!(monitor.match_conditions.events.is_empty());
361		assert!(monitor.match_conditions.transactions.is_empty());
362	}
363
364	#[test]
365	fn test_trigger_conditions() {
366		let monitor = MonitorBuilder::new()
367			.trigger_condition("script.py", 1000, ScriptLanguage::Python, None)
368			.trigger_condition(
369				"script.js",
370				2000,
371				ScriptLanguage::JavaScript,
372				Some(vec!["-verbose".to_string()]),
373			)
374			.build();
375
376		assert_eq!(monitor.trigger_conditions.len(), 2);
377		assert_eq!(monitor.trigger_conditions[0].script_path, "script.py");
378		assert_eq!(monitor.trigger_conditions[0].timeout_ms, 1000);
379		assert_eq!(
380			monitor.trigger_conditions[0].language,
381			ScriptLanguage::Python
382		);
383		assert_eq!(monitor.trigger_conditions[1].script_path, "script.js");
384		assert_eq!(monitor.trigger_conditions[1].timeout_ms, 2000);
385		assert_eq!(
386			monitor.trigger_conditions[1].language,
387			ScriptLanguage::JavaScript
388		);
389		assert_eq!(
390			monitor.trigger_conditions[1].arguments,
391			Some(vec!["-verbose".to_string()])
392		);
393	}
394
395	#[test]
396	fn test_triggers() {
397		let monitor = MonitorBuilder::new()
398			.triggers(vec!["trigger1".to_string(), "trigger2".to_string()])
399			.build();
400
401		assert_eq!(monitor.triggers.len(), 2);
402		assert_eq!(monitor.triggers[0], "trigger1");
403		assert_eq!(monitor.triggers[1], "trigger2");
404	}
405
406	#[test]
407	fn test_complex_monitor_build() {
408		let abi = json!({
409			"functions": [
410				{
411					"name": "transfer",
412					"parameters": [
413						{"name": "to", "type": "address"},
414						{"name": "amount", "type": "i128"}
415					]
416				}
417			]
418		});
419		let monitor = MonitorBuilder::new()
420			.name("ComplexMonitor")
421			.networks(vec![
422				"stellar_mainnet".to_string(),
423				"stellar_testnet".to_string(),
424			])
425			.paused(true)
426			.addresses(vec![
427				"GBXGQJWVLWOYHFLVTKWV5FGHA3LNYY2JQKM7OAJAUEQFU6LPCSEFVXON".to_string(),
428				"GCXKMKSWKQYYGTJA7KKTHG3ICTYR7VCXKCO7SBTBHADS45TVCJ4O6NA".to_string(),
429			])
430			.add_address("GC5SXLNAM3C4NMGK2PXK4R34B5GNZ47FYQ24ZIBFDFOCU6D4KBN4POAE")
431			.address_with_spec(
432				"GBXGQJWVLWOYHFLVTKWV5FGHA3LNYY2JQKM7OAJAUEQFU6LPCSEFVXON",
433				ContractSpec::Stellar(StellarContractSpec::from(abi.clone())),
434			)
435			.function(
436				"transfer(to:address,amount:i128)",
437				Some("amount >= 0".to_string()),
438			)
439			.event("transfer_event(from:address,to:address,amount:i128)", None)
440			.transaction(TransactionStatus::Success, None)
441			.trigger_condition("script.py", 1000, ScriptLanguage::Python, None)
442			.triggers(vec!["trigger1".to_string(), "trigger2".to_string()])
443			.build();
444
445		// Verify final state
446		assert_eq!(monitor.name, "ComplexMonitor");
447		assert_eq!(monitor.networks, vec!["stellar_mainnet", "stellar_testnet"]);
448		assert!(monitor.paused);
449		assert_eq!(monitor.addresses.len(), 1); // address_with_abi overwrites previous addresses
450		assert_eq!(
451			monitor.addresses[0].address,
452			"GBXGQJWVLWOYHFLVTKWV5FGHA3LNYY2JQKM7OAJAUEQFU6LPCSEFVXON"
453		);
454		assert_eq!(
455			monitor.addresses[0].contract_spec,
456			Some(ContractSpec::Stellar(StellarContractSpec::from(abi)))
457		);
458		assert_eq!(monitor.match_conditions.functions.len(), 1);
459		assert_eq!(
460			monitor.match_conditions.functions[0].expression,
461			Some("amount >= 0".to_string())
462		);
463		assert_eq!(monitor.match_conditions.events.len(), 1);
464		assert_eq!(monitor.match_conditions.transactions.len(), 1);
465		assert_eq!(monitor.trigger_conditions.len(), 1);
466		assert_eq!(monitor.triggers.len(), 2);
467	}
468}