Files
roboco/tests/unit/api/auth/test_revocation.py
T

48 lines
1.6 KiB
Python
Raw Normal View History

"""Redis jti revocation (M36) — round-trip + fail-open on Redis down."""
from __future__ import annotations
from unittest.mock import AsyncMock, MagicMock
import pytest
from roboco.api.auth import revocation
_REVOKE_TTL = 60
@pytest.mark.asyncio
async def test_revoke_then_check_round_trip(monkeypatch: pytest.MonkeyPatch) -> None:
fake = MagicMock()
fake.set = AsyncMock()
fake.exists = AsyncMock(return_value=1)
fake.__aenter__ = AsyncMock(return_value=fake)
fake.__aexit__ = AsyncMock(return_value=None)
monkeypatch.setattr(revocation.redis, "from_url", lambda _url: fake)
await revocation.revoke_jti("abc", _REVOKE_TTL)
assert await revocation.is_jti_revoked("abc") is True
# The revoked key carries the namespace prefix.
args, kwargs = fake.set.call_args
assert args[0].startswith("roboco:jwt_revoked:")
assert kwargs.get("ex") == _REVOKE_TTL
@pytest.mark.asyncio
async def test_is_jti_revoked_failopen_on_redis_error(
monkeypatch: pytest.MonkeyPatch,
) -> None:
fake = MagicMock()
fake.exists = AsyncMock(side_effect=ConnectionError("redis down"))
fake.__aenter__ = AsyncMock(return_value=fake)
fake.__aexit__ = AsyncMock(return_value=None)
monkeypatch.setattr(revocation.redis, "from_url", lambda _url: fake)
# Fail-open: a Redis error => not revoked (pwd_fp remains the strong check).
assert await revocation.is_jti_revoked("abc") is False
@pytest.mark.asyncio
async def test_revoke_jti_noop_on_zero_ttl() -> None:
# No Redis interaction when ttl <= 0.
await revocation.revoke_jti("abc", 0) # must not raise