Files
Nexus/tests/test_alert_streak.py
T

141 lines
4.6 KiB
Python
Raw Normal View History

"""Alert streak gate — consecutive over-threshold before broadcast."""
from __future__ import annotations
from unittest.mock import AsyncMock, patch
import pytest
from server.utils.alert_streak import (
process_metric_alerts,
record_over_threshold,
reset_streak,
streak_required,
)
def test_streak_required_minimum_one():
with patch("server.utils.alert_streak.settings") as mock_settings:
mock_settings.ALERT_STREAK_REQUIRED = 0
assert streak_required() == 1
mock_settings.ALERT_STREAK_REQUIRED = 3
assert streak_required() == 3
@pytest.mark.asyncio
async def test_record_and_reset_streak():
redis = AsyncMock()
redis.incr = AsyncMock(return_value=2)
redis.expire = AsyncMock()
redis.delete = AsyncMock()
assert await record_over_threshold(redis, 10, "cpu") == 2
redis.incr.assert_called_once_with("alert_streak:10:cpu")
redis.expire.assert_called_once()
await reset_streak(redis, 10, "cpu")
redis.delete.assert_called_once_with("alert_streak:10:cpu")
@pytest.mark.asyncio
async def test_suppresses_until_streak_reached():
redis = AsyncMock()
redis.smembers = AsyncMock(return_value=set())
redis.incr = AsyncMock(side_effect=[1, 2])
redis.expire = AsyncMock()
redis.sadd = AsyncMock()
redis.delete = AsyncMock()
thresholds = {"cpu": 80, "mem": 80, "disk": 80}
system_info = {"cpu_usage": 95.0, "mem_usage": 10.0, "disk_usage": 10.0}
with patch("server.utils.alert_streak.streak_required", return_value=3):
with patch("server.utils.alert_streak.broadcast_alert", AsyncMock()) as mock_alert:
await process_metric_alerts(
redis=redis,
server_id=477,
server_name="test",
system_info=system_info,
thresholds=thresholds,
)
mock_alert.assert_not_called()
await process_metric_alerts(
redis=redis,
server_id=477,
server_name="test",
system_info=system_info,
thresholds=thresholds,
)
mock_alert.assert_not_called()
@pytest.mark.asyncio
async def test_fires_on_third_consecutive_sample():
redis = AsyncMock()
redis.smembers = AsyncMock(return_value=set())
redis.incr = AsyncMock(return_value=3)
redis.expire = AsyncMock()
redis.sadd = AsyncMock()
thresholds = {"cpu": 80, "mem": 80, "disk": 80}
system_info = {"cpu_usage": 100.0, "mem_usage": 10.0, "disk_usage": 10.0}
with patch("server.utils.alert_streak.streak_required", return_value=3):
with patch("server.utils.alert_streak.broadcast_alert", AsyncMock()) as mock_alert:
await process_metric_alerts(
redis=redis,
server_id=477,
server_name="飒遂",
system_info=system_info,
thresholds=thresholds,
)
mock_alert.assert_called_once_with(477, "cpu", 100.0, "飒遂")
redis.sadd.assert_called_once()
@pytest.mark.asyncio
async def test_does_not_rebroadcast_while_already_active():
redis = AsyncMock()
redis.smembers = AsyncMock(return_value={"cpu"})
redis.incr = AsyncMock(return_value=5)
redis.expire = AsyncMock()
redis.sadd = AsyncMock()
thresholds = {"cpu": 80, "mem": 80, "disk": 80}
system_info = {"cpu_usage": 100.0, "mem_usage": 10.0, "disk_usage": 10.0}
with patch("server.utils.alert_streak.streak_required", return_value=3):
with patch("server.utils.alert_streak.broadcast_alert", AsyncMock()) as mock_alert:
await process_metric_alerts(
redis=redis,
server_id=477,
server_name="飒遂",
system_info=system_info,
thresholds=thresholds,
)
mock_alert.assert_not_called()
redis.sadd.assert_not_called()
@pytest.mark.asyncio
async def test_reset_streak_on_recovery_path():
redis = AsyncMock()
redis.smembers = AsyncMock(return_value={"cpu"})
redis.delete = AsyncMock()
redis.srem = AsyncMock()
thresholds = {"cpu": 80, "mem": 80, "disk": 80}
system_info = {"cpu_usage": 40.0, "mem_usage": 10.0, "disk_usage": 10.0}
with patch("server.utils.alert_streak.broadcast_recovery", AsyncMock()) as mock_recovery:
await process_metric_alerts(
redis=redis,
server_id=477,
server_name="飒遂",
system_info=system_info,
thresholds=thresholds,
)
mock_recovery.assert_called_once_with(477, "cpu", 40.0, "飒遂")
redis.delete.assert_any_call("alert_streak:477:cpu")