Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 977322dc28 | |||
| a8899b5e58 | |||
| e4458c05b3 |
40
.drone.yml
Normal file
40
.drone.yml
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
kind: pipeline
|
||||||
|
type: docker
|
||||||
|
name: test-on-amd64
|
||||||
|
|
||||||
|
platform:
|
||||||
|
arch: amd64
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: build-release
|
||||||
|
image: rust:1.89
|
||||||
|
commands:
|
||||||
|
- cargo build --verbose --workspace --release
|
||||||
|
- name: test
|
||||||
|
image: rust:1.89
|
||||||
|
commands:
|
||||||
|
- cargo test --verbose --workspace
|
||||||
|
- name: lint
|
||||||
|
image: rust:1.89
|
||||||
|
commands:
|
||||||
|
- rustup component add clippy
|
||||||
|
- cargo clippy --all-targets --all-features
|
||||||
|
- name: push-binary-release
|
||||||
|
image: plugins/gitea-release
|
||||||
|
settings:
|
||||||
|
base_url: https://gitea.lardenois.cc
|
||||||
|
api_key:
|
||||||
|
from_secret: package-drone
|
||||||
|
files:
|
||||||
|
- target/release/node-notifier
|
||||||
|
title: "Release ${DRONE_TAG:-v${DRONE_BUILD_NUMBER}}"
|
||||||
|
note: "Automated release from commit ${DRONE_COMMIT_SHA}"
|
||||||
|
checksum:
|
||||||
|
- sha256
|
||||||
|
when:
|
||||||
|
event: tag
|
||||||
|
status: success
|
||||||
|
depends_on:
|
||||||
|
- build-release
|
||||||
|
- test
|
||||||
|
- lint
|
||||||
@@ -53,8 +53,4 @@ impl ForFormattingMessage for AlertFormatter {
|
|||||||
|
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
fn format_resolution(&self, metric_name: &str) -> String {
|
|
||||||
format!("✅ {} has been resolved", metric_name)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ pub trait ForMonitoringSystem {
|
|||||||
pub trait ForFormattingMessage {
|
pub trait ForFormattingMessage {
|
||||||
fn format_violation(&self, violation: Box<dyn ForGettingViolationData>) -> String;
|
fn format_violation(&self, violation: Box<dyn ForGettingViolationData>) -> String;
|
||||||
fn format_summary(&self, metrics: &HashMap<String, f32>) -> String;
|
fn format_summary(&self, metrics: &HashMap<String, f32>) -> String;
|
||||||
fn format_resolution(&self, metric_name: &str) -> String;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(test, automock)]
|
#[cfg_attr(test, automock)]
|
||||||
|
|||||||
@@ -4,11 +4,28 @@ use crate::app::ports::driven::{
|
|||||||
ForFormattingMessage, ForMonitoringSystem, ForSendingNotification,
|
ForFormattingMessage, ForMonitoringSystem, ForSendingNotification,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
pub enum AlertLevel {
|
||||||
|
Resolved = 0, // No violation
|
||||||
|
Warning = 1,
|
||||||
|
Critical = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AlertLevel {
|
||||||
|
fn from_is_critical(is_critical: bool) -> Self {
|
||||||
|
if is_critical {
|
||||||
|
AlertLevel::Critical
|
||||||
|
} else {
|
||||||
|
AlertLevel::Warning
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct AlertService {
|
pub struct AlertService {
|
||||||
notifier: Box<dyn ForSendingNotification>,
|
notifier: Box<dyn ForSendingNotification>,
|
||||||
monitor: Box<dyn ForMonitoringSystem>,
|
monitor: Box<dyn ForMonitoringSystem>,
|
||||||
formatter: Box<dyn ForFormattingMessage>,
|
formatter: Box<dyn ForFormattingMessage>,
|
||||||
map_alerts_criticity: HashMap<String, bool>,
|
highest_alert_levels: HashMap<String, AlertLevel>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new(
|
pub fn new(
|
||||||
@@ -20,7 +37,7 @@ pub fn new(
|
|||||||
notifier,
|
notifier,
|
||||||
monitor,
|
monitor,
|
||||||
formatter,
|
formatter,
|
||||||
map_alerts_criticity: HashMap::new(),
|
highest_alert_levels: HashMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,44 +45,30 @@ impl AlertService {
|
|||||||
/// Check for threshold violations and send alerts on level changes (escalation, improvement, resolution)
|
/// Check for threshold violations and send alerts on level changes (escalation, improvement, resolution)
|
||||||
pub fn alert_on_threshold_violation(&mut self) -> Result<(), Box<dyn std::error::Error>> {
|
pub fn alert_on_threshold_violation(&mut self) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let violations = self.monitor.check_thresholds();
|
let violations = self.monitor.check_thresholds();
|
||||||
let current_levels = self.violations_to_map(&violations);
|
|
||||||
|
|
||||||
self.process_resolved_violations(¤t_levels);
|
// Build a map of current violation levels
|
||||||
self.process_current_violations(violations)?;
|
let mut current_levels: HashMap<String, AlertLevel> = HashMap::new();
|
||||||
|
for violation in &violations {
|
||||||
self.update_map_alerts_criticity(current_levels);
|
let metric_name = violation.get_metric_name();
|
||||||
|
let level = AlertLevel::from_is_critical(violation.is_critical());
|
||||||
Ok(())
|
current_levels.insert(metric_name, level);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn violations_to_map(
|
// Check for metrics that had violations but now don't (resolution)
|
||||||
&self,
|
for (metric_name, &previous_level) in &self.highest_alert_levels {
|
||||||
violations: &[Box<dyn crate::app::ports::driven::ForGettingViolationData>],
|
|
||||||
) -> HashMap<String, bool> {
|
|
||||||
violations
|
|
||||||
.iter()
|
|
||||||
.map(|v| (v.get_metric_name(), v.is_critical()))
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn process_resolved_violations(&mut self, current_levels: &HashMap<String, bool>) {
|
|
||||||
for (metric_name, &_previous_level) in &self.map_alerts_criticity {
|
|
||||||
if !current_levels.contains_key(metric_name) {
|
if !current_levels.contains_key(metric_name) {
|
||||||
let message = self.formatter.format_resolution(metric_name);
|
// This metric is now resolved
|
||||||
self.notifier.send_notification(&message).unwrap();
|
let message = format!("✅ {} has been resolved (was {:?})", metric_name, previous_level);
|
||||||
}
|
self.notifier.send_notification(&message)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_current_violations(
|
// Process current violations
|
||||||
&mut self,
|
|
||||||
violations: Vec<Box<dyn crate::app::ports::driven::ForGettingViolationData>>,
|
|
||||||
) -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
for violation in violations {
|
for violation in violations {
|
||||||
let metric_name = violation.get_metric_name();
|
let metric_name = violation.get_metric_name();
|
||||||
let current_level = violation.is_critical();
|
let current_level = AlertLevel::from_is_critical(violation.is_critical());
|
||||||
|
|
||||||
let should_alert = match self.map_alerts_criticity.get(&metric_name) {
|
let should_alert = match self.highest_alert_levels.get(&metric_name) {
|
||||||
None => true, // First time seeing this metric
|
None => true, // First time seeing this metric
|
||||||
Some(&previous_level) => current_level != previous_level, // Any level change
|
Some(&previous_level) => current_level != previous_level, // Any level change
|
||||||
};
|
};
|
||||||
@@ -76,143 +79,294 @@ impl AlertService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update the level for this metric
|
// Update the level for this metric
|
||||||
self.map_alerts_criticity.insert(metric_name, current_level);
|
self.highest_alert_levels.insert(metric_name, current_level);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove resolved metrics from our tracking
|
||||||
|
self.highest_alert_levels.retain(|metric_name, _| current_levels.contains_key(metric_name));
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_map_alerts_criticity(&mut self, current_levels: HashMap<String, bool>) {
|
|
||||||
self.map_alerts_criticity = current_levels;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use rstest::rstest;
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::collections::HashSet;
|
use rstest::rstest;
|
||||||
|
|
||||||
use crate::app::{
|
use crate::app::ports::driven::{MockForFormattingMessage, MockForMonitoringSystem, MockForSendingNotification};
|
||||||
ports::driven::{
|
|
||||||
ForGettingViolationData, MockForFormattingMessage, MockForGettingViolationData,
|
|
||||||
MockForMonitoringSystem, MockForSendingNotification,
|
|
||||||
},
|
|
||||||
services::alert_service,
|
|
||||||
};
|
|
||||||
|
|
||||||
const TEST_VALUE: f32 = 75.0;
|
use super::*;
|
||||||
const TEST_THRESHOLD: f32 = 70.0;
|
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
// No violations
|
#[case(vec![], 0, true, "no violations")]
|
||||||
#[case(HashMap::new(), HashMap::new(), vec![])]
|
#[case(vec![true], 1, true, "single critical violation")]
|
||||||
// Single critical violation
|
#[case(vec![false], 1, true, "single warning 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(
|
fn test_alert_on_threshold_violation_scenarios(
|
||||||
#[case] first_violations: HashMap<String, bool>,
|
#[case] violations_criticality: Vec<bool>,
|
||||||
#[case] second_violations: HashMap<String, bool>,
|
#[case] expected_notifications: usize,
|
||||||
#[case] expected_notifications: Vec<String>,
|
#[case] should_succeed: bool,
|
||||||
|
#[case] _description: &str,
|
||||||
) {
|
) {
|
||||||
let mut mock_monitor = MockForMonitoringSystem::new();
|
let mut notifier = MockForSendingNotification::new();
|
||||||
let mut mock_formatter = MockForFormattingMessage::new();
|
let mut monitor = MockForMonitoringSystem::new();
|
||||||
let mut mock_notifier = MockForSendingNotification::new();
|
let mut formatter = MockForFormattingMessage::new();
|
||||||
|
|
||||||
// Helper to build violations from a HashMap
|
// Setup monitor expectations
|
||||||
fn build_violations(map: &HashMap<String, bool>) -> Vec<Box<dyn ForGettingViolationData>> {
|
monitor.expect_check_thresholds().returning(move || {
|
||||||
map.iter()
|
violations_criticality.iter().enumerate().map(|(i, &is_critical)| {
|
||||||
.map(|(name, &is_critical)| {
|
let mut violation = crate::app::ports::driven::MockForGettingViolationData::new();
|
||||||
let mut violation = MockForGettingViolationData::new();
|
violation.expect_get_metric_name().return_const(format!("metric_{}", i));
|
||||||
let name = name.clone();
|
|
||||||
violation.expect_is_critical().return_const(is_critical);
|
violation.expect_is_critical().return_const(is_critical);
|
||||||
violation
|
Box::new(violation) as Box<dyn crate::app::ports::driven::ForGettingViolationData>
|
||||||
.expect_get_metric_name()
|
}).collect()
|
||||||
.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()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 returns a string based on the violation
|
monitor.expect_get_metrics().returning(|| HashMap::new());
|
||||||
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));
|
|
||||||
|
|
||||||
// Collect notifications sent
|
if expected_notifications > 0 {
|
||||||
let notifications = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
|
formatter.expect_format_violation()
|
||||||
let notifications_clone = notifications.clone();
|
.times(expected_notifications)
|
||||||
mock_notifier
|
.returning(|_| "Violation message".to_string());
|
||||||
.expect_send_notification()
|
|
||||||
.returning(move |message| {
|
formatter.expect_format_summary().returning(|_| "Summary".to_string());
|
||||||
notifications_clone
|
|
||||||
.lock()
|
notifier.expect_send_notification()
|
||||||
.unwrap()
|
.times(expected_notifications)
|
||||||
.push(message.to_string());
|
.returning(move |_| {
|
||||||
|
if should_succeed {
|
||||||
Ok(())
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err("Network error".into())
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
formatter.expect_format_summary().returning(|_| "Summary".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
let mut alert_service = alert_service::new(
|
let mut service = new(
|
||||||
Box::new(mock_notifier),
|
Box::new(notifier),
|
||||||
Box::new(mock_monitor),
|
Box::new(monitor),
|
||||||
Box::new(mock_formatter),
|
Box::new(formatter),
|
||||||
);
|
);
|
||||||
|
|
||||||
// First run
|
let result = service.alert_on_threshold_violation();
|
||||||
alert_service.alert_on_threshold_violation().unwrap();
|
if should_succeed {
|
||||||
// Second run
|
assert!(result.is_ok());
|
||||||
alert_service.alert_on_threshold_violation().unwrap();
|
} else {
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Hashmap is not ordered, so we need to compare sets
|
#[test]
|
||||||
let sent = notifications.lock().unwrap();
|
fn test_alert_on_threshold_violation_handles_send_error() {
|
||||||
let sent_set: HashSet<_> = sent.iter().collect();
|
let mut notifier = MockForSendingNotification::new();
|
||||||
let expected_set: HashSet<_> = expected_notifications.iter().collect();
|
let mut monitor = MockForMonitoringSystem::new();
|
||||||
assert_eq!(sent_set, expected_set);
|
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_usage".to_string());
|
||||||
|
violation.expect_is_critical().return_const(true);
|
||||||
|
vec![Box::new(violation)]
|
||||||
|
});
|
||||||
|
formatter.expect_format_violation().returning(|_| "Test violation".to_string());
|
||||||
|
|
||||||
|
notifier.expect_send_notification()
|
||||||
|
.returning(|_| Err("Network error".into()));
|
||||||
|
|
||||||
|
let mut service = new(
|
||||||
|
Box::new(notifier),
|
||||||
|
Box::new(monitor),
|
||||||
|
Box::new(formatter),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(service.alert_on_threshold_violation().is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_multiple_different_metrics() {
|
||||||
|
let mut notifier = MockForSendingNotification::new();
|
||||||
|
let mut monitor = MockForMonitoringSystem::new();
|
||||||
|
let mut formatter = MockForFormattingMessage::new();
|
||||||
|
|
||||||
|
// Different metrics should all be sent
|
||||||
|
monitor.expect_check_thresholds().returning(|| {
|
||||||
|
vec![
|
||||||
|
{
|
||||||
|
let mut violation = crate::app::ports::driven::MockForGettingViolationData::new();
|
||||||
|
violation.expect_get_metric_name().return_const("cpu_usage".to_string());
|
||||||
|
violation.expect_is_critical().return_const(true);
|
||||||
|
Box::new(violation) as Box<dyn crate::app::ports::driven::ForGettingViolationData>
|
||||||
|
},
|
||||||
|
{
|
||||||
|
let mut violation = crate::app::ports::driven::MockForGettingViolationData::new();
|
||||||
|
violation.expect_get_metric_name().return_const("memory_usage".to_string());
|
||||||
|
violation.expect_is_critical().return_const(false);
|
||||||
|
Box::new(violation) as Box<dyn crate::app::ports::driven::ForGettingViolationData>
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
formatter.expect_format_violation()
|
||||||
|
.times(2) // Both should be sent
|
||||||
|
.returning(|_| "Violation message".to_string());
|
||||||
|
|
||||||
|
notifier.expect_send_notification()
|
||||||
|
.times(2) // Both should be sent
|
||||||
|
.returning(|_| Ok(()));
|
||||||
|
|
||||||
|
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_resolution() {
|
||||||
|
let mut notifier = MockForSendingNotification::new();
|
||||||
|
let mut monitor = MockForMonitoringSystem::new();
|
||||||
|
let mut formatter = MockForFormattingMessage::new();
|
||||||
|
|
||||||
|
// First call: critical violation
|
||||||
|
// Second call: no violations (resolution)
|
||||||
|
monitor.expect_check_thresholds()
|
||||||
|
.times(2)
|
||||||
|
.returning_st({
|
||||||
|
let mut call_count = 0;
|
||||||
|
move || {
|
||||||
|
call_count += 1;
|
||||||
|
if call_count == 1 {
|
||||||
|
let mut violation = crate::app::ports::driven::MockForGettingViolationData::new();
|
||||||
|
violation.expect_get_metric_name().return_const("cpu_usage".to_string());
|
||||||
|
violation.expect_is_critical().return_const(true);
|
||||||
|
vec![Box::new(violation)]
|
||||||
|
} else {
|
||||||
|
vec![] // No violations - resolved
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
formatter.expect_format_violation()
|
||||||
|
.times(1) // Only for the initial violation
|
||||||
|
.returning(|_| "CPU usage is critical".to_string());
|
||||||
|
|
||||||
|
// Two notifications: initial critical + resolution
|
||||||
|
notifier.expect_send_notification()
|
||||||
|
.times(2)
|
||||||
|
.withf(|msg| {
|
||||||
|
msg.contains("CPU usage is critical") || msg.contains("cpu_usage has been resolved")
|
||||||
|
})
|
||||||
|
.returning(|_| Ok(()));
|
||||||
|
|
||||||
|
let mut service = new(
|
||||||
|
Box::new(notifier),
|
||||||
|
Box::new(monitor),
|
||||||
|
Box::new(formatter),
|
||||||
|
);
|
||||||
|
|
||||||
|
// First call: critical should be sent
|
||||||
|
assert!(service.alert_on_threshold_violation().is_ok());
|
||||||
|
|
||||||
|
// Second call: resolution should be sent
|
||||||
|
assert!(service.alert_on_threshold_violation().is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[case(true, true, 1, "same critical level twice")]
|
||||||
|
#[case(false, false, 1, "same warning level twice")]
|
||||||
|
fn test_same_level_alerts_are_not_sent_twice(
|
||||||
|
#[case] is_critical: bool,
|
||||||
|
#[case] _second_is_critical: bool, // Same as first for these test cases
|
||||||
|
#[case] expected_notifications: usize,
|
||||||
|
#[case] _description: &str,
|
||||||
|
) {
|
||||||
|
let mut notifier = MockForSendingNotification::new();
|
||||||
|
let mut monitor = MockForMonitoringSystem::new();
|
||||||
|
let mut formatter = MockForFormattingMessage::new();
|
||||||
|
|
||||||
|
// Monitor should return the same violation twice
|
||||||
|
monitor.expect_check_thresholds()
|
||||||
|
.times(2)
|
||||||
|
.returning(move || {
|
||||||
|
let mut violation = crate::app::ports::driven::MockForGettingViolationData::new();
|
||||||
|
violation.expect_get_metric_name().return_const("cpu_usage".to_string());
|
||||||
|
violation.expect_is_critical().return_const(is_critical);
|
||||||
|
vec![Box::new(violation)]
|
||||||
|
});
|
||||||
|
|
||||||
|
formatter.expect_format_violation()
|
||||||
|
.times(expected_notifications)
|
||||||
|
.returning(|_| "CPU usage violation".to_string());
|
||||||
|
|
||||||
|
notifier.expect_send_notification()
|
||||||
|
.times(expected_notifications)
|
||||||
|
.returning(|_| Ok(()));
|
||||||
|
|
||||||
|
let mut service = new(
|
||||||
|
Box::new(notifier),
|
||||||
|
Box::new(monitor),
|
||||||
|
Box::new(formatter),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(service.alert_on_threshold_violation().is_ok());
|
||||||
|
|
||||||
|
assert!(service.alert_on_threshold_violation().is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[case(false, true, 2, "escalation from warning to critical")]
|
||||||
|
#[case(true, false, 2, "improvement from critical to warning")] // Now sends alert
|
||||||
|
#[case(false, false, 1, "warning then warning again")]
|
||||||
|
#[case(true, true, 1, "critical then critical again")]
|
||||||
|
fn test_alert_level_changes(
|
||||||
|
#[case] first_is_critical: bool,
|
||||||
|
#[case] second_is_critical: bool,
|
||||||
|
#[case] expected_notifications: usize,
|
||||||
|
#[case] _description: &str,
|
||||||
|
) {
|
||||||
|
let mut notifier = MockForSendingNotification::new();
|
||||||
|
let mut monitor = MockForMonitoringSystem::new();
|
||||||
|
let mut formatter = MockForFormattingMessage::new();
|
||||||
|
|
||||||
|
// Monitor returns different levels on consecutive calls
|
||||||
|
monitor.expect_check_thresholds()
|
||||||
|
.times(2)
|
||||||
|
.returning_st({
|
||||||
|
let mut call_count = 0;
|
||||||
|
move || {
|
||||||
|
call_count += 1;
|
||||||
|
let mut violation = crate::app::ports::driven::MockForGettingViolationData::new();
|
||||||
|
violation.expect_get_metric_name().return_const("cpu_usage".to_string());
|
||||||
|
if call_count == 1 {
|
||||||
|
violation.expect_is_critical().return_const(first_is_critical);
|
||||||
|
} else {
|
||||||
|
violation.expect_is_critical().return_const(second_is_critical);
|
||||||
|
}
|
||||||
|
vec![Box::new(violation)]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
formatter.expect_format_violation()
|
||||||
|
.times(expected_notifications)
|
||||||
|
.returning(|_| "CPU usage violation".to_string());
|
||||||
|
|
||||||
|
notifier.expect_send_notification()
|
||||||
|
.times(expected_notifications)
|
||||||
|
.returning(|_| Ok(()));
|
||||||
|
|
||||||
|
let mut service = new(
|
||||||
|
Box::new(notifier),
|
||||||
|
Box::new(monitor),
|
||||||
|
Box::new(formatter),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(service.alert_on_threshold_violation().is_ok());
|
||||||
|
|
||||||
|
assert!(service.alert_on_threshold_violation().is_ok());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user