test(app): refactor test to parametrized tests

This commit is contained in:
2025-08-12 09:51:58 +02:00
parent fc31a953aa
commit d79799da90

View File

@@ -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<String, bool>,
#[case] second_violations: HashMap<String, bool>,
#[case] expected_notifications: Vec<String>,
) {
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<String, bool>) -> Vec<Box<dyn ForGettingViolationData>> {
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<dyn ForGettingViolationData>
})
.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);
}
}