191 lines
7.0 KiB
Python
191 lines
7.0 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""Unit tests for Redis client module.
|
|
|
|
Tests Redis connection management with mocked Redis.
|
|
"""
|
|
|
|
import pytest
|
|
from unittest.mock import patch, MagicMock
|
|
import importlib
|
|
|
|
|
|
class TestRedisClient:
|
|
"""Test Redis client connection management."""
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def reset_module(self):
|
|
"""Reset module state before each test."""
|
|
import mes_dashboard.core.redis_client as rc
|
|
rc._REDIS_CLIENT = None
|
|
yield
|
|
rc._REDIS_CLIENT = None
|
|
|
|
def test_get_redis_client_success(self, reset_module):
|
|
"""Test successful Redis client creation."""
|
|
import mes_dashboard.core.redis_client as rc
|
|
|
|
with patch.object(rc, 'REDIS_ENABLED', True):
|
|
with patch.object(rc.redis.Redis, 'from_url') as mock_from_url:
|
|
mock_client = MagicMock()
|
|
mock_client.ping.return_value = True
|
|
mock_from_url.return_value = mock_client
|
|
|
|
client = rc.get_redis_client()
|
|
|
|
assert client is mock_client
|
|
mock_from_url.assert_called_once()
|
|
|
|
def test_get_redis_client_disabled(self, reset_module):
|
|
"""Test Redis client returns None when disabled."""
|
|
import mes_dashboard.core.redis_client as rc
|
|
|
|
with patch.object(rc, 'REDIS_ENABLED', False):
|
|
client = rc.get_redis_client()
|
|
assert client is None
|
|
|
|
def test_get_redis_client_connection_error(self, reset_module):
|
|
"""Test Redis client handles connection errors gracefully."""
|
|
import mes_dashboard.core.redis_client as rc
|
|
import redis as redis_lib
|
|
|
|
with patch.object(rc, 'REDIS_ENABLED', True):
|
|
with patch.object(rc.redis.Redis, 'from_url') as mock_from_url:
|
|
mock_from_url.side_effect = redis_lib.RedisError("Connection refused")
|
|
|
|
client = rc.get_redis_client()
|
|
|
|
assert client is None
|
|
|
|
def test_redis_available_true(self, reset_module):
|
|
"""Test redis_available returns True when Redis is connected."""
|
|
import mes_dashboard.core.redis_client as rc
|
|
|
|
with patch.object(rc, 'REDIS_ENABLED', True):
|
|
with patch.object(rc.redis.Redis, 'from_url') as mock_from_url:
|
|
mock_client = MagicMock()
|
|
mock_client.ping.return_value = True
|
|
mock_from_url.return_value = mock_client
|
|
|
|
assert rc.redis_available() is True
|
|
|
|
def test_redis_available_disabled(self, reset_module):
|
|
"""Test redis_available returns False when disabled."""
|
|
import mes_dashboard.core.redis_client as rc
|
|
|
|
with patch.object(rc, 'REDIS_ENABLED', False):
|
|
assert rc.redis_available() is False
|
|
|
|
def test_get_key_with_prefix(self):
|
|
"""Test get_key adds prefix correctly."""
|
|
import mes_dashboard.core.redis_client as rc
|
|
|
|
with patch.object(rc, 'REDIS_KEY_PREFIX', 'test_prefix'):
|
|
key = rc.get_key('mykey')
|
|
assert key == 'test_prefix:mykey'
|
|
|
|
def test_get_key_without_prefix(self):
|
|
"""Test get_key works with empty prefix."""
|
|
import mes_dashboard.core.redis_client as rc
|
|
|
|
with patch.object(rc, 'REDIS_KEY_PREFIX', ''):
|
|
key = rc.get_key('mykey')
|
|
assert key == ':mykey'
|
|
|
|
def test_redact_connection_url_masks_password(self):
|
|
import mes_dashboard.core.redis_client as rc
|
|
|
|
redacted = rc.redact_connection_url("redis://user:secret@localhost:6379/0")
|
|
assert redacted == "redis://user:***@localhost:6379/0"
|
|
|
|
def test_redact_connection_url_without_credentials(self):
|
|
import mes_dashboard.core.redis_client as rc
|
|
|
|
redacted = rc.redact_connection_url("redis://localhost:6379/0")
|
|
assert redacted == "redis://localhost:6379/0"
|
|
|
|
def test_get_redis_client_logs_redacted_url(self, reset_module):
|
|
import mes_dashboard.core.redis_client as rc
|
|
|
|
with patch.object(rc, 'REDIS_ENABLED', True):
|
|
with patch.object(rc, 'REDIS_URL', 'redis://user:secret@localhost:6379/0'):
|
|
with patch.object(rc.redis.Redis, 'from_url') as mock_from_url:
|
|
with patch.object(rc.logger, 'info') as mock_info:
|
|
mock_client = MagicMock()
|
|
mock_client.ping.return_value = True
|
|
mock_from_url.return_value = mock_client
|
|
|
|
rc.get_redis_client()
|
|
|
|
logged_url = mock_info.call_args.args[1]
|
|
assert logged_url == 'redis://user:***@localhost:6379/0'
|
|
|
|
|
|
class TestRedisClientSingleton:
|
|
"""Test Redis client singleton behavior."""
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def reset_module(self):
|
|
"""Reset module state before each test."""
|
|
import mes_dashboard.core.redis_client as rc
|
|
rc._REDIS_CLIENT = None
|
|
yield
|
|
rc._REDIS_CLIENT = None
|
|
|
|
def test_client_is_singleton(self, reset_module):
|
|
"""Test that get_redis_client returns same instance."""
|
|
import mes_dashboard.core.redis_client as rc
|
|
|
|
with patch.object(rc, 'REDIS_ENABLED', True):
|
|
with patch.object(rc.redis.Redis, 'from_url') as mock_from_url:
|
|
mock_client = MagicMock()
|
|
mock_client.ping.return_value = True
|
|
mock_from_url.return_value = mock_client
|
|
|
|
client1 = rc.get_redis_client()
|
|
client2 = rc.get_redis_client()
|
|
|
|
assert client1 is client2
|
|
# from_url should only be called once
|
|
assert mock_from_url.call_count == 1
|
|
|
|
|
|
class TestCloseRedis:
|
|
"""Test Redis client cleanup."""
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def reset_module(self):
|
|
"""Reset module state before each test."""
|
|
import mes_dashboard.core.redis_client as rc
|
|
rc._REDIS_CLIENT = None
|
|
yield
|
|
rc._REDIS_CLIENT = None
|
|
|
|
def test_close_redis(self, reset_module):
|
|
"""Test close_redis properly closes connection."""
|
|
import mes_dashboard.core.redis_client as rc
|
|
|
|
with patch.object(rc, 'REDIS_ENABLED', True):
|
|
with patch.object(rc.redis.Redis, 'from_url') as mock_from_url:
|
|
mock_client = MagicMock()
|
|
mock_client.ping.return_value = True
|
|
mock_from_url.return_value = mock_client
|
|
|
|
# Get client first
|
|
client = rc.get_redis_client()
|
|
assert client is not None
|
|
|
|
# Close it
|
|
rc.close_redis()
|
|
|
|
# Verify close was called
|
|
mock_client.close.assert_called_once()
|
|
assert rc._REDIS_CLIENT is None
|
|
|
|
def test_close_redis_when_none(self, reset_module):
|
|
"""Test close_redis does nothing when no client."""
|
|
import mes_dashboard.core.redis_client as rc
|
|
|
|
# Should not raise any errors
|
|
rc.close_redis()
|
|
assert rc._REDIS_CLIENT is None
|