Coverage for app/backend/src/tests/fixtures/api_helpers.py: 92%
42 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-12 20:52 +0000
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-12 20:52 +0000
1"""
2Exposes typed functions mapping 1-to-1 to backend APIs, encapsulating protobuf encoding boilerplate.
3Functions have neutral defaults for tests that don't need specific parameter values.
4"""
6from datetime import date, datetime, time, timedelta
8from google.protobuf import wrappers_pb2
10from couchers.proto import events_pb2
11from couchers.utils import today
12from tests.fixtures.misc import Moderator
13from tests.fixtures.sessions import events_session
15# Event location constants which timezone_areas.sql-fake has regions for.
16# Neutral default location. Resolves to Etc/GMT with real dataset and Etc/UTC with fake dataset.
17EVENT_LOCATION_DEFAULT_UTC = events_pb2.EventLocation(address="Near Null Island", lat=0.1, lng=0.2)
18EVENT_LOCATION_HELSINKI = events_pb2.EventLocation(address="Helsinki, Finland", lat=60.192059, lng=24.945831)
19EVENT_LOCATION_NEW_YORK = events_pb2.EventLocation(address="New York, New York", lat=40.712776, lng=-74.005974)
22def _date_time_to_iso8601(value: tuple[date, time]) -> str:
23 return f"{value[0].isoformat()}T{value[1].isoformat()}"
26def _one_hour_after(value: tuple[date, time]) -> tuple[date, time]:
27 end_datetime = datetime.combine(value[0], value[1]) + timedelta(hours=1)
28 return end_datetime.date(), end_datetime.time()
31def _string_value(value: str | None) -> wrappers_pb2.StringValue | None:
32 return wrappers_pb2.StringValue(value=value) if value is not None else None
35def create_event(
36 *,
37 token: str,
38 title: str | None = "Dummy Title",
39 content: str | None = "Dummy content.",
40 photo_key: str | None = None,
41 parent_community_id: int | None = None,
42 location: events_pb2.EventLocation | None = EVENT_LOCATION_DEFAULT_UTC,
43 # Force callers to provide local date/times, not datetimes with timezones,
44 # since the API accepts local times and resolves the timezone from the location.
45 start_date_time: tuple[date, time] | None = None,
46 end_date_time: tuple[date, time] | None = None,
47 approve_by: Moderator | None = None,
48) -> events_pb2.Event:
49 if start_date_time is None:
50 start_date_time = (today() + timedelta(days=1), time(12, 0))
51 if end_date_time is None:
52 end_date_time = _one_hour_after(start_date_time)
54 with events_session(token) as api:
55 event: events_pb2.Event = api.CreateEvent(
56 events_pb2.CreateEventReq(
57 title=title,
58 content=content,
59 photo_key=photo_key,
60 parent_community_id=parent_community_id,
61 location=location,
62 start_datetime_iso8601_local=_date_time_to_iso8601(start_date_time),
63 end_datetime_iso8601_local=_date_time_to_iso8601(end_date_time),
64 )
65 )
67 if approve_by is not None:
68 approve_by.approve_event_occurrence(event.event_id)
70 return event
73def schedule_event(
74 *,
75 token: str,
76 event_id: int,
77 content: str | None = "Dummy content.",
78 photo_key: str | None = None,
79 location: events_pb2.EventLocation | None = EVENT_LOCATION_DEFAULT_UTC,
80 # Force callers to provide local date/times, not datetimes with timezones,
81 # since the API accepts local times and resolves the timezone from the location.
82 start_date_time: tuple[date, time],
83 end_date_time: tuple[date, time] | None = None,
84 approve_by: Moderator | None = None,
85) -> events_pb2.Event:
86 if end_date_time is None: 86 ↛ 87line 86 didn't jump to line 87 because the condition on line 86 was never true
87 end_date_time = _one_hour_after(start_date_time)
89 with events_session(token) as api:
90 event: events_pb2.Event = api.ScheduleEvent(
91 events_pb2.ScheduleEventReq(
92 event_id=event_id,
93 content=content,
94 photo_key=photo_key,
95 location=location,
96 start_datetime_iso8601_local=_date_time_to_iso8601(start_date_time),
97 end_datetime_iso8601_local=_date_time_to_iso8601(end_date_time),
98 )
99 )
101 if approve_by is not None: 101 ↛ 102line 101 didn't jump to line 102 because the condition on line 101 was never true
102 approve_by.approve_event_occurrence(event.event_id)
104 return event
107def update_event(
108 *,
109 token: str,
110 event_id: int,
111 # None leaves the field unchanged.
112 title: str | None = None,
113 content: str | None = None,
114 photo_key: str | None = None,
115 location: events_pb2.EventLocation | None = None,
116 start_date_time: tuple[date, time] | None = None,
117 end_date_time: tuple[date, time] | None = None,
118 update_all_future: bool = False,
119 should_notify: bool = False,
120) -> events_pb2.Event:
121 with events_session(token) as api:
122 event: events_pb2.Event = api.UpdateEvent(
123 events_pb2.UpdateEventReq(
124 event_id=event_id,
125 update_all_future=update_all_future,
126 title=_string_value(title),
127 content=_string_value(content),
128 photo_key=_string_value(photo_key),
129 location=location,
130 start_datetime_iso8601_local=_string_value(
131 _date_time_to_iso8601(start_date_time) if start_date_time is not None else None
132 ),
133 end_datetime_iso8601_local=_string_value(
134 _date_time_to_iso8601(end_date_time) if end_date_time is not None else None
135 ),
136 should_notify=should_notify,
137 )
138 )
139 return event
142def get_event(token: str, event_id: int) -> events_pb2.Event:
143 with events_session(token) as api:
144 event: events_pb2.Event = api.GetEvent(events_pb2.GetEventReq(event_id=event_id))
145 return event