"""Unit tests for CalDAV client."""
from datetime import date, datetime
from unittest.mock import MagicMock, patch
import pytest
from mcp_caldav.client import (
CalDAVClient,
_escape_ical_text,
_format_attendees,
_format_categories,
_format_rrule,
_parse_attendees,
_parse_categories,
)
def test_caldav_client_init():
"""Test CalDAV client initialization."""
client = CalDAVClient(
url="https://caldav.yandex.ru/",
username="test@example.com",
password="test-password",
)
assert client.url == "https://caldav.yandex.ru/"
assert client.username == "test@example.com"
assert client.password == "test-password"
assert client.client is None
assert client.principal is None
@patch("mcp_caldav.client.caldav.DAVClient")
def test_caldav_client_connect(mock_dav_client):
"""Test CalDAV client connection."""
mock_client_instance = MagicMock()
mock_principal = MagicMock()
mock_client_instance.principal.return_value = mock_principal
mock_dav_client.return_value = mock_client_instance
client = CalDAVClient(
url="https://caldav.yandex.ru/",
username="test@example.com",
password="test-password",
)
result = client.connect()
assert result is True
assert client.client == mock_client_instance
assert client.principal == mock_principal
mock_dav_client.assert_called_once_with(
url="https://caldav.yandex.ru/",
username="test@example.com",
password="test-password",
)
@patch("mcp_caldav.client.caldav.DAVClient")
def test_caldav_client_connect_error(mock_dav_client):
"""Test CalDAV client connection error handling."""
mock_dav_client.side_effect = Exception("Connection failed")
client = CalDAVClient(
url="https://caldav.yandex.ru/",
username="test@example.com",
password="test-password",
)
with pytest.raises(ConnectionError) as exc_info:
client.connect()
assert "Connection failed" in str(exc_info.value)
@patch("mcp_caldav.client.caldav.DAVClient")
def test_caldav_client_list_calendars(mock_dav_client):
"""Test listing calendars."""
mock_client_instance = MagicMock()
mock_principal = MagicMock()
mock_calendar1 = MagicMock()
mock_calendar1.name = "Calendar 1"
mock_calendar1.url = "https://caldav.yandex.ru/calendars/1"
mock_calendar2 = MagicMock()
mock_calendar2.name = "Calendar 2"
mock_calendar2.url = "https://caldav.yandex.ru/calendars/2"
mock_principal.calendars.return_value = [mock_calendar1, mock_calendar2]
mock_client_instance.principal.return_value = mock_principal
mock_dav_client.return_value = mock_client_instance
client = CalDAVClient(
url="https://caldav.yandex.ru/",
username="test@example.com",
password="test-password",
)
client.connect()
calendars = client.list_calendars()
assert len(calendars) == 2
assert calendars[0]["index"] == 0
assert calendars[0]["name"] == "Calendar 1"
assert calendars[1]["index"] == 1
assert calendars[1]["name"] == "Calendar 2"
@patch("mcp_caldav.client.caldav.DAVClient")
def test_caldav_client_create_event(mock_dav_client):
"""Test creating an event."""
mock_client_instance = MagicMock()
mock_principal = MagicMock()
mock_calendar = MagicMock()
mock_calendar.name = "Test Calendar"
mock_principal.calendars.return_value = [mock_calendar]
mock_client_instance.principal.return_value = mock_principal
mock_dav_client.return_value = mock_client_instance
client = CalDAVClient(
url="https://caldav.yandex.ru/",
username="test@example.com",
password="test-password",
)
client.connect()
start_time = datetime(2025, 1, 20, 14, 0)
end_time = datetime(2025, 1, 20, 15, 0)
result = client.create_event(
calendar_index=0,
title="Test Event",
description="Test description",
location="Test location",
start_time=start_time,
end_time=end_time,
)
assert result["success"] is True
assert result["title"] == "Test Event"
assert "uid" in result
assert result["calendar"] == "Test Calendar"
mock_calendar.save_event.assert_called_once()
@patch("mcp_caldav.client.caldav.DAVClient")
def test_caldav_client_create_event_with_reminders(mock_dav_client):
"""Test creating an event with reminders."""
mock_client_instance = MagicMock()
mock_principal = MagicMock()
mock_calendar = MagicMock()
mock_calendar.name = "Test Calendar"
mock_principal.calendars.return_value = [mock_calendar]
mock_client_instance.principal.return_value = mock_principal
mock_dav_client.return_value = mock_client_instance
client = CalDAVClient(
url="https://caldav.yandex.ru/",
username="test@example.com",
password="test-password",
)
client.connect()
start_time = datetime(2025, 1, 20, 14, 0)
result = client.create_event(
calendar_index=0,
title="Test Event",
start_time=start_time,
reminders=[
{"minutes_before": 15, "action": "DISPLAY"},
{"minutes_before": 60, "action": "EMAIL", "description": "Reminder"},
],
)
assert result["success"] is True
mock_calendar.save_event.assert_called_once()
# Check that VALARM is in the saved event
saved_event = mock_calendar.save_event.call_args[0][0]
assert "VALARM" in saved_event
assert "TRIGGER:-PT15M" in saved_event
assert "TRIGGER:-PT60M" in saved_event
@patch("mcp_caldav.client.caldav.DAVClient")
def test_caldav_client_create_event_with_attendees(mock_dav_client):
"""Test creating an event with attendees."""
mock_client_instance = MagicMock()
mock_principal = MagicMock()
mock_calendar = MagicMock()
mock_calendar.name = "Test Calendar"
mock_principal.calendars.return_value = [mock_calendar]
mock_client_instance.principal.return_value = mock_principal
mock_dav_client.return_value = mock_client_instance
client = CalDAVClient(
url="https://caldav.yandex.ru/",
username="test@example.com",
password="test-password",
)
client.connect()
start_time = datetime(2025, 1, 20, 14, 0)
result = client.create_event(
calendar_index=0,
title="Test Event",
start_time=start_time,
attendees=["attendee1@example.com", "attendee2@example.com"],
)
assert result["success"] is True
mock_calendar.save_event.assert_called_once()
# Check that ATTENDEE is in the saved event
saved_event = mock_calendar.save_event.call_args[0][0]
assert "ATTENDEE" in saved_event
assert "attendee1@example.com" in saved_event
assert "attendee2@example.com" in saved_event
# Helper function tests
def test_escape_ical_text():
"""Test escaping text for iCalendar."""
assert _escape_ical_text("normal text") == "normal text"
assert _escape_ical_text("text,with,commas") == "text\\,with\\,commas"
assert _escape_ical_text("text;with;semicolons") == "text\\;with\\;semicolons"
assert _escape_ical_text("text\nwith\nnewlines") == "text\\nwith\\nnewlines"
assert _escape_ical_text("text\\with\\backslashes") == "text\\\\with\\\\backslashes"
assert (
_escape_ical_text("complex,text;with\nall\\chars")
== "complex\\,text\\;with\\nall\\\\chars"
)
assert _escape_ical_text("") == ""
assert _escape_ical_text(123) == "123" # Non-string input
def test_format_rrule():
"""Test formatting recurrence rules."""
# Basic daily
rrule = _format_rrule({"frequency": "DAILY"})
assert rrule == "RRULE:FREQ=DAILY"
# Weekly with interval
rrule = _format_rrule({"frequency": "WEEKLY", "interval": 2})
assert rrule == "RRULE:FREQ=WEEKLY;INTERVAL=2"
# Monthly with count
rrule = _format_rrule({"frequency": "MONTHLY", "count": 5})
assert rrule == "RRULE:FREQ=MONTHLY;COUNT=5"
# Yearly with until (datetime)
until_dt = datetime(2025, 12, 31, 23, 59, 59)
rrule = _format_rrule({"frequency": "YEARLY", "until": until_dt})
assert "FREQ=YEARLY" in rrule
assert "UNTIL=" in rrule
# With until (date)
until_date = date(2025, 12, 31)
rrule = _format_rrule({"frequency": "DAILY", "until": until_date})
assert "FREQ=DAILY" in rrule
assert "UNTIL=20251231" in rrule
# With byday
rrule = _format_rrule({"frequency": "WEEKLY", "byday": "MO,WE,FR"})
assert rrule == "RRULE:FREQ=WEEKLY;BYDAY=MO,WE,FR"
# With bymonthday
rrule = _format_rrule({"frequency": "MONTHLY", "bymonthday": 15})
assert rrule == "RRULE:FREQ=MONTHLY;BYMONTHDAY=15"
# With bymonth
rrule = _format_rrule({"frequency": "YEARLY", "bymonth": 6})
assert rrule == "RRULE:FREQ=YEARLY;BYMONTH=6"
# Complex rule
rrule = _format_rrule(
{
"frequency": "WEEKLY",
"interval": 2,
"count": 10,
"byday": "MO,WE",
}
)
assert "FREQ=WEEKLY" in rrule
assert "INTERVAL=2" in rrule
assert "COUNT=10" in rrule
assert "BYDAY=MO,WE" in rrule
# Empty recurrence
assert _format_rrule({}) == ""
assert _format_rrule(None) == ""
# Invalid frequency
with pytest.raises(ValueError, match="Invalid frequency"):
_format_rrule({"frequency": "INVALID"})
def test_format_categories():
"""Test formatting categories."""
assert _format_categories([]) == ""
assert _format_categories(["Work"]) == "CATEGORIES:Work"
assert _format_categories(["Work", "Important"]) == "CATEGORIES:Work,Important"
assert _format_categories(["Work,Project"]) == "CATEGORIES:Work\\,Project"
assert _format_categories(["Work;Urgent"]) == "CATEGORIES:Work\\;Urgent"
assert (
_format_categories(["Work,Project", "Important"])
== "CATEGORIES:Work\\,Project,Important"
)
def test_format_attendees():
"""Test formatting attendees."""
# Empty
assert _format_attendees([]) == ""
assert _format_attendees(None) == ""
# String emails
result = _format_attendees(["user1@example.com", "user2@example.com"])
assert "user1@example.com" in result
assert "user2@example.com" in result
assert "ATTENDEE" in result
assert "RSVP=TRUE" in result
# Dict with status
result = _format_attendees(
[
{"email": "user1@example.com", "status": "ACCEPTED"},
{"email": "user2@example.com", "status": "DECLINED"},
]
)
assert "user1@example.com" in result
assert "user2@example.com" in result
assert "PARTSTAT=ACCEPTED" in result
assert "PARTSTAT=DECLINED" in result
# Dict with name
result = _format_attendees(
[
{"email": "user@example.com", "name": "John Doe", "status": "TENTATIVE"},
]
)
assert "user@example.com" in result
assert "CN=John Doe" in result or "CN=John\\ Doe" in result
assert "PARTSTAT=TENTATIVE" in result
# Invalid email (no @)
result = _format_attendees(["not-an-email"])
assert result == ""
# Mixed format
result = _format_attendees(
[
"user1@example.com",
{"email": "user2@example.com", "status": "NEEDS-ACTION"},
]
)
assert "user1@example.com" in result
assert "user2@example.com" in result
# Invalid status (should be ignored)
result = _format_attendees(
[
{"email": "user@example.com", "status": "INVALID"},
]
)
assert "user@example.com" in result
assert "PARTSTAT=INVALID" not in result
def test_parse_categories():
"""Test parsing categories."""
# Empty
assert _parse_categories(None) == []
assert _parse_categories("") == []
assert _parse_categories([]) == []
# String format
assert _parse_categories("Work") == ["Work"]
assert _parse_categories("Work,Important") == ["Work", "Important"]
assert _parse_categories("Work, Important, Urgent") == [
"Work",
"Important",
"Urgent",
]
# Bytes
assert _parse_categories(b"Work,Important") == ["Work", "Important"]
# List of strings
assert _parse_categories(["Work", "Important"]) == ["Work", "Important"]
# Mock object with value attribute - use simple string instead
# The function handles objects with .value attribute
result = _parse_categories("Work,Important")
assert "Work" in result
assert "Important" in result
# Mock object with cats attribute
mock_cat1 = MagicMock()
mock_cat1.value = "Work"
mock_cat2 = MagicMock()
mock_cat2.value = "Important"
mock_obj = MagicMock()
mock_obj.cats = [mock_cat1, mock_cat2]
result = _parse_categories(mock_obj)
assert len(result) == 2
# Exception handling - function converts to string, so we get string representation
result = _parse_categories(object())
# Function tries to convert to string, so result may contain string representation
assert isinstance(result, list)
def test_parse_attendees():
"""Test parsing attendees."""
# Empty
mock_component = MagicMock()
mock_component.get.return_value = []
assert _parse_attendees(mock_component) == []
# Single attendee with params
mock_attendee = MagicMock()
mock_attendee.params = {"PARTSTAT": ["ACCEPTED"]}
mock_attendee.__str__ = lambda x: "mailto:user@example.com"
mock_component = MagicMock()
mock_component.get.return_value = [mock_attendee]
result = _parse_attendees(mock_component)
assert len(result) == 1
assert result[0]["email"] == "user@example.com"
assert result[0]["status"] == "ACCEPTED"
# Multiple attendees
mock_attendee1 = MagicMock()
mock_attendee1.params = {"PARTSTAT": ["ACCEPTED"]}
mock_attendee1.__str__ = lambda x: "mailto:user1@example.com"
mock_attendee2 = MagicMock()
mock_attendee2.params = {"PARTSTAT": ["DECLINED"]}
mock_attendee2.__str__ = lambda x: "mailto:user2@example.com"
mock_component = MagicMock()
mock_component.get.return_value = [mock_attendee1, mock_attendee2]
result = _parse_attendees(mock_component)
assert len(result) == 2
# String format (fallback)
mock_component = MagicMock()
mock_component.get.return_value = ["mailto:user@example.com"]
result = _parse_attendees(mock_component)
assert len(result) == 1
assert result[0]["email"] == "user@example.com"
assert result[0]["status"] == "NEEDS-ACTION"
# Exception handling
mock_attendee = MagicMock()
mock_attendee.params = {}
mock_attendee.__str__ = MagicMock(side_effect=Exception("Error"))
mock_component = MagicMock()
mock_component.get.return_value = [mock_attendee]
result = _parse_attendees(mock_component)
assert result == []
# Additional client method tests
@patch("mcp_caldav.client.caldav.DAVClient")
def test_caldav_client_create_event_with_categories_and_priority(mock_dav_client):
"""Test creating event with categories and priority."""
mock_client_instance = MagicMock()
mock_principal = MagicMock()
mock_calendar = MagicMock()
mock_calendar.name = "Test Calendar"
mock_principal.calendars.return_value = [mock_calendar]
mock_client_instance.principal.return_value = mock_principal
mock_dav_client.return_value = mock_client_instance
client = CalDAVClient(
url="https://caldav.yandex.ru/",
username="test@example.com",
password="test-password",
)
client.connect()
start_time = datetime(2025, 1, 20, 14, 0)
result = client.create_event(
calendar_index=0,
title="Test Event",
start_time=start_time,
categories=["Work", "Important"],
priority=1,
)
assert result["success"] is True
saved_event = mock_calendar.save_event.call_args[0][0]
assert "CATEGORIES" in saved_event
assert "PRIORITY:1" in saved_event
@patch("mcp_caldav.client.caldav.DAVClient")
def test_caldav_client_create_event_with_recurrence(mock_dav_client):
"""Test creating event with recurrence."""
mock_client_instance = MagicMock()
mock_principal = MagicMock()
mock_calendar = MagicMock()
mock_calendar.name = "Test Calendar"
mock_principal.calendars.return_value = [mock_calendar]
mock_client_instance.principal.return_value = mock_principal
mock_dav_client.return_value = mock_client_instance
client = CalDAVClient(
url="https://caldav.yandex.ru/",
username="test@example.com",
password="test-password",
)
client.connect()
start_time = datetime(2025, 1, 20, 14, 0)
result = client.create_event(
calendar_index=0,
title="Recurring Event",
start_time=start_time,
recurrence={"frequency": "DAILY", "count": 5},
)
assert result["success"] is True
saved_event = mock_calendar.save_event.call_args[0][0]
assert "RRULE" in saved_event
assert "FREQ=DAILY" in saved_event
assert "COUNT=5" in saved_event
@patch("mcp_caldav.client.caldav.DAVClient")
def test_caldav_client_create_event_with_attendees_dict(mock_dav_client):
"""Test creating event with attendees as dicts."""
mock_client_instance = MagicMock()
mock_principal = MagicMock()
mock_calendar = MagicMock()
mock_calendar.name = "Test Calendar"
mock_principal.calendars.return_value = [mock_calendar]
mock_client_instance.principal.return_value = mock_principal
mock_dav_client.return_value = mock_client_instance
client = CalDAVClient(
url="https://caldav.yandex.ru/",
username="test@example.com",
password="test-password",
)
client.connect()
start_time = datetime(2025, 1, 20, 14, 0)
result = client.create_event(
calendar_index=0,
title="Test Event",
start_time=start_time,
attendees=[
{"email": "user1@example.com", "status": "ACCEPTED"},
{"email": "user2@example.com", "status": "TENTATIVE", "name": "John Doe"},
],
)
assert result["success"] is True
saved_event = mock_calendar.save_event.call_args[0][0]
assert "ATTENDEE" in saved_event
assert "PARTSTAT=ACCEPTED" in saved_event
assert "PARTSTAT=TENTATIVE" in saved_event
@patch("mcp_caldav.client.caldav.DAVClient")
def test_caldav_client_create_event_with_audio_reminder(mock_dav_client):
"""Test creating event with AUDIO reminder."""
mock_client_instance = MagicMock()
mock_principal = MagicMock()
mock_calendar = MagicMock()
mock_calendar.name = "Test Calendar"
mock_principal.calendars.return_value = [mock_calendar]
mock_client_instance.principal.return_value = mock_principal
mock_dav_client.return_value = mock_client_instance
client = CalDAVClient(
url="https://caldav.yandex.ru/",
username="test@example.com",
password="test-password",
)
client.connect()
start_time = datetime(2025, 1, 20, 14, 0)
result = client.create_event(
calendar_index=0,
title="Test Event",
start_time=start_time,
reminders=[{"minutes_before": 30, "action": "AUDIO"}],
)
assert result["success"] is True
saved_event = mock_calendar.save_event.call_args[0][0]
assert "VALARM" in saved_event
assert "ACTION:AUDIO" in saved_event
assert "TRIGGER:-PT30M" in saved_event
@patch("mcp_caldav.client.caldav.DAVClient")
def test_caldav_client_create_event_with_escaped_text(mock_dav_client):
"""Test that special characters in text are escaped."""
mock_client_instance = MagicMock()
mock_principal = MagicMock()
mock_calendar = MagicMock()
mock_calendar.name = "Test Calendar"
mock_principal.calendars.return_value = [mock_calendar]
mock_client_instance.principal.return_value = mock_principal
mock_dav_client.return_value = mock_client_instance
client = CalDAVClient(
url="https://caldav.yandex.ru/",
username="test@example.com",
password="test-password",
)
client.connect()
start_time = datetime(2025, 1, 20, 14, 0)
result = client.create_event(
calendar_index=0,
title="Event, with; commas\nand newlines",
description="Description, with; special\\chars",
location="Location, with; commas",
start_time=start_time,
)
assert result["success"] is True
saved_event = mock_calendar.save_event.call_args[0][0]
# Check that special characters are escaped
assert (
"Event\\, with\\; commas\\nand newlines" in saved_event
or "Event" in saved_event
)
assert "Description" in saved_event
@patch("mcp_caldav.client.caldav.DAVClient")
def test_caldav_client_create_event_default_times(mock_dav_client):
"""Test creating event with default times."""
mock_client_instance = MagicMock()
mock_principal = MagicMock()
mock_calendar = MagicMock()
mock_calendar.name = "Test Calendar"
mock_principal.calendars.return_value = [mock_calendar]
mock_client_instance.principal.return_value = mock_principal
mock_dav_client.return_value = mock_client_instance
client = CalDAVClient(
url="https://caldav.yandex.ru/",
username="test@example.com",
password="test-password",
)
client.connect()
result = client.create_event(
calendar_index=0,
title="Test Event",
)
assert result["success"] is True
assert "start_time" in result
assert "end_time" in result
@patch("mcp_caldav.client.caldav.DAVClient")
def test_caldav_client_create_event_invalid_calendar_index(mock_dav_client):
"""Test creating event with invalid calendar index."""
mock_client_instance = MagicMock()
mock_principal = MagicMock()
mock_calendar = MagicMock()
mock_principal.calendars.return_value = [mock_calendar]
mock_client_instance.principal.return_value = mock_principal
mock_dav_client.return_value = mock_client_instance
client = CalDAVClient(
url="https://caldav.yandex.ru/",
username="test@example.com",
password="test-password",
)
client.connect()
with pytest.raises(RuntimeError, match="Calendar index 1 not found"):
client.create_event(calendar_index=1, title="Test Event")
@patch("mcp_caldav.client.caldav.DAVClient")
def test_caldav_client_create_event_not_connected(mock_dav_client):
"""Test creating event without connection."""
client = CalDAVClient(
url="https://caldav.yandex.ru/",
username="test@example.com",
password="test-password",
)
with pytest.raises(RuntimeError, match="Not connected"):
client.create_event(title="Test Event")
@patch("mcp_caldav.client.caldav.DAVClient")
def test_caldav_client_get_events(mock_dav_client):
"""Test getting events."""
mock_client_instance = MagicMock()
mock_principal = MagicMock()
mock_calendar = MagicMock()
mock_calendar.name = "Test Calendar"
# Mock event
mock_event = MagicMock()
mock_component = MagicMock()
mock_dtstart = MagicMock()
mock_dtstart.dt = datetime(2025, 1, 20, 14, 0)
mock_dtend = MagicMock()
mock_dtend.dt = datetime(2025, 1, 20, 15, 0)
def get_component(key, default=None):
result = {
"SUMMARY": "Test Event",
"DESCRIPTION": "Test description",
"LOCATION": "Test location",
"UID": "test-uid-123",
"DTSTART": mock_dtstart,
"DTEND": mock_dtend,
"CATEGORIES": None,
"PRIORITY": None,
"RRULE": None,
"ATTENDEE": [],
}.get(key, default)
return result
mock_component.get = get_component
mock_event.icalendar_component = mock_component
mock_calendar.date_search.return_value = [mock_event]
mock_principal.calendars.return_value = [mock_calendar]
mock_client_instance.principal.return_value = mock_principal
mock_dav_client.return_value = mock_client_instance
client = CalDAVClient(
url="https://caldav.yandex.ru/",
username="test@example.com",
password="test-password",
)
client.connect()
start_date = datetime(2025, 1, 20, 0, 0)
end_date = datetime(2025, 1, 21, 0, 0)
events = client.get_events(
calendar_index=0,
start_date=start_date,
end_date=end_date,
)
assert len(events) == 1
assert events[0]["title"] == "Test Event"
assert events[0]["uid"] == "test-uid-123"
@patch("mcp_caldav.client.caldav.DAVClient")
def test_caldav_client_get_events_all_day(mock_dav_client):
"""Test getting events with all-day event."""
mock_client_instance = MagicMock()
mock_principal = MagicMock()
mock_calendar = MagicMock()
# Mock all-day event
mock_event = MagicMock()
mock_component = MagicMock()
mock_dtstart = MagicMock()
mock_dtstart.dt = date(2025, 1, 20)
mock_dtend = MagicMock()
mock_dtend.dt = date(2025, 1, 21)
def get_component(key, default=None):
return {
"SUMMARY": "All Day Event",
"UID": "all-day-uid",
"DTSTART": mock_dtstart,
"DTEND": mock_dtend,
"DESCRIPTION": None,
"LOCATION": None,
"CATEGORIES": None,
"PRIORITY": None,
"RRULE": None,
"ATTENDEE": [],
}.get(key, default)
mock_component.get = get_component
mock_event.icalendar_component = mock_component
mock_calendar.date_search.return_value = [mock_event]
mock_principal.calendars.return_value = [mock_calendar]
mock_client_instance.principal.return_value = mock_principal
mock_dav_client.return_value = mock_client_instance
client = CalDAVClient(
url="https://caldav.yandex.ru/",
username="test@example.com",
password="test-password",
)
client.connect()
events = client.get_events(calendar_index=0, include_all_day=True)
assert len(events) == 1
assert events[0]["all_day"] is True
events = client.get_events(calendar_index=0, include_all_day=False)
assert len(events) == 0
@patch("mcp_caldav.client.caldav.DAVClient")
def test_caldav_client_get_events_default_dates(mock_dav_client):
"""Test getting events with default dates."""
mock_client_instance = MagicMock()
mock_principal = MagicMock()
mock_calendar = MagicMock()
mock_calendar.date_search.return_value = []
mock_principal.calendars.return_value = [mock_calendar]
mock_client_instance.principal.return_value = mock_principal
mock_dav_client.return_value = mock_client_instance
client = CalDAVClient(
url="https://caldav.yandex.ru/",
username="test@example.com",
password="test-password",
)
client.connect()
events = client.get_events(calendar_index=0)
assert isinstance(events, list)
mock_calendar.date_search.assert_called_once()
@patch("mcp_caldav.client.caldav.DAVClient")
def test_caldav_client_get_today_events(mock_dav_client):
"""Test getting today's events."""
mock_client_instance = MagicMock()
mock_principal = MagicMock()
mock_calendar = MagicMock()
mock_calendar.date_search.return_value = []
mock_principal.calendars.return_value = [mock_calendar]
mock_client_instance.principal.return_value = mock_principal
mock_dav_client.return_value = mock_client_instance
client = CalDAVClient(
url="https://caldav.yandex.ru/",
username="test@example.com",
password="test-password",
)
client.connect()
events = client.get_today_events(calendar_index=0)
assert isinstance(events, list)
@patch("mcp_caldav.client.caldav.DAVClient")
def test_caldav_client_get_week_events(mock_dav_client):
"""Test getting week's events."""
mock_client_instance = MagicMock()
mock_principal = MagicMock()
mock_calendar = MagicMock()
mock_calendar.date_search.return_value = []
mock_principal.calendars.return_value = [mock_calendar]
mock_client_instance.principal.return_value = mock_principal
mock_dav_client.return_value = mock_client_instance
client = CalDAVClient(
url="https://caldav.yandex.ru/",
username="test@example.com",
password="test-password",
)
client.connect()
events = client.get_week_events(calendar_index=0, start_from_today=True)
assert isinstance(events, list)
events = client.get_week_events(calendar_index=0, start_from_today=False)
assert isinstance(events, list)
@patch("mcp_caldav.client.caldav.DAVClient")
def test_caldav_client_get_event_by_uid_found(mock_dav_client):
"""Test getting event by UID when found."""
mock_client_instance = MagicMock()
mock_principal = MagicMock()
mock_calendar = MagicMock()
mock_event = MagicMock()
mock_component = MagicMock()
mock_dtstart = MagicMock()
mock_dtstart.dt = datetime(2025, 1, 20, 14, 0)
mock_dtend = MagicMock()
mock_dtend.dt = datetime(2025, 1, 20, 15, 0)
def get_component(key, default=None):
return {
"UID": "test-uid-123",
"SUMMARY": "Test Event",
"DESCRIPTION": "Test description",
"LOCATION": "Test location",
"DTSTART": mock_dtstart,
"DTEND": mock_dtend,
"CATEGORIES": None,
"PRIORITY": None,
"RRULE": None,
"ATTENDEE": [],
}.get(key, default)
mock_component.get = get_component
mock_event.icalendar_component = mock_component
mock_calendar.date_search.return_value = [mock_event]
mock_principal.calendars.return_value = [mock_calendar]
mock_client_instance.principal.return_value = mock_principal
mock_dav_client.return_value = mock_client_instance
client = CalDAVClient(
url="https://caldav.yandex.ru/",
username="test@example.com",
password="test-password",
)
client.connect()
event = client.get_event_by_uid("test-uid-123", calendar_index=0)
assert event is not None
assert event["uid"] == "test-uid-123"
assert event["title"] == "Test Event"
@patch("mcp_caldav.client.caldav.DAVClient")
def test_caldav_client_get_event_by_uid_not_found(mock_dav_client):
"""Test getting event by UID when not found."""
mock_client_instance = MagicMock()
mock_principal = MagicMock()
mock_calendar = MagicMock()
mock_event = MagicMock()
mock_component = MagicMock()
mock_component.get.side_effect = lambda key: {
"UID": "other-uid",
"SUMMARY": "Other Event",
"DTSTART": MagicMock(dt=datetime(2025, 1, 20, 14, 0)),
"DTEND": MagicMock(dt=datetime(2025, 1, 20, 15, 0)),
"DESCRIPTION": None,
"LOCATION": None,
"CATEGORIES": None,
"PRIORITY": None,
"RRULE": None,
"ATTENDEE": [],
}.get(key)
mock_event.icalendar_component = mock_component
mock_calendar.date_search.return_value = [mock_event]
mock_principal.calendars.return_value = [mock_calendar]
mock_client_instance.principal.return_value = mock_principal
mock_dav_client.return_value = mock_client_instance
client = CalDAVClient(
url="https://caldav.yandex.ru/",
username="test@example.com",
password="test-password",
)
client.connect()
event = client.get_event_by_uid("test-uid-123", calendar_index=0)
assert event is None
@patch("mcp_caldav.client.caldav.DAVClient")
def test_caldav_client_delete_event(mock_dav_client):
"""Test deleting an event."""
mock_client_instance = MagicMock()
mock_principal = MagicMock()
mock_calendar = MagicMock()
mock_event = MagicMock()
mock_component = MagicMock()
mock_dtstart = MagicMock()
mock_dtstart.dt = datetime(2025, 1, 20, 14, 0)
mock_dtend = MagicMock()
mock_dtend.dt = datetime(2025, 1, 20, 15, 0)
def get_component(key, default=None):
return {
"UID": "test-uid-123",
"SUMMARY": "Test Event",
"DESCRIPTION": None,
"LOCATION": None,
"DTSTART": mock_dtstart,
"DTEND": mock_dtend,
"CATEGORIES": None,
"PRIORITY": None,
"RRULE": None,
"ATTENDEE": [],
}.get(key, default)
mock_component.get = get_component
mock_event.icalendar_component = mock_component
mock_calendar.date_search.return_value = [mock_event]
mock_principal.calendars.return_value = [mock_calendar]
mock_client_instance.principal.return_value = mock_principal
mock_dav_client.return_value = mock_client_instance
client = CalDAVClient(
url="https://caldav.yandex.ru/",
username="test@example.com",
password="test-password",
)
client.connect()
result = client.delete_event("test-uid-123", calendar_index=0)
assert result["success"] is True
assert result["uid"] == "test-uid-123"
mock_event.delete.assert_called_once()
@patch("mcp_caldav.client.caldav.DAVClient")
def test_caldav_client_delete_event_not_found(mock_dav_client):
"""Test deleting an event that doesn't exist."""
mock_client_instance = MagicMock()
mock_principal = MagicMock()
mock_calendar = MagicMock()
mock_event = MagicMock()
mock_component = MagicMock()
mock_dtstart = MagicMock()
mock_dtstart.dt = datetime(2025, 1, 20, 14, 0)
mock_dtend = MagicMock()
mock_dtend.dt = datetime(2025, 1, 20, 15, 0)
def get_component(key, default=None):
return {
"UID": "other-uid",
"SUMMARY": "Other Event",
"DESCRIPTION": None,
"LOCATION": None,
"DTSTART": mock_dtstart,
"DTEND": mock_dtend,
"CATEGORIES": None,
"PRIORITY": None,
"RRULE": None,
"ATTENDEE": [],
}.get(key, default)
mock_component.get = get_component
mock_event.icalendar_component = mock_component
mock_calendar.date_search.return_value = [mock_event]
mock_principal.calendars.return_value = [mock_calendar]
mock_client_instance.principal.return_value = mock_principal
mock_dav_client.return_value = mock_client_instance
client = CalDAVClient(
url="https://caldav.yandex.ru/",
username="test@example.com",
password="test-password",
)
client.connect()
with pytest.raises(RuntimeError, match="Event with UID test-uid-123 not found"):
client.delete_event("test-uid-123", calendar_index=0)
@patch("mcp_caldav.client.caldav.DAVClient")
def test_caldav_client_search_events(mock_dav_client):
"""Test searching events."""
mock_client_instance = MagicMock()
mock_principal = MagicMock()
mock_calendar = MagicMock()
# Mock events
mock_event1 = MagicMock()
mock_component1 = MagicMock()
mock_dtstart1 = MagicMock()
mock_dtstart1.dt = datetime(2025, 1, 20, 14, 0)
mock_dtend1 = MagicMock()
mock_dtend1.dt = datetime(2025, 1, 20, 15, 0)
def get_component1(key, default=None):
return {
"SUMMARY": "Meeting with John",
"DESCRIPTION": "Important meeting",
"LOCATION": "Office",
"UID": "uid-1",
"DTSTART": mock_dtstart1,
"DTEND": mock_dtend1,
"CATEGORIES": None,
"PRIORITY": None,
"RRULE": None,
"ATTENDEE": [],
}.get(key, default)
mock_component1.get = get_component1
mock_event1.icalendar_component = mock_component1
mock_event2 = MagicMock()
mock_component2 = MagicMock()
mock_dtstart2 = MagicMock()
mock_dtstart2.dt = datetime(2025, 1, 21, 14, 0)
mock_dtend2 = MagicMock()
mock_dtend2.dt = datetime(2025, 1, 21, 15, 0)
def get_component2(key, default=None):
return {
"SUMMARY": "Other Event",
"DESCRIPTION": "Not important",
"LOCATION": "Home",
"UID": "uid-2",
"DTSTART": mock_dtstart2,
"DTEND": mock_dtend2,
"CATEGORIES": None,
"PRIORITY": None,
"RRULE": None,
"ATTENDEE": [],
}.get(key, default)
mock_component2.get = get_component2
mock_event2.icalendar_component = mock_component2
mock_calendar.date_search.return_value = [mock_event1, mock_event2]
mock_principal.calendars.return_value = [mock_calendar]
mock_client_instance.principal.return_value = mock_principal
mock_dav_client.return_value = mock_client_instance
client = CalDAVClient(
url="https://caldav.yandex.ru/",
username="test@example.com",
password="test-password",
)
client.connect()
start_date = datetime(2025, 1, 20, 0, 0)
end_date = datetime(2025, 1, 22, 0, 0)
# Search by title
results = client.search_events(
calendar_index=0,
query="Meeting",
search_fields=["title"],
start_date=start_date,
end_date=end_date,
)
assert len(results) == 1
assert "Meeting" in results[0]["title"]
# Search by description
results = client.search_events(
calendar_index=0,
query="Important meeting",
search_fields=["description"],
start_date=start_date,
end_date=end_date,
)
assert len(results) == 1
assert "Important meeting" in results[0]["description"]
# Search by location
results = client.search_events(
calendar_index=0,
query="Office",
search_fields=["location"],
start_date=start_date,
end_date=end_date,
)
assert len(results) == 1
# Search without query (returns all)
results = client.search_events(
calendar_index=0,
query=None,
start_date=start_date,
end_date=end_date,
)
assert len(results) == 2
@patch("mcp_caldav.client.caldav.DAVClient")
def test_caldav_client_search_events_missing_dates(mock_dav_client):
"""Test searching events without required dates."""
mock_client_instance = MagicMock()
mock_principal = MagicMock()
mock_calendar = MagicMock()
mock_principal.calendars.return_value = [mock_calendar]
mock_client_instance.principal.return_value = mock_principal
mock_dav_client.return_value = mock_client_instance
client = CalDAVClient(
url="https://caldav.yandex.ru/",
username="test@example.com",
password="test-password",
)
client.connect()
with pytest.raises(
ValueError, match="Both start_date and end_date must be provided"
):
client.search_events(calendar_index=0, query="test")
with pytest.raises(
ValueError, match="Both start_date and end_date must be provided"
):
client.search_events(calendar_index=0, query="test", start_date=datetime.now())
@patch("mcp_caldav.client.caldav.DAVClient")
def test_caldav_client_list_calendars_error(mock_dav_client):
"""Test list_calendars error handling."""
mock_client_instance = MagicMock()
mock_principal = MagicMock()
mock_principal.calendars.side_effect = Exception("Calendar error")
mock_client_instance.principal.return_value = mock_principal
mock_dav_client.return_value = mock_client_instance
client = CalDAVClient(
url="https://caldav.yandex.ru/",
username="test@example.com",
password="test-password",
)
client.connect()
with pytest.raises(RuntimeError, match="Failed to list calendars"):
client.list_calendars()
@patch("mcp_caldav.client.caldav.DAVClient")
def test_caldav_client_get_events_error(mock_dav_client):
"""Test get_events error handling."""
mock_client_instance = MagicMock()
mock_principal = MagicMock()
mock_calendar = MagicMock()
mock_calendar.date_search.side_effect = Exception("Search error")
mock_principal.calendars.return_value = [mock_calendar]
mock_client_instance.principal.return_value = mock_principal
mock_dav_client.return_value = mock_client_instance
client = CalDAVClient(
url="https://caldav.yandex.ru/",
username="test@example.com",
password="test-password",
)
client.connect()
with pytest.raises(RuntimeError, match="Failed to get events"):
client.get_events(calendar_index=0)
@patch("mcp_caldav.client.caldav.DAVClient")
def test_caldav_client_get_events_invalid_index(mock_dav_client):
"""Test get_events with invalid calendar index."""
mock_client_instance = MagicMock()
mock_principal = MagicMock()
mock_calendar = MagicMock()
mock_principal.calendars.return_value = [mock_calendar]
mock_client_instance.principal.return_value = mock_principal
mock_dav_client.return_value = mock_client_instance
client = CalDAVClient(
url="https://caldav.yandex.ru/",
username="test@example.com",
password="test-password",
)
client.connect()
with pytest.raises(RuntimeError, match="Calendar index 1 not found"):
client.get_events(calendar_index=1)
def test_caldav_client_yandex_detection():
"""Test Yandex Calendar detection."""
client1 = CalDAVClient(
url="https://caldav.yandex.ru/",
username="test@example.com",
password="test-password",
)
assert client1.is_yandex is True
client2 = CalDAVClient(
url="https://caldav.yandex.com/",
username="test@example.com",
password="test-password",
)
assert client2.is_yandex is True
client3 = CalDAVClient(
url="https://caldav.example.com/",
username="test@example.com",
password="test-password",
)
assert client3.is_yandex is False