ad06fe7de4
Consolidate backend modules/tests under the backend package while syncing Supabase compose/env config and related plans.
173 lines
5.1 KiB
Python
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
|