fix: show subscribed calendar event details
This commit is contained in:
@@ -18,6 +18,8 @@ from icalendar import Calendar
|
|||||||
MAX_ICS_BYTES = 2_000_000
|
MAX_ICS_BYTES = 2_000_000
|
||||||
MAX_REDIRECTS = 3
|
MAX_REDIRECTS = 3
|
||||||
DEFAULT_RECURRENCE_LIMIT = 10_000
|
DEFAULT_RECURRENCE_LIMIT = 10_000
|
||||||
|
MAX_DESCRIPTION_LENGTH = 2_000
|
||||||
|
MAX_LOCATION_LENGTH = 500
|
||||||
TIMEOUT_SECONDS = 10
|
TIMEOUT_SECONDS = 10
|
||||||
_ALLOWED_CONTENT_TYPES = {"text/calendar", "text/plain", "application/octet-stream"}
|
_ALLOWED_CONTENT_TYPES = {"text/calendar", "text/plain", "application/octet-stream"}
|
||||||
|
|
||||||
@@ -159,9 +161,13 @@ def _overlaps(start: datetime, end: datetime, window_start: datetime, window_end
|
|||||||
def _event_dict(event: Any, source_id: str, source: str, color: str, start: datetime, end: datetime, all_day: bool) -> dict:
|
def _event_dict(event: Any, source_id: str, source: str, color: str, start: datetime, end: datetime, all_day: bool) -> dict:
|
||||||
uid = str(event.get("uid") or "")
|
uid = str(event.get("uid") or "")
|
||||||
title = str(event.get("summary") or "Untitled event").strip() or "Untitled event"
|
title = str(event.get("summary") or "Untitled event").strip() or "Untitled event"
|
||||||
|
description = str(event.get("description") or "").strip()[:MAX_DESCRIPTION_LENGTH]
|
||||||
|
location = str(event.get("location") or "").strip()[:MAX_LOCATION_LENGTH]
|
||||||
return {
|
return {
|
||||||
"id": f"{uid or title}:{start.isoformat()}",
|
"id": f"{uid or title}:{start.isoformat()}",
|
||||||
"title": title,
|
"title": title,
|
||||||
|
"description": description or None,
|
||||||
|
"location": location or None,
|
||||||
"starts_at": start,
|
"starts_at": start,
|
||||||
"ends_at": end,
|
"ends_at": end,
|
||||||
"all_day": all_day,
|
"all_day": all_day,
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ UID:one\r
|
|||||||
DTSTART:20260920T090000Z\r
|
DTSTART:20260920T090000Z\r
|
||||||
DTEND:20260920T100000Z\r
|
DTEND:20260920T100000Z\r
|
||||||
SUMMARY:Meeting\r
|
SUMMARY:Meeting\r
|
||||||
|
DESCRIPTION:Body line one\\nBody line two\r
|
||||||
|
LOCATION:Meeting room\r
|
||||||
END:VEVENT\r
|
END:VEVENT\r
|
||||||
END:VCALENDAR\r
|
END:VCALENDAR\r
|
||||||
"""
|
"""
|
||||||
@@ -148,6 +150,8 @@ def test_subscription_crud_refresh_events_and_stale_cache(client, monkeypatch):
|
|||||||
)
|
)
|
||||||
assert events.status_code == 200
|
assert events.status_code == 200
|
||||||
assert events.json()["events"][0]["title"] == "Meeting"
|
assert events.json()["events"][0]["title"] == "Meeting"
|
||||||
|
assert events.json()["events"][0]["description"] == "Body line one\nBody line two"
|
||||||
|
assert events.json()["events"][0]["location"] == "Meeting room"
|
||||||
assert events.json()["sources"][0]["stale"] is False
|
assert events.json()["sources"][0]["stale"] is False
|
||||||
|
|
||||||
refreshed = client.post(f"/api/v1/calendar-subscriptions/{body['id']}/refresh")
|
refreshed = client.post(f"/api/v1/calendar-subscriptions/{body['id']}/refresh")
|
||||||
@@ -166,6 +170,26 @@ def test_subscription_crud_refresh_events_and_stale_cache(client, monkeypatch):
|
|||||||
assert client.delete(f"/api/v1/calendar-subscriptions/{body['id']}").status_code == 204
|
assert client.delete(f"/api/v1/calendar-subscriptions/{body['id']}").status_code == 204
|
||||||
|
|
||||||
|
|
||||||
|
def test_parser_bounds_large_event_text_fields():
|
||||||
|
oversized = ICS.replace(
|
||||||
|
b"DESCRIPTION:Body line one\\nBody line two",
|
||||||
|
b"DESCRIPTION:" + b"x" * 3_000,
|
||||||
|
).replace(
|
||||||
|
b"LOCATION:Meeting room",
|
||||||
|
b"LOCATION:" + b"y" * 1_000,
|
||||||
|
)
|
||||||
|
event = parse_ics_events(
|
||||||
|
oversized,
|
||||||
|
"Work",
|
||||||
|
"#123456",
|
||||||
|
datetime(2026, 9, 20, tzinfo=UTC),
|
||||||
|
datetime(2026, 9, 21, tzinfo=UTC),
|
||||||
|
"UTC",
|
||||||
|
)[0]
|
||||||
|
assert event["description"] == "x" * 2_000
|
||||||
|
assert event["location"] == "y" * 500
|
||||||
|
|
||||||
|
|
||||||
def test_events_validate_window_and_disabled_sources_are_skipped(client, monkeypatch):
|
def test_events_validate_window_and_disabled_sources_are_skipped(client, monkeypatch):
|
||||||
client = initialized(client)
|
client = initialized(client)
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
|
|||||||
Reference in New Issue
Block a user