Move duplicated context-manager, health-check cache, and connection guards from Redis and Minio adapters into an internal base class. Co-authored-by: Cursor <[email protected]>
185 lines
6.4 KiB
Python
185 lines
6.4 KiB
Python
"""Tests for TTL-cached connection health checks on adapters."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import pytest
|
|
import redis
|
|
|
|
from python_repositories.adapters.minio_adapter import MinioAdapter
|
|
from python_repositories.adapters.redis_adapter import RedisAdapter
|
|
|
|
|
|
@pytest.fixture
|
|
def redis_adapter(monkeypatch: pytest.MonkeyPatch) -> RedisAdapter:
|
|
monkeypatch.setenv("REDIS_URI", "redis://localhost:6379")
|
|
return RedisAdapter()
|
|
|
|
|
|
@pytest.fixture
|
|
def minio_adapter(monkeypatch: pytest.MonkeyPatch) -> MinioAdapter:
|
|
monkeypatch.setenv("MINIO_ENDPOINT", "localhost:9000")
|
|
monkeypatch.setenv("MINIO_ACCESS_KEY", "minioadmin")
|
|
monkeypatch.setenv("MINIO_SECRET_KEY", "minioadmin")
|
|
monkeypatch.setenv("MINIO_BUCKET", "test-bucket")
|
|
return MinioAdapter()
|
|
|
|
|
|
class TestRedisConnectionHealth:
|
|
def test_not_connected_when_no_client(self, redis_adapter: RedisAdapter) -> None:
|
|
assert not redis_adapter.is_connected()
|
|
|
|
def test_connected_when_probe_succeeds(self, redis_adapter: RedisAdapter) -> None:
|
|
mock_client = MagicMock(spec=redis.Redis)
|
|
mock_client.ping.return_value = True
|
|
redis_adapter._client = mock_client
|
|
|
|
assert redis_adapter.is_connected()
|
|
mock_client.ping.assert_called_once()
|
|
|
|
def test_stale_connection_when_probe_fails(
|
|
self, redis_adapter: RedisAdapter
|
|
) -> None:
|
|
mock_client = MagicMock(spec=redis.Redis)
|
|
mock_client.ping.side_effect = redis.ConnectionError("connection lost")
|
|
redis_adapter._client = mock_client
|
|
|
|
assert not redis_adapter.is_connected()
|
|
|
|
def test_cache_hit_avoids_second_probe(self, redis_adapter: RedisAdapter) -> None:
|
|
mock_client = MagicMock(spec=redis.Redis)
|
|
mock_client.ping.return_value = True
|
|
redis_adapter._client = mock_client
|
|
|
|
with patch(
|
|
"python_repositories.adapters.connection_aware_adapter.time.monotonic",
|
|
return_value=100.0,
|
|
):
|
|
assert redis_adapter.is_connected()
|
|
assert redis_adapter.is_connected()
|
|
|
|
mock_client.ping.assert_called_once()
|
|
|
|
def test_cache_miss_runs_probe_again(self, redis_adapter: RedisAdapter) -> None:
|
|
mock_client = MagicMock(spec=redis.Redis)
|
|
mock_client.ping.return_value = True
|
|
redis_adapter._client = mock_client
|
|
|
|
with patch(
|
|
"python_repositories.adapters.connection_aware_adapter.time.monotonic",
|
|
side_effect=[100.0, 102.0],
|
|
):
|
|
assert redis_adapter.is_connected()
|
|
assert redis_adapter.is_connected()
|
|
|
|
assert mock_client.ping.call_count == 2
|
|
|
|
def test_disconnect_clears_cache(self, redis_adapter: RedisAdapter) -> None:
|
|
mock_client = MagicMock(spec=redis.Redis)
|
|
mock_client.ping.return_value = True
|
|
redis_adapter._client = mock_client
|
|
|
|
with patch(
|
|
"python_repositories.adapters.connection_aware_adapter.time.monotonic",
|
|
return_value=100.0,
|
|
):
|
|
assert redis_adapter.is_connected()
|
|
|
|
redis_adapter.disconnect()
|
|
redis_adapter._client = mock_client
|
|
|
|
with patch(
|
|
"python_repositories.adapters.connection_aware_adapter.time.monotonic",
|
|
return_value=100.0,
|
|
):
|
|
assert redis_adapter.is_connected()
|
|
|
|
assert mock_client.ping.call_count == 2
|
|
|
|
|
|
class TestMinioConnectionHealth:
|
|
def test_not_connected_when_no_client(self, minio_adapter: MinioAdapter) -> None:
|
|
assert not minio_adapter.is_connected()
|
|
|
|
def test_not_connected_when_bucket_name_missing(
|
|
self, minio_adapter: MinioAdapter
|
|
) -> None:
|
|
minio_adapter._client = MagicMock()
|
|
minio_adapter._bucket_name = None
|
|
|
|
assert not minio_adapter.is_connected()
|
|
|
|
def test_connected_when_probe_succeeds(self, minio_adapter: MinioAdapter) -> None:
|
|
mock_client = MagicMock()
|
|
mock_client.bucket_exists.return_value = True
|
|
minio_adapter._client = mock_client
|
|
minio_adapter._bucket_name = "test-bucket"
|
|
|
|
assert minio_adapter.is_connected()
|
|
mock_client.bucket_exists.assert_called_once_with("test-bucket")
|
|
|
|
def test_stale_connection_when_probe_fails(
|
|
self, minio_adapter: MinioAdapter
|
|
) -> None:
|
|
mock_client = MagicMock()
|
|
mock_client.bucket_exists.side_effect = Exception("connection lost")
|
|
minio_adapter._client = mock_client
|
|
minio_adapter._bucket_name = "test-bucket"
|
|
|
|
assert not minio_adapter.is_connected()
|
|
|
|
def test_cache_hit_avoids_second_probe(self, minio_adapter: MinioAdapter) -> None:
|
|
mock_client = MagicMock()
|
|
mock_client.bucket_exists.return_value = True
|
|
minio_adapter._client = mock_client
|
|
minio_adapter._bucket_name = "test-bucket"
|
|
|
|
with patch(
|
|
"python_repositories.adapters.connection_aware_adapter.time.monotonic",
|
|
return_value=100.0,
|
|
):
|
|
assert minio_adapter.is_connected()
|
|
assert minio_adapter.is_connected()
|
|
|
|
mock_client.bucket_exists.assert_called_once()
|
|
|
|
def test_cache_miss_runs_probe_again(self, minio_adapter: MinioAdapter) -> None:
|
|
mock_client = MagicMock()
|
|
mock_client.bucket_exists.return_value = True
|
|
minio_adapter._client = mock_client
|
|
minio_adapter._bucket_name = "test-bucket"
|
|
|
|
with patch(
|
|
"python_repositories.adapters.connection_aware_adapter.time.monotonic",
|
|
side_effect=[100.0, 102.0],
|
|
):
|
|
assert minio_adapter.is_connected()
|
|
assert minio_adapter.is_connected()
|
|
|
|
assert mock_client.bucket_exists.call_count == 2
|
|
|
|
def test_disconnect_clears_cache(self, minio_adapter: MinioAdapter) -> None:
|
|
mock_client = MagicMock()
|
|
mock_client.bucket_exists.return_value = True
|
|
minio_adapter._client = mock_client
|
|
minio_adapter._bucket_name = "test-bucket"
|
|
|
|
with patch(
|
|
"python_repositories.adapters.connection_aware_adapter.time.monotonic",
|
|
return_value=100.0,
|
|
):
|
|
assert minio_adapter.is_connected()
|
|
|
|
minio_adapter.disconnect()
|
|
minio_adapter._client = mock_client
|
|
minio_adapter._bucket_name = "test-bucket"
|
|
|
|
with patch(
|
|
"python_repositories.adapters.connection_aware_adapter.time.monotonic",
|
|
return_value=100.0,
|
|
):
|
|
assert minio_adapter.is_connected()
|
|
|
|
assert mock_client.bucket_exists.call_count == 2
|