Merge branch 'feature-calendar-sharing' into dev

This commit is contained in:
qzl
2026-02-28 13:28:49 +08:00
19 changed files with 2161 additions and 32 deletions
@@ -0,0 +1,156 @@
from __future__ import annotations
from datetime import datetime, timezone
from typing import Callable
from uuid import UUID, uuid4
from fastapi import HTTPException
from fastapi.testclient import TestClient
from app import app
from v1.inbox_messages.dependencies import get_inbox_message_service
from v1.inbox_messages.schemas import (
InboxMessageAcceptRequest,
InboxMessageListRequest,
InboxMessageResponse,
InboxMessageStatus,
InboxMessageType,
)
from v1.inbox_messages.service import InboxMessageService
class FakeInboxMessageService:
def __init__(
self,
messages: list[InboxMessageResponse],
accepted: InboxMessageResponse,
dismissed: InboxMessageResponse,
) -> None:
self._messages = messages
self._accepted = accepted
self._dismissed = dismissed
async def list_messages(
self, request: InboxMessageListRequest
) -> list[InboxMessageResponse]:
if request.status is None:
return self._messages
return [
message for message in self._messages if message.status == request.status
]
async def accept_invitation(
self,
message_id: UUID,
request: InboxMessageAcceptRequest,
) -> InboxMessageResponse:
if message_id != self._accepted.id:
raise HTTPException(status_code=404, detail="Inbox message not found")
if not request.permission_view:
raise HTTPException(status_code=400, detail="permission_view is required")
return self._accepted
async def dismiss_invitation(self, message_id: UUID) -> InboxMessageResponse:
if message_id != self._dismissed.id:
raise HTTPException(status_code=404, detail="Inbox message not found")
return self._dismissed
def _override_inbox_message_service(
service: FakeInboxMessageService,
) -> Callable[[], InboxMessageService]:
def _get_service() -> InboxMessageService:
return service # type: ignore[return-value]
return _get_service
def _build_message(
message_id: UUID,
status: InboxMessageStatus,
) -> InboxMessageResponse:
return InboxMessageResponse(
id=message_id,
recipient_id=uuid4(),
sender_id=uuid4(),
message_type=InboxMessageType.CALENDAR,
schedule_item_id=uuid4(),
content='{"permission": 1}',
is_read=False,
status=status,
created_at=datetime(2026, 2, 28, 9, 0, 0, tzinfo=timezone.utc),
)
def test_list_inbox_messages_returns_200() -> None:
pending_message = _build_message(uuid4(), InboxMessageStatus.PENDING)
accepted_message = _build_message(uuid4(), InboxMessageStatus.ACCEPTED)
service = FakeInboxMessageService(
messages=[pending_message, accepted_message],
accepted=accepted_message,
dismissed=_build_message(uuid4(), InboxMessageStatus.DISMISSED),
)
app.dependency_overrides[get_inbox_message_service] = (
_override_inbox_message_service(service)
)
client = TestClient(app)
try:
response = client.get("/api/v1/inbox/messages", params={"status": "pending"})
assert response.status_code == 200
body = response.json()
assert len(body) == 1
assert body[0]["status"] == "pending"
finally:
app.dependency_overrides = {}
def test_accept_inbox_message_returns_200() -> None:
accepted_message = _build_message(uuid4(), InboxMessageStatus.ACCEPTED)
service = FakeInboxMessageService(
messages=[accepted_message],
accepted=accepted_message,
dismissed=_build_message(uuid4(), InboxMessageStatus.DISMISSED),
)
app.dependency_overrides[get_inbox_message_service] = (
_override_inbox_message_service(service)
)
client = TestClient(app)
try:
response = client.post(
f"/api/v1/inbox/messages/{accepted_message.id}/accept",
json={
"permission_view": True,
"permission_edit": True,
"permission_invite": False,
},
)
assert response.status_code == 200
body = response.json()
assert body["id"] == str(accepted_message.id)
assert body["status"] == "accepted"
finally:
app.dependency_overrides = {}
def test_dismiss_inbox_message_returns_200() -> None:
dismissed_message = _build_message(uuid4(), InboxMessageStatus.DISMISSED)
service = FakeInboxMessageService(
messages=[dismissed_message],
accepted=_build_message(uuid4(), InboxMessageStatus.ACCEPTED),
dismissed=dismissed_message,
)
app.dependency_overrides[get_inbox_message_service] = (
_override_inbox_message_service(service)
)
client = TestClient(app)
try:
response = client.post(f"/api/v1/inbox/messages/{dismissed_message.id}/dismiss")
assert response.status_code == 200
body = response.json()
assert body["id"] == str(dismissed_message.id)
assert body["status"] == "dismissed"
finally:
app.dependency_overrides = {}
@@ -0,0 +1,68 @@
from __future__ import annotations
from typing import Callable
from uuid import UUID, uuid4
from fastapi import HTTPException
from fastapi.testclient import TestClient
from app import app
from v1.schedule_items.dependencies import get_schedule_item_service
from v1.schedule_items.schemas import (
ScheduleItemShareRequest,
ScheduleItemShareResponse,
)
from v1.schedule_items.service import ScheduleItemService
class FakeScheduleItemShareService:
def __init__(self, item_id: UUID) -> None:
self._item_id = item_id
self.last_share_request: ScheduleItemShareRequest | None = None
async def share(
self,
item_id: UUID,
request: ScheduleItemShareRequest,
) -> ScheduleItemShareResponse:
if item_id != self._item_id:
raise HTTPException(status_code=404, detail="Schedule item not found")
self.last_share_request = request
return ScheduleItemShareResponse(message="Calendar invitation sent")
def _override_schedule_item_service(
service: FakeScheduleItemShareService,
) -> Callable[[], ScheduleItemService]:
def _get_service() -> ScheduleItemService:
return service # type: ignore[return-value]
return _get_service
def test_share_schedule_item_returns_200() -> None:
item_id = uuid4()
service = FakeScheduleItemShareService(item_id=item_id)
app.dependency_overrides[get_schedule_item_service] = (
_override_schedule_item_service(service)
)
client = TestClient(app)
try:
response = client.post(
f"/api/v1/schedule-items/{item_id}/share",
json={
"email": "friend@example.com",
"permission_view": True,
"permission_edit": False,
"permission_invite": True,
},
)
assert response.status_code == 200
body = response.json()
assert body["message"] == "Calendar invitation sent"
assert service.last_share_request is not None
assert service.last_share_request.email == "friend@example.com"
assert service.last_share_request.permission_invite is True
finally:
app.dependency_overrides = {}