from __future__ import annotations from unittest.mock import AsyncMock, MagicMock from uuid import UUID import pytest from fastapi import HTTPException from core.auth.models import CurrentUser from models.profile import Profile from v1.profile.repository import ProfileRepository from v1.profile.schemas import ProfileUpdateRequest from v1.profile.service import ProfileService def _create_mock_profile( user_id: UUID = UUID("00000000-0000-0000-0000-000000000001"), username: str = "demo", display_name: str | None = "Demo User", avatar_url: str | None = None, bio: str | None = None, ) -> Profile: """Create a mock Profile ORM object.""" profile = MagicMock(spec=Profile) profile.id = user_id profile.username = username profile.display_name = display_name profile.avatar_url = avatar_url profile.bio = bio return profile class FakeRepo: """Fake repository for testing that conforms to ProfileRepository protocol.""" def __init__(self, profile: Profile | None) -> None: self._profile = profile async def get_by_user_id(self, user_id: UUID) -> Profile | None: if self._profile and user_id == self._profile.id: return self._profile return None async def get_by_username(self, username: str) -> Profile | None: if self._profile and username == self._profile.username: return self._profile return None async def update_by_user_id( self, user_id: UUID, update_data: dict[str, str | None] ) -> Profile | None: if not self._profile or user_id != self._profile.id: return None # Apply updates to mock for key, value in update_data.items(): if hasattr(self._profile, key): setattr(self._profile, key, value) return self._profile # Verify FakeRepo implements the protocol _repo_check: ProfileRepository = FakeRepo(None) @pytest.fixture def mock_session() -> AsyncMock: """Create a mock AsyncSession.""" session = AsyncMock() session.commit = AsyncMock() session.rollback = AsyncMock() return session @pytest.mark.asyncio async def test_get_me_returns_profile(mock_session: AsyncMock) -> None: user_id = UUID("00000000-0000-0000-0000-000000000001") profile = _create_mock_profile(user_id=user_id, username="demo") user = CurrentUser(id=user_id) service = ProfileService( repository=FakeRepo(profile), session=mock_session, current_user=user, ) result = await service.get_me() assert result.username == "demo" assert result.id == str(user_id) @pytest.mark.asyncio async def test_get_me_not_found_raises_404(mock_session: AsyncMock) -> None: user_id = UUID("00000000-0000-0000-0000-000000000001") user = CurrentUser(id=user_id) service = ProfileService( repository=FakeRepo(None), session=mock_session, current_user=user, ) with pytest.raises(HTTPException) as exc_info: await service.get_me() assert exc_info.value.status_code == 404 @pytest.mark.asyncio async def test_update_me_updates_fields(mock_session: AsyncMock) -> None: user_id = UUID("00000000-0000-0000-0000-000000000001") profile = _create_mock_profile(user_id=user_id, username="demo") user = CurrentUser(id=user_id) service = ProfileService( repository=FakeRepo(profile), session=mock_session, current_user=user, ) result = await service.update_me(ProfileUpdateRequest(display_name="Updated")) assert result.display_name == "Updated" mock_session.commit.assert_awaited_once() @pytest.mark.asyncio async def test_update_me_no_fields_raises_400(mock_session: AsyncMock) -> None: user_id = UUID("00000000-0000-0000-0000-000000000001") profile = _create_mock_profile(user_id=user_id) user = CurrentUser(id=user_id) service = ProfileService( repository=FakeRepo(profile), session=mock_session, current_user=user, ) # Create a request with all None values by bypassing validation update = MagicMock(spec=ProfileUpdateRequest) update.display_name = None update.avatar_url = None update.bio = None with pytest.raises(HTTPException) as exc_info: await service.update_me(update) assert exc_info.value.status_code == 400 @pytest.mark.asyncio async def test_get_by_username_returns_profile(mock_session: AsyncMock) -> None: profile = _create_mock_profile(username="demo") service = ProfileService( repository=FakeRepo(profile), session=mock_session, current_user=CurrentUser(id=UUID("00000000-0000-0000-0000-000000000001")), ) result = await service.get_by_username("demo") assert result.username == "demo" @pytest.mark.asyncio async def test_get_by_username_not_found_raises_404(mock_session: AsyncMock) -> None: service = ProfileService( repository=FakeRepo(None), session=mock_session, current_user=CurrentUser(id=UUID("00000000-0000-0000-0000-000000000001")), ) with pytest.raises(HTTPException) as exc_info: await service.get_by_username("unknown") assert exc_info.value.status_code == 404