Files
social-app/backend/tests/unit/v1/friendships/test_friendship_repository.py
T
qzl 17551d662b feat(friendships): implement FriendshipService with TDD
- Add send_request(), accept_request(), decline_request(), cancel_request()
- Add get_inbox(), get_outgoing_requests(), get_friends_list(), remove_friend()
- Add unit tests for all service methods (14 tests)
- Update FriendRequestResponse schema to include 'canceled' status
- Follow async SQLAlchemy patterns and BaseService conventions
2026-02-28 12:01:57 +08:00

381 lines
12 KiB
Python

from __future__ import annotations
import uuid
from typing import Any
from unittest.mock import AsyncMock, MagicMock
import pytest
from models.friendships import Friendship, FriendshipStatus
from models.inbox_messages import InboxMessage, InboxMessageStatus, InboxMessageType
class FakeFriendshipRepository:
"""Fake implementation for testing."""
def __init__(self) -> None:
self.friendships: dict[uuid.UUID, Friendship] = {}
self.inbox_messages: dict[uuid.UUID, InboxMessage] = {}
async def create_request(
self,
initiator_id: uuid.UUID,
recipient_id: uuid.UUID,
) -> tuple[Friendship, InboxMessage]:
raise NotImplementedError
async def get_friendship_between_users(
self, user_id_1: uuid.UUID, user_id_2: uuid.UUID
) -> Friendship | None:
raise NotImplementedError
async def get_pending_inbox_for_recipient(
self, recipient_id: uuid.UUID, friendship_id: uuid.UUID
) -> InboxMessage | None:
raise NotImplementedError
async def get_friendship_by_id(self, friendship_id: uuid.UUID) -> Friendship | None:
raise NotImplementedError
async def get_inbox_messages_for_user(
self, user_id: uuid.UUID, status: InboxMessageStatus | None = None
) -> list[InboxMessage]:
raise NotImplementedError
async def get_outgoing_requests(self, user_id: uuid.UUID) -> list[Friendship]:
raise NotImplementedError
async def get_friends_list(self, user_id: uuid.UUID) -> list[Friendship]:
raise NotImplementedError
class TestFriendshipRepository:
"""Tests for FriendshipRepository."""
@pytest.fixture
def mock_session(self) -> MagicMock:
session = MagicMock()
session.execute = AsyncMock()
session.flush = AsyncMock()
session.add = MagicMock()
session.commit = AsyncMock()
return session
@pytest.fixture
def repository(self, mock_session: MagicMock) -> Any:
from v1.friendships.repository import SQLAlchemyFriendshipRepository
return SQLAlchemyFriendshipRepository(mock_session)
@pytest.mark.asyncio
async def test_create_request_creates_friendship_and_inbox(
self, repository: Any, mock_session: MagicMock
) -> None:
initiator_id = uuid.uuid4()
recipient_id = uuid.uuid4()
result_friendship = Friendship(
id=uuid.uuid4(),
user_low_id=min(initiator_id, recipient_id),
user_high_id=max(initiator_id, recipient_id),
initiator_id=initiator_id,
status=FriendshipStatus.PENDING,
requested_at=uuid.uuid4(),
)
result_inbox = InboxMessage(
id=uuid.uuid4(),
recipient_id=recipient_id,
sender_id=initiator_id,
message_type=InboxMessageType.FRIEND_REQUEST,
status=InboxMessageStatus.PENDING,
)
mock_execute = AsyncMock(
return_value=MagicMock(scalar_one_or_none=MagicMock(return_value=None))
)
mock_session.execute = mock_execute
mock_session.add = MagicMock()
class MockExecuteResult:
def __init__(self, returning):
self._returning = returning
def scalar_one_or_none(self):
return self._returning
async def mock_execute_func(stmt):
if "INSERT INTO friendships" in str(stmt):
return MockExecuteResult(result_friendship)
elif "INSERT INTO inbox_messages" in str(stmt):
return MockExecuteResult(result_inbox)
return MockExecuteResult(None)
mock_session.execute = AsyncMock(side_effect=mock_execute_func)
friendship, inbox = await repository.create_request(initiator_id, recipient_id)
assert friendship is not None
assert inbox is not None
assert friendship.initiator_id == initiator_id
assert inbox.recipient_id == recipient_id
@pytest.mark.asyncio
async def test_get_friendship_between_users_returns_friendship(
self, repository: Any, mock_session: MagicMock
) -> None:
user_id_1 = uuid.uuid4()
user_id_2 = uuid.uuid4()
expected_friendship = Friendship(
id=uuid.uuid4(),
user_low_id=min(user_id_1, user_id_2),
user_high_id=max(user_id_1, user_id_2),
initiator_id=user_id_1,
status=FriendshipStatus.ACCEPTED,
)
class MockExecuteResult:
def scalar_one_or_none(self):
return expected_friendship
mock_session.execute = AsyncMock(return_value=MockExecuteResult())
result = await repository.get_friendship_between_users(user_id_1, user_id_2)
assert result is not None
assert result.id == expected_friendship.id
@pytest.mark.asyncio
async def test_get_friendship_between_users_returns_none(
self, repository: Any, mock_session: MagicMock
) -> None:
user_id_1 = uuid.uuid4()
user_id_2 = uuid.uuid4()
class MockExecuteResult:
def scalar_one_or_none(self):
return None
mock_session.execute = AsyncMock(return_value=MockExecuteResult())
result = await repository.get_friendship_between_users(user_id_1, user_id_2)
assert result is None
@pytest.mark.asyncio
async def test_get_pending_inbox_for_recipient_returns_inbox(
self, repository: Any, mock_session: MagicMock
) -> None:
recipient_id = uuid.uuid4()
friendship_id = uuid.uuid4()
expected_inbox = InboxMessage(
id=uuid.uuid4(),
recipient_id=recipient_id,
sender_id=uuid.uuid4(),
message_type=InboxMessageType.FRIEND_REQUEST,
friendship_id=friendship_id,
status=InboxMessageStatus.PENDING,
)
class MockExecuteResult:
def scalar_one_or_none(self):
return expected_inbox
mock_session.execute = AsyncMock(return_value=MockExecuteResult())
result = await repository.get_pending_inbox_for_recipient(
recipient_id, friendship_id
)
assert result is not None
assert result.id == expected_inbox.id
@pytest.mark.asyncio
async def test_get_pending_inbox_for_recipient_returns_none(
self, repository: Any, mock_session: MagicMock
) -> None:
recipient_id = uuid.uuid4()
friendship_id = uuid.uuid4()
class MockExecuteResult:
def scalar_one_or_none(self):
return None
mock_session.execute = AsyncMock(return_value=MockExecuteResult())
result = await repository.get_pending_inbox_for_recipient(
recipient_id, friendship_id
)
assert result is None
@pytest.mark.asyncio
async def test_get_friendship_by_id_returns_friendship(
self, repository: Any, mock_session: MagicMock
) -> None:
friendship_id = uuid.uuid4()
expected_friendship = Friendship(
id=friendship_id,
user_low_id=uuid.uuid4(),
user_high_id=uuid.uuid4(),
initiator_id=uuid.uuid4(),
status=FriendshipStatus.ACCEPTED,
)
class MockExecuteResult:
def scalar_one_or_none(self):
return expected_friendship
mock_session.execute = AsyncMock(return_value=MockExecuteResult())
result = await repository.get_friendship_by_id(friendship_id)
assert result is not None
assert result.id == friendship_id
@pytest.mark.asyncio
async def test_get_friendship_by_id_returns_none(
self, repository: Any, mock_session: MagicMock
) -> None:
friendship_id = uuid.uuid4()
class MockExecuteResult:
def scalar_one_or_none(self):
return None
mock_session.execute = AsyncMock(return_value=MockExecuteResult())
result = await repository.get_friendship_by_id(friendship_id)
assert result is None
@pytest.mark.asyncio
async def test_get_inbox_messages_for_user_returns_messages(
self, repository: Any, mock_session: MagicMock
) -> None:
user_id = uuid.uuid4()
expected_messages = [
InboxMessage(
id=uuid.uuid4(),
recipient_id=user_id,
sender_id=uuid.uuid4(),
message_type=InboxMessageType.FRIEND_REQUEST,
status=InboxMessageStatus.PENDING,
),
InboxMessage(
id=uuid.uuid4(),
recipient_id=user_id,
sender_id=uuid.uuid4(),
message_type=InboxMessageType.FRIEND_REQUEST,
status=InboxMessageStatus.ACCEPTED,
),
]
class MockScalars:
def all(self):
return expected_messages
class MockExecuteResult:
def scalars(self):
return MockScalars()
mock_session.execute = AsyncMock(return_value=MockExecuteResult())
result = await repository.get_inbox_messages_for_user(user_id)
assert len(result) == 2
@pytest.mark.asyncio
async def test_get_inbox_messages_for_user_filters_by_status(
self, repository: Any, mock_session: MagicMock
) -> None:
user_id = uuid.uuid4()
expected_messages = [
InboxMessage(
id=uuid.uuid4(),
recipient_id=user_id,
sender_id=uuid.uuid4(),
message_type=InboxMessageType.FRIEND_REQUEST,
status=InboxMessageStatus.PENDING,
),
]
class MockScalars:
def all(self):
return expected_messages
class MockExecuteResult:
def scalars(self):
return MockScalars()
mock_session.execute = AsyncMock(return_value=MockExecuteResult())
result = await repository.get_inbox_messages_for_user(
user_id, status=InboxMessageStatus.PENDING
)
assert len(result) == 1
assert result[0].status == InboxMessageStatus.PENDING
@pytest.mark.asyncio
async def test_get_outgoing_requests_returns_pending_requests(
self, repository: Any, mock_session: MagicMock
) -> None:
user_id = uuid.uuid4()
expected_requests = [
Friendship(
id=uuid.uuid4(),
user_low_id=user_id,
user_high_id=uuid.uuid4(),
initiator_id=user_id,
status=FriendshipStatus.PENDING,
),
]
class MockScalars:
def all(self):
return expected_requests
class MockExecuteResult:
def scalars(self):
return MockScalars()
mock_session.execute = AsyncMock(return_value=MockExecuteResult())
result = await repository.get_outgoing_requests(user_id)
assert len(result) == 1
assert result[0].initiator_id == user_id
assert result[0].status == FriendshipStatus.PENDING
@pytest.mark.asyncio
async def test_get_friends_list_returns_accepted_friends(
self, repository: Any, mock_session: MagicMock
) -> None:
user_id = uuid.uuid4()
friend_id = uuid.uuid4()
expected_friends = [
Friendship(
id=uuid.uuid4(),
user_low_id=min(user_id, friend_id),
user_high_id=max(user_id, friend_id),
initiator_id=user_id,
status=FriendshipStatus.ACCEPTED,
accepted_at=uuid.uuid4(),
),
]
class MockScalars:
def all(self):
return expected_friends
class MockExecuteResult:
def scalars(self):
return MockScalars()
mock_session.execute = AsyncMock(return_value=MockExecuteResult())
result = await repository.get_friends_list(user_id)
assert len(result) == 1
assert result[0].status == FriendshipStatus.ACCEPTED