Files
social-app/backend/tests/unit/v1/schedule_items/test_service.py
T
qzl 50b38de488 feat: add schedule items CRUD API
- Add ScheduleItem Pydantic schemas with metadata support
- Add repository layer with CRUD operations
- Add service layer with authorization
- Add FastAPI router with all endpoints
- Add unit and integration tests
- Update API documentation
2026-02-28 11:34:49 +08:00

186 lines
5.5 KiB
Python

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.schedule_items import (
ScheduleItem,
ScheduleItemSourceType,
ScheduleItemStatus,
)
from v1.schedule_items.schemas import (
ScheduleItemCreateRequest,
ScheduleItemUpdateRequest,
)
from v1.schedule_items.service import ScheduleItemService
def _create_mock_schedule_item(
item_id: UUID = uuid4(),
owner_id: UUID = UUID("00000000-0000-0000-0000-000000000001"),
title: str = "Test Event",
) -> ScheduleItem:
item = MagicMock(spec=ScheduleItem)
item.id = item_id
item.owner_id = owner_id
item.title = title
item.description = None
item.start_at = datetime(2026, 2, 28, 16, 0, 0, tzinfo=timezone.utc)
item.end_at = datetime(2026, 2, 28, 17, 0, 0, tzinfo=timezone.utc)
item.timezone = "UTC"
item.extra_metadata = {}
item.source_type = ScheduleItemSourceType.MANUAL
item.status = ScheduleItemStatus.ACTIVE
item.created_at = datetime(2026, 2, 27, 10, 0, 0, tzinfo=timezone.utc)
item.updated_at = datetime(2026, 2, 27, 10, 0, 0, tzinfo=timezone.utc)
return item
class FakeRepo:
def __init__(self, item: ScheduleItem | None) -> None:
self._item = item
async def get_by_item_id(
self, item_id: UUID, owner_id: UUID
) -> ScheduleItem | None:
if self._item and item_id == self._item.id:
return self._item
return None
async def create(self, data: dict) -> ScheduleItem:
return _create_mock_schedule_item(
owner_id=data["owner_id"],
title=data["title"],
)
async def update_by_item_id(
self, item_id: UUID, owner_id: UUID, data: dict
) -> ScheduleItem | None:
if not self._item or item_id != self._item.id:
return None
if "title" in data:
self._item.title = data["title"]
return self._item
async def delete_by_item_id(
self, item_id: UUID, owner_id: UUID
) -> ScheduleItem | None:
if not self._item or item_id != self._item.id:
return None
return self._item
async def list_by_date_range(
self, owner_id: UUID, start_at: datetime, end_at: datetime
) -> list[ScheduleItem]:
return [self._item] if self._item else []
@pytest.fixture
def mock_session() -> AsyncMock:
session = AsyncMock()
session.commit = AsyncMock()
session.rollback = AsyncMock()
return session
@pytest.mark.asyncio
async def test_create_success(mock_session: AsyncMock) -> None:
user_id = UUID("00000000-0000-0000-0000-000000000001")
request = ScheduleItemCreateRequest(
title="Test Event",
start_at=datetime(2026, 2, 28, 16, 0, 0, tzinfo=timezone.utc),
)
service = ScheduleItemService(
repository=FakeRepo(None),
session=mock_session,
current_user=CurrentUser(id=user_id),
)
result = await service.create(request)
assert result.title == "Test Event"
mock_session.commit.assert_awaited_once()
@pytest.mark.asyncio
async def test_create_invalid_end_at(mock_session: AsyncMock) -> None:
user_id = UUID("00000000-0000-0000-0000-000000000001")
request = ScheduleItemCreateRequest(
title="Test Event",
start_at=datetime(2026, 2, 28, 17, 0, 0, tzinfo=timezone.utc),
end_at=datetime(2026, 2, 28, 16, 0, 0, tzinfo=timezone.utc),
)
service = ScheduleItemService(
repository=FakeRepo(None),
session=mock_session,
current_user=CurrentUser(id=user_id),
)
with pytest.raises(HTTPException) as exc_info:
await service.create(request)
assert exc_info.value.status_code == 400
@pytest.mark.asyncio
async def test_get_by_id_success(mock_session: AsyncMock) -> None:
user_id = UUID("00000000-0000-0000-0000-000000000001")
item = _create_mock_schedule_item()
service = ScheduleItemService(
repository=FakeRepo(item),
session=mock_session,
current_user=CurrentUser(id=user_id),
)
result = await service.get_by_id(item.id)
assert result.id == item.id
@pytest.mark.asyncio
async def test_get_by_id_not_found(mock_session: AsyncMock) -> None:
user_id = UUID("00000000-0000-0000-0000-000000000001")
service = ScheduleItemService(
repository=FakeRepo(None),
session=mock_session,
current_user=CurrentUser(id=user_id),
)
with pytest.raises(HTTPException) as exc_info:
await service.get_by_id(uuid4())
assert exc_info.value.status_code == 404
@pytest.mark.asyncio
async def test_update_success(mock_session: AsyncMock) -> None:
user_id = UUID("00000000-0000-0000-0000-000000000001")
item = _create_mock_schedule_item()
service = ScheduleItemService(
repository=FakeRepo(item),
session=mock_session,
current_user=CurrentUser(id=user_id),
)
result = await service.update(item.id, ScheduleItemUpdateRequest(title="Updated"))
assert result.title == "Updated"
@pytest.mark.asyncio
async def test_delete_success(mock_session: AsyncMock) -> None:
user_id = UUID("00000000-0000-0000-0000-000000000001")
item = _create_mock_schedule_item()
service = ScheduleItemService(
repository=FakeRepo(item),
session=mock_session,
current_user=CurrentUser(id=user_id),
)
await service.delete(item.id)
mock_session.commit.assert_awaited_once()