Coverage for app / backend / src / tests / test_calendar_events.py: 100%
69 statements
« prev ^ index » next coverage.py v7.14.0, created at 2026-05-13 02:44 +0000
« prev ^ index » next coverage.py v7.14.0, created at 2026-05-13 02:44 +0000
1import re
2from datetime import timedelta
4import ics
5import pytest
7from couchers.email.calendar_events import create_host_request_ics
8from couchers.i18n.context import LocalizationContext
9from couchers.proto import conversations_pb2, requests_pb2
10from couchers.proto.requests_pb2 import HostRequest
11from couchers.utils import today
12from tests.fixtures.db import generate_user
13from tests.fixtures.misc import email_fields, mock_notification_email
14from tests.fixtures.sessions import requests_session
15from tests.test_requests import valid_request_text
18@pytest.fixture(autouse=True)
19def _(testconfig):
20 pass
23def test_ics_content():
24 host_request = HostRequest(
25 host_request_id=42, from_date="2000-01-01", to_date="2000-01-02", hosting_city="New York"
26 )
28 ics = create_host_request_ics(
29 host_request, other_name="Bob", hosting=True, loc_context=LocalizationContext.en_utc()
30 )
31 ics = ics.replace("\r\n", "\n")
33 # Strip the domain in the UID, which depends on environment variables
34 ics = re.sub(
35 r"^UID:.*@(.*)$", lambda match: match[0].removesuffix(match[1]) + "<stripped>", ics, flags=re.MULTILINE
36 )
38 # Strip the domain in the URL and DESCRIPTION, which depends on environment variables
39 ics = re.sub(r"^(DESCRIPTION|URL):(.*)/(\d+)", r"\1:<stripped>/\3", ics, flags=re.MULTILINE)
41 assert (
42 ics
43 == """
44BEGIN:VCALENDAR
45VERSION:2.0
46PRODID:-//Couchers.org//Couchers//EN
47BEGIN:VEVENT
48DTSTART;VALUE=DATE:20000101
49DTEND;VALUE=DATE:20000103
50DESCRIPTION:<stripped>/42
51LOCATION:New York
52SUMMARY:Hosting Bob
53UID:host_request.42@<stripped>
54URL:<stripped>/42
55END:VEVENT
56END:VCALENDAR
57 """.strip()
58 )
61def test_host_request_attachments(db, moderator):
62 host, host_token = generate_user(complete_profile=True)
63 surfer, surfer_token = generate_user(complete_profile=True)
65 from_date = today() + timedelta(days=2)
66 to_date = today() + timedelta(days=3)
68 # Send the host request, no calendar attachment yet
69 with requests_session(surfer_token) as api:
70 hr_id = api.CreateHostRequest(
71 requests_pb2.CreateHostRequestReq(
72 host_user_id=host.id,
73 from_date=from_date.isoformat(),
74 to_date=to_date.isoformat(),
75 text=valid_request_text("can i stay plz"),
76 )
77 ).host_request_id
79 with mock_notification_email() as mock:
80 moderator.approve_host_request(hr_id)
82 mock.assert_called_once()
83 e = email_fields(mock)
84 assert "request" in e.subject and surfer.name in e.subject
85 assert not e.attachments
87 # Host accepts, surfer gets a calendar attachment
88 with requests_session(host_token) as api:
89 with mock_notification_email() as mock:
90 api.RespondHostRequest(
91 requests_pb2.RespondHostRequestReq(
92 host_request_id=hr_id,
93 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED,
94 text="Accepting host request",
95 )
96 )
98 e = email_fields(mock)
99 assert "accept" in e.subject and host.name in e.subject
100 ics_event = _get_email_ics_attachment_calendar_event(e)
101 assert not ics_event.status
102 assert ics_event.begin.date() == from_date
103 assert ics_event.end.date() == (to_date + timedelta(days=1))
104 assert ics_event.name == f"Surfing with {host.name}"
105 assert ics_event.location == host.city
107 # Surfer confirms, hosts gets a calendar attachment
108 with requests_session(surfer_token) as api:
109 with mock_notification_email() as mock:
110 api.RespondHostRequest(
111 requests_pb2.RespondHostRequestReq(
112 host_request_id=hr_id,
113 status=conversations_pb2.HOST_REQUEST_STATUS_CONFIRMED,
114 text="Confirming host request",
115 )
116 )
118 e = email_fields(mock)
119 assert "confirm" in e.subject and surfer.name in e.subject
120 ics_event = _get_email_ics_attachment_calendar_event(e)
121 assert not ics_event.status
122 assert ics_event.name == f"Hosting {surfer.name}"
124 # Host cancels, surfer gets a calendar attachment
125 with requests_session(surfer_token) as api:
126 with mock_notification_email() as mock:
127 api.RespondHostRequest(
128 requests_pb2.RespondHostRequestReq(
129 host_request_id=hr_id,
130 status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED,
131 text="Cancelling host request",
132 )
133 )
135 e = email_fields(mock)
136 assert "cancel" in e.subject and surfer.name in e.subject
137 ics_event = _get_email_ics_attachment_calendar_event(e)
138 assert ics_event.status == "CANCELLED"
141def _get_email_ics_attachment_calendar_event(e) -> ics.Event:
142 assert len(e.attachments or []) == 1
143 ics_attachment = e.attachments[0]
144 assert ics_attachment.filename.endswith(".ics")
145 ics_calendar = ics.Calendar(ics_attachment.data.decode("utf-8"))
146 assert len(ics_calendar.events) == 1
147 return next(iter(ics_calendar.events))