Files
social-app/backend/tests/unit/v1/profile/test_profile_service.py
T
qzl ad06fe7de4 refactor: align backend layout and supabase infra
Consolidate backend modules/tests under the backend package while syncing Supabase compose/env config and related plans.
2026-02-05 15:13:06 +08:00

173 lines
5.1 KiB
Python

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