"""Unit tests for redis_repository.RedisRepository. Regression coverage for QA-round-3 B21: user state must live on a dedicated Redis logical DB (default db3) with all keys prefixed by ``wrongmove:user:``. """ from datetime import timedelta from unittest import mock import pytest def _patched_app() -> mock.MagicMock: """Return a MagicMock standing in for celery_app.app.broker_connection().""" fake_app = mock.MagicMock() fake_app.broker_connection.return_value.info.return_value = { "hostname": "redis.test", "port": 6379, } return fake_app class TestRedisRepositoryDbSelection: """B21 regression: RedisRepository writes to a dedicated DB (default 3).""" def test_defaults_to_db_3(self): import redis_repository with mock.patch.dict("os.environ", {}, clear=False) as _env: _env.pop("REDIS_USER_DB", None) with mock.patch.object(redis_repository, "redis") as mock_redis, \ mock.patch.object(redis_repository, "app", _patched_app()): redis_repository.RedisRepository() kwargs = mock_redis.Redis.call_args.kwargs assert kwargs["db"] == 3 assert kwargs["socket_keepalive"] is True assert kwargs["health_check_interval"] == 25 def test_honours_REDIS_USER_DB_env_var(self): import redis_repository with mock.patch.dict("os.environ", {"REDIS_USER_DB": "7"}): with mock.patch.object(redis_repository, "redis") as mock_redis, \ mock.patch.object(redis_repository, "app", _patched_app()): redis_repository.RedisRepository() kwargs = mock_redis.Redis.call_args.kwargs assert kwargs["db"] == 7 class TestRedisRepositoryKeyPrefix: """B21 regression: all keys written via set_key/get_key are namespaced with the ``wrongmove:user:`` prefix to avoid collisions on a shared Redis instance.""" def _fresh_repo(self) -> tuple[object, mock.MagicMock]: import redis_repository with mock.patch.object(redis_repository, "redis"), \ mock.patch.object(redis_repository, "app", _patched_app()): repo = redis_repository.RedisRepository() fake_client = mock.MagicMock() repo.redis_client = fake_client return repo, fake_client def test_set_key_prepends_prefix(self): repo, fake_client = self._fresh_repo() repo.set_key("webauthn:challenge:abc", {"x": 1}) # First positional arg to .set is the key. set_call_key = fake_client.set.call_args.args[0] assert set_call_key == "wrongmove:user:webauthn:challenge:abc" # expire uses the same prefixed key. expire_call_key = fake_client.expire.call_args.args[0] assert expire_call_key == "wrongmove:user:webauthn:challenge:abc" def test_get_key_uses_prefix(self): repo, fake_client = self._fresh_repo() fake_client.get.return_value = '{"hello": "world"}' result = repo.get_key("webauthn:challenge:abc") fake_client.get.assert_called_once_with( "wrongmove:user:webauthn:challenge:abc" ) assert result == {"hello": "world"} def test_does_not_double_prefix_already_prefixed_key(self): repo, fake_client = self._fresh_repo() repo.set_key("wrongmove:user:already-prefixed", {"x": 1}) set_call_key = fake_client.set.call_args.args[0] assert set_call_key == "wrongmove:user:already-prefixed" def test_add_task_for_user_uses_prefixed_key(self): repo, fake_client = self._fresh_repo() fake_client.get.return_value = None # no prior tasks from api.auth import User user = User(sub="", email="test@example.com", name="") repo.add_task_for_user(user, "task-xyz") # The redis SET should target the namespaced key. set_call_key = fake_client.set.call_args.args[0] assert set_call_key == "wrongmove:user:test@example.com/tasks"