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()