feat: add calendar subscription token functionality and ICS generation
- Introduced a new database model for calendar subscription tokens, allowing users to generate unique tokens for accessing their personal calendar. - Implemented API endpoint to return ICS files containing only the subscribing user's duties, enhancing user experience with personalized calendar access. - Added utility functions for generating ICS files from user duties, ensuring proper formatting and timezone handling. - Updated command handlers to support the new calendar link feature, providing users with easy access to their personal calendar subscriptions. - Included unit tests for the new functionality, ensuring reliability and correctness of token generation and ICS file creation.
This commit is contained in:
60
tests/test_personal_calendar_ics.py
Normal file
60
tests/test_personal_calendar_ics.py
Normal file
@@ -0,0 +1,60 @@
|
||||
"""Unit tests for personal calendar ICS generation."""
|
||||
|
||||
from types import SimpleNamespace
|
||||
|
||||
from icalendar import Calendar as ICalendar
|
||||
|
||||
from duty_teller.api.personal_calendar_ics import build_personal_ics
|
||||
|
||||
|
||||
# Minimal Duty-like object for build_personal_ics(duties_with_name: list[tuple[Duty, str]])
|
||||
def _duty(id_, start_at, end_at, event_type="duty"):
|
||||
return (
|
||||
SimpleNamespace(
|
||||
id=id_, start_at=start_at, end_at=end_at, event_type=event_type
|
||||
),
|
||||
"Test User",
|
||||
)
|
||||
|
||||
|
||||
def test_build_personal_ics_empty_returns_valid_calendar():
|
||||
"""Empty list produces VCALENDAR with no VEVENTs."""
|
||||
ics = build_personal_ics([])
|
||||
assert ics.startswith(b"BEGIN:VCALENDAR")
|
||||
assert ics.rstrip().endswith(b"END:VCALENDAR")
|
||||
cal = ICalendar.from_ical(ics)
|
||||
assert cal is not None
|
||||
events = [c for c in cal.walk() if c.name == "VEVENT"]
|
||||
assert len(events) == 0
|
||||
|
||||
|
||||
def test_build_personal_ics_one_duty():
|
||||
"""One duty produces one VEVENT with correct DTSTART/DTEND/SUMMARY."""
|
||||
duties = [
|
||||
_duty(1, "2025-02-20T09:00:00Z", "2025-02-20T18:00:00Z", "duty"),
|
||||
]
|
||||
ics = build_personal_ics(duties)
|
||||
cal = ICalendar.from_ical(ics)
|
||||
assert cal is not None
|
||||
events = [c for c in cal.walk() if c.name == "VEVENT"]
|
||||
assert len(events) == 1
|
||||
ev = events[0]
|
||||
assert ev.get("summary") == "Duty"
|
||||
assert "2025-02-20" in str(ev.get("dtstart").dt)
|
||||
assert "2025-02-20" in str(ev.get("dtend").dt)
|
||||
assert b"duty-1@duty-teller" in ics or "duty-1@duty-teller" in ics.decode("utf-8")
|
||||
|
||||
|
||||
def test_build_personal_ics_event_types():
|
||||
"""Unavailable and vacation get correct SUMMARY."""
|
||||
duties = [
|
||||
_duty(1, "2025-02-20T09:00:00Z", "2025-02-21T09:00:00Z", "unavailable"),
|
||||
_duty(2, "2025-02-25T00:00:00Z", "2025-03-01T00:00:00Z", "vacation"),
|
||||
]
|
||||
ics = build_personal_ics(duties)
|
||||
cal = ICalendar.from_ical(ics)
|
||||
events = [c for c in cal.walk() if c.name == "VEVENT"]
|
||||
assert len(events) == 2
|
||||
summaries = [str(ev.get("summary")) for ev in events]
|
||||
assert "Unavailable" in summaries
|
||||
assert "Vacation" in summaries
|
||||
Reference in New Issue
Block a user