Add config and client injection with test reorganization.
Code Quality Pipeline / code-quality (pull_request) Successful in 52s
PR Title Check / check-title (pull_request) Successful in 6s
Test Python Package / test (pull_request) Successful in 1m3s

Introduce typed config objects, optional adapter injection, and .env loading to simplify testing while preserving env-based defaults for production usage.

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
Brian Bjarke Jensen
2026-07-05 21:34:14 +02:00
co-authored by Cursor
parent 7ed1b34233
commit 5e32787b90
28 changed files with 645 additions and 377 deletions
+27 -92
View File
@@ -1,11 +1,15 @@
"""Integration tests for the RedisAdapter."""
from collections.abc import Generator
import pytest
import redis
from redis.commands.json.path import Path as RedisPath
from python_repositories.adapters.redis_adapter import RedisAdapter
from python_repositories.interfaces import JsonRepositoryInterface
from python_repositories.config import RedisConfig
pytestmark = pytest.mark.integration
@pytest.fixture(scope="module")
@@ -26,14 +30,13 @@ def data_in_redis(
yield key, data
# Cleanup
raw_redis_client.delete(key)
@pytest.fixture(scope="module")
def redis_adapter(redis_container: str) -> Generator[RedisAdapter, None, None]:
def redis_adapter(redis_config: RedisConfig) -> Generator[RedisAdapter, None, None]:
"""Fixture to provide a connected RedisAdapter instance."""
adapter = RedisAdapter()
adapter = RedisAdapter(config=redis_config)
adapter.connect()
yield adapter
adapter.disconnect()
@@ -42,38 +45,12 @@ def redis_adapter(redis_container: str) -> Generator[RedisAdapter, None, None]:
@pytest.fixture(scope="function", autouse=True)
def clear_redis(raw_redis_client: redis.Redis) -> None:
"""Fixture to clear all Redis keys before each test."""
# Clear all keys before each test
raw_redis_client.flushall()
def test_should_adhere_to_interface(redis_container: str) -> None:
"""Test that the RedisAdapter adheres to the expected interface."""
assert issubclass(RedisAdapter, JsonRepositoryInterface)
_ = RedisAdapter()
def test_should_have_logger_when_instantiated(redis_container: str) -> None:
"""Test that the RedisAdapter has a logger when instantiated."""
adapter = RedisAdapter()
assert hasattr(adapter, "logger")
assert adapter.logger is not None
def test_should_not_be_connected_when_instantiated(redis_container: str) -> None:
"""Test that the RedisAdapter is not connected when instantiated."""
adapter = RedisAdapter()
assert adapter._client is None
assert not adapter.is_connected()
def test_should_raise_connection_error_when_unable_to_connect(
monkeypatch: pytest.MonkeyPatch,
) -> None:
def test_should_raise_connection_error_when_unable_to_connect() -> None:
"""Test that the RedisAdapter raises ConnectionError when unable to connect."""
# Arrange
monkeypatch.setenv("REDIS_URI", "redis://invalid:6379")
adapter = RedisAdapter()
# Act & Assert
adapter = RedisAdapter(config=RedisConfig(uri="redis://invalid:6379"))
with pytest.raises(ConnectionError):
adapter.connect()
assert adapter._client is None
@@ -84,42 +61,37 @@ def test_connect_raises_connection_error_when_unable_to_ping(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""Test that the RedisAdapter raises ConnectionError when ping fails."""
# Set an invalid URI
monkeypatch.setenv("REDIS_URI", "redis://invalid:6379")
# Monkeypatch redis.Redis.from_url to return a mock client
class MockRedis:
"""A mock Redis client that simulates a failed ping."""
def ping(self) -> bool:
"""Simulate a failed ping."""
return False # Simulate failed ping
return False
monkeypatch.setattr("redis.Redis.from_url", lambda *a, **kw: MockRedis())
adapter = RedisAdapter()
adapter = RedisAdapter(config=RedisConfig(uri="redis://invalid:6379"))
with pytest.raises(ConnectionError, match="Could not connect to Redis"):
adapter.connect()
def test_should_log_error_on_exception_during_exit(
redis_container: str,
redis_config: RedisConfig,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test that the RedisAdapter logs an error when an exception occurs during context exit."""
try:
with RedisAdapter() as adapter:
with RedisAdapter(config=redis_config) as adapter:
assert adapter.is_connected()
raise ValueError("Simulated error")
except ValueError:
pass # Expected
# Assert error was logged
pass
assert "Error while exiting context" in caplog.text
def test_should_have_context_manager(redis_container: str) -> None:
def test_should_have_context_manager(redis_config: RedisConfig) -> None:
"""Test that the RedisAdapter can be used as a context manager."""
with RedisAdapter() as adapter:
with RedisAdapter(config=redis_config) as adapter:
assert adapter._client is not None
assert adapter._client is None
@@ -129,11 +101,8 @@ def test_should_get_value(
redis_adapter: RedisAdapter,
) -> None:
"""Test that the RedisAdapter can get a value."""
# Arrange
key, data = data_in_redis
# Act
value = redis_adapter.get(key)
# Assert
assert value is not None
assert value == data
@@ -142,9 +111,7 @@ def test_should_get_none_for_missing_key(
redis_adapter: RedisAdapter,
) -> None:
"""Test that getting a non-existent key returns None."""
# Act
value = redis_adapter.get("nonexistent_key")
# Assert
assert value is None
@@ -152,21 +119,17 @@ def test_should_raise_value_error_on_invalid_get_key(
redis_adapter: RedisAdapter,
) -> None:
"""Test that the RedisAdapter raises ValueError when getting with an invalid key."""
# Arrange
invalid_keys = ["", 123, None]
# Act & Assert
for key in invalid_keys:
with pytest.raises(ValueError):
redis_adapter.get(key) # type: ignore
def test_should_raise_connection_error_on_get_when_not_connected(
redis_adapter: RedisAdapter,
redis_config: RedisConfig,
) -> None:
"""Test that the RedisAdapter raises ConnectionError when getting while not connected."""
# Arrange
adapter = RedisAdapter() # not connected
# Act & Assert
adapter = RedisAdapter(config=redis_config)
with pytest.raises(ConnectionError):
adapter.get("some_key")
@@ -176,13 +139,10 @@ def test_should_set_value(
redis_adapter: RedisAdapter,
) -> None:
"""Test that the RedisAdapter can set a value."""
# Arrange
key = "test_key"
received_data = redis_adapter.get(key)
assert received_data is None # Ensure key does not exist
# Act
assert received_data is None
redis_adapter.set(key, data)
# Assert
received_data = redis_adapter.get(key)
assert received_data is not None
assert received_data == data
@@ -193,15 +153,12 @@ def test_should_update_value(
redis_adapter: RedisAdapter,
) -> None:
"""Test that the RedisAdapter can update an existing value."""
# Arrange
key, _ = data_in_redis
new_data = {"new_key": "new_value"}
received_data = redis_adapter.get(key)
assert received_data is not None
assert received_data != new_data
# Act
redis_adapter.set(key, new_data)
# Assert
assert redis_adapter.get(key) == new_data
@@ -210,9 +167,7 @@ def test_should_raise_value_error_on_invalid_set_key(
data: dict[str, str],
) -> None:
"""Test that the RedisAdapter raises ValueError when setting with an invalid key."""
# Arrange
invalid_keys = ["", 123, None]
# Act & Assert
for key in invalid_keys:
with pytest.raises(ValueError):
redis_adapter.set(key, data) # type: ignore
@@ -222,26 +177,21 @@ def test_should_raise_value_error_on_invalid_set_data(
redis_adapter: RedisAdapter,
) -> None:
"""Test that the RedisAdapter raises ValueError when setting with invalid data."""
# Arrange
key = "test_key"
invalid_data = ["", 123, None, [], {}]
# Act & Assert
for data in invalid_data:
with pytest.raises(ValueError):
redis_adapter.set(key, data) # type: ignore
def test_should_raise_connection_error_on_set_when_not_connected(
redis_adapter: RedisAdapter,
redis_config: RedisConfig,
data: dict[str, str],
) -> None:
"""Test that the RedisAdapter raises ConnectionError when setting while not connected."""
# Arrange
adapter = RedisAdapter()
key = "test_key"
# Act & Assert
adapter = RedisAdapter(config=redis_config)
with pytest.raises(ConnectionError):
adapter.set(key, data)
adapter.set("test_key", data)
def test_should_delete_key(
@@ -249,13 +199,10 @@ def test_should_delete_key(
redis_adapter: RedisAdapter,
) -> None:
"""Test that deleting a key removes it from Redis."""
# Arrange
key, _ = data_in_redis
received_data = redis_adapter.get(key)
assert received_data is not None # Ensure key exists
# Act
assert received_data is not None
redis_adapter.delete(key)
# Assert
assert redis_adapter.get(key) is None
@@ -263,21 +210,17 @@ def test_should_raise_value_error_on_invalid_delete_key(
redis_adapter: RedisAdapter,
) -> None:
"""Test that the RedisAdapter raises ValueError when deleting with an invalid key."""
# Arrange
invalid_keys = ["", 123, None]
# Act & Assert
for key in invalid_keys:
with pytest.raises(ValueError):
redis_adapter.delete(key) # type: ignore
def test_should_raise_connection_error_on_delete_when_not_connected(
redis_adapter: RedisAdapter,
redis_config: RedisConfig,
) -> None:
"""Test that the RedisAdapter raises ConnectionError when deleting while not connected."""
# Arrange
adapter = RedisAdapter()
# Act & Assert
adapter = RedisAdapter(config=redis_config)
with pytest.raises(ConnectionError):
adapter.delete("some_key")
@@ -286,12 +229,9 @@ def test_should_list_keys(
redis_adapter: RedisAdapter,
) -> None:
"""Test listing keys matching a pattern returns correct keys."""
# Arrange
redis_adapter.set("key1", {"a": 1})
redis_adapter.set("key2", {"b": 2})
# Act
keys = redis_adapter.list_keys("key*")
# Assert
assert set(keys) == {"key1", "key2"}
@@ -299,25 +239,20 @@ def test_should_raise_value_error_on_invalid_list_keys_pattern(
redis_adapter: RedisAdapter,
) -> None:
"""Test that the RedisAdapter raises ValueError when listing keys with an invalid pattern."""
# Arrange
invalid_patterns = ["", 123, None]
# Act & Assert
for pattern in invalid_patterns:
with pytest.raises(ValueError):
redis_adapter.list_keys(pattern) # type: ignore
def test_should_raise_connection_error_on_list_keys_when_not_connected(
redis_adapter: RedisAdapter,
redis_config: RedisConfig,
) -> None:
"""Test that the RedisAdapter raises ConnectionError when listing keys while not connected."""
# Arrange
adapter = RedisAdapter()
# Act & Assert
adapter = RedisAdapter(config=redis_config)
with pytest.raises(ConnectionError):
adapter.list_keys("some_pattern")
# allows local debugging by running file as script
if __name__ == "__main__":
pytest.main(["-s", "-v", __file__])