from datetime import datetime, timezone from unittest.mock import AsyncMock, MagicMock from uuid import UUID, uuid4 import pytest from fastapi import HTTPException from sqlalchemy.exc import SQLAlchemyError from core.auth.models import CurrentUser from models.inbox_messages import ( InboxMessage, InboxMessageStatus as InboxMessageModelStatus, InboxMessageType as InboxMessageModelType, ) 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: dict[str, object] = {"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.friendship_id = None 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() 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_mark_as_read_updates_message() -> None: user_id = uuid4() message_id = uuid4() updated_message = _build_message( message_id=message_id, recipient_id=user_id, status=InboxMessageModelStatus.PENDING, schedule_item_id=uuid4(), ) updated_message.is_read = True repo = AsyncMock() repo.mark_as_read.return_value = updated_message session = AsyncMock() service = InboxMessageService( repository=repo, session=session, current_user=CurrentUser(id=user_id), ) result = await service.mark_as_read(message_id) repo.mark_as_read.assert_awaited_once_with(message_id, user_id) session.commit.assert_awaited_once() assert result.is_read is True @pytest.mark.asyncio async def test_mark_as_read_raises_404_when_message_missing() -> None: user_id = uuid4() message_id = uuid4() repo = AsyncMock() repo.mark_as_read.return_value = None session = AsyncMock() service = InboxMessageService( repository=repo, session=session, current_user=CurrentUser(id=user_id), ) with pytest.raises(HTTPException) as exc_info: await service.mark_as_read(message_id) assert exc_info.value.status_code == 404 assert exc_info.value.detail == "Inbox message not found" session.commit.assert_not_awaited() @pytest.mark.asyncio async def test_mark_as_read_store_error_returns_503() -> None: user_id = uuid4() message_id = uuid4() repo = AsyncMock() repo.mark_as_read.side_effect = SQLAlchemyError("boom") session = AsyncMock() service = InboxMessageService( repository=repo, session=session, current_user=CurrentUser(id=user_id), ) with pytest.raises(HTTPException) as exc_info: await service.mark_as_read(message_id) assert exc_info.value.status_code == 503 assert exc_info.value.detail == "Inbox message store unavailable" session.rollback.assert_awaited_once()