Spaces:
Sleeping
Sleeping
| """Lockout module — exercises the failure-silent behaviour without Redis. | |
| We only test the public-facing shape (silent-fail returns False/0) because | |
| the real counter logic needs a live Redis; that's covered in integration. | |
| """ | |
| from unittest.mock import AsyncMock, patch | |
| import pytest | |
| from redis.exceptions import RedisError | |
| from app.auth import lockout | |
| class TestLockoutFailsSilent: | |
| async def test_is_locked_out_returns_false_when_redis_unreachable(self): | |
| async def boom(*_, **__): | |
| raise RedisError("connection refused") | |
| with patch.object(lockout, "_client") as mock_ctx: | |
| mock_ctx.return_value.__aenter__ = AsyncMock(side_effect=boom) | |
| mock_ctx.return_value.__aexit__ = AsyncMock(return_value=False) | |
| locked, ttl = await lockout.is_locked_out("alice@example.com") | |
| assert locked is False | |
| assert ttl == 0 | |
| async def test_record_failed_login_returns_zero_when_redis_unreachable(self): | |
| async def boom(*_, **__): | |
| raise RedisError("connection refused") | |
| with patch.object(lockout, "_client") as mock_ctx: | |
| mock_ctx.return_value.__aenter__ = AsyncMock(side_effect=boom) | |
| mock_ctx.return_value.__aexit__ = AsyncMock(return_value=False) | |
| count = await lockout.record_failed_login("alice@example.com") | |
| assert count == 0 | |
| async def test_clear_failed_logins_returns_none_when_redis_unreachable(self): | |
| async def boom(*_, **__): | |
| raise RedisError("connection refused") | |
| with patch.object(lockout, "_client") as mock_ctx: | |
| mock_ctx.return_value.__aenter__ = AsyncMock(side_effect=boom) | |
| mock_ctx.return_value.__aexit__ = AsyncMock(return_value=False) | |
| # Just ensure it doesn't raise. | |
| await lockout.clear_failed_logins("alice@example.com") | |
| class TestKeyNamespacing: | |
| def test_email_is_lowercased(self): | |
| assert lockout._key("Alice@Example.com") == "auth:lockout:alice@example.com" | |
| def test_key_prefix(self): | |
| assert lockout._key("a@b.com").startswith("auth:lockout:") | |