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
|
||||
148
Cargo.lock
generated
148
Cargo.lock
generated
@@ -17,6 +17,15 @@ version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.20"
|
||||
@@ -310,6 +319,17 @@ version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
|
||||
|
||||
[[package]]
|
||||
name = "futures-macro"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.31"
|
||||
@@ -322,6 +342,12 @@ version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
|
||||
|
||||
[[package]]
|
||||
name = "futures-timer"
|
||||
version = "3.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24"
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.31"
|
||||
@@ -330,6 +356,7 @@ checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-macro",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"memchr",
|
||||
@@ -367,6 +394,12 @@ version = "0.31.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.4.12"
|
||||
@@ -789,6 +822,7 @@ dependencies = [
|
||||
"discord_client",
|
||||
"dotenvy",
|
||||
"mockall",
|
||||
"rstest",
|
||||
"system_monitor",
|
||||
]
|
||||
|
||||
@@ -944,6 +978,15 @@ dependencies = [
|
||||
"termtree",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-crate"
|
||||
version = "3.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35"
|
||||
dependencies = [
|
||||
"toml_edit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.95"
|
||||
@@ -968,6 +1011,41 @@ version = "5.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
||||
|
||||
[[package]]
|
||||
name = "relative-path"
|
||||
version = "1.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2"
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.12.22"
|
||||
@@ -1024,12 +1102,50 @@ dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rstest"
|
||||
version = "0.26.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f5a3193c063baaa2a95a33f03035c8a72b83d97a54916055ba22d35ed3839d49"
|
||||
dependencies = [
|
||||
"futures-timer",
|
||||
"futures-util",
|
||||
"rstest_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rstest_macros"
|
||||
version = "0.26.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c845311f0ff7951c5506121a9ad75aec44d083c31583b2ea5a30bcb0b0abba0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"glob",
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"relative-path",
|
||||
"rustc_version",
|
||||
"syn",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace"
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
|
||||
dependencies = [
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "1.0.8"
|
||||
@@ -1120,6 +1236,12 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.219"
|
||||
@@ -1362,6 +1484,23 @@ dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.22.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"toml_datetime",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower"
|
||||
version = "0.5.2"
|
||||
@@ -1878,6 +2017,15 @@ version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.7.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rt"
|
||||
version = "0.39.0"
|
||||
|
||||
@@ -11,4 +11,5 @@ clap = { version = "4.5", features = ["derive"] }
|
||||
|
||||
[dev-dependencies]
|
||||
mockall = "0.13.1"
|
||||
rstest = "0.26.1"
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ impl CronAlertsActor {
|
||||
Self { alert_monitor, period }
|
||||
}
|
||||
|
||||
pub fn start(&self) {
|
||||
pub fn start(&mut self) {
|
||||
loop {
|
||||
if let Err(e) = self.alert_monitor.alert_on_threshold_violation() {
|
||||
eprintln!("Error occurred: {}", e);
|
||||
|
||||
@@ -12,7 +12,7 @@ impl ForMonitoringAlertsAdapter {
|
||||
}
|
||||
|
||||
impl ForMonitoringAlerts for ForMonitoringAlertsAdapter {
|
||||
fn alert_on_threshold_violation(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
fn alert_on_threshold_violation(&mut self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
self.app.alert_on_threshold_violation()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
pub trait ForMonitoringAlerts {
|
||||
fn alert_on_threshold_violation(&self) -> Result<(), Box<dyn std::error::Error>>;
|
||||
fn alert_on_threshold_violation(&mut self) -> Result<(), Box<dyn std::error::Error>>;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,31 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::app::ports::driven::{
|
||||
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 {
|
||||
notifier: Box<dyn ForSendingNotification>,
|
||||
monitor: Box<dyn ForMonitoringSystem>,
|
||||
formatter: Box<dyn ForFormattingMessage>,
|
||||
highest_alert_levels: HashMap<String, AlertLevel>,
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
@@ -17,18 +37,54 @@ pub fn new(
|
||||
notifier,
|
||||
monitor,
|
||||
formatter,
|
||||
highest_alert_levels: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
impl AlertService {
|
||||
/// Check for threshold violations and send alerts
|
||||
pub fn alert_on_threshold_violation(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// 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>> {
|
||||
let violations = self.monitor.check_thresholds();
|
||||
for violation in violations {
|
||||
let message = self.formatter.format_violation(violation);
|
||||
self.notifier.send_notification(&message)?;
|
||||
|
||||
// Build a map of current violation levels
|
||||
let mut current_levels: HashMap<String, AlertLevel> = HashMap::new();
|
||||
for violation in &violations {
|
||||
let metric_name = violation.get_metric_name();
|
||||
let level = AlertLevel::from_is_critical(violation.is_critical());
|
||||
current_levels.insert(metric_name, level);
|
||||
}
|
||||
|
||||
// Check for metrics that had violations but now don't (resolution)
|
||||
for (metric_name, &previous_level) in &self.highest_alert_levels {
|
||||
if !current_levels.contains_key(metric_name) {
|
||||
// This metric is now resolved
|
||||
let message = format!("✅ {} has been resolved (was {:?})", metric_name, previous_level);
|
||||
self.notifier.send_notification(&message)?;
|
||||
}
|
||||
}
|
||||
|
||||
// Process current violations
|
||||
for violation in violations {
|
||||
let metric_name = violation.get_metric_name();
|
||||
let current_level = AlertLevel::from_is_critical(violation.is_critical());
|
||||
|
||||
let should_alert = match self.highest_alert_levels.get(&metric_name) {
|
||||
None => true, // First time seeing this metric
|
||||
Some(&previous_level) => current_level != previous_level, // Any level change
|
||||
};
|
||||
|
||||
if should_alert {
|
||||
let message = self.formatter.format_violation(violation);
|
||||
self.notifier.send_notification(&message)?;
|
||||
}
|
||||
|
||||
// Update the level for this metric
|
||||
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(())
|
||||
}
|
||||
}
|
||||
@@ -37,30 +93,70 @@ impl AlertService {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::collections::HashMap;
|
||||
use rstest::rstest;
|
||||
|
||||
use crate::app::ports::driven::{MockForFormattingMessage, MockForMonitoringSystem, MockForSendingNotification};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_alert_on_threshold_violation() {
|
||||
#[rstest]
|
||||
#[case(vec![], 0, true, "no violations")]
|
||||
#[case(vec![true], 1, true, "single critical violation")]
|
||||
#[case(vec![false], 1, true, "single warning violation")]
|
||||
fn test_alert_on_threshold_violation_scenarios(
|
||||
#[case] violations_criticality: Vec<bool>,
|
||||
#[case] expected_notifications: usize,
|
||||
#[case] should_succeed: bool,
|
||||
#[case] _description: &str,
|
||||
) {
|
||||
let mut notifier = MockForSendingNotification::new();
|
||||
let mut monitor = MockForMonitoringSystem::new();
|
||||
let mut formatter = MockForFormattingMessage::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());
|
||||
// Setup monitor expectations
|
||||
monitor.expect_check_thresholds().returning(move || {
|
||||
violations_criticality.iter().enumerate().map(|(i, &is_critical)| {
|
||||
let mut violation = crate::app::ports::driven::MockForGettingViolationData::new();
|
||||
violation.expect_get_metric_name().return_const(format!("metric_{}", i));
|
||||
violation.expect_is_critical().return_const(is_critical);
|
||||
Box::new(violation) as Box<dyn crate::app::ports::driven::ForGettingViolationData>
|
||||
}).collect()
|
||||
});
|
||||
|
||||
let service = new(
|
||||
monitor.expect_get_metrics().returning(|| HashMap::new());
|
||||
|
||||
if expected_notifications > 0 {
|
||||
formatter.expect_format_violation()
|
||||
.times(expected_notifications)
|
||||
.returning(|_| "Violation message".to_string());
|
||||
|
||||
formatter.expect_format_summary().returning(|_| "Summary".to_string());
|
||||
|
||||
notifier.expect_send_notification()
|
||||
.times(expected_notifications)
|
||||
.returning(move |_| {
|
||||
if should_succeed {
|
||||
Ok(())
|
||||
} else {
|
||||
Err("Network error".into())
|
||||
}
|
||||
});
|
||||
} else {
|
||||
formatter.expect_format_summary().returning(|_| "Summary".to_string());
|
||||
}
|
||||
|
||||
let mut service = new(
|
||||
Box::new(notifier),
|
||||
Box::new(monitor),
|
||||
Box::new(formatter),
|
||||
);
|
||||
|
||||
assert!(service.alert_on_threshold_violation().is_ok());
|
||||
let result = service.alert_on_threshold_violation();
|
||||
if should_succeed {
|
||||
assert!(result.is_ok());
|
||||
} else {
|
||||
assert!(result.is_err());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -70,14 +166,17 @@ mod tests {
|
||||
let mut formatter = MockForFormattingMessage::new();
|
||||
|
||||
monitor.expect_check_thresholds().returning(|| {
|
||||
vec![Box::new(crate::app::ports::driven::MockForGettingViolationData::new())]
|
||||
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 service = new(
|
||||
let mut service = new(
|
||||
Box::new(notifier),
|
||||
Box::new(monitor),
|
||||
Box::new(formatter),
|
||||
@@ -85,4 +184,189 @@ mod tests {
|
||||
|
||||
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