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

1//! Test helper utilities for Solana Monitor
2//!
3//! - `MonitorBuilder`: Builder for creating test Monitor instances with Solana configuration
4
5use crate::models::{
6	AddressWithSpec, ChainConfiguration, ContractSpec, EventCondition, FunctionCondition,
7	MatchConditions, Monitor, ScriptLanguage, SolanaMonitorConfig, TransactionCondition,
8	TransactionStatus, TriggerConditions,
9};
10
11/// Builder for creating test Monitor instances with Solana configuration
12pub struct MonitorBuilder {
13	name: String,
14	networks: Vec<String>,
15	paused: bool,
16	addresses: Vec<AddressWithSpec>,
17	match_conditions: MatchConditions,
18	trigger_conditions: Vec<TriggerConditions>,
19	triggers: Vec<String>,
20	chain_configurations: Vec<ChainConfiguration>,
21}
22
23impl Default for MonitorBuilder {
24	fn default() -> Self {
25		Self {
26			name: "TestSolanaMonitor".to_string(),
27			networks: vec!["solana_mainnet".to_string()],
28			paused: false,
29			addresses: vec![AddressWithSpec {
30				address: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA".to_string(),
31				contract_spec: None,
32			}],
33			match_conditions: MatchConditions {
34				functions: vec![],
35				events: vec![],
36				transactions: vec![],
37			},
38			trigger_conditions: vec![],
39			triggers: vec![],
40			chain_configurations: vec![ChainConfiguration {
41				solana: Some(SolanaMonitorConfig::default()),
42				..Default::default()
43			}],
44		}
45	}
46}
47
48impl MonitorBuilder {
49	pub fn new() -> Self {
50		Self::default()
51	}
52
53	pub fn name(mut self, name: &str) -> Self {
54		self.name = name.to_string();
55		self
56	}
57
58	pub fn networks(mut self, networks: Vec<String>) -> Self {
59		self.networks = networks;
60		self
61	}
62
63	pub fn paused(mut self, paused: bool) -> Self {
64		self.paused = paused;
65		self
66	}
67
68	pub fn address(mut self, address: &str) -> Self {
69		self.addresses = vec![AddressWithSpec {
70			address: address.to_string(),
71			contract_spec: None,
72		}];
73		self
74	}
75
76	pub fn addresses(mut self, addresses: Vec<String>) -> Self {
77		self.addresses = addresses
78			.into_iter()
79			.map(|addr| AddressWithSpec {
80				address: addr,
81				contract_spec: None,
82			})
83			.collect();
84		self
85	}
86
87	pub fn add_address(mut self, address: &str) -> Self {
88		self.addresses.push(AddressWithSpec {
89			address: address.to_string(),
90			contract_spec: None,
91		});
92		self
93	}
94
95	pub fn address_with_spec(mut self, address: &str, spec: Option<ContractSpec>) -> Self {
96		self.addresses = vec![AddressWithSpec {
97			address: address.to_string(),
98			contract_spec: spec,
99		}];
100		self
101	}
102
103	pub fn addresses_with_spec(mut self, addresses: Vec<(String, Option<ContractSpec>)>) -> Self {
104		self.addresses = addresses
105			.into_iter()
106			.map(|(addr, spec)| AddressWithSpec {
107				address: addr,
108				contract_spec: spec,
109			})
110			.collect();
111		self
112	}
113
114	/// Adds a function (instruction) condition for Solana
115	pub fn function(mut self, signature: &str, expression: Option<String>) -> Self {
116		self.match_conditions.functions.push(FunctionCondition {
117			signature: signature.to_string(),
118			expression,
119		});
120		self
121	}
122
123	/// Adds an event (log) condition for Solana
124	pub fn event(mut self, signature: &str, expression: Option<String>) -> Self {
125		self.match_conditions.events.push(EventCondition {
126			signature: signature.to_string(),
127			expression,
128		});
129		self
130	}
131
132	pub fn transaction(mut self, status: TransactionStatus, expression: Option<String>) -> Self {
133		self.match_conditions
134			.transactions
135			.push(TransactionCondition { status, expression });
136		self
137	}
138
139	pub fn trigger_condition(
140		mut self,
141		script_path: &str,
142		timeout_ms: u32,
143		language: ScriptLanguage,
144		arguments: Option<Vec<String>>,
145	) -> Self {
146		self.trigger_conditions.push(TriggerConditions {
147			script_path: script_path.to_string(),
148			timeout_ms,
149			arguments,
150			language,
151		});
152		self
153	}
154
155	pub fn triggers(mut self, triggers: Vec<String>) -> Self {
156		self.triggers = triggers;
157		self
158	}
159
160	pub fn match_conditions(mut self, match_conditions: MatchConditions) -> Self {
161		self.match_conditions = match_conditions;
162		self
163	}
164
165	pub fn build(self) -> Monitor {
166		Monitor {
167			name: self.name,
168			networks: self.networks,
169			paused: self.paused,
170			addresses: self.addresses,
171			match_conditions: self.match_conditions,
172			trigger_conditions: self.trigger_conditions,
173			triggers: self.triggers,
174			chain_configurations: self.chain_configurations,
175		}
176	}
177}
178
179#[cfg(test)]
180mod tests {
181	use super::*;
182
183	#[test]
184	fn test_default_solana_monitor() {
185		let monitor = MonitorBuilder::new().build();
186
187		assert_eq!(monitor.name, "TestSolanaMonitor");
188		assert_eq!(monitor.networks, vec!["solana_mainnet"]);
189		assert!(!monitor.paused);
190		assert_eq!(monitor.addresses.len(), 1);
191		assert_eq!(
192			monitor.addresses[0].address,
193			"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
194		);
195		assert!(monitor.addresses[0].contract_spec.is_none());
196		assert!(monitor.match_conditions.functions.is_empty());
197		assert!(monitor.match_conditions.events.is_empty());
198		assert!(monitor.match_conditions.transactions.is_empty());
199		assert!(monitor.trigger_conditions.is_empty());
200		assert!(monitor.triggers.is_empty());
201		// Verify Solana chain configuration is set
202		assert!(monitor.chain_configurations[0].solana.is_some());
203		assert!(monitor.chain_configurations[0].evm.is_none());
204	}
205
206	#[test]
207	fn test_basic_builder_methods() {
208		let monitor = MonitorBuilder::new()
209			.name("MySolanaMonitor")
210			.networks(vec!["solana_devnet".to_string()])
211			.paused(true)
212			.address("11111111111111111111111111111111")
213			.build();
214
215		assert_eq!(monitor.name, "MySolanaMonitor");
216		assert_eq!(monitor.networks, vec!["solana_devnet"]);
217		assert!(monitor.paused);
218		assert_eq!(monitor.addresses.len(), 1);
219		assert_eq!(
220			monitor.addresses[0].address,
221			"11111111111111111111111111111111"
222		);
223	}
224
225	#[test]
226	fn test_address_methods() {
227		let monitor = MonitorBuilder::new()
228			.addresses(vec![
229				"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA".to_string(),
230				"11111111111111111111111111111111".to_string(),
231			])
232			.add_address("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL")
233			.build();
234
235		assert_eq!(monitor.addresses.len(), 3);
236		assert_eq!(
237			monitor.addresses[0].address,
238			"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
239		);
240		assert_eq!(
241			monitor.addresses[1].address,
242			"11111111111111111111111111111111"
243		);
244		assert_eq!(
245			monitor.addresses[2].address,
246			"ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"
247		);
248	}
249
250	#[test]
251	fn test_match_conditions() {
252		let monitor = MonitorBuilder::new()
253			.function("transfer", Some("amount > 1000000".to_string()))
254			.event("TransferEvent", None)
255			.transaction(TransactionStatus::Success, None)
256			.build();
257
258		assert_eq!(monitor.match_conditions.functions.len(), 1);
259		assert_eq!(monitor.match_conditions.functions[0].signature, "transfer");
260		assert_eq!(
261			monitor.match_conditions.functions[0].expression,
262			Some("amount > 1000000".to_string())
263		);
264		assert_eq!(monitor.match_conditions.events.len(), 1);
265		assert_eq!(
266			monitor.match_conditions.events[0].signature,
267			"TransferEvent"
268		);
269		assert_eq!(monitor.match_conditions.transactions.len(), 1);
270		assert_eq!(
271			monitor.match_conditions.transactions[0].status,
272			TransactionStatus::Success
273		);
274	}
275
276	#[test]
277	fn test_trigger_conditions() {
278		let monitor = MonitorBuilder::new()
279			.trigger_condition("script.py", 1000, ScriptLanguage::Python, None)
280			.trigger_condition(
281				"script.js",
282				2000,
283				ScriptLanguage::JavaScript,
284				Some(vec!["-verbose".to_string()]),
285			)
286			.build();
287
288		assert_eq!(monitor.trigger_conditions.len(), 2);
289		assert_eq!(monitor.trigger_conditions[0].script_path, "script.py");
290		assert_eq!(monitor.trigger_conditions[0].timeout_ms, 1000);
291		assert_eq!(
292			monitor.trigger_conditions[0].language,
293			ScriptLanguage::Python
294		);
295		assert_eq!(monitor.trigger_conditions[1].script_path, "script.js");
296		assert_eq!(monitor.trigger_conditions[1].timeout_ms, 2000);
297		assert_eq!(
298			monitor.trigger_conditions[1].language,
299			ScriptLanguage::JavaScript
300		);
301		assert_eq!(
302			monitor.trigger_conditions[1].arguments,
303			Some(vec!["-verbose".to_string()])
304		);
305	}
306
307	#[test]
308	fn test_triggers() {
309		let monitor = MonitorBuilder::new()
310			.triggers(vec!["trigger1".to_string(), "trigger2".to_string()])
311			.build();
312
313		assert_eq!(monitor.triggers.len(), 2);
314		assert_eq!(monitor.triggers[0], "trigger1");
315		assert_eq!(monitor.triggers[1], "trigger2");
316	}
317}