from datetime import datetime, timezone from types import SimpleNamespace from unittest.mock import AsyncMock from uuid import uuid4 import pytest from fastapi import HTTPException from app.auth import metadata_dependencies @pytest.fixture def anyio_backend(): return "asyncio" def _user(**overrides): data = { "id": uuid4(), "keycloak_id": uuid4(), "username": "old-name", "email": "old@example.com", "role": "user", "is_active": True, "is_superuser": False, "created_at": datetime(2026, 1, 1, tzinfo=timezone.utc), "updated_at": datetime(2026, 1, 1, tzinfo=timezone.utc), "last_login_at": None, } data.update(overrides) return SimpleNamespace(**data) @pytest.mark.anyio async def test_current_metadata_user_refreshes_keycloak_claim_snapshot(): keycloak_id = uuid4() user = _user(keycloak_id=keycloak_id) refreshed = _user( id=user.id, keycloak_id=keycloak_id, username="alice", email="alice@example.com", last_login_at=datetime(2026, 6, 12, tzinfo=timezone.utc), ) repo = SimpleNamespace( get_user_by_keycloak_id=AsyncMock(return_value=user), refresh_user_keycloak_snapshot=AsyncMock(return_value=refreshed), ) response = await metadata_dependencies.get_current_metadata_user( { "sub": str(keycloak_id), "preferred_username": "alice", "email": "alice@example.com", }, metadata_repo=repo, ) assert response.username == "alice" repo.get_user_by_keycloak_id.assert_awaited_once_with(keycloak_id) repo.refresh_user_keycloak_snapshot.assert_awaited_once_with( user, username="alice", email="alice@example.com", ) @pytest.mark.anyio async def test_current_metadata_user_rejects_invalid_keycloak_sub(): repo = SimpleNamespace( get_user_by_keycloak_id=AsyncMock(), refresh_user_keycloak_snapshot=AsyncMock(), ) with pytest.raises(HTTPException) as exc: await metadata_dependencies.get_current_metadata_user( {"sub": "not-a-uuid"}, metadata_repo=repo, ) assert exc.value.status_code == 401 repo.get_user_by_keycloak_id.assert_not_called() repo.refresh_user_keycloak_snapshot.assert_not_called() @pytest.mark.anyio async def test_current_metadata_user_rejects_username_claim_fallback(): keycloak_id = uuid4() repo = SimpleNamespace( get_user_by_keycloak_id=AsyncMock(), refresh_user_keycloak_snapshot=AsyncMock(), ) with pytest.raises(HTTPException) as exc: await metadata_dependencies.get_current_metadata_user( { "sub": str(keycloak_id), "username": "legacy-name", }, metadata_repo=repo, ) assert exc.value.status_code == 401 assert exc.value.detail == "Missing preferred_username claim" repo.get_user_by_keycloak_id.assert_not_called() repo.refresh_user_keycloak_snapshot.assert_not_called()