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::HttpRetryConfig,
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: HttpRetryConfig::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: HttpRetryConfig::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: HttpRetryConfig::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: HttpRetryConfig::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: HttpRetryConfig::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		};
191		self
192	}
193
194	pub fn email_port(mut self, port: u16) -> Self {
195		if let TriggerTypeConfig::Email { port: p, .. } = &mut self.config {
196			*p = Some(port);
197		}
198		self
199	}
200
201	pub fn email_subject(mut self, subject: &str) -> Self {
202		if let TriggerTypeConfig::Email { message, .. } = &mut self.config {
203			message.title = subject.to_string();
204		}
205		self
206	}
207
208	pub fn email_username(mut self, username: SecretValue) -> Self {
209		if let TriggerTypeConfig::Email { username: u, .. } = &mut self.config {
210			*u = username;
211		}
212		self
213	}
214
215	pub fn email_password(mut self, password: SecretValue) -> Self {
216		if let TriggerTypeConfig::Email { password: p, .. } = &mut self.config {
217			*p = password;
218		}
219		self
220	}
221
222	pub fn webhook_method(mut self, method: &str) -> Self {
223		if let TriggerTypeConfig::Webhook { method: m, .. } = &mut self.config {
224			*m = Some(method.to_string());
225		}
226		self
227	}
228
229	pub fn webhook_secret(mut self, secret: SecretValue) -> Self {
230		if let TriggerTypeConfig::Webhook { secret: s, .. } = &mut self.config {
231			*s = Some(secret);
232		}
233		self
234	}
235
236	pub fn webhook_headers(mut self, headers: std::collections::HashMap<String, String>) -> Self {
237		if let TriggerTypeConfig::Webhook { headers: h, .. } = &mut self.config {
238			*h = Some(headers);
239		}
240		self
241	}
242
243	pub fn url(mut self, url: SecretValue) -> Self {
244		self.config = match self.config {
245			TriggerTypeConfig::Webhook {
246				url: _,
247				method,
248				headers,
249				secret,
250				message,
251				retry_policy,
252			} => TriggerTypeConfig::Webhook {
253				url,
254				method,
255				headers,
256				secret,
257				message,
258				retry_policy,
259			},
260			TriggerTypeConfig::Discord {
261				discord_url: _,
262				message,
263				retry_policy,
264			} => TriggerTypeConfig::Discord {
265				discord_url: url,
266				message,
267				retry_policy,
268			},
269			TriggerTypeConfig::Slack {
270				slack_url: _,
271				message,
272				retry_policy,
273			} => TriggerTypeConfig::Slack {
274				slack_url: url,
275				message,
276				retry_policy,
277			},
278			config => config,
279		};
280		self
281	}
282
283	pub fn build(self) -> Trigger {
284		Trigger {
285			name: self.name,
286			trigger_type: self.trigger_type,
287			config: self.config,
288		}
289	}
290}
291
292#[cfg(test)]
293mod tests {
294	use super::*;
295
296	#[test]
297	fn test_default_trigger() {
298		let trigger = TriggerBuilder::new().build();
299
300		assert_eq!(trigger.name, "test_trigger");
301		assert_eq!(trigger.trigger_type, TriggerType::Webhook);
302
303		match trigger.config {
304			TriggerTypeConfig::Webhook { url, method, .. } => {
305				assert_eq!(url.as_ref().to_string(), "https://api.example.com/webhook");
306				assert_eq!(method, Some("POST".to_string()));
307			}
308			_ => panic!("Expected webhook config"),
309		}
310	}
311
312	#[test]
313	fn test_trigger_with_config() {
314		let trigger = TriggerBuilder::new()
315			.name("my_trigger")
316			.config(TriggerTypeConfig::Webhook {
317				url: SecretValue::Plain(SecretString::new(
318					"https://api.example.com/webhook".to_string(),
319				)),
320				secret: Some(SecretValue::Plain(SecretString::new("secret".to_string()))),
321				method: Some("POST".to_string()),
322				headers: None,
323				message: NotificationMessage {
324					title: "Alert".to_string(),
325					body: "Test message".to_string(),
326				},
327				retry_policy: HttpRetryConfig::default(),
328			})
329			.build();
330
331		assert_eq!(trigger.name, "my_trigger");
332		assert_eq!(trigger.trigger_type, TriggerType::Webhook);
333
334		match trigger.config {
335			TriggerTypeConfig::Webhook { url, method, .. } => {
336				assert_eq!(url.as_ref().to_string(), "https://api.example.com/webhook");
337				assert_eq!(method, Some("POST".to_string()));
338			}
339			_ => panic!("Expected webhook config"),
340		}
341	}
342
343	#[test]
344	fn test_webhook_trigger() {
345		let trigger = TriggerBuilder::new()
346			.name("my_webhook")
347			.webhook("https://webhook.example.com")
348			.message("Custom Alert", "Something happened!")
349			.build();
350
351		assert_eq!(trigger.name, "my_webhook");
352		assert_eq!(trigger.trigger_type, TriggerType::Webhook);
353
354		match trigger.config {
355			TriggerTypeConfig::Webhook { url, message, .. } => {
356				assert_eq!(url.as_ref().to_string(), "https://webhook.example.com");
357				assert_eq!(message.title, "Custom Alert");
358				assert_eq!(message.body, "Something happened!");
359			}
360			_ => panic!("Expected webhook config"),
361		}
362	}
363
364	#[test]
365	fn test_webhook_trigger_with_config() {
366		let mut headers = std::collections::HashMap::new();
367		headers.insert("Content-Type".to_string(), "application/json".to_string());
368
369		let trigger = TriggerBuilder::new()
370			.name("my_webhook")
371			.webhook("https://webhook.example.com")
372			.webhook_method("POST")
373			.webhook_secret(SecretValue::Plain(SecretString::new(
374				"secret123".to_string(),
375			)))
376			.webhook_headers(headers.clone())
377			.message("Custom Alert", "Something happened!")
378			.build();
379
380		assert_eq!(trigger.name, "my_webhook");
381		assert_eq!(trigger.trigger_type, TriggerType::Webhook);
382
383		match trigger.config {
384			TriggerTypeConfig::Webhook {
385				url,
386				method,
387				secret,
388				headers: h,
389				message,
390				retry_policy: _,
391			} => {
392				assert_eq!(url.as_ref().to_string(), "https://webhook.example.com");
393				assert_eq!(method, Some("POST".to_string()));
394				assert_eq!(
395					secret.as_ref().map(|s| s.as_ref().to_string()),
396					Some("secret123".to_string())
397				);
398				assert_eq!(h, Some(headers));
399				assert_eq!(message.title, "Custom Alert");
400				assert_eq!(message.body, "Something happened!");
401			}
402			_ => panic!("Expected webhook config"),
403		}
404	}
405
406	#[test]
407	fn test_slack_trigger() {
408		let trigger = TriggerBuilder::new()
409			.name("slack_alert")
410			.slack("https://slack.webhook.com")
411			.message("Alert", "Test message")
412			.build();
413
414		assert_eq!(trigger.trigger_type, TriggerType::Slack);
415		match trigger.config {
416			TriggerTypeConfig::Slack {
417				slack_url,
418				message,
419				retry_policy: _,
420			} => {
421				assert_eq!(slack_url.as_ref().to_string(), "https://slack.webhook.com");
422				assert_eq!(message.title, "Alert");
423				assert_eq!(message.body, "Test message");
424			}
425			_ => panic!("Expected slack config"),
426		}
427	}
428
429	#[test]
430	fn test_discord_trigger() {
431		let trigger = TriggerBuilder::new()
432			.name("discord_alert")
433			.discord("https://discord.webhook.com")
434			.message("Alert", "Test message")
435			.build();
436
437		assert_eq!(trigger.trigger_type, TriggerType::Discord);
438		match trigger.config {
439			TriggerTypeConfig::Discord {
440				discord_url,
441				message,
442				retry_policy: _,
443			} => {
444				assert_eq!(
445					discord_url.as_ref().to_string(),
446					"https://discord.webhook.com"
447				);
448				assert_eq!(message.title, "Alert");
449				assert_eq!(message.body, "Test message");
450			}
451			_ => panic!("Expected discord config"),
452		}
453	}
454
455	#[test]
456	fn test_script_trigger() {
457		let trigger = TriggerBuilder::new()
458			.name("script_trigger")
459			.script("test.py", ScriptLanguage::Python)
460			.build();
461
462		assert_eq!(trigger.trigger_type, TriggerType::Script);
463		match trigger.config {
464			TriggerTypeConfig::Script {
465				script_path,
466				language,
467				timeout_ms,
468				..
469			} => {
470				assert_eq!(script_path, "test.py");
471				assert_eq!(language, ScriptLanguage::Python);
472				assert_eq!(timeout_ms, 1000);
473			}
474			_ => panic!("Expected script config"),
475		}
476	}
477
478	#[test]
479	fn test_script_trigger_with_arguments() {
480		let trigger = TriggerBuilder::new()
481			.name("script_trigger")
482			.script("test.py", ScriptLanguage::Python)
483			.script_arguments(vec!["arg1".to_string()])
484			.build();
485
486		assert_eq!(trigger.trigger_type, TriggerType::Script);
487		match trigger.config {
488			TriggerTypeConfig::Script { arguments, .. } => {
489				assert_eq!(arguments, Some(vec!["arg1".to_string()]));
490			}
491			_ => panic!("Expected script config"),
492		}
493	}
494
495	#[test]
496	fn test_script_trigger_with_timeout() {
497		let trigger = TriggerBuilder::new()
498			.name("script_trigger")
499			.script("test.py", ScriptLanguage::Python)
500			.script_timeout_ms(2000)
501			.build();
502
503		assert_eq!(trigger.trigger_type, TriggerType::Script);
504		match trigger.config {
505			TriggerTypeConfig::Script { timeout_ms, .. } => {
506				assert_eq!(timeout_ms, 2000);
507			}
508			_ => panic!("Expected script config"),
509		}
510	}
511
512	#[test]
513	fn test_telegram_trigger() {
514		let trigger = TriggerBuilder::new()
515			.name("telegram_alert")
516			.telegram(
517				"1234567890:ABCdefGHIjklMNOpqrSTUvwxYZ123456789", // noboost
518				"1234567890",
519				false,
520			)
521			.message("Alert", "Test message")
522			.build();
523
524		assert_eq!(trigger.trigger_type, TriggerType::Telegram);
525		match trigger.config {
526			TriggerTypeConfig::Telegram {
527				token,
528				chat_id,
529				message,
530				..
531			} => {
532				assert_eq!(
533					token.as_ref().to_string(),
534					"1234567890:ABCdefGHIjklMNOpqrSTUvwxYZ123456789".to_string() // noboost
535				);
536				assert_eq!(chat_id, "1234567890");
537				assert_eq!(message.title, "Alert");
538				assert_eq!(message.body, "Test message");
539			}
540			_ => panic!("Expected telegram config"),
541		}
542	}
543
544	#[test]
545	fn test_email_trigger() {
546		let trigger = TriggerBuilder::new()
547			.name("email_alert")
548			.email(
549				"smtp.example.com",
550				"user",
551				"pass",
552				"sender@example.com",
553				vec!["recipient@example.com"],
554			)
555			.email_port(465)
556			.email_subject("Custom Subject")
557			.build();
558
559		assert_eq!(trigger.trigger_type, TriggerType::Email);
560		match trigger.config {
561			TriggerTypeConfig::Email {
562				host,
563				port,
564				username,
565				password,
566				message,
567				sender,
568				recipients,
569				..
570			} => {
571				assert_eq!(host, "smtp.example.com");
572				assert_eq!(port, Some(465));
573				assert_eq!(username.as_ref().to_string(), "user");
574				assert_eq!(password.as_ref().to_string(), "pass");
575				assert_eq!(message.title, "Custom Subject");
576				assert_eq!(sender.as_str(), "sender@example.com");
577				assert_eq!(recipients.len(), 1);
578				assert_eq!(recipients[0].as_str(), "recipient@example.com");
579			}
580			_ => panic!("Expected email config"),
581		}
582	}
583
584	#[test]
585	fn test_telegram_token() {
586		let token = SecretValue::Environment("TELEGRAM_TOKEN".to_string());
587		let trigger = TriggerBuilder::new()
588			.name("telegram_alert")
589			.telegram("dummy_token", "1234567890", false)
590			.telegram_token(token.clone())
591			.build();
592
593		assert_eq!(trigger.trigger_type, TriggerType::Telegram);
594		match trigger.config {
595			TriggerTypeConfig::Telegram { token: t, .. } => {
596				assert_eq!(t, token);
597			}
598			_ => panic!("Expected telegram config"),
599		}
600	}
601
602	#[test]
603	fn test_email_username() {
604		let username = SecretValue::Environment("SMTP_USERNAME".to_string());
605		let trigger = TriggerBuilder::new()
606			.name("email_alert")
607			.email(
608				"smtp.example.com",
609				"dummy_user",
610				"pass",
611				"sender@example.com",
612				vec!["recipient@example.com"],
613			)
614			.email_username(username.clone())
615			.build();
616
617		assert_eq!(trigger.trigger_type, TriggerType::Email);
618		match trigger.config {
619			TriggerTypeConfig::Email { username: u, .. } => {
620				assert_eq!(u, username);
621			}
622			_ => panic!("Expected email config"),
623		}
624	}
625
626	#[test]
627	fn test_email_password() {
628		let password = SecretValue::Environment("SMTP_PASSWORD".to_string());
629		let trigger = TriggerBuilder::new()
630			.name("email_alert")
631			.email(
632				"smtp.example.com",
633				"user",
634				"dummy_pass",
635				"sender@example.com",
636				vec!["recipient@example.com"],
637			)
638			.email_password(password.clone())
639			.build();
640
641		assert_eq!(trigger.trigger_type, TriggerType::Email);
642		match trigger.config {
643			TriggerTypeConfig::Email { password: p, .. } => {
644				assert_eq!(p, password);
645			}
646			_ => panic!("Expected email config"),
647		}
648	}
649
650	#[test]
651	fn test_url() {
652		let url = SecretValue::Environment("WEBHOOK_URL".to_string());
653
654		// Test with webhook
655		let webhook_trigger = TriggerBuilder::new()
656			.name("webhook_alert")
657			.webhook("dummy_url")
658			.url(url.clone())
659			.build();
660
661		assert_eq!(webhook_trigger.trigger_type, TriggerType::Webhook);
662		match webhook_trigger.config {
663			TriggerTypeConfig::Webhook { url: u, .. } => {
664				assert_eq!(u, url);
665			}
666			_ => panic!("Expected webhook config"),
667		}
668
669		// Test with discord
670		let discord_trigger = TriggerBuilder::new()
671			.name("discord_alert")
672			.discord("dummy_url")
673			.url(url.clone())
674			.build();
675
676		assert_eq!(discord_trigger.trigger_type, TriggerType::Discord);
677		match discord_trigger.config {
678			TriggerTypeConfig::Discord { discord_url: u, .. } => {
679				assert_eq!(u, url);
680			}
681			_ => panic!("Expected discord config"),
682		}
683
684		// Test with slack
685		let slack_trigger = TriggerBuilder::new()
686			.name("slack_alert")
687			.slack("dummy_url")
688			.url(url.clone())
689			.build();
690
691		assert_eq!(slack_trigger.trigger_type, TriggerType::Slack);
692		match slack_trigger.config {
693			TriggerTypeConfig::Slack { slack_url: u, .. } => {
694				assert_eq!(u, url);
695			}
696			_ => panic!("Expected slack config"),
697		}
698	}
699}