"""Unit tests for the Redis connector.""" import asyncio from unittest.mock import AsyncMock import pytest import redis.asyncio as redis from fakeredis.aioredis import FakeRedis from pytest_mock import MockerFixture from redis.exceptions import RedisError from contributor import config from contributor.connectors.redis import RedisConnector async def _set_raw_value( redis_connector: RedisConnector, key: str, value: str, ttl: int ) -> None: await redis_connector.client.set(key, value, ex=ttl) @pytest.fixture def redis_connector() -> RedisConnector: """Return a Redis connector backed by fakeredis.""" return RedisConnector("127.0.0.1", 6379, use_redis_cache=False) def test_init_uses_fakeredis(redis_connector: RedisConnector) -> None: assert isinstance(redis_connector.client, FakeRedis) def test_init_uses_real_redis_when_enabled(mocker: MockerFixture) -> None: mock_client = AsyncMock(spec=redis.Redis) redis_constructor = mocker.patch.object( redis, "Redis", return_value=mock_client, ) connector = RedisConnector("example", 6379, use_redis_cache=True) redis_constructor.assert_called_once_with( host="example", port=6379, ssl=config.REDIS_SSL, socket_connect_timeout=10.0, socket_timeout=1.0, ) assert connector.client is mock_client assert not isinstance(connector.client, FakeRedis) def test_ping_returns_true(redis_connector: RedisConnector) -> None: result = asyncio.run(redis_connector.ping()) assert result is True def test_set_and_get_round_trip(redis_connector: RedisConnector) -> None: payload = {"id": "abc", "name": "test"} was_set = asyncio.run(redis_connector.set("artists:abc", payload, ttl=10)) result = asyncio.run(redis_connector.get("artists:abc")) assert was_set is True assert result == payload def test_get_returns_none_for_missing_key(redis_connector: RedisConnector) -> None: result = asyncio.run(redis_connector.get("artists:missing")) assert result is None def test_mget_returns_none_for_invalid_json(redis_connector: RedisConnector) -> None: asyncio.run(redis_connector.set("artists:ok", {"id": "ok"}, ttl=10)) asyncio.run(_set_raw_value(redis_connector, "artists:bad", "{not-json", ttl=10)) result = asyncio.run(redis_connector.mget(["artists:ok", "artists:bad"])) assert result == [{"id": "ok"}, None] def test_mget_returns_empty_list_on_redis_error( redis_connector: RedisConnector, mocker: MockerFixture, ) -> None: mocker.patch.object( redis_connector.client, "mget", new=AsyncMock(side_effect=RedisError("boom")), ) result = asyncio.run(redis_connector.mget(["artists:1"])) assert result == [] def test_set_returns_false_on_redis_error( redis_connector: RedisConnector, mocker: MockerFixture, ) -> None: mocker.patch.object( redis_connector.client, "set", new=AsyncMock(side_effect=RedisError("boom")), ) result = asyncio.run(redis_connector.set("artists:1", {"id": 1}, ttl=10)) assert result is False def test_mset_returns_result_map(redis_connector: RedisConnector) -> None: result = asyncio.run( redis_connector.mset( { "artists:ok": {"id": "ok"}, "artists:empty": {}, }, ttl=10, ) ) assert result == { "artists:ok": True, "artists:empty": False, } def test_mset_marks_key_false_when_set_raises( redis_connector: RedisConnector, mocker: MockerFixture, ) -> None: mocker.patch.object( redis_connector, "set", new=AsyncMock(side_effect=RedisError("boom")), ) result = asyncio.run(redis_connector.mset({"artists:1": {"id": 1}}, ttl=10)) assert result == {"artists:1": False}