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