From d79799da900300d0668a6eff751e161cb440a1bb Mon Sep 17 00:00:00 2001 From: JeremyLARDENOIS Date: Tue, 12 Aug 2025 09:51:58 +0200 Subject: [PATCH] test(app): refactor test to parametrized tests --- src/app/services/alert_service.rs | 155 ++++++++++++++++++++++-------- 1 file changed, 114 insertions(+), 41 deletions(-) diff --git a/src/app/services/alert_service.rs b/src/app/services/alert_service.rs index dc5130e..c4b575d 100644 --- a/src/app/services/alert_service.rs +++ b/src/app/services/alert_service.rs @@ -89,57 +89,130 @@ impl AlertService { #[cfg(test)] mod tests { + use rstest::rstest; use std::collections::HashMap; + use std::collections::HashSet; - use crate::app::ports::driven::{MockForFormattingMessage, MockForMonitoringSystem, MockForSendingNotification}; + use crate::app::{ + ports::driven::{ + ForGettingViolationData, MockForFormattingMessage, MockForGettingViolationData, + MockForMonitoringSystem, MockForSendingNotification, + }, + services::alert_service, + }; - use super::*; + const TEST_VALUE: f32 = 75.0; + const TEST_THRESHOLD: f32 = 70.0; - #[test] - fn test_alert_on_threshold_violation() { - let mut notifier = MockForSendingNotification::new(); - let mut monitor = MockForMonitoringSystem::new(); - let mut formatter = MockForFormattingMessage::new(); + #[rstest] + // No violations + #[case(HashMap::new(), HashMap::new(), vec![])] + // Single critical violation + #[case(HashMap::from([("CPU".to_string(), true)]), HashMap::new(), vec!["critical CPU".to_string(), "resolved CPU".to_string()])] + // Single warning violation + #[case(HashMap::from([("Memory".to_string(), false)]), HashMap::new(), vec!["warning Memory".to_string(), "resolved Memory".to_string()])] + // Multiple violations with level changes and one resolution + #[case(HashMap::from([("CPU".to_string(), true), ("Memory".to_string(), false), ("Disk".to_string(), false)]), + HashMap::from([("CPU".to_string(), false), ("Disk".to_string(), true)]), + vec![ + "critical CPU".to_string(), + "warning Memory".to_string(), + "warning Disk".to_string(), + "resolved Memory".to_string(), + "warning CPU".to_string(), + "critical Disk".to_string() + ] + )] + // No level changes + #[case( + HashMap::from([("CPU".to_string(), true), ("Memory".to_string(), false)]), + HashMap::from([("CPU".to_string(), true), ("Memory".to_string(), false)]), + vec!["critical CPU".to_string(), "warning Memory".to_string()] + )] + fn test_alert_on_threshold_violation_scenarios( + #[case] first_violations: HashMap, + #[case] second_violations: HashMap, + #[case] expected_notifications: Vec, + ) { + let mut mock_monitor = MockForMonitoringSystem::new(); + let mut mock_formatter = MockForFormattingMessage::new(); + let mut mock_notifier = MockForSendingNotification::new(); - notifier.expect_send_notification().returning(|_| Ok(())); - monitor.expect_check_thresholds().returning(|| vec![]); - monitor.expect_get_metrics().returning(|| HashMap::new()); - formatter.expect_format_violation().returning(|_| "".to_string()); - formatter.expect_format_summary().returning(|_| "".to_string()); + // Helper to build violations from a HashMap + fn build_violations(map: &HashMap) -> Vec> { + map.iter() + .map(|(name, &is_critical)| { + let mut violation = MockForGettingViolationData::new(); + let name = name.clone(); + violation.expect_is_critical().return_const(is_critical); + violation + .expect_get_metric_name() + .return_const(name.clone()); + violation.expect_get_metric_value().return_const(TEST_VALUE); + violation + .expect_get_threshold() + .return_const(TEST_THRESHOLD); + Box::new(violation) as Box + }) + .collect() + } - let mut service = new( - Box::new(notifier), - Box::new(monitor), - Box::new(formatter), - ); - - assert!(service.alert_on_threshold_violation().is_ok()); - } - - #[test] - fn test_alert_on_threshold_violation_handles_send_error() { - let mut notifier = MockForSendingNotification::new(); - let mut monitor = MockForMonitoringSystem::new(); - let mut formatter = MockForFormattingMessage::new(); - - - monitor.expect_check_thresholds().returning(|| { - let mut violation = crate::app::ports::driven::MockForGettingViolationData::new(); - violation.expect_get_metric_name().return_const("cpu".to_string()); - violation.expect_is_critical().return_const(true); - vec![Box::new(violation)] + // The test will call check_thresholds twice: first and second run + let mut call_count = 0; + let first_violations_clone = first_violations.clone(); + let second_violations_clone = second_violations.clone(); + mock_monitor.expect_check_thresholds().returning(move || { + call_count += 1; + if call_count == 1 { + build_violations(&first_violations_clone) + } else { + build_violations(&second_violations_clone) + } }); - formatter.expect_format_violation().returning(|_| "Test violation".to_string()); - notifier.expect_send_notification() - .returning(|_| Err("Network error".into())); + // Formatter returns a string based on the violation + mock_formatter + .expect_format_violation() + .returning(|violation| { + let level = if violation.is_critical() { + "critical" + } else { + "warning" + }; + format!("{} {}", level, violation.get_metric_name()) + }); + mock_formatter + .expect_format_resolution() + .returning(|metric_name| format!("resolved {}", metric_name)); - let mut service = new( - Box::new(notifier), - Box::new(monitor), - Box::new(formatter), + // Collect notifications sent + let notifications = std::sync::Arc::new(std::sync::Mutex::new(Vec::new())); + let notifications_clone = notifications.clone(); + mock_notifier + .expect_send_notification() + .returning(move |message| { + notifications_clone + .lock() + .unwrap() + .push(message.to_string()); + Ok(()) + }); + + let mut alert_service = alert_service::new( + Box::new(mock_notifier), + Box::new(mock_monitor), + Box::new(mock_formatter), ); - assert!(service.alert_on_threshold_violation().is_err()); + // First run + alert_service.alert_on_threshold_violation().unwrap(); + // Second run + alert_service.alert_on_threshold_violation().unwrap(); + + // Hashmap is not ordered, so we need to compare sets + let sent = notifications.lock().unwrap(); + let sent_set: HashSet<_> = sent.iter().collect(); + let expected_set: HashSet<_> = expected_notifications.iter().collect(); + assert_eq!(sent_set, expected_set); } }