Files
duty-teller/tests/test_calendar_ics.py
Nikolay Tatarinov e25eb7be2f
Some checks failed
CI / lint-and-test (push) Failing after 11s
chore: update development dependencies and improve test coverage
- Upgraded `pytest-asyncio` to version 1.0 to ensure compatibility with the latest features and improvements.
- Increased the coverage threshold in pytest configuration to 80%, enhancing the quality assurance process.
- Added a new `conftest.py` file to manage shared fixtures and improve test organization.
- Introduced multiple new test files to cover various components, ensuring comprehensive test coverage across the application.
- Updated the `.coverage` file to reflect the latest coverage metrics.
2026-02-20 17:33:04 +03:00

154 lines
5.3 KiB
Python

"""Tests for duty_teller.api.calendar_ics (_to_date, _event_date_range, _get_events_from_ics, get_calendar_events)."""
from datetime import date, datetime
from unittest.mock import patch
import pytest
from icalendar import Calendar, Event
from duty_teller.api import calendar_ics as mod
class TestToDate:
"""Tests for _to_date."""
def test_datetime_returns_date(self):
dt = datetime(2025, 3, 15, 10, 0, 0)
assert mod._to_date(dt) == date(2025, 3, 15)
def test_date_returns_same(self):
d = date(2025, 3, 15)
assert mod._to_date(d) == date(2025, 3, 15)
def test_invalid_returns_none(self):
assert mod._to_date("2025-03-15") is None
assert mod._to_date(None) is None
assert mod._to_date(123) is None
class TestEventDateRange:
"""Tests for _event_date_range. DTEND is exclusive in iCalendar."""
def _vevent(self, dtstart_value, dtend_value=None):
"""Build a minimal VEVENT component using icalendar.Event."""
cal = Calendar()
ev = Event()
ev.add("dtstart", dtstart_value)
if dtend_value is not None:
ev.add("dtend", dtend_value)
cal.add_component(ev)
for c in cal.walk():
if c.name == "VEVENT":
return c
raise AssertionError("no vevent")
def test_dtstart_and_dtend_exclusive_end(self):
start = date(2025, 1, 10)
end_exclusive = date(2025, 1, 13) # last day of event = 2025-01-12
comp = self._vevent(start, end_exclusive)
s, e = mod._event_date_range(comp)
assert s == date(2025, 1, 10)
assert e == date(2025, 1, 12)
def test_only_dtstart_returns_same_for_start_and_end(self):
start = date(2025, 2, 1)
comp = self._vevent(start)
s, e = mod._event_date_range(comp)
assert s == date(2025, 2, 1)
assert e == date(2025, 2, 1)
def test_no_dtstart_returns_none_none(self):
cal = Calendar()
ev = Event()
ev.add("summary", "No dates")
cal.add_component(ev)
for c in cal.walk():
if c.name == "VEVENT":
s, e = mod._event_date_range(c)
assert s is None
assert e is None
return
pytest.fail("no vevent")
class TestGetEventsFromIcs:
"""Tests for _get_events_from_ics."""
def test_minimal_ics_one_event(self):
cal = Calendar()
ev = Event()
ev.add("dtstart", date(2025, 1, 15))
ev.add("summary", "Meeting")
cal.add_component(ev)
raw = cal.to_ical()
result = mod._get_events_from_ics(raw, "2025-01-01", "2025-01-31")
assert len(result) == 1
assert result[0]["date"] == "2025-01-15"
assert result[0]["summary"] == "Meeting"
def test_event_filtered_by_date_range(self):
cal = Calendar()
ev = Event()
ev.add("dtstart", date(2025, 1, 10))
ev.add("dtend", date(2025, 1, 14)) # 10,11,12,13
ev.add("summary", "Multi")
cal.add_component(ev)
raw = cal.to_ical()
result = mod._get_events_from_ics(raw, "2025-01-12", "2025-01-13")
assert len(result) == 2
assert result[0]["date"] == "2025-01-12"
assert result[1]["date"] == "2025-01-13"
def test_empty_ics_returns_empty_list(self):
cal = Calendar()
raw = cal.to_ical()
result = mod._get_events_from_ics(raw, "2025-01-01", "2025-01-31")
assert result == []
def test_broken_ics_returns_empty_list(self):
result = mod._get_events_from_ics(b"not ical at all", "2025-01-01", "2025-01-31")
assert result == []
def test_recurring_events_skipped(self):
cal = Calendar()
ev = Event()
ev.add("dtstart", date(2025, 1, 15))
ev.add("rrule", {"freq": "daily", "count": 3})
ev.add("summary", "Recurring")
cal.add_component(ev)
raw = cal.to_ical()
result = mod._get_events_from_ics(raw, "2025-01-01", "2025-01-31")
assert result == []
class TestGetCalendarEvents:
"""Tests for get_calendar_events (with mocked _fetch_ics, no real HTTP)."""
def test_empty_url_returns_empty(self):
assert mod.get_calendar_events("", "2025-01-01", "2025-01-31") == []
def test_from_after_to_returns_empty(self):
assert mod.get_calendar_events("https://example.com/a.ics", "2025-02-01", "2025-01-01") == []
@patch.object(mod, "_fetch_ics", return_value=None)
def test_fetch_returns_none_returns_empty(self, mock_fetch):
result = mod.get_calendar_events("https://example.com/a.ics", "2025-01-01", "2025-01-31")
assert result == []
mock_fetch.assert_called_once()
@patch.object(mod, "_fetch_ics")
def test_success_returns_events(self, mock_fetch):
cal = Calendar()
ev = Event()
ev.add("dtstart", date(2025, 1, 20))
ev.add("summary", "Test event")
cal.add_component(ev)
mock_fetch.return_value = cal.to_ical()
result = mod.get_calendar_events(
"https://example.com/cal.ics", "2025-01-01", "2025-01-31"
)
assert len(result) == 1
assert result[0]["date"] == "2025-01-20"
assert result[0]["summary"] == "Test event"
mock_fetch.assert_called_once_with("https://example.com/cal.ics")