"""Refresh token Redis + MySQL dual persistence.""" from datetime import datetime, timedelta, timezone from unittest.mock import AsyncMock, MagicMock, patch import pytest from server.application.services.auth_service import AuthService from server.domain.models import Admin @pytest.mark.asyncio async def test_membership_mysql_fallback_rehydrates_redis(): admin = Admin(id=1, username="admin", password_hash="x", is_active=True, token_version=1) refresh_repo = AsyncMock() refresh_repo.exists_valid = AsyncMock(return_value=True) service = AuthService( admin_repo=AsyncMock(), attempt_repo=AsyncMock(), audit_repo=AsyncMock(), refresh_token_repo=refresh_repo, ) service._redis_sadd_refresh = AsyncMock(return_value=True) with patch("server.infrastructure.redis.client.get_redis") as get_redis: redis = AsyncMock() redis.sismember = AsyncMock(return_value=False) get_redis.return_value = redis result = await service._refresh_token_membership(1, "secret:1:1") assert result is True refresh_repo.exists_valid.assert_awaited_once() service._redis_sadd_refresh.assert_awaited_once() @pytest.mark.asyncio async def test_store_writes_redis_then_mysql(): refresh_repo = AsyncMock() service = AuthService( admin_repo=AsyncMock(), attempt_repo=AsyncMock(), audit_repo=AsyncMock(), refresh_token_repo=refresh_repo, ) service._redis_sadd_refresh = AsyncMock(return_value=True) await service._store_refresh_token(2, "tok:2:0") service._redis_sadd_refresh.assert_awaited_once() refresh_repo.add.assert_awaited_once() _admin_id, token_hash, expires_at = refresh_repo.add.await_args.args assert _admin_id == 2 assert len(token_hash) == 64 assert expires_at > datetime.now(timezone.utc) @pytest.mark.asyncio async def test_delete_all_clears_both_layers(): refresh_repo = AsyncMock() service = AuthService( admin_repo=AsyncMock(), attempt_repo=AsyncMock(), audit_repo=AsyncMock(), refresh_token_repo=refresh_repo, ) service._redis_delete_all_refresh = AsyncMock() await service._delete_all_refresh_tokens(3) service._redis_delete_all_refresh.assert_awaited_once_with(3) refresh_repo.delete_all_for_admin.assert_awaited_once_with(3)