openzeppelin_monitor/utils/tests/builders/
trigger.rs

1//! Test helper utilities for Trigger configuration
2//!
3//! - `TriggerBuilder`: Builder for creating test Trigger instances
4
5use crate::{
6	models::{
7		NotificationMessage, ScriptLanguage, SecretString, SecretValue, Trigger, TriggerType,
8		TriggerTypeConfig,
9	},
10	utils::RetryConfig,
11};
12use email_address::EmailAddress;
13
14/// Builder for creating test Trigger instances
15pub struct TriggerBuilder {
16	name: String,
17	trigger_type: TriggerType,
18	config: TriggerTypeConfig,
19}
20
21impl Default for TriggerBuilder {
22	fn default() -> Self {
23		Self {
24			name: "test_trigger".to_string(),
25			trigger_type: TriggerType::Webhook,
26			config: TriggerTypeConfig::Webhook {
27				url: SecretValue::Plain(SecretString::new(
28					"https://api.example.com/webhook".to_string(),
29				)),
30				secret: None,
31				method: Some("POST".to_string()),
32				headers: None,
33				message: NotificationMessage {
34					title: "Alert".to_string(),
35					body: "Test message".to_string(),
36				},
37				retry_policy: RetryConfig::default(),
38			},
39		}
40	}
41}
42
43impl TriggerBuilder {
44	pub fn new() -> Self {
45		Self::default()
46	}
47
48	pub fn name(mut self, name: &str) -> Self {
49		self.name = name.to_string();
50		self
51	}
52
53	pub fn config(mut self, config: TriggerTypeConfig) -> Self {
54		self.config = config;
55		self
56	}
57
58	pub fn webhook(mut self, url: &str) -> Self {
59		self.trigger_type = TriggerType::Webhook;
60		self.config = TriggerTypeConfig::Webhook {
61			url: SecretValue::Plain(SecretString::new(url.to_string())),
62			secret: None,
63			method: Some("POST".to_string()),
64			headers: None,
65			message: NotificationMessage {
66				title: "Alert".to_string(),
67				body: "Test message".to_string(),
68			},
69			retry_policy: RetryConfig::default(),
70		};
71		self
72	}
73
74	pub fn slack(mut self, webhook_url: &str) -> Self {
75		self.trigger_type = TriggerType::Slack;
76		self.config = TriggerTypeConfig::Slack {
77			slack_url: SecretValue::Plain(SecretString::new(webhook_url.to_string())),
78			message: NotificationMessage {
79				title: "Alert".to_string(),
80				body: "Test message".to_string(),
81			},
82			retry_policy: RetryConfig::default(),
83		};
84		self
85	}
86
87	pub fn discord(mut self, webhook_url: &str) -> Self {
88		self.trigger_type = TriggerType::Discord;
89		self.config = TriggerTypeConfig::Discord {
90			discord_url: SecretValue::Plain(SecretString::new(webhook_url.to_string())),
91			message: NotificationMessage {
92				title: "Alert".to_string(),
93				body: "Test message".to_string(),
94			},
95			retry_policy: RetryConfig::default(),
96		};
97		self
98	}
99
100	pub fn telegram(mut self, token: &str, chat_id: &str, disable_web_preview: bool) -> Self {
101		self.trigger_type = TriggerType::Telegram;
102		self.config = TriggerTypeConfig::Telegram {
103			token: SecretValue::Plain(SecretString::new(token.to_string())),
104			chat_id: chat_id.to_string(),
105			disable_web_preview: Some(disable_web_preview),
106			message: NotificationMessage {
107				title: "Test title".to_string(),
108				body: "Test message".to_string(),
109			},
110			retry_policy: RetryConfig::default(),
111		};
112		self
113	}
114
115	pub fn telegram_token(mut self, token: SecretValue) -> Self {
116		if let TriggerTypeConfig::Telegram { token: t, .. } = &mut self.config {
117			*t = token;
118		}
119		self
120	}
121
122	pub fn script(mut self, script_path: &str, language: ScriptLanguage) -> Self {
123		self.trigger_type = TriggerType::Script;
124		self.config = TriggerTypeConfig::Script {
125			script_path: script_path.to_string(),
126			arguments: None,
127			language,
128			timeout_ms: 1000,
129		};
130		self
131	}
132
133	pub fn script_arguments(mut self, arguments: Vec<String>) -> Self {
134		if let TriggerTypeConfig::Script { arguments: a, .. } = &mut self.config {
135			*a = Some(arguments);
136		}
137		self
138	}
139
140	pub fn script_timeout_ms(mut self, timeout_ms: u32) -> Self {
141		if let TriggerTypeConfig::Script { timeout_ms: t, .. } = &mut self.config {
142			*t = timeout_ms;
143		}
144		self
145	}
146
147	pub fn message(mut self, title: &str, body: &str) -> Self {
148		match &mut self.config {
149			TriggerTypeConfig::Webhook { message, .. }
150			| TriggerTypeConfig::Slack { message, .. }
151			| TriggerTypeConfig::Discord { message, .. }
152			| TriggerTypeConfig::Telegram { message, .. }
153			| TriggerTypeConfig::Email { message, .. } => {
154				message.title = title.to_string();
155				message.body = body.to_string();
156			}
157			_ => {}
158		}
159		self
160	}
161
162	pub fn trigger_type(mut self, trigger_type: TriggerType) -> Self {
163		self.trigger_type = trigger_type;
164		self
165	}
166
167	pub fn email(
168		mut self,
169		host: &str,
170		username: &str,
171		password: &str,
172		sender: &str,
173		recipients: Vec<&str>,
174	) -> Self {
175		self.trigger_type = TriggerType::Email;
176		self.config = TriggerTypeConfig::Email {
177			host: host.to_string(),
178			port: Some(587),
179			username: SecretValue::Plain(SecretString::new(username.to_string())),
180			password: SecretValue::Plain(SecretString::new(password.to_string())),
181			message: NotificationMessage {
182				title: "Test Subject".to_string(),
183				body: "Test Body".to_string(),
184			},
185			sender: EmailAddress::new_unchecked(sender),
186			recipients: recipients
187				.into_iter()
188				.map(EmailAddress::new_unchecked)
189				.collect(),
190			retry_policy: RetryConfig::default(),
191		};
192		self
193	}
194
195	pub fn email_port(mut self, port: u16) -> Self {
196		if let TriggerTypeConfig::Email { port: p, .. } = &mut self.config {
197			*p = Some(port);
198		}
199		self
200	}
201
202	pub fn email_subject(mut self, subject: &str) -> Self {
203		if let TriggerTypeConfig::Email { message, .. } = &mut self.config {
204			message.title = subject.to_string();
205		}
206		self
207	}
208
209	pub fn email_username(mut self, username: SecretValue) -> Self {
210		if let TriggerTypeConfig::Email { username: u, .. } = &mut self.config {
211			*u = username;
212		}
213		self
214	}
215
216	pub fn email_password(mut self, password: SecretValue) -> Self {
217		if let TriggerTypeConfig::Email { password: p, .. } = &mut self.config {
218			*p = password;
219		}
220		self
221	}
222
223	pub fn webhook_method(mut self, method: &str) -> Self {
224		if let TriggerTypeConfig::Webhook { method: m, .. } = &mut self.config {
225			*m = Some(method.to_string());
226		}
227		self
228	}
229
230	pub fn webhook_secret(mut self, secret: SecretValue) -> Self {
231		if let TriggerTypeConfig::Webhook { secret: s, .. } = &mut self.config {
232			*s = Some(secret);
233		}
234		self
235	}
236
237	pub fn webhook_headers(mut self, headers: std::collections::HashMap<String, String>) -> Self {
238		if let TriggerTypeConfig::Webhook { headers: h, .. } = &mut self.config {
239			*h = Some(headers);
240		}
241		self
242	}
243
244	pub fn url(mut self, url: SecretValue) -> Self {
245		self.config = match self.config {
246			TriggerTypeConfig::Webhook {
247				url: _,
248				method,
249				headers,
250				secret,
251				message,
252				retry_policy,
253			} => TriggerTypeConfig::Webhook {
254				url,
255				method,
256				headers,
257				secret,
258				message,
259				retry_policy,
260			},
261			TriggerTypeConfig::Discord {
262				discord_url: _,
263				message,
264				retry_policy,
265			} => TriggerTypeConfig::Discord {
266				discord_url: url,
267				message,
268				retry_policy,
269			},
270			TriggerTypeConfig::Slack {
271				slack_url: _,
272				message,
273				retry_policy,
274			} => TriggerTypeConfig::Slack {
275				slack_url: url,
276				message,
277				retry_policy,
278			},
279			config => config,
280		};
281		self
282	}
283
284	pub fn build(self) -> Trigger {
285		Trigger {
286			name: self.name,
287			trigger_type: self.trigger_type,
288			config: self.config,
289		}
290	}
291}
292
293#[cfg(test)]
294mod tests {
295	use super::*;
296
297	#[test]
298	fn test_default_trigger() {
299		let trigger = TriggerBuilder::new().build();
300
301		assert_eq!(trigger.name, "test_trigger");
302		assert_eq!(trigger.trigger_type, TriggerType::Webhook);
303
304		match trigger.config {
305			TriggerTypeConfig::Webhook { url, method, .. } => {
306				assert_eq!(url.as_ref().to_string(), "https://api.example.com/webhook");
307				assert_eq!(method, Some("POST".to_string()));
308			}
309			_ => panic!("Expected webhook config"),
310		}
311	}
312
313	#[test]
314	fn test_trigger_with_config() {
315		let trigger = TriggerBuilder::new()
316			.name("my_trigger")
317			.config(TriggerTypeConfig::Webhook {
318				url: SecretValue::Plain(SecretString::new(
319					"https://api.example.com/webhook".to_string(),
320				)),
321				secret: Some(SecretValue::Plain(SecretString::new("secret".to_string()))),
322				method: Some("POST".to_string()),
323				headers: None,
324				message: NotificationMessage {
325					title: "Alert".to_string(),
326					body: "Test message".to_string(),
327				},
328				retry_policy: RetryConfig::default(),
329			})
330			.build();
331
332		assert_eq!(trigger.name, "my_trigger");
333		assert_eq!(trigger.trigger_type, TriggerType::Webhook);
334
335		match trigger.config {
336			TriggerTypeConfig::Webhook { url, method, .. } => {
337				assert_eq!(url.as_ref().to_string(), "https://api.example.com/webhook");
338				assert_eq!(method, Some("POST".to_string()));
339			}
340			_ => panic!("Expected webhook config"),
341		}
342	}
343
344	#[test]
345	fn test_webhook_trigger() {
346		let trigger = TriggerBuilder::new()
347			.name("my_webhook")
348			.webhook("https://webhook.example.com")
349			.message("Custom Alert", "Something happened!")
350			.build();
351
352		assert_eq!(trigger.name, "my_webhook");
353		assert_eq!(trigger.trigger_type, TriggerType::Webhook);
354
355		match trigger.config {
356			TriggerTypeConfig::Webhook { url, message, .. } => {
357				assert_eq!(url.as_ref().to_string(), "https://webhook.example.com");
358				assert_eq!(message.title, "Custom Alert");
359				assert_eq!(message.body, "Something happened!");
360			}
361			_ => panic!("Expected webhook config"),
362		}
363	}
364
365	#[test]
366	fn test_webhook_trigger_with_config() {
367		let mut headers = std::collections::HashMap::new();
368		headers.insert("Content-Type".to_string(), "application/json".to_string());
369
370		let trigger = TriggerBuilder::new()
371			.name("my_webhook")
372			.webhook("https://webhook.example.com")
373			.webhook_method("POST")
374			.webhook_secret(SecretValue::Plain(SecretString::new(
375				"secret123".to_string(),
376			)))
377			.webhook_headers(headers.clone())
378			.message("Custom Alert", "Something happened!")
379			.build();
380
381		assert_eq!(trigger.name, "my_webhook");
382		assert_eq!(trigger.trigger_type, TriggerType::Webhook);
383
384		match trigger.config {
385			TriggerTypeConfig::Webhook {
386				url,
387				method,
388				secret,
389				headers: h,
390				message,
391				retry_policy: _,
392			} => {
393				assert_eq!(url.as_ref().to_string(), "https://webhook.example.com");
394				assert_eq!(method, Some("POST".to_string()));
395				assert_eq!(
396					secret.as_ref().map(|s| s.as_ref().to_string()),
397					Some("secret123".to_string())
398				);
399				assert_eq!(h, Some(headers));
400				assert_eq!(message.title, "Custom Alert");
401				assert_eq!(message.body, "Something happened!");
402			}
403			_ => panic!("Expected webhook config"),
404		}
405	}
406
407	#[test]
408	fn test_slack_trigger() {
409		let trigger = TriggerBuilder::new()
410			.name("slack_alert")
411			.slack("https://slack.webhook.com")
412			.message("Alert", "Test message")
413			.build();
414
415		assert_eq!(trigger.trigger_type, TriggerType::Slack);
416		match trigger.config {
417			TriggerTypeConfig::Slack {
418				slack_url,
419				message,
420				retry_policy: _,
421			} => {
422				assert_eq!(slack_url.as_ref().to_string(), "https://slack.webhook.com");
423				assert_eq!(message.title, "Alert");
424				assert_eq!(message.body, "Test message");
425			}
426			_ => panic!("Expected slack config"),
427		}
428	}
429
430	#[test]
431	fn test_discord_trigger() {
432		let trigger = TriggerBuilder::new()
433			.name("discord_alert")
434			.discord("https://discord.webhook.com")
435			.message("Alert", "Test message")
436			.build();
437
438		assert_eq!(trigger.trigger_type, TriggerType::Discord);
439		match trigger.config {
440			TriggerTypeConfig::Discord {
441				discord_url,
442				message,
443				retry_policy: _,
444			} => {
445				assert_eq!(
446					discord_url.as_ref().to_string(),
447					"https://discord.webhook.com"
448				);
449				assert_eq!(message.title, "Alert");
450				assert_eq!(message.body, "Test message");
451			}
452			_ => panic!("Expected discord config"),
453		}
454	}
455
456	#[test]
457	fn test_script_trigger() {
458		let trigger = TriggerBuilder::new()
459			.name("script_trigger")
460			.script("test.py", ScriptLanguage::Python)
461			.build();
462
463		assert_eq!(trigger.trigger_type, TriggerType::Script);
464		match trigger.config {
465			TriggerTypeConfig::Script {
466				script_path,
467				language,
468				timeout_ms,
469				..
470			} => {
471				assert_eq!(script_path, "test.py");
472				assert_eq!(language, ScriptLanguage::Python);
473				assert_eq!(timeout_ms, 1000);
474			}
475			_ => panic!("Expected script config"),
476		}
477	}
478
479	#[test]
480	fn test_script_trigger_with_arguments() {
481		let trigger = TriggerBuilder::new()
482			.name("script_trigger")
483			.script("test.py", ScriptLanguage::Python)
484			.script_arguments(vec!["arg1".to_string()])
485			.build();
486
487		assert_eq!(trigger.trigger_type, TriggerType::Script);
488		match trigger.config {
489			TriggerTypeConfig::Script { arguments, .. } => {
490				assert_eq!(arguments, Some(vec!["arg1".to_string()]));
491			}
492			_ => panic!("Expected script config"),
493		}
494	}
495
496	#[test]
497	fn test_script_trigger_with_timeout() {
498		let trigger = TriggerBuilder::new()
499			.name("script_trigger")
500			.script("test.py", ScriptLanguage::Python)
501			.script_timeout_ms(2000)
502			.build();
503
504		assert_eq!(trigger.trigger_type, TriggerType::Script);
505		match trigger.config {
506			TriggerTypeConfig::Script { timeout_ms, .. } => {
507				assert_eq!(timeout_ms, 2000);
508			}
509			_ => panic!("Expected script config"),
510		}
511	}
512
513	#[test]
514	fn test_telegram_trigger() {
515		let trigger = TriggerBuilder::new()
516			.name("telegram_alert")
517			.telegram(
518				"1234567890:ABCdefGHIjklMNOpqrSTUvwxYZ123456789", // noboost
519				"1234567890",
520				false,
521			)
522			.message("Alert", "Test message")
523			.build();
524
525		assert_eq!(trigger.trigger_type, TriggerType::Telegram);
526		match trigger.config {
527			TriggerTypeConfig::Telegram {
528				token,
529				chat_id,
530				message,
531				..
532			} => {
533				assert_eq!(
534					token.as_ref().to_string(),
535					"1234567890:ABCdefGHIjklMNOpqrSTUvwxYZ123456789".to_string() // noboost
536				);
537				assert_eq!(chat_id, "1234567890");
538				assert_eq!(message.title, "Alert");
539				assert_eq!(message.body, "Test message");
540			}
541			_ => panic!("Expected telegram config"),
542		}
543	}
544
545	#[test]
546	fn test_email_trigger() {
547		let trigger = TriggerBuilder::new()
548			.name("email_alert")
549			.email(
550				"smtp.example.com",
551				"user",
552				"pass",
553				"sender@example.com",
554				vec!["recipient@example.com"],
555			)
556			.email_port(465)
557			.email_subject("Custom Subject")
558			.build();
559
560		assert_eq!(trigger.trigger_type, TriggerType::Email);
561		match trigger.config {
562			TriggerTypeConfig::Email {
563				host,
564				port,
565				username,
566				password,
567				message,
568				sender,
569				recipients,
570				..
571			} => {
572				assert_eq!(host, "smtp.example.com");
573				assert_eq!(port, Some(465));
574				assert_eq!(username.as_ref().to_string(), "user");
575				assert_eq!(password.as_ref().to_string(), "pass");
576				assert_eq!(message.title, "Custom Subject");
577				assert_eq!(sender.as_str(), "sender@example.com");
578				assert_eq!(recipients.len(), 1);
579				assert_eq!(recipients[0].as_str(), "recipient@example.com");
580			}
581			_ => panic!("Expected email config"),
582		}
583	}
584
585	#[test]
586	fn test_telegram_token() {
587		let token = SecretValue::Environment("TELEGRAM_TOKEN".to_string());
588		let trigger = TriggerBuilder::new()
589			.name("telegram_alert")
590			.telegram("dummy_token", "1234567890", false)
591			.telegram_token(token.clone())
592			.build();
593
594		assert_eq!(trigger.trigger_type, TriggerType::Telegram);
595		match trigger.config {
596			TriggerTypeConfig::Telegram { token: t, .. } => {
597				assert_eq!(t, token);
598			}
599			_ => panic!("Expected telegram config"),
600		}
601	}
602
603	#[test]
604	fn test_email_username() {
605		let username = SecretValue::Environment("SMTP_USERNAME".to_string());
606		let trigger = TriggerBuilder::new()
607			.name("email_alert")
608			.email(
609				"smtp.example.com",
610				"dummy_user",
611				"pass",
612				"sender@example.com",
613				vec!["recipient@example.com"],
614			)
615			.email_username(username.clone())
616			.build();
617
618		assert_eq!(trigger.trigger_type, TriggerType::Email);
619		match trigger.config {
620			TriggerTypeConfig::Email { username: u, .. } => {
621				assert_eq!(u, username);
622			}
623			_ => panic!("Expected email config"),
624		}
625	}
626
627	#[test]
628	fn test_email_password() {
629		let password = SecretValue::Environment("SMTP_PASSWORD".to_string());
630		let trigger = TriggerBuilder::new()
631			.name("email_alert")
632			.email(
633				"smtp.example.com",
634				"user",
635				"dummy_pass",
636				"sender@example.com",
637				vec!["recipient@example.com"],
638			)
639			.email_password(password.clone())
640			.build();
641
642		assert_eq!(trigger.trigger_type, TriggerType::Email);
643		match trigger.config {
644			TriggerTypeConfig::Email { password: p, .. } => {
645				assert_eq!(p, password);
646			}
647			_ => panic!("Expected email config"),
648		}
649	}
650
651	#[test]
652	fn test_url() {
653		let url = SecretValue::Environment("WEBHOOK_URL".to_string());
654
655		// Test with webhook
656		let webhook_trigger = TriggerBuilder::new()
657			.name("webhook_alert")
658			.webhook("dummy_url")
659			.url(url.clone())
660			.build();
661
662		assert_eq!(webhook_trigger.trigger_type, TriggerType::Webhook);
663		match webhook_trigger.config {
664			TriggerTypeConfig::Webhook { url: u, .. } => {
665				assert_eq!(u, url);
666			}
667			_ => panic!("Expected webhook config"),
668		}
669
670		// Test with discord
671		let discord_trigger = TriggerBuilder::new()
672			.name("discord_alert")
673			.discord("dummy_url")
674			.url(url.clone())
675			.build();
676
677		assert_eq!(discord_trigger.trigger_type, TriggerType::Discord);
678		match discord_trigger.config {
679			TriggerTypeConfig::Discord { discord_url: u, .. } => {
680				assert_eq!(u, url);
681			}
682			_ => panic!("Expected discord config"),
683		}
684
685		// Test with slack
686		let slack_trigger = TriggerBuilder::new()
687			.name("slack_alert")
688			.slack("dummy_url")
689			.url(url.clone())
690			.build();
691
692		assert_eq!(slack_trigger.trigger_type, TriggerType::Slack);
693		match slack_trigger.config {
694			TriggerTypeConfig::Slack { slack_url: u, .. } => {
695				assert_eq!(u, url);
696			}
697			_ => panic!("Expected slack config"),
698		}
699	}
700}