from datetime import datetime, timezone from unittest.mock import AsyncMock, MagicMock from uuid import UUID, uuid4 import pytest from fastapi import HTTPException from core.auth.models import CurrentUser from models.inbox_messages import ( InboxMessage, InboxMessageStatus as InboxMessageModelStatus, InboxMessageType as InboxMessageModelType, ) from models.schedule_subscriptions import ScheduleSubscription, SubscriptionStatus from v1.inbox_messages.schemas import InboxMessageAcceptRequest, InboxMessageListRequest from v1.inbox_messages.service import InboxMessageService def _build_message( *, message_id: UUID, recipient_id: UUID, status: InboxMessageModelStatus = InboxMessageModelStatus.PENDING, message_type: InboxMessageModelType = InboxMessageModelType.CALENDAR, schedule_item_id: UUID | None = None, content: str = '{"permission": 7}', ) -> InboxMessage: message = MagicMock(spec=InboxMessage) message.id = message_id message.recipient_id = recipient_id message.sender_id = uuid4() message.message_type = message_type message.schedule_item_id = schedule_item_id message.content = content message.is_read = False message.status = status message.created_at = datetime(2026, 2, 28, 10, 0, 0, tzinfo=timezone.utc) return message @pytest.mark.asyncio async def test_list_messages_returns_messages() -> None: user_id = uuid4() repo = AsyncMock() repo.list_by_recipient.return_value = [ _build_message( message_id=uuid4(), recipient_id=user_id, schedule_item_id=uuid4(), ) ] session = AsyncMock() service = InboxMessageService( repository=repo, session=session, current_user=CurrentUser(id=user_id), ) result = await service.list_messages(InboxMessageListRequest()) assert len(result) == 1 assert result[0].recipient_id == user_id assert result[0].status.value == "pending" repo.list_by_recipient.assert_awaited_once_with(user_id, None) @pytest.mark.asyncio async def test_accept_invitation_creates_subscription() -> None: user_id = uuid4() message_id = uuid4() item_id = uuid4() pending_message = _build_message( message_id=message_id, recipient_id=user_id, schedule_item_id=item_id, ) accepted_message = _build_message( message_id=message_id, recipient_id=user_id, status=InboxMessageModelStatus.ACCEPTED, schedule_item_id=item_id, ) repo = AsyncMock() repo.get_by_id.return_value = pending_message repo.update_status.return_value = accepted_message session = AsyncMock() session.add = MagicMock() service = InboxMessageService( repository=repo, session=session, current_user=CurrentUser(id=user_id), ) result = await service.accept_invitation( message_id, InboxMessageAcceptRequest( permission_view=True, permission_edit=True, permission_invite=False, ), ) session.add.assert_called_once() subscription = session.add.call_args.args[0] assert isinstance(subscription, ScheduleSubscription) assert subscription.item_id == item_id assert subscription.subscriber_id == user_id assert subscription.permission == 5 # view(1) + edit(4) = 5 assert subscription.status == SubscriptionStatus.ACTIVE repo.update_status.assert_awaited_once_with(message_id, user_id, "accepted") session.commit.assert_awaited_once() assert result.status.value == "accepted" @pytest.mark.asyncio async def test_dismiss_invitation_updates_status() -> None: user_id = uuid4() message_id = uuid4() pending_message = _build_message( message_id=message_id, recipient_id=user_id, schedule_item_id=uuid4(), ) dismissed_message = _build_message( message_id=message_id, recipient_id=user_id, status=InboxMessageModelStatus.DISMISSED, schedule_item_id=uuid4(), ) repo = AsyncMock() repo.get_by_id.return_value = pending_message repo.update_status.return_value = dismissed_message session = AsyncMock() service = InboxMessageService( repository=repo, session=session, current_user=CurrentUser(id=user_id), ) result = await service.dismiss_invitation(message_id) repo.update_status.assert_awaited_once_with(message_id, user_id, "dismissed") session.commit.assert_awaited_once() assert result.status.value == "dismissed" @pytest.mark.asyncio async def test_accept_noncalendar_message_fails() -> None: user_id = uuid4() message_id = uuid4() non_calendar_message = _build_message( message_id=message_id, recipient_id=user_id, message_type=InboxMessageModelType.FRIEND_REQUEST, schedule_item_id=None, ) repo = AsyncMock() repo.get_by_id.return_value = non_calendar_message session = AsyncMock() session.add = MagicMock() service = InboxMessageService( repository=repo, session=session, current_user=CurrentUser(id=user_id), ) with pytest.raises(HTTPException) as exc_info: await service.accept_invitation(message_id, InboxMessageAcceptRequest()) assert exc_info.value.status_code == 400 assert exc_info.value.detail == "Message is not a calendar invitation" session.add.assert_not_called() session.commit.assert_not_awaited()