mirror of
https://github.com/BEDOLAGA-DEV/remnawave-bedolaga-telegram-bot.git
synced 2026-01-19 19:32:10 +00:00
Новый функционал: - Быстрая проверка (TRAFFIC_FAST_CHECK_*) — отслеживает дельту трафика за интервал через snapshot - Суточная проверка (TRAFFIC_DAILY_CHECK_*) — анализирует трафик за 24 часа через bandwidth API - Фильтрация по нодам (TRAFFIC_MONIT
381 lines
13 KiB
Python
381 lines
13 KiB
Python
"""
|
||
Тесты для хранения 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
|