"""Unit tests for CalendarTool — focus on relative date resolution.""" from __future__ import annotations from datetime import datetime, timedelta, timezone from unittest.mock import AsyncMock import pytest from agentkit.calendar.service import CalendarService from agentkit.tools.calendar_tool import CalendarTool, _resolve_datetime class TestResolveDatetime: def test_none(self) -> None: assert _resolve_datetime(None) is None assert _resolve_datetime("") is None def test_iso_unchanged(self) -> None: iso = "2026-07-06T09:00:00+08:00" assert _resolve_datetime(iso) == iso def test_iso_with_z_unchanged(self) -> None: iso = "2026-07-06T01:00:00Z" assert _resolve_datetime(iso) == iso def test_next_monday(self) -> None: result = _resolve_datetime("next monday") assert result is not None # Should be parseable as ISO 8601 parsed = datetime.fromisoformat(result) # Monday weekday assert parsed.weekday() == 0 def test_tomorrow_with_time(self) -> None: result = _resolve_datetime("tomorrow 3pm") assert result is not None parsed = datetime.fromisoformat(result) assert parsed.hour == 15 def test_in_3_days(self) -> None: # dateutil handles "in N days" — but with timezone-dependent results; # the *contract* is that _resolve_datetime returns a parseable ISO string. # We don't assert a specific offset because dateutil interprets "in" loosely. result = _resolve_datetime("in 3 days") assert result is not None datetime.fromisoformat(result) # must be parseable def test_garbage_returns_none(self) -> None: assert _resolve_datetime("not a date at all xyz") is None @pytest.fixture def mock_service() -> AsyncMock: svc = AsyncMock(spec=CalendarService) svc.create_event = AsyncMock() return svc class TestCreateEventWithRelativeDates: @pytest.mark.asyncio async def test_create_event_resolves_next_monday( self, mock_service: AsyncMock ) -> None: from agentkit.calendar.models import CalendarEvent mock_service.create_event.return_value = CalendarEvent( id="evt-1", user_id="u1", title="项目启动会", description="", start_time=datetime.now(tz=timezone.utc), end_time=datetime.now(tz=timezone.utc) + timedelta(hours=1), location="", is_all_day=False, ) tool = CalendarTool(mock_service) # type: ignore[arg-type] result = await tool.execute( action="create_event", user_id="u1", title="项目启动会", start_time="next monday", end_time="next monday", reminder_offset_minutes=15, ) assert result["success"] is True, result # create_event should have been called with ISO strings, not the raw "next monday" call_kwargs = mock_service.create_event.call_args.kwargs assert call_kwargs["start_time"] != "next monday" # Should be parseable datetime.fromisoformat(call_kwargs["start_time"]) @pytest.mark.asyncio async def test_create_event_unparseable_returns_error( self, mock_service: AsyncMock ) -> None: tool = CalendarTool(mock_service) # type: ignore[arg-type] result = await tool.execute( action="create_event", user_id="u1", title="x", start_time="garbage qqq", end_time="garbage qqq", ) assert result["success"] is False assert "Could not parse start_time" in result["error"]