test: add calendar sharing tests and update API docs

This commit is contained in:
qzl
2026-02-28 12:28:45 +08:00
parent 7a49783156
commit 173d91086f
7 changed files with 1451 additions and 0 deletions
@@ -0,0 +1,88 @@
from unittest.mock import AsyncMock, MagicMock
from uuid import uuid4
import pytest
from sqlalchemy.exc import SQLAlchemyError
from models.inbox_messages import InboxMessageType
from v1.inbox_messages.repository import SQLAlchemyInboxMessageRepository
@pytest.mark.asyncio
async def test_create_adds_message_and_flushes() -> None:
session = AsyncMock()
session.add = MagicMock()
repository = SQLAlchemyInboxMessageRepository(session)
recipient_id = uuid4()
result = await repository.create(
{
"recipient_id": recipient_id,
"sender_id": uuid4(),
"message_type": InboxMessageType.CALENDAR,
"schedule_item_id": uuid4(),
"content": "invite",
"created_by": uuid4(),
}
)
session.add.assert_called_once_with(result)
session.flush.assert_awaited_once()
assert result.recipient_id == recipient_id
@pytest.mark.asyncio
async def test_get_by_id_returns_message_when_exists() -> None:
session = AsyncMock()
repository = SQLAlchemyInboxMessageRepository(session)
expected = MagicMock()
execute_result = MagicMock()
execute_result.scalar_one_or_none.return_value = expected
session.execute.return_value = execute_result
result = await repository.get_by_id(uuid4(), uuid4())
assert result is expected
session.execute.assert_awaited_once()
@pytest.mark.asyncio
async def test_list_by_recipient_returns_messages() -> None:
session = AsyncMock()
repository = SQLAlchemyInboxMessageRepository(session)
message_one = MagicMock()
message_two = MagicMock()
execute_result = MagicMock()
execute_result.scalars.return_value.all.return_value = [message_one, message_two]
session.execute.return_value = execute_result
result = await repository.list_by_recipient(uuid4(), "pending")
assert result == [message_one, message_two]
session.execute.assert_awaited_once()
@pytest.mark.asyncio
async def test_update_status_returns_updated_message_and_flushes() -> None:
session = AsyncMock()
repository = SQLAlchemyInboxMessageRepository(session)
updated = MagicMock()
execute_result = MagicMock()
execute_result.scalar_one_or_none.return_value = updated
session.execute.return_value = execute_result
result = await repository.update_status(uuid4(), uuid4(), "dismissed")
assert result is updated
session.execute.assert_awaited_once()
session.flush.assert_awaited_once()
@pytest.mark.asyncio
async def test_get_by_id_propagates_sqlalchemy_error() -> None:
session = AsyncMock()
repository = SQLAlchemyInboxMessageRepository(session)
session.execute.side_effect = SQLAlchemyError("boom")
with pytest.raises(SQLAlchemyError):
await repository.get_by_id(uuid4(), uuid4())
@@ -0,0 +1,180 @@
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,
) -> 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 = "calendar invite"
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 == 3
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()