mirror of
https://github.com/BEDOLAGA-DEV/remnawave-bedolaga-telegram-bot.git
synced 2026-03-01 07:42:30 +00:00
- Add pyproject.toml with uv and ruff configuration - Pin Python version to 3.13 via .python-version - Add Makefile commands: lint, format, fix - Apply ruff formatting to entire codebase - Remove unused imports (base64 in yookassa/simple_subscription) - Update .gitignore for new config files
394 lines
13 KiB
Python
394 lines
13 KiB
Python
"""
|
||
Тесты для хранения snapshot трафика в Redis.
|
||
"""
|
||
|
||
from datetime import datetime, timedelta
|
||
from unittest.mock import AsyncMock, MagicMock, patch
|
||
|
||
import pytest
|
||
|
||
from app.services.traffic_monitoring_service import (
|
||
TRAFFIC_SNAPSHOT_KEY,
|
||
TRAFFIC_SNAPSHOT_TIME_KEY,
|
||
TrafficMonitoringServiceV2,
|
||
)
|
||
|
||
|
||
@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
|