fischer-agentkit/tests/unit/calendar/test_calendar_tool.py

109 lines
3.6 KiB
Python

"""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"]