Files
remnawave-bedolaga-telegram…/tests/services/test_traffic_monitoring_redis.py
gy9vin 6e1d671df2 feat(traffic): добавлен новый мониторинг трафика v2 с проверкой дельты и snapshot
Новый функционал:
- Быстрая проверка (TRAFFIC_FAST_CHECK_*) — отслеживает дельту трафика за интервал через snapshot
- Суточная проверка (TRAFFIC_DAILY_CHECK_*) — анализирует трафик за 24 часа через bandwidth API
- Фильтрация по нодам (TRAFFIC_MONIT
2026-01-10 00:47:23 +03:00

381 lines
13 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
Тесты для хранения snapshot трафика в Redis.
"""
import pytest
from datetime import datetime, timedelta
from unittest.mock import AsyncMock, MagicMock, patch
from app.services.traffic_monitoring_service import (
TrafficMonitoringServiceV2,
TRAFFIC_SNAPSHOT_KEY,
TRAFFIC_SNAPSHOT_TIME_KEY,
TRAFFIC_NOTIFICATION_CACHE_KEY,
)
@pytest.fixture
def service():
"""Создаёт экземпляр сервиса для тестов."""
return TrafficMonitoringServiceV2()
@pytest.fixture
def mock_cache():
"""Мок для cache сервиса."""
with patch('app.services.traffic_monitoring_service.cache') as mock:
mock.set = AsyncMock(return_value=True)
mock.get = AsyncMock(return_value=None)
yield mock
@pytest.fixture
def sample_snapshot():
"""Пример snapshot данных."""
return {
"uuid-1": 1073741824.0, # 1 GB
"uuid-2": 2147483648.0, # 2 GB
"uuid-3": 5368709120.0, # 5 GB
}
# ============== Тесты сохранения snapshot в Redis ==============
async def test_save_snapshot_to_redis_success(service, mock_cache, sample_snapshot):
"""Тест успешного сохранения snapshot в Redis."""
mock_cache.set = AsyncMock(return_value=True)
result = await service._save_snapshot_to_redis(sample_snapshot)
assert result is True
assert mock_cache.set.call_count == 2 # snapshot + time
# Проверяем что сохранён snapshot
first_call = mock_cache.set.call_args_list[0]
assert first_call[0][0] == TRAFFIC_SNAPSHOT_KEY
assert first_call[0][1] == sample_snapshot
async def test_save_snapshot_to_redis_failure(service, mock_cache, sample_snapshot):
"""Тест неудачного сохранения snapshot в Redis."""
mock_cache.set = AsyncMock(return_value=False)
result = await service._save_snapshot_to_redis(sample_snapshot)
assert result is False
async def test_save_snapshot_to_redis_exception(service, mock_cache, sample_snapshot):
"""Тест обработки исключения при сохранении."""
mock_cache.set = AsyncMock(side_effect=Exception("Redis error"))
result = await service._save_snapshot_to_redis(sample_snapshot)
assert result is False
# ============== Тесты загрузки snapshot из Redis ==============
async def test_load_snapshot_from_redis_success(service, mock_cache, sample_snapshot):
"""Тест успешной загрузки snapshot из Redis."""
mock_cache.get = AsyncMock(return_value=sample_snapshot)
result = await service._load_snapshot_from_redis()
assert result == sample_snapshot
mock_cache.get.assert_called_once_with(TRAFFIC_SNAPSHOT_KEY)
async def test_load_snapshot_from_redis_empty(service, mock_cache):
"""Тест загрузки когда snapshot отсутствует."""
mock_cache.get = AsyncMock(return_value=None)
result = await service._load_snapshot_from_redis()
assert result is None
async def test_load_snapshot_from_redis_invalid_data(service, mock_cache):
"""Тест загрузки невалидных данных."""
mock_cache.get = AsyncMock(return_value="not a dict")
result = await service._load_snapshot_from_redis()
assert result is None
async def test_load_snapshot_from_redis_exception(service, mock_cache):
"""Тест обработки исключения при загрузке."""
mock_cache.get = AsyncMock(side_effect=Exception("Redis error"))
result = await service._load_snapshot_from_redis()
assert result is None
# ============== Тесты времени snapshot ==============
async def test_get_snapshot_time_from_redis_success(service, mock_cache):
"""Тест получения времени snapshot."""
test_time = datetime(2024, 1, 15, 12, 30, 0)
mock_cache.get = AsyncMock(return_value=test_time.isoformat())
result = await service._get_snapshot_time_from_redis()
assert result == test_time
mock_cache.get.assert_called_once_with(TRAFFIC_SNAPSHOT_TIME_KEY)
async def test_get_snapshot_time_from_redis_empty(service, mock_cache):
"""Тест когда время отсутствует."""
mock_cache.get = AsyncMock(return_value=None)
result = await service._get_snapshot_time_from_redis()
assert result is None
# ============== Тесты has_snapshot ==============
async def test_has_snapshot_redis_exists(service, mock_cache, sample_snapshot):
"""Тест has_snapshot когда snapshot есть в Redis."""
mock_cache.get = AsyncMock(return_value=sample_snapshot)
result = await service.has_snapshot()
assert result is True
async def test_has_snapshot_memory_fallback(service, mock_cache):
"""Тест has_snapshot с fallback на память."""
mock_cache.get = AsyncMock(return_value=None)
# Устанавливаем данные в память
service._memory_snapshot = {"uuid-1": 1000.0}
service._memory_snapshot_time = datetime.utcnow()
result = await service.has_snapshot()
assert result is True
async def test_has_snapshot_none(service, mock_cache):
"""Тест has_snapshot когда snapshot нет нигде."""
mock_cache.get = AsyncMock(return_value=None)
service._memory_snapshot = {}
service._memory_snapshot_time = None
result = await service.has_snapshot()
assert result is False
# ============== Тесты get_snapshot_age_minutes ==============
async def test_get_snapshot_age_minutes_from_redis(service, mock_cache):
"""Тест возраста snapshot из Redis."""
# Snapshot создан 30 минут назад
past_time = datetime.utcnow() - timedelta(minutes=30)
mock_cache.get = AsyncMock(return_value=past_time.isoformat())
result = await service.get_snapshot_age_minutes()
assert 29 <= result <= 31 # Допуск на время выполнения
async def test_get_snapshot_age_minutes_memory_fallback(service, mock_cache):
"""Тест возраста snapshot из памяти."""
mock_cache.get = AsyncMock(return_value=None)
service._memory_snapshot_time = datetime.utcnow() - timedelta(minutes=15)
result = await service.get_snapshot_age_minutes()
assert 14 <= result <= 16
async def test_get_snapshot_age_minutes_no_snapshot(service, mock_cache):
"""Тест возраста когда snapshot нет."""
mock_cache.get = AsyncMock(return_value=None)
service._memory_snapshot_time = None
result = await service.get_snapshot_age_minutes()
assert result == float('inf')
# ============== Тесты _save_snapshot (с fallback) ==============
async def test_save_snapshot_redis_success(service, mock_cache, sample_snapshot):
"""Тест сохранения snapshot в Redis успешно."""
mock_cache.set = AsyncMock(return_value=True)
# Заполняем память чтобы проверить что она очистится
service._memory_snapshot = {"old": 123.0}
service._memory_snapshot_time = datetime.utcnow()
result = await service._save_snapshot(sample_snapshot)
assert result is True
assert service._memory_snapshot == {} # Память очищена
assert service._memory_snapshot_time is None
async def test_save_snapshot_fallback_to_memory(service, mock_cache, sample_snapshot):
"""Тест fallback на память когда Redis недоступен."""
mock_cache.set = AsyncMock(return_value=False)
result = await service._save_snapshot(sample_snapshot)
assert result is True
assert service._memory_snapshot == sample_snapshot
assert service._memory_snapshot_time is not None
# ============== Тесты _get_current_snapshot ==============
async def test_get_current_snapshot_from_redis(service, mock_cache, sample_snapshot):
"""Тест получения snapshot из Redis."""
mock_cache.get = AsyncMock(return_value=sample_snapshot)
result = await service._get_current_snapshot()
assert result == sample_snapshot
async def test_get_current_snapshot_fallback_to_memory(service, mock_cache, sample_snapshot):
"""Тест fallback на память."""
mock_cache.get = AsyncMock(return_value=None)
service._memory_snapshot = sample_snapshot
result = await service._get_current_snapshot()
assert result == sample_snapshot
# ============== Тесты уведомлений ==============
async def test_save_notification_to_redis(service, mock_cache):
"""Тест сохранения времени уведомления."""
mock_cache.set = AsyncMock(return_value=True)
result = await service._save_notification_to_redis("uuid-123")
assert result is True
mock_cache.set.assert_called_once()
call_args = mock_cache.set.call_args
assert "traffic:notifications:uuid-123" in call_args[0][0]
async def test_get_notification_time_from_redis(service, mock_cache):
"""Тест получения времени уведомления."""
test_time = datetime(2024, 1, 15, 10, 0, 0)
mock_cache.get = AsyncMock(return_value=test_time.isoformat())
result = await service._get_notification_time_from_redis("uuid-123")
assert result == test_time
async def test_should_send_notification_no_previous(service, mock_cache):
"""Тест should_send_notification когда уведомлений не было."""
mock_cache.get = AsyncMock(return_value=None)
service._memory_notification_cache = {}
result = await service.should_send_notification("uuid-123")
assert result is True
async def test_should_send_notification_cooldown_active(service, mock_cache):
"""Тест should_send_notification когда кулдаун активен."""
# Уведомление было 5 минут назад, кулдаун 60 минут
recent_time = datetime.utcnow() - timedelta(minutes=5)
mock_cache.get = AsyncMock(return_value=recent_time.isoformat())
result = await service.should_send_notification("uuid-123")
assert result is False
async def test_should_send_notification_cooldown_expired(service, mock_cache):
"""Тест should_send_notification когда кулдаун истёк."""
# Уведомление было 120 минут назад, кулдаун 60 минут
old_time = datetime.utcnow() - timedelta(minutes=120)
mock_cache.get = AsyncMock(return_value=old_time.isoformat())
result = await service.should_send_notification("uuid-123")
assert result is True
async def test_record_notification_redis(service, mock_cache):
"""Тест record_notification сохраняет в Redis."""
mock_cache.set = AsyncMock(return_value=True)
await service.record_notification("uuid-123")
mock_cache.set.assert_called_once()
async def test_record_notification_fallback_to_memory(service, mock_cache):
"""Тест record_notification с fallback на память."""
mock_cache.set = AsyncMock(return_value=False)
await service.record_notification("uuid-123")
assert "uuid-123" in service._memory_notification_cache
# ============== Тесты create_initial_snapshot ==============
async def test_create_initial_snapshot_uses_existing_redis(service, mock_cache, sample_snapshot):
"""Тест что create_initial_snapshot использует существующий snapshot из Redis."""
mock_cache.get = AsyncMock(side_effect=[
sample_snapshot, # _load_snapshot_from_redis
(datetime.utcnow() - timedelta(minutes=10)).isoformat(), # _get_snapshot_time_from_redis
])
with patch.object(service, 'get_all_users_with_traffic', new_callable=AsyncMock) as mock_get_users:
result = await service.create_initial_snapshot()
# Не должен вызывать API - используем существующий snapshot
mock_get_users.assert_not_called()
assert result == len(sample_snapshot)
async def test_create_initial_snapshot_creates_new(service, mock_cache):
"""Тест создания нового snapshot когда в Redis пусто."""
mock_cache.get = AsyncMock(return_value=None)
mock_cache.set = AsyncMock(return_value=True)
# Мокаем пользователей из API
mock_user = MagicMock()
mock_user.uuid = "uuid-1"
mock_user.user_traffic = MagicMock()
mock_user.user_traffic.used_traffic_bytes = 1073741824 # 1 GB
with patch.object(service, 'get_all_users_with_traffic', new_callable=AsyncMock) as mock_get_users:
mock_get_users.return_value = [mock_user]
result = await service.create_initial_snapshot()
mock_get_users.assert_called_once()
assert result == 1
# ============== Тесты cleanup_notification_cache ==============
async def test_cleanup_notification_cache_removes_old(service, mock_cache):
"""Тест очистки старых записей из памяти."""
old_time = datetime.utcnow() - timedelta(hours=25)
recent_time = datetime.utcnow() - timedelta(hours=1)
service._memory_notification_cache = {
"uuid-old": old_time,
"uuid-recent": recent_time,
}
await service.cleanup_notification_cache()
assert "uuid-old" not in service._memory_notification_cache
assert "uuid-recent" in service._memory_notification_cache