Coverage for app / backend / src / tests / test_events.py: 100%
1230 statements
« prev ^ index » next coverage.py v7.13.2, created at 2026-02-03 06:18 +0000
« prev ^ index » next coverage.py v7.13.2, created at 2026-02-03 06:18 +0000
1from datetime import timedelta
3import grpc
4import pytest
5from google.protobuf import wrappers_pb2
6from psycopg2.extras import DateTimeTZRange
7from sqlalchemy.sql.expression import update
9from couchers.db import session_scope
10from couchers.models import BackgroundJob, EventOccurrence, Upload
11from couchers.proto import editor_pb2, events_pb2, threads_pb2
12from couchers.tasks import enforce_community_memberships
13from couchers.utils import Timestamp_from_datetime, now, to_aware_datetime
14from tests.fixtures.db import generate_user
15from tests.fixtures.misc import PushCollector, email_fields, mock_notification_email, process_jobs
16from tests.fixtures.sessions import events_session, real_editor_session, threads_session
17from tests.test_communities import create_community, create_group
20@pytest.fixture(autouse=True)
21def _(testconfig):
22 pass
25def test_CreateEvent(db, push_collector: PushCollector):
26 # test cases:
27 # can create event
28 # cannot create event with missing details
29 # can create online event
30 # can create in person event
31 # can't create event that starts in the past
32 # can create in different timezones
34 # event creator
35 user1, token1 = generate_user()
36 # community moderator
37 user2, token2 = generate_user()
38 # third party
39 user3, token3 = generate_user()
41 with session_scope() as session:
42 c_id = create_community(session, 0, 2, "Community", [user2], [], None).id
44 time_before = now()
45 start_time = now() + timedelta(hours=2)
46 end_time = start_time + timedelta(hours=3)
48 with events_session(token1) as api:
49 # in person event
50 res = api.CreateEvent(
51 events_pb2.CreateEventReq(
52 title="Dummy Title",
53 content="Dummy content.",
54 photo_key=None,
55 offline_information=events_pb2.OfflineEventInformation(
56 address="Near Null Island",
57 lat=0.1,
58 lng=0.2,
59 ),
60 start_time=Timestamp_from_datetime(start_time),
61 end_time=Timestamp_from_datetime(end_time),
62 timezone="UTC",
63 )
64 )
66 assert res.is_next
67 assert res.title == "Dummy Title"
68 assert res.slug == "dummy-title"
69 assert res.content == "Dummy content."
70 assert not res.photo_url
71 assert res.WhichOneof("mode") == "offline_information"
72 assert res.offline_information.lat == 0.1
73 assert res.offline_information.lng == 0.2
74 assert res.offline_information.address == "Near Null Island"
75 assert time_before <= to_aware_datetime(res.created) <= now()
76 assert time_before <= to_aware_datetime(res.last_edited) <= now()
77 assert res.creator_user_id == user1.id
78 assert to_aware_datetime(res.start_time) == start_time
79 assert to_aware_datetime(res.end_time) == end_time
80 # assert res.timezone == "UTC"
81 assert res.start_time_display
82 assert res.end_time_display
83 assert res.attendance_state == events_pb2.ATTENDANCE_STATE_GOING
84 assert res.organizer
85 assert res.subscriber
86 assert res.going_count == 1
87 assert res.maybe_count == 0
88 assert res.organizer_count == 1
89 assert res.subscriber_count == 1
90 assert res.owner_user_id == user1.id
91 assert not res.owner_community_id
92 assert not res.owner_group_id
93 assert res.thread.thread_id
94 assert res.can_edit
95 assert not res.can_moderate
97 event_id = res.event_id
99 with events_session(token2) as api:
100 res = api.GetEvent(events_pb2.GetEventReq(event_id=event_id))
102 assert res.is_next
103 assert res.title == "Dummy Title"
104 assert res.slug == "dummy-title"
105 assert res.content == "Dummy content."
106 assert not res.photo_url
107 assert res.WhichOneof("mode") == "offline_information"
108 assert res.offline_information.lat == 0.1
109 assert res.offline_information.lng == 0.2
110 assert res.offline_information.address == "Near Null Island"
111 assert time_before <= to_aware_datetime(res.created) <= now()
112 assert time_before <= to_aware_datetime(res.last_edited) <= now()
113 assert res.creator_user_id == user1.id
114 assert to_aware_datetime(res.start_time) == start_time
115 assert to_aware_datetime(res.end_time) == end_time
116 # assert res.timezone == "UTC"
117 assert res.start_time_display
118 assert res.end_time_display
119 assert res.attendance_state == events_pb2.ATTENDANCE_STATE_NOT_GOING
120 assert not res.organizer
121 assert not res.subscriber
122 assert res.going_count == 1
123 assert res.maybe_count == 0
124 assert res.organizer_count == 1
125 assert res.subscriber_count == 1
126 assert res.owner_user_id == user1.id
127 assert not res.owner_community_id
128 assert not res.owner_group_id
129 assert res.thread.thread_id
130 assert res.can_edit
131 assert res.can_moderate
133 with events_session(token3) as api:
134 res = api.GetEvent(events_pb2.GetEventReq(event_id=event_id))
136 assert res.is_next
137 assert res.title == "Dummy Title"
138 assert res.slug == "dummy-title"
139 assert res.content == "Dummy content."
140 assert not res.photo_url
141 assert res.WhichOneof("mode") == "offline_information"
142 assert res.offline_information.lat == 0.1
143 assert res.offline_information.lng == 0.2
144 assert res.offline_information.address == "Near Null Island"
145 assert time_before <= to_aware_datetime(res.created) <= now()
146 assert time_before <= to_aware_datetime(res.last_edited) <= now()
147 assert res.creator_user_id == user1.id
148 assert to_aware_datetime(res.start_time) == start_time
149 assert to_aware_datetime(res.end_time) == end_time
150 # assert res.timezone == "UTC"
151 assert res.start_time_display
152 assert res.end_time_display
153 assert res.attendance_state == events_pb2.ATTENDANCE_STATE_NOT_GOING
154 assert not res.organizer
155 assert not res.subscriber
156 assert res.going_count == 1
157 assert res.maybe_count == 0
158 assert res.organizer_count == 1
159 assert res.subscriber_count == 1
160 assert res.owner_user_id == user1.id
161 assert not res.owner_community_id
162 assert not res.owner_group_id
163 assert res.thread.thread_id
164 assert not res.can_edit
165 assert not res.can_moderate
167 with events_session(token1) as api:
168 # online only event
169 res = api.CreateEvent(
170 events_pb2.CreateEventReq(
171 title="Dummy Title",
172 content="Dummy content.",
173 photo_key=None,
174 online_information=events_pb2.OnlineEventInformation(
175 link="https://couchers.org/meet/",
176 ),
177 parent_community_id=c_id,
178 start_time=Timestamp_from_datetime(start_time),
179 end_time=Timestamp_from_datetime(end_time),
180 timezone="UTC",
181 )
182 )
184 assert res.is_next
185 assert res.title == "Dummy Title"
186 assert res.slug == "dummy-title"
187 assert res.content == "Dummy content."
188 assert not res.photo_url
189 assert res.WhichOneof("mode") == "online_information"
190 assert res.online_information.link == "https://couchers.org/meet/"
191 assert time_before <= to_aware_datetime(res.created) <= now()
192 assert time_before <= to_aware_datetime(res.last_edited) <= now()
193 assert res.creator_user_id == user1.id
194 assert to_aware_datetime(res.start_time) == start_time
195 assert to_aware_datetime(res.end_time) == end_time
196 # assert res.timezone == "UTC"
197 assert res.start_time_display
198 assert res.end_time_display
199 assert res.attendance_state == events_pb2.ATTENDANCE_STATE_GOING
200 assert res.organizer
201 assert res.subscriber
202 assert res.going_count == 1
203 assert res.maybe_count == 0
204 assert res.organizer_count == 1
205 assert res.subscriber_count == 1
206 assert res.owner_user_id == user1.id
207 assert not res.owner_community_id
208 assert not res.owner_group_id
209 assert res.thread.thread_id
210 assert res.can_edit
211 assert not res.can_moderate
213 event_id = res.event_id
215 with events_session(token2) as api:
216 res = api.GetEvent(events_pb2.GetEventReq(event_id=event_id))
218 assert res.is_next
219 assert res.title == "Dummy Title"
220 assert res.slug == "dummy-title"
221 assert res.content == "Dummy content."
222 assert not res.photo_url
223 assert res.WhichOneof("mode") == "online_information"
224 assert res.online_information.link == "https://couchers.org/meet/"
225 assert time_before <= to_aware_datetime(res.created) <= now()
226 assert time_before <= to_aware_datetime(res.last_edited) <= now()
227 assert res.creator_user_id == user1.id
228 assert to_aware_datetime(res.start_time) == start_time
229 assert to_aware_datetime(res.end_time) == end_time
230 # assert res.timezone == "UTC"
231 assert res.start_time_display
232 assert res.end_time_display
233 assert res.attendance_state == events_pb2.ATTENDANCE_STATE_NOT_GOING
234 assert not res.organizer
235 assert not res.subscriber
236 assert res.going_count == 1
237 assert res.maybe_count == 0
238 assert res.organizer_count == 1
239 assert res.subscriber_count == 1
240 assert res.owner_user_id == user1.id
241 assert not res.owner_community_id
242 assert not res.owner_group_id
243 assert res.thread.thread_id
244 assert res.can_edit
245 assert res.can_moderate
247 with events_session(token3) as api:
248 res = api.GetEvent(events_pb2.GetEventReq(event_id=event_id))
250 assert res.is_next
251 assert res.title == "Dummy Title"
252 assert res.slug == "dummy-title"
253 assert res.content == "Dummy content."
254 assert not res.photo_url
255 assert res.WhichOneof("mode") == "online_information"
256 assert res.online_information.link == "https://couchers.org/meet/"
257 assert time_before <= to_aware_datetime(res.created) <= now()
258 assert time_before <= to_aware_datetime(res.last_edited) <= now()
259 assert res.creator_user_id == user1.id
260 assert to_aware_datetime(res.start_time) == start_time
261 assert to_aware_datetime(res.end_time) == end_time
262 # assert res.timezone == "UTC"
263 assert res.start_time_display
264 assert res.end_time_display
265 assert res.attendance_state == events_pb2.ATTENDANCE_STATE_NOT_GOING
266 assert not res.organizer
267 assert not res.subscriber
268 assert res.going_count == 1
269 assert res.maybe_count == 0
270 assert res.organizer_count == 1
271 assert res.subscriber_count == 1
272 assert res.owner_user_id == user1.id
273 assert not res.owner_community_id
274 assert not res.owner_group_id
275 assert res.thread.thread_id
276 assert not res.can_edit
277 assert not res.can_moderate
279 with events_session(token1) as api:
280 with pytest.raises(grpc.RpcError) as e:
281 api.CreateEvent(
282 events_pb2.CreateEventReq(
283 title="Dummy Title",
284 content="Dummy content.",
285 photo_key=None,
286 online_information=events_pb2.OnlineEventInformation(
287 link="https://couchers.org/meet/",
288 ),
289 start_time=Timestamp_from_datetime(start_time),
290 end_time=Timestamp_from_datetime(end_time),
291 timezone="UTC",
292 )
293 )
294 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
295 assert e.value.details() == "The online event is missing a parent community."
297 with pytest.raises(grpc.RpcError) as e:
298 api.CreateEvent(
299 events_pb2.CreateEventReq(
300 # title="Dummy Title",
301 content="Dummy content.",
302 photo_key=None,
303 offline_information=events_pb2.OfflineEventInformation(
304 address="Near Null Island",
305 lat=0.1,
306 lng=0.1,
307 ),
308 start_time=Timestamp_from_datetime(start_time),
309 end_time=Timestamp_from_datetime(end_time),
310 timezone="UTC",
311 )
312 )
313 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
314 assert e.value.details() == "Missing event title."
316 with pytest.raises(grpc.RpcError) as e:
317 api.CreateEvent(
318 events_pb2.CreateEventReq(
319 title="Dummy Title",
320 # content="Dummy content.",
321 photo_key=None,
322 offline_information=events_pb2.OfflineEventInformation(
323 address="Near Null Island",
324 lat=0.1,
325 lng=0.1,
326 ),
327 start_time=Timestamp_from_datetime(start_time),
328 end_time=Timestamp_from_datetime(end_time),
329 timezone="UTC",
330 )
331 )
332 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
333 assert e.value.details() == "Missing event content."
335 with pytest.raises(grpc.RpcError) as e:
336 api.CreateEvent(
337 events_pb2.CreateEventReq(
338 title="Dummy Title",
339 content="Dummy content.",
340 photo_key="nonexistent",
341 offline_information=events_pb2.OfflineEventInformation(
342 address="Near Null Island",
343 lat=0.1,
344 lng=0.1,
345 ),
346 start_time=Timestamp_from_datetime(start_time),
347 end_time=Timestamp_from_datetime(end_time),
348 timezone="UTC",
349 )
350 )
351 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
352 assert e.value.details() == "Photo not found."
354 with pytest.raises(grpc.RpcError) as e:
355 api.CreateEvent(
356 events_pb2.CreateEventReq(
357 title="Dummy Title",
358 content="Dummy content.",
359 photo_key=None,
360 offline_information=events_pb2.OfflineEventInformation(
361 address="Near Null Island",
362 ),
363 start_time=Timestamp_from_datetime(start_time),
364 end_time=Timestamp_from_datetime(end_time),
365 timezone="UTC",
366 )
367 )
368 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
369 assert e.value.details() == "Missing event address or location."
371 with pytest.raises(grpc.RpcError) as e:
372 api.CreateEvent(
373 events_pb2.CreateEventReq(
374 title="Dummy Title",
375 content="Dummy content.",
376 photo_key=None,
377 offline_information=events_pb2.OfflineEventInformation(
378 lat=0.1,
379 lng=0.1,
380 ),
381 start_time=Timestamp_from_datetime(start_time),
382 end_time=Timestamp_from_datetime(end_time),
383 timezone="UTC",
384 )
385 )
386 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
387 assert e.value.details() == "Missing event address or location."
389 with pytest.raises(grpc.RpcError) as e:
390 api.CreateEvent(
391 events_pb2.CreateEventReq(
392 title="Dummy Title",
393 content="Dummy content.",
394 photo_key=None,
395 online_information=events_pb2.OnlineEventInformation(),
396 start_time=Timestamp_from_datetime(start_time),
397 end_time=Timestamp_from_datetime(end_time),
398 timezone="UTC",
399 )
400 )
401 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
402 assert e.value.details() == "An online-only event requires a link."
404 with pytest.raises(grpc.RpcError) as e:
405 api.CreateEvent(
406 events_pb2.CreateEventReq(
407 title="Dummy Title",
408 content="Dummy content.",
409 parent_community_id=c_id,
410 online_information=events_pb2.OnlineEventInformation(
411 link="https://couchers.org/meet/",
412 ),
413 start_time=Timestamp_from_datetime(now() - timedelta(hours=2)),
414 end_time=Timestamp_from_datetime(end_time),
415 timezone="UTC",
416 )
417 )
418 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
419 assert e.value.details() == "The event must be in the future."
421 with pytest.raises(grpc.RpcError) as e:
422 api.CreateEvent(
423 events_pb2.CreateEventReq(
424 title="Dummy Title",
425 content="Dummy content.",
426 parent_community_id=c_id,
427 online_information=events_pb2.OnlineEventInformation(
428 link="https://couchers.org/meet/",
429 ),
430 start_time=Timestamp_from_datetime(end_time),
431 end_time=Timestamp_from_datetime(start_time),
432 timezone="UTC",
433 )
434 )
435 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
436 assert e.value.details() == "The event must end after it starts."
438 with pytest.raises(grpc.RpcError) as e:
439 api.CreateEvent(
440 events_pb2.CreateEventReq(
441 title="Dummy Title",
442 content="Dummy content.",
443 parent_community_id=c_id,
444 online_information=events_pb2.OnlineEventInformation(
445 link="https://couchers.org/meet/",
446 ),
447 start_time=Timestamp_from_datetime(now() + timedelta(days=500, hours=2)),
448 end_time=Timestamp_from_datetime(now() + timedelta(days=500, hours=5)),
449 timezone="UTC",
450 )
451 )
452 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
453 assert e.value.details() == "The event needs to start within the next year."
455 with pytest.raises(grpc.RpcError) as e:
456 api.CreateEvent(
457 events_pb2.CreateEventReq(
458 title="Dummy Title",
459 content="Dummy content.",
460 parent_community_id=c_id,
461 online_information=events_pb2.OnlineEventInformation(
462 link="https://couchers.org/meet/",
463 ),
464 start_time=Timestamp_from_datetime(start_time),
465 end_time=Timestamp_from_datetime(now() + timedelta(days=100)),
466 timezone="UTC",
467 )
468 )
469 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
470 assert e.value.details() == "Events cannot last longer than 7 days."
473def test_CreateEvent_incomplete_profile(db):
474 user1, token1 = generate_user(complete_profile=False)
475 user2, token2 = generate_user()
477 with session_scope() as session:
478 c_id = create_community(session, 0, 2, "Community", [user2], [], None).id
480 start_time = now() + timedelta(hours=2)
481 end_time = start_time + timedelta(hours=3)
483 with events_session(token1) as api:
484 with pytest.raises(grpc.RpcError) as e:
485 api.CreateEvent(
486 events_pb2.CreateEventReq(
487 title="Dummy Title",
488 content="Dummy content.",
489 photo_key=None,
490 offline_information=events_pb2.OfflineEventInformation(
491 address="Near Null Island",
492 lat=0.1,
493 lng=0.2,
494 ),
495 start_time=Timestamp_from_datetime(start_time),
496 end_time=Timestamp_from_datetime(end_time),
497 timezone="UTC",
498 )
499 )
500 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
501 assert e.value.details() == "You have to complete your profile before you can create an event."
504def test_ScheduleEvent(db):
505 # test cases:
506 # can schedule a new event occurrence
508 user, token = generate_user()
510 with session_scope() as session:
511 c_id = create_community(session, 0, 2, "Community", [user], [], None).id
513 time_before = now()
514 start_time = now() + timedelta(hours=2)
515 end_time = start_time + timedelta(hours=3)
517 with events_session(token) as api:
518 res = api.CreateEvent(
519 events_pb2.CreateEventReq(
520 title="Dummy Title",
521 content="Dummy content.",
522 parent_community_id=c_id,
523 online_information=events_pb2.OnlineEventInformation(
524 link="https://couchers.org/meet/",
525 ),
526 start_time=Timestamp_from_datetime(start_time),
527 end_time=Timestamp_from_datetime(end_time),
528 timezone="UTC",
529 )
530 )
532 new_start_time = now() + timedelta(hours=6)
533 new_end_time = new_start_time + timedelta(hours=2)
535 res = api.ScheduleEvent(
536 events_pb2.ScheduleEventReq(
537 event_id=res.event_id,
538 content="New event occurrence",
539 offline_information=events_pb2.OfflineEventInformation(
540 address="A bit further but still near Null Island",
541 lat=0.3,
542 lng=0.2,
543 ),
544 start_time=Timestamp_from_datetime(new_start_time),
545 end_time=Timestamp_from_datetime(new_end_time),
546 timezone="UTC",
547 )
548 )
550 res = api.GetEvent(events_pb2.GetEventReq(event_id=res.event_id))
552 assert not res.is_next
553 assert res.title == "Dummy Title"
554 assert res.slug == "dummy-title"
555 assert res.content == "New event occurrence"
556 assert not res.photo_url
557 assert res.WhichOneof("mode") == "offline_information"
558 assert res.offline_information.lat == 0.3
559 assert res.offline_information.lng == 0.2
560 assert res.offline_information.address == "A bit further but still near Null Island"
561 assert time_before <= to_aware_datetime(res.created) <= now()
562 assert time_before <= to_aware_datetime(res.last_edited) <= now()
563 assert res.creator_user_id == user.id
564 assert to_aware_datetime(res.start_time) == new_start_time
565 assert to_aware_datetime(res.end_time) == new_end_time
566 # assert res.timezone == "UTC"
567 assert res.start_time_display
568 assert res.end_time_display
569 assert res.attendance_state == events_pb2.ATTENDANCE_STATE_GOING
570 assert res.organizer
571 assert res.subscriber
572 assert res.going_count == 1
573 assert res.maybe_count == 0
574 assert res.organizer_count == 1
575 assert res.subscriber_count == 1
576 assert res.owner_user_id == user.id
577 assert not res.owner_community_id
578 assert not res.owner_group_id
579 assert res.thread.thread_id
580 assert res.can_edit
581 assert res.can_moderate
584def test_cannot_overlap_occurrences_schedule(db):
585 user, token = generate_user()
587 with session_scope() as session:
588 c_id = create_community(session, 0, 2, "Community", [user], [], None).id
590 start = now()
592 with events_session(token) as api:
593 res = api.CreateEvent(
594 events_pb2.CreateEventReq(
595 title="Dummy Title",
596 content="Dummy content.",
597 parent_community_id=c_id,
598 online_information=events_pb2.OnlineEventInformation(
599 link="https://couchers.org/meet/",
600 ),
601 start_time=Timestamp_from_datetime(start + timedelta(hours=1)),
602 end_time=Timestamp_from_datetime(start + timedelta(hours=3)),
603 timezone="UTC",
604 )
605 )
607 with pytest.raises(grpc.RpcError) as e:
608 api.ScheduleEvent(
609 events_pb2.ScheduleEventReq(
610 event_id=res.event_id,
611 content="New event occurrence",
612 offline_information=events_pb2.OfflineEventInformation(
613 address="A bit further but still near Null Island",
614 lat=0.3,
615 lng=0.2,
616 ),
617 start_time=Timestamp_from_datetime(start + timedelta(hours=2)),
618 end_time=Timestamp_from_datetime(start + timedelta(hours=6)),
619 timezone="UTC",
620 )
621 )
622 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
623 assert e.value.details() == "An event cannot have overlapping occurrences."
626def test_cannot_overlap_occurrences_update(db):
627 user, token = generate_user()
629 with session_scope() as session:
630 c_id = create_community(session, 0, 2, "Community", [user], [], None).id
632 start = now()
634 with events_session(token) as api:
635 res = api.CreateEvent(
636 events_pb2.CreateEventReq(
637 title="Dummy Title",
638 content="Dummy content.",
639 parent_community_id=c_id,
640 online_information=events_pb2.OnlineEventInformation(
641 link="https://couchers.org/meet/",
642 ),
643 start_time=Timestamp_from_datetime(start + timedelta(hours=1)),
644 end_time=Timestamp_from_datetime(start + timedelta(hours=3)),
645 timezone="UTC",
646 )
647 )
649 event_id = api.ScheduleEvent(
650 events_pb2.ScheduleEventReq(
651 event_id=res.event_id,
652 content="New event occurrence",
653 offline_information=events_pb2.OfflineEventInformation(
654 address="A bit further but still near Null Island",
655 lat=0.3,
656 lng=0.2,
657 ),
658 start_time=Timestamp_from_datetime(start + timedelta(hours=4)),
659 end_time=Timestamp_from_datetime(start + timedelta(hours=6)),
660 timezone="UTC",
661 )
662 ).event_id
664 # can overlap with this current existing occurrence
665 api.UpdateEvent(
666 events_pb2.UpdateEventReq(
667 event_id=event_id,
668 start_time=Timestamp_from_datetime(start + timedelta(hours=5)),
669 end_time=Timestamp_from_datetime(start + timedelta(hours=6)),
670 )
671 )
673 with pytest.raises(grpc.RpcError) as e:
674 api.UpdateEvent(
675 events_pb2.UpdateEventReq(
676 event_id=event_id,
677 start_time=Timestamp_from_datetime(start + timedelta(hours=2)),
678 end_time=Timestamp_from_datetime(start + timedelta(hours=4)),
679 )
680 )
681 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
682 assert e.value.details() == "An event cannot have overlapping occurrences."
685def test_UpdateEvent_single(db):
686 # test cases:
687 # owner can update
688 # community owner can update
689 # can't mess up online/in person dichotomy
690 # notifies attendees
692 # event creator
693 user1, token1 = generate_user()
694 # community moderator
695 user2, token2 = generate_user()
696 # third parties
697 user3, token3 = generate_user()
698 user4, token4 = generate_user()
699 user5, token5 = generate_user()
700 user6, token6 = generate_user()
702 with session_scope() as session:
703 c_id = create_community(session, 0, 2, "Community", [user2], [], None).id
705 time_before = now()
706 start_time = now() + timedelta(hours=2)
707 end_time = start_time + timedelta(hours=3)
709 with events_session(token1) as api:
710 res = api.CreateEvent(
711 events_pb2.CreateEventReq(
712 title="Dummy Title",
713 content="Dummy content.",
714 offline_information=events_pb2.OfflineEventInformation(
715 address="Near Null Island",
716 lat=0.1,
717 lng=0.2,
718 ),
719 start_time=Timestamp_from_datetime(start_time),
720 end_time=Timestamp_from_datetime(end_time),
721 timezone="UTC",
722 )
723 )
725 event_id = res.event_id
727 with events_session(token4) as api:
728 api.SetEventSubscription(events_pb2.SetEventSubscriptionReq(event_id=event_id, subscribe=True))
730 with events_session(token5) as api:
731 api.SetEventAttendance(
732 events_pb2.SetEventAttendanceReq(event_id=event_id, attendance_state=events_pb2.ATTENDANCE_STATE_GOING)
733 )
735 with events_session(token6) as api:
736 api.SetEventSubscription(events_pb2.SetEventSubscriptionReq(event_id=event_id, subscribe=True))
737 api.SetEventAttendance(
738 events_pb2.SetEventAttendanceReq(event_id=event_id, attendance_state=events_pb2.ATTENDANCE_STATE_MAYBE)
739 )
741 time_before_update = now()
743 with events_session(token1) as api:
744 res = api.UpdateEvent(
745 events_pb2.UpdateEventReq(
746 event_id=event_id,
747 )
748 )
750 with events_session(token1) as api:
751 res = api.GetEvent(events_pb2.GetEventReq(event_id=event_id))
753 assert res.is_next
754 assert res.title == "Dummy Title"
755 assert res.slug == "dummy-title"
756 assert res.content == "Dummy content."
757 assert not res.photo_url
758 assert res.WhichOneof("mode") == "offline_information"
759 assert res.offline_information.lat == 0.1
760 assert res.offline_information.lng == 0.2
761 assert res.offline_information.address == "Near Null Island"
762 assert time_before <= to_aware_datetime(res.created) <= time_before_update
763 assert time_before_update <= to_aware_datetime(res.last_edited) <= now()
764 assert res.creator_user_id == user1.id
765 assert to_aware_datetime(res.start_time) == start_time
766 assert to_aware_datetime(res.end_time) == end_time
767 # assert res.timezone == "UTC"
768 assert res.start_time_display
769 assert res.end_time_display
770 assert res.attendance_state == events_pb2.ATTENDANCE_STATE_GOING
771 assert res.organizer
772 assert res.subscriber
773 assert res.going_count == 2
774 assert res.maybe_count == 1
775 assert res.organizer_count == 1
776 assert res.subscriber_count == 3
777 assert res.owner_user_id == user1.id
778 assert not res.owner_community_id
779 assert not res.owner_group_id
780 assert res.thread.thread_id
781 assert res.can_edit
782 assert not res.can_moderate
784 with events_session(token2) as api:
785 res = api.GetEvent(events_pb2.GetEventReq(event_id=event_id))
787 assert res.is_next
788 assert res.title == "Dummy Title"
789 assert res.slug == "dummy-title"
790 assert res.content == "Dummy content."
791 assert not res.photo_url
792 assert res.WhichOneof("mode") == "offline_information"
793 assert res.offline_information.lat == 0.1
794 assert res.offline_information.lng == 0.2
795 assert res.offline_information.address == "Near Null Island"
796 assert time_before <= to_aware_datetime(res.created) <= time_before_update
797 assert time_before_update <= to_aware_datetime(res.last_edited) <= now()
798 assert res.creator_user_id == user1.id
799 assert to_aware_datetime(res.start_time) == start_time
800 assert to_aware_datetime(res.end_time) == end_time
801 # assert res.timezone == "UTC"
802 assert res.start_time_display
803 assert res.end_time_display
804 assert res.attendance_state == events_pb2.ATTENDANCE_STATE_NOT_GOING
805 assert not res.organizer
806 assert not res.subscriber
807 assert res.going_count == 2
808 assert res.maybe_count == 1
809 assert res.organizer_count == 1
810 assert res.subscriber_count == 3
811 assert res.owner_user_id == user1.id
812 assert not res.owner_community_id
813 assert not res.owner_group_id
814 assert res.thread.thread_id
815 assert res.can_edit
816 assert res.can_moderate
818 with events_session(token3) as api:
819 res = api.GetEvent(events_pb2.GetEventReq(event_id=event_id))
821 assert res.is_next
822 assert res.title == "Dummy Title"
823 assert res.slug == "dummy-title"
824 assert res.content == "Dummy content."
825 assert not res.photo_url
826 assert res.WhichOneof("mode") == "offline_information"
827 assert res.offline_information.lat == 0.1
828 assert res.offline_information.lng == 0.2
829 assert res.offline_information.address == "Near Null Island"
830 assert time_before <= to_aware_datetime(res.created) <= time_before_update
831 assert time_before_update <= to_aware_datetime(res.last_edited) <= now()
832 assert res.creator_user_id == user1.id
833 assert to_aware_datetime(res.start_time) == start_time
834 assert to_aware_datetime(res.end_time) == end_time
835 # assert res.timezone == "UTC"
836 assert res.start_time_display
837 assert res.end_time_display
838 assert res.attendance_state == events_pb2.ATTENDANCE_STATE_NOT_GOING
839 assert not res.organizer
840 assert not res.subscriber
841 assert res.going_count == 2
842 assert res.maybe_count == 1
843 assert res.organizer_count == 1
844 assert res.subscriber_count == 3
845 assert res.owner_user_id == user1.id
846 assert not res.owner_community_id
847 assert not res.owner_group_id
848 assert res.thread.thread_id
849 assert not res.can_edit
850 assert not res.can_moderate
852 with events_session(token1) as api:
853 res = api.UpdateEvent(
854 events_pb2.UpdateEventReq(
855 event_id=event_id,
856 title=wrappers_pb2.StringValue(value="Dummy Title"),
857 content=wrappers_pb2.StringValue(value="Dummy content."),
858 online_information=events_pb2.OnlineEventInformation(link="https://couchers.org/meet/"),
859 start_time=Timestamp_from_datetime(start_time),
860 end_time=Timestamp_from_datetime(end_time),
861 timezone=wrappers_pb2.StringValue(value="UTC"),
862 )
863 )
865 assert res.is_next
866 assert res.title == "Dummy Title"
867 assert res.slug == "dummy-title"
868 assert res.content == "Dummy content."
869 assert not res.photo_url
870 assert res.WhichOneof("mode") == "online_information"
871 assert res.online_information.link == "https://couchers.org/meet/"
872 assert time_before <= to_aware_datetime(res.created) <= time_before_update
873 assert time_before_update <= to_aware_datetime(res.last_edited) <= now()
874 assert res.creator_user_id == user1.id
875 assert to_aware_datetime(res.start_time) == start_time
876 assert to_aware_datetime(res.end_time) == end_time
877 # assert res.timezone == "UTC"
878 assert res.start_time_display
879 assert res.end_time_display
880 assert res.attendance_state == events_pb2.ATTENDANCE_STATE_GOING
881 assert res.organizer
882 assert res.subscriber
883 assert res.going_count == 2
884 assert res.maybe_count == 1
885 assert res.organizer_count == 1
886 assert res.subscriber_count == 3
887 assert res.owner_user_id == user1.id
888 assert not res.owner_community_id
889 assert not res.owner_group_id
890 assert res.thread.thread_id
891 assert res.can_edit
892 assert not res.can_moderate
894 event_id = res.event_id
896 with events_session(token2) as api:
897 res = api.GetEvent(events_pb2.GetEventReq(event_id=event_id))
899 assert res.is_next
900 assert res.title == "Dummy Title"
901 assert res.slug == "dummy-title"
902 assert res.content == "Dummy content."
903 assert not res.photo_url
904 assert res.WhichOneof("mode") == "online_information"
905 assert res.online_information.link == "https://couchers.org/meet/"
906 assert time_before <= to_aware_datetime(res.created) <= time_before_update
907 assert time_before_update <= to_aware_datetime(res.last_edited) <= now()
908 assert res.creator_user_id == user1.id
909 assert to_aware_datetime(res.start_time) == start_time
910 assert to_aware_datetime(res.end_time) == end_time
911 # assert res.timezone == "UTC"
912 assert res.start_time_display
913 assert res.end_time_display
914 assert res.attendance_state == events_pb2.ATTENDANCE_STATE_NOT_GOING
915 assert not res.organizer
916 assert not res.subscriber
917 assert res.going_count == 2
918 assert res.maybe_count == 1
919 assert res.organizer_count == 1
920 assert res.subscriber_count == 3
921 assert res.owner_user_id == user1.id
922 assert not res.owner_community_id
923 assert not res.owner_group_id
924 assert res.thread.thread_id
925 assert res.can_edit
926 assert res.can_moderate
928 with events_session(token3) as api:
929 res = api.GetEvent(events_pb2.GetEventReq(event_id=event_id))
931 assert res.is_next
932 assert res.title == "Dummy Title"
933 assert res.slug == "dummy-title"
934 assert res.content == "Dummy content."
935 assert not res.photo_url
936 assert res.WhichOneof("mode") == "online_information"
937 assert res.online_information.link == "https://couchers.org/meet/"
938 assert time_before <= to_aware_datetime(res.created) <= time_before_update
939 assert time_before_update <= to_aware_datetime(res.last_edited) <= now()
940 assert res.creator_user_id == user1.id
941 assert to_aware_datetime(res.start_time) == start_time
942 assert to_aware_datetime(res.end_time) == end_time
943 # assert res.timezone == "UTC"
944 assert res.start_time_display
945 assert res.end_time_display
946 assert res.attendance_state == events_pb2.ATTENDANCE_STATE_NOT_GOING
947 assert not res.organizer
948 assert not res.subscriber
949 assert res.going_count == 2
950 assert res.maybe_count == 1
951 assert res.organizer_count == 1
952 assert res.subscriber_count == 3
953 assert res.owner_user_id == user1.id
954 assert not res.owner_community_id
955 assert not res.owner_group_id
956 assert res.thread.thread_id
957 assert not res.can_edit
958 assert not res.can_moderate
960 with events_session(token1) as api:
961 res = api.UpdateEvent(
962 events_pb2.UpdateEventReq(
963 event_id=event_id,
964 offline_information=events_pb2.OfflineEventInformation(
965 address="Near Null Island",
966 lat=0.1,
967 lng=0.2,
968 ),
969 )
970 )
972 with events_session(token3) as api:
973 res = api.GetEvent(events_pb2.GetEventReq(event_id=event_id))
975 assert res.WhichOneof("mode") == "offline_information"
976 assert res.offline_information.address == "Near Null Island"
977 assert res.offline_information.lat == 0.1
978 assert res.offline_information.lng == 0.2
981def test_UpdateEvent_all(db):
982 # event creator
983 user1, token1 = generate_user()
984 # community moderator
985 user2, token2 = generate_user()
986 # third parties
987 user3, token3 = generate_user()
988 user4, token4 = generate_user()
989 user5, token5 = generate_user()
990 user6, token6 = generate_user()
992 with session_scope() as session:
993 c_id = create_community(session, 0, 2, "Community", [user2], [], None).id
995 time_before = now()
996 start_time = now() + timedelta(hours=1)
997 end_time = start_time + timedelta(hours=1.5)
999 event_ids = []
1001 with events_session(token1) as api:
1002 res = api.CreateEvent(
1003 events_pb2.CreateEventReq(
1004 title="Dummy Title",
1005 content="0th occurrence",
1006 offline_information=events_pb2.OfflineEventInformation(
1007 address="Near Null Island",
1008 lat=0.1,
1009 lng=0.2,
1010 ),
1011 start_time=Timestamp_from_datetime(start_time),
1012 end_time=Timestamp_from_datetime(end_time),
1013 timezone="UTC",
1014 )
1015 )
1017 event_id = res.event_id
1018 event_ids.append(event_id)
1020 with events_session(token4) as api:
1021 api.SetEventSubscription(events_pb2.SetEventSubscriptionReq(event_id=event_id, subscribe=True))
1023 with events_session(token5) as api:
1024 api.SetEventAttendance(
1025 events_pb2.SetEventAttendanceReq(event_id=event_id, attendance_state=events_pb2.ATTENDANCE_STATE_GOING)
1026 )
1028 with events_session(token6) as api:
1029 api.SetEventSubscription(events_pb2.SetEventSubscriptionReq(event_id=event_id, subscribe=True))
1030 api.SetEventAttendance(
1031 events_pb2.SetEventAttendanceReq(event_id=event_id, attendance_state=events_pb2.ATTENDANCE_STATE_MAYBE)
1032 )
1034 with events_session(token1) as api:
1035 for i in range(5):
1036 res = api.ScheduleEvent(
1037 events_pb2.ScheduleEventReq(
1038 event_id=event_ids[-1],
1039 content=f"{i + 1}th occurrence",
1040 online_information=events_pb2.OnlineEventInformation(
1041 link="https://couchers.org/meet/",
1042 ),
1043 start_time=Timestamp_from_datetime(start_time + timedelta(hours=2 + i)),
1044 end_time=Timestamp_from_datetime(start_time + timedelta(hours=2.5 + i)),
1045 timezone="UTC",
1046 )
1047 )
1049 event_ids.append(res.event_id)
1051 updated_event_id = event_ids[3]
1053 time_before_update = now()
1055 with events_session(token1) as api:
1056 res = api.UpdateEvent(
1057 events_pb2.UpdateEventReq(
1058 event_id=updated_event_id,
1059 title=wrappers_pb2.StringValue(value="New Title"),
1060 content=wrappers_pb2.StringValue(value="New content."),
1061 online_information=events_pb2.OnlineEventInformation(link="https://couchers.org/meet/"),
1062 update_all_future=True,
1063 )
1064 )
1066 time_after_update = now()
1068 with events_session(token2) as api:
1069 for i in range(3):
1070 res = api.GetEvent(events_pb2.GetEventReq(event_id=event_ids[i]))
1071 assert res.content == f"{i}th occurrence"
1072 assert time_before <= to_aware_datetime(res.last_edited) <= time_before_update
1074 for i in range(3, 6):
1075 res = api.GetEvent(events_pb2.GetEventReq(event_id=event_ids[i]))
1076 assert res.content == "New content."
1077 assert time_before_update <= to_aware_datetime(res.last_edited) <= time_after_update
1080def test_GetEvent(db):
1081 # event creator
1082 user1, token1 = generate_user()
1083 # community moderator
1084 user2, token2 = generate_user()
1085 # third parties
1086 user3, token3 = generate_user()
1087 user4, token4 = generate_user()
1088 user5, token5 = generate_user()
1089 user6, token6 = generate_user()
1091 with session_scope() as session:
1092 c_id = create_community(session, 0, 2, "Community", [user2], [], None).id
1094 time_before = now()
1095 start_time = now() + timedelta(hours=2)
1096 end_time = start_time + timedelta(hours=3)
1098 with events_session(token1) as api:
1099 # in person event
1100 res = api.CreateEvent(
1101 events_pb2.CreateEventReq(
1102 title="Dummy Title",
1103 content="Dummy content.",
1104 offline_information=events_pb2.OfflineEventInformation(
1105 address="Near Null Island",
1106 lat=0.1,
1107 lng=0.2,
1108 ),
1109 start_time=Timestamp_from_datetime(start_time),
1110 end_time=Timestamp_from_datetime(end_time),
1111 timezone="UTC",
1112 )
1113 )
1115 event_id = res.event_id
1117 with events_session(token4) as api:
1118 api.SetEventSubscription(events_pb2.SetEventSubscriptionReq(event_id=event_id, subscribe=True))
1120 with events_session(token5) as api:
1121 api.SetEventAttendance(
1122 events_pb2.SetEventAttendanceReq(event_id=event_id, attendance_state=events_pb2.ATTENDANCE_STATE_GOING)
1123 )
1125 with events_session(token6) as api:
1126 api.SetEventSubscription(events_pb2.SetEventSubscriptionReq(event_id=event_id, subscribe=True))
1127 api.SetEventAttendance(
1128 events_pb2.SetEventAttendanceReq(event_id=event_id, attendance_state=events_pb2.ATTENDANCE_STATE_MAYBE)
1129 )
1131 with events_session(token1) as api:
1132 res = api.GetEvent(events_pb2.GetEventReq(event_id=event_id))
1134 assert res.is_next
1135 assert res.title == "Dummy Title"
1136 assert res.slug == "dummy-title"
1137 assert res.content == "Dummy content."
1138 assert not res.photo_url
1139 assert res.WhichOneof("mode") == "offline_information"
1140 assert res.offline_information.lat == 0.1
1141 assert res.offline_information.lng == 0.2
1142 assert res.offline_information.address == "Near Null Island"
1143 assert time_before <= to_aware_datetime(res.created) <= now()
1144 assert time_before <= to_aware_datetime(res.last_edited) <= now()
1145 assert res.creator_user_id == user1.id
1146 assert to_aware_datetime(res.start_time) == start_time
1147 assert to_aware_datetime(res.end_time) == end_time
1148 # assert res.timezone == "UTC"
1149 assert res.start_time_display
1150 assert res.end_time_display
1151 assert res.attendance_state == events_pb2.ATTENDANCE_STATE_GOING
1152 assert res.organizer
1153 assert res.subscriber
1154 assert res.going_count == 2
1155 assert res.maybe_count == 1
1156 assert res.organizer_count == 1
1157 assert res.subscriber_count == 3
1158 assert res.owner_user_id == user1.id
1159 assert not res.owner_community_id
1160 assert not res.owner_group_id
1161 assert res.thread.thread_id
1162 assert res.can_edit
1163 assert not res.can_moderate
1165 with events_session(token2) as api:
1166 res = api.GetEvent(events_pb2.GetEventReq(event_id=event_id))
1168 assert res.is_next
1169 assert res.title == "Dummy Title"
1170 assert res.slug == "dummy-title"
1171 assert res.content == "Dummy content."
1172 assert not res.photo_url
1173 assert res.WhichOneof("mode") == "offline_information"
1174 assert res.offline_information.lat == 0.1
1175 assert res.offline_information.lng == 0.2
1176 assert res.offline_information.address == "Near Null Island"
1177 assert time_before <= to_aware_datetime(res.created) <= now()
1178 assert time_before <= to_aware_datetime(res.last_edited) <= now()
1179 assert res.creator_user_id == user1.id
1180 assert to_aware_datetime(res.start_time) == start_time
1181 assert to_aware_datetime(res.end_time) == end_time
1182 # assert res.timezone == "UTC"
1183 assert res.start_time_display
1184 assert res.end_time_display
1185 assert res.attendance_state == events_pb2.ATTENDANCE_STATE_NOT_GOING
1186 assert not res.organizer
1187 assert not res.subscriber
1188 assert res.going_count == 2
1189 assert res.maybe_count == 1
1190 assert res.organizer_count == 1
1191 assert res.subscriber_count == 3
1192 assert res.owner_user_id == user1.id
1193 assert not res.owner_community_id
1194 assert not res.owner_group_id
1195 assert res.thread.thread_id
1196 assert res.can_edit
1197 assert res.can_moderate
1199 with events_session(token3) as api:
1200 res = api.GetEvent(events_pb2.GetEventReq(event_id=event_id))
1202 assert res.is_next
1203 assert res.title == "Dummy Title"
1204 assert res.slug == "dummy-title"
1205 assert res.content == "Dummy content."
1206 assert not res.photo_url
1207 assert res.WhichOneof("mode") == "offline_information"
1208 assert res.offline_information.lat == 0.1
1209 assert res.offline_information.lng == 0.2
1210 assert res.offline_information.address == "Near Null Island"
1211 assert time_before <= to_aware_datetime(res.created) <= now()
1212 assert time_before <= to_aware_datetime(res.last_edited) <= now()
1213 assert res.creator_user_id == user1.id
1214 assert to_aware_datetime(res.start_time) == start_time
1215 assert to_aware_datetime(res.end_time) == end_time
1216 # assert res.timezone == "UTC"
1217 assert res.start_time_display
1218 assert res.end_time_display
1219 assert res.attendance_state == events_pb2.ATTENDANCE_STATE_NOT_GOING
1220 assert not res.organizer
1221 assert not res.subscriber
1222 assert res.going_count == 2
1223 assert res.maybe_count == 1
1224 assert res.organizer_count == 1
1225 assert res.subscriber_count == 3
1226 assert res.owner_user_id == user1.id
1227 assert not res.owner_community_id
1228 assert not res.owner_group_id
1229 assert res.thread.thread_id
1230 assert not res.can_edit
1231 assert not res.can_moderate
1234def test_CancelEvent(db):
1235 # event creator
1236 user1, token1 = generate_user()
1237 # community moderator
1238 user2, token2 = generate_user()
1239 # third parties
1240 user3, token3 = generate_user()
1241 user4, token4 = generate_user()
1242 user5, token5 = generate_user()
1243 user6, token6 = generate_user()
1245 with session_scope() as session:
1246 c_id = create_community(session, 0, 2, "Community", [user2], [], None).id
1248 start_time = now() + timedelta(hours=2)
1249 end_time = start_time + timedelta(hours=3)
1251 with events_session(token1) as api:
1252 res = api.CreateEvent(
1253 events_pb2.CreateEventReq(
1254 title="Dummy Title",
1255 content="Dummy content.",
1256 offline_information=events_pb2.OfflineEventInformation(
1257 address="Near Null Island",
1258 lat=0.1,
1259 lng=0.2,
1260 ),
1261 start_time=Timestamp_from_datetime(start_time),
1262 end_time=Timestamp_from_datetime(end_time),
1263 timezone="UTC",
1264 )
1265 )
1267 event_id = res.event_id
1269 with events_session(token4) as api:
1270 api.SetEventSubscription(events_pb2.SetEventSubscriptionReq(event_id=event_id, subscribe=True))
1272 with events_session(token5) as api:
1273 api.SetEventAttendance(
1274 events_pb2.SetEventAttendanceReq(event_id=event_id, attendance_state=events_pb2.ATTENDANCE_STATE_GOING)
1275 )
1277 with events_session(token6) as api:
1278 api.SetEventSubscription(events_pb2.SetEventSubscriptionReq(event_id=event_id, subscribe=True))
1279 api.SetEventAttendance(
1280 events_pb2.SetEventAttendanceReq(event_id=event_id, attendance_state=events_pb2.ATTENDANCE_STATE_MAYBE)
1281 )
1283 with events_session(token1) as api:
1284 res = api.CancelEvent(
1285 events_pb2.CancelEventReq(
1286 event_id=event_id,
1287 )
1288 )
1290 with events_session(token1) as api:
1291 res = api.GetEvent(events_pb2.GetEventReq(event_id=event_id))
1292 assert res.is_cancelled
1294 with events_session(token1) as api:
1295 with pytest.raises(grpc.RpcError) as e:
1296 api.UpdateEvent(
1297 events_pb2.UpdateEventReq(
1298 event_id=event_id,
1299 title=wrappers_pb2.StringValue(value="New Title"),
1300 )
1301 )
1302 assert e.value.code() == grpc.StatusCode.PERMISSION_DENIED
1303 assert e.value.details() == "You can't modify, subscribe to, or attend to an event that's been cancelled."
1305 with pytest.raises(grpc.RpcError) as e:
1306 api.InviteEventOrganizer(
1307 events_pb2.InviteEventOrganizerReq(
1308 event_id=event_id,
1309 user_id=user3.id,
1310 )
1311 )
1312 assert e.value.code() == grpc.StatusCode.PERMISSION_DENIED
1313 assert e.value.details() == "You can't modify, subscribe to, or attend to an event that's been cancelled."
1315 with pytest.raises(grpc.RpcError) as e:
1316 api.TransferEvent(events_pb2.TransferEventReq(event_id=event_id, new_owner_community_id=c_id))
1317 assert e.value.code() == grpc.StatusCode.PERMISSION_DENIED
1318 assert e.value.details() == "You can't modify, subscribe to, or attend to an event that's been cancelled."
1320 with events_session(token3) as api:
1321 with pytest.raises(grpc.RpcError) as e:
1322 api.SetEventSubscription(events_pb2.SetEventSubscriptionReq(event_id=event_id, subscribe=True))
1323 assert e.value.code() == grpc.StatusCode.PERMISSION_DENIED
1324 assert e.value.details() == "You can't modify, subscribe to, or attend to an event that's been cancelled."
1326 with pytest.raises(grpc.RpcError) as e:
1327 api.SetEventAttendance(
1328 events_pb2.SetEventAttendanceReq(event_id=event_id, attendance_state=events_pb2.ATTENDANCE_STATE_GOING)
1329 )
1330 assert e.value.code() == grpc.StatusCode.PERMISSION_DENIED
1331 assert e.value.details() == "You can't modify, subscribe to, or attend to an event that's been cancelled."
1333 with events_session(token1) as api:
1334 for include_cancelled in [True, False]:
1335 res = api.ListEventOccurrences(
1336 events_pb2.ListEventOccurrencesReq(
1337 event_id=event_id,
1338 include_cancelled=include_cancelled,
1339 )
1340 )
1341 if include_cancelled:
1342 assert len(res.events) > 0
1343 else:
1344 assert len(res.events) == 0
1346 res = api.ListMyEvents(
1347 events_pb2.ListMyEventsReq(
1348 include_cancelled=include_cancelled,
1349 )
1350 )
1351 if include_cancelled:
1352 assert len(res.events) > 0
1353 else:
1354 assert len(res.events) == 0
1357def test_ListEventAttendees(db):
1358 # event creator
1359 user1, token1 = generate_user()
1360 # others
1361 user2, token2 = generate_user()
1362 user3, token3 = generate_user()
1363 user4, token4 = generate_user()
1364 user5, token5 = generate_user()
1365 user6, token6 = generate_user()
1367 with session_scope() as session:
1368 c_id = create_community(session, 0, 2, "Community", [user1], [], None).id
1370 with events_session(token1) as api:
1371 event_id = api.CreateEvent(
1372 events_pb2.CreateEventReq(
1373 title="Dummy Title",
1374 content="Dummy content.",
1375 offline_information=events_pb2.OfflineEventInformation(
1376 address="Near Null Island",
1377 lat=0.1,
1378 lng=0.2,
1379 ),
1380 start_time=Timestamp_from_datetime(now() + timedelta(hours=2)),
1381 end_time=Timestamp_from_datetime(now() + timedelta(hours=5)),
1382 timezone="UTC",
1383 )
1384 ).event_id
1386 for token in [token2, token3, token4, token5]:
1387 with events_session(token) as api:
1388 api.SetEventAttendance(
1389 events_pb2.SetEventAttendanceReq(event_id=event_id, attendance_state=events_pb2.ATTENDANCE_STATE_GOING)
1390 )
1392 with events_session(token6) as api:
1393 assert api.GetEvent(events_pb2.GetEventReq(event_id=event_id)).going_count == 5
1395 res = api.ListEventAttendees(events_pb2.ListEventAttendeesReq(event_id=event_id, page_size=2))
1396 assert res.attendee_user_ids == [user1.id, user2.id]
1398 res = api.ListEventAttendees(
1399 events_pb2.ListEventAttendeesReq(event_id=event_id, page_size=2, page_token=res.next_page_token)
1400 )
1401 assert res.attendee_user_ids == [user3.id, user4.id]
1403 res = api.ListEventAttendees(
1404 events_pb2.ListEventAttendeesReq(event_id=event_id, page_size=2, page_token=res.next_page_token)
1405 )
1406 assert res.attendee_user_ids == [user5.id]
1407 assert not res.next_page_token
1410def test_ListEventSubscribers(db):
1411 # event creator
1412 user1, token1 = generate_user()
1413 # others
1414 user2, token2 = generate_user()
1415 user3, token3 = generate_user()
1416 user4, token4 = generate_user()
1417 user5, token5 = generate_user()
1418 user6, token6 = generate_user()
1420 with session_scope() as session:
1421 c_id = create_community(session, 0, 2, "Community", [user1], [], None).id
1423 with events_session(token1) as api:
1424 event_id = api.CreateEvent(
1425 events_pb2.CreateEventReq(
1426 title="Dummy Title",
1427 content="Dummy content.",
1428 offline_information=events_pb2.OfflineEventInformation(
1429 address="Near Null Island",
1430 lat=0.1,
1431 lng=0.2,
1432 ),
1433 start_time=Timestamp_from_datetime(now() + timedelta(hours=2)),
1434 end_time=Timestamp_from_datetime(now() + timedelta(hours=5)),
1435 timezone="UTC",
1436 )
1437 ).event_id
1439 for token in [token2, token3, token4, token5]:
1440 with events_session(token) as api:
1441 api.SetEventSubscription(events_pb2.SetEventSubscriptionReq(event_id=event_id, subscribe=True))
1443 with events_session(token6) as api:
1444 assert api.GetEvent(events_pb2.GetEventReq(event_id=event_id)).subscriber_count == 5
1446 res = api.ListEventSubscribers(events_pb2.ListEventSubscribersReq(event_id=event_id, page_size=2))
1447 assert res.subscriber_user_ids == [user1.id, user2.id]
1449 res = api.ListEventSubscribers(
1450 events_pb2.ListEventSubscribersReq(event_id=event_id, page_size=2, page_token=res.next_page_token)
1451 )
1452 assert res.subscriber_user_ids == [user3.id, user4.id]
1454 res = api.ListEventSubscribers(
1455 events_pb2.ListEventSubscribersReq(event_id=event_id, page_size=2, page_token=res.next_page_token)
1456 )
1457 assert res.subscriber_user_ids == [user5.id]
1458 assert not res.next_page_token
1461def test_ListEventOrganizers(db):
1462 # event creator
1463 user1, token1 = generate_user()
1464 # others
1465 user2, token2 = generate_user()
1466 user3, token3 = generate_user()
1467 user4, token4 = generate_user()
1468 user5, token5 = generate_user()
1469 user6, token6 = generate_user()
1471 with session_scope() as session:
1472 c_id = create_community(session, 0, 2, "Community", [user1], [], None).id
1474 with events_session(token1) as api:
1475 event_id = api.CreateEvent(
1476 events_pb2.CreateEventReq(
1477 title="Dummy Title",
1478 content="Dummy content.",
1479 offline_information=events_pb2.OfflineEventInformation(
1480 address="Near Null Island",
1481 lat=0.1,
1482 lng=0.2,
1483 ),
1484 start_time=Timestamp_from_datetime(now() + timedelta(hours=2)),
1485 end_time=Timestamp_from_datetime(now() + timedelta(hours=5)),
1486 timezone="UTC",
1487 )
1488 ).event_id
1490 with events_session(token1) as api:
1491 for user_id in [user2.id, user3.id, user4.id, user5.id]:
1492 api.InviteEventOrganizer(events_pb2.InviteEventOrganizerReq(event_id=event_id, user_id=user_id))
1494 with events_session(token6) as api:
1495 assert api.GetEvent(events_pb2.GetEventReq(event_id=event_id)).organizer_count == 5
1497 res = api.ListEventOrganizers(events_pb2.ListEventOrganizersReq(event_id=event_id, page_size=2))
1498 assert res.organizer_user_ids == [user1.id, user2.id]
1500 res = api.ListEventOrganizers(
1501 events_pb2.ListEventOrganizersReq(event_id=event_id, page_size=2, page_token=res.next_page_token)
1502 )
1503 assert res.organizer_user_ids == [user3.id, user4.id]
1505 res = api.ListEventOrganizers(
1506 events_pb2.ListEventOrganizersReq(event_id=event_id, page_size=2, page_token=res.next_page_token)
1507 )
1508 assert res.organizer_user_ids == [user5.id]
1509 assert not res.next_page_token
1512def test_TransferEvent(db):
1513 user1, token1 = generate_user()
1514 user2, token2 = generate_user()
1515 user3, token3 = generate_user()
1516 user4, token4 = generate_user()
1518 with session_scope() as session:
1519 c = create_community(session, 0, 2, "Community", [user3], [], None)
1520 h = create_group(session, "Group", [user4], [], c)
1521 c_id = c.id
1522 h_id = h.id
1524 with events_session(token1) as api:
1525 event_id = api.CreateEvent(
1526 events_pb2.CreateEventReq(
1527 title="Dummy Title",
1528 content="Dummy content.",
1529 offline_information=events_pb2.OfflineEventInformation(
1530 address="Near Null Island",
1531 lat=0.1,
1532 lng=0.2,
1533 ),
1534 start_time=Timestamp_from_datetime(now() + timedelta(hours=2)),
1535 end_time=Timestamp_from_datetime(now() + timedelta(hours=5)),
1536 timezone="UTC",
1537 )
1538 ).event_id
1540 api.TransferEvent(
1541 events_pb2.TransferEventReq(
1542 event_id=event_id,
1543 new_owner_community_id=c_id,
1544 )
1545 )
1547 # remove ourselves as organizer, otherwise we can still edit it
1548 api.RemoveEventOrganizer(events_pb2.RemoveEventOrganizerReq(event_id=event_id))
1550 with pytest.raises(grpc.RpcError) as e:
1551 api.TransferEvent(
1552 events_pb2.TransferEventReq(
1553 event_id=event_id,
1554 new_owner_group_id=h_id,
1555 )
1556 )
1557 assert e.value.code() == grpc.StatusCode.PERMISSION_DENIED
1558 assert e.value.details() == "You're not allowed to transfer that event."
1560 event_id = api.CreateEvent(
1561 events_pb2.CreateEventReq(
1562 title="Dummy Title",
1563 content="Dummy content.",
1564 offline_information=events_pb2.OfflineEventInformation(
1565 address="Near Null Island",
1566 lat=0.1,
1567 lng=0.2,
1568 ),
1569 start_time=Timestamp_from_datetime(now() + timedelta(hours=2)),
1570 end_time=Timestamp_from_datetime(now() + timedelta(hours=5)),
1571 timezone="UTC",
1572 )
1573 ).event_id
1575 api.TransferEvent(
1576 events_pb2.TransferEventReq(
1577 event_id=event_id,
1578 new_owner_group_id=h_id,
1579 )
1580 )
1582 # remove ourselves as organizer, otherwise we can still edit it
1583 api.RemoveEventOrganizer(events_pb2.RemoveEventOrganizerReq(event_id=event_id))
1585 with pytest.raises(grpc.RpcError) as e:
1586 api.TransferEvent(
1587 events_pb2.TransferEventReq(
1588 event_id=event_id,
1589 new_owner_community_id=c_id,
1590 )
1591 )
1592 assert e.value.code() == grpc.StatusCode.PERMISSION_DENIED
1593 assert e.value.details() == "You're not allowed to transfer that event."
1596def test_SetEventSubscription(db):
1597 user1, token1 = generate_user()
1598 user2, token2 = generate_user()
1600 with session_scope() as session:
1601 c_id = create_community(session, 0, 2, "Community", [user1], [], None).id
1603 with events_session(token1) as api:
1604 event_id = api.CreateEvent(
1605 events_pb2.CreateEventReq(
1606 title="Dummy Title",
1607 content="Dummy content.",
1608 offline_information=events_pb2.OfflineEventInformation(
1609 address="Near Null Island",
1610 lat=0.1,
1611 lng=0.2,
1612 ),
1613 start_time=Timestamp_from_datetime(now() + timedelta(hours=2)),
1614 end_time=Timestamp_from_datetime(now() + timedelta(hours=5)),
1615 timezone="UTC",
1616 )
1617 ).event_id
1619 with events_session(token2) as api:
1620 assert not api.GetEvent(events_pb2.GetEventReq(event_id=event_id)).subscriber
1621 api.SetEventSubscription(events_pb2.SetEventSubscriptionReq(event_id=event_id, subscribe=True))
1622 assert api.GetEvent(events_pb2.GetEventReq(event_id=event_id)).subscriber
1623 api.SetEventSubscription(events_pb2.SetEventSubscriptionReq(event_id=event_id, subscribe=False))
1624 assert not api.GetEvent(events_pb2.GetEventReq(event_id=event_id)).subscriber
1627def test_SetEventAttendance(db):
1628 user1, token1 = generate_user()
1629 user2, token2 = generate_user()
1631 with session_scope() as session:
1632 c_id = create_community(session, 0, 2, "Community", [user1], [], None).id
1634 with events_session(token1) as api:
1635 event_id = api.CreateEvent(
1636 events_pb2.CreateEventReq(
1637 title="Dummy Title",
1638 content="Dummy content.",
1639 offline_information=events_pb2.OfflineEventInformation(
1640 address="Near Null Island",
1641 lat=0.1,
1642 lng=0.2,
1643 ),
1644 start_time=Timestamp_from_datetime(now() + timedelta(hours=2)),
1645 end_time=Timestamp_from_datetime(now() + timedelta(hours=5)),
1646 timezone="UTC",
1647 )
1648 ).event_id
1650 with events_session(token2) as api:
1651 assert (
1652 api.GetEvent(events_pb2.GetEventReq(event_id=event_id)).attendance_state
1653 == events_pb2.ATTENDANCE_STATE_NOT_GOING
1654 )
1655 api.SetEventAttendance(
1656 events_pb2.SetEventAttendanceReq(event_id=event_id, attendance_state=events_pb2.ATTENDANCE_STATE_GOING)
1657 )
1658 assert (
1659 api.GetEvent(events_pb2.GetEventReq(event_id=event_id)).attendance_state
1660 == events_pb2.ATTENDANCE_STATE_GOING
1661 )
1662 api.SetEventAttendance(
1663 events_pb2.SetEventAttendanceReq(event_id=event_id, attendance_state=events_pb2.ATTENDANCE_STATE_MAYBE)
1664 )
1665 assert (
1666 api.GetEvent(events_pb2.GetEventReq(event_id=event_id)).attendance_state
1667 == events_pb2.ATTENDANCE_STATE_MAYBE
1668 )
1669 api.SetEventAttendance(
1670 events_pb2.SetEventAttendanceReq(event_id=event_id, attendance_state=events_pb2.ATTENDANCE_STATE_NOT_GOING)
1671 )
1672 assert (
1673 api.GetEvent(events_pb2.GetEventReq(event_id=event_id)).attendance_state
1674 == events_pb2.ATTENDANCE_STATE_NOT_GOING
1675 )
1678def test_InviteEventOrganizer(db):
1679 user1, token1 = generate_user()
1680 user2, token2 = generate_user()
1682 with session_scope() as session:
1683 c_id = create_community(session, 0, 2, "Community", [user1], [], None).id
1685 with events_session(token1) as api:
1686 event_id = api.CreateEvent(
1687 events_pb2.CreateEventReq(
1688 title="Dummy Title",
1689 content="Dummy content.",
1690 offline_information=events_pb2.OfflineEventInformation(
1691 address="Near Null Island",
1692 lat=0.1,
1693 lng=0.2,
1694 ),
1695 start_time=Timestamp_from_datetime(now() + timedelta(hours=2)),
1696 end_time=Timestamp_from_datetime(now() + timedelta(hours=5)),
1697 timezone="UTC",
1698 )
1699 ).event_id
1701 with events_session(token2) as api:
1702 assert not api.GetEvent(events_pb2.GetEventReq(event_id=event_id)).organizer
1704 with pytest.raises(grpc.RpcError) as e:
1705 api.InviteEventOrganizer(events_pb2.InviteEventOrganizerReq(event_id=event_id, user_id=user1.id))
1706 assert e.value.code() == grpc.StatusCode.PERMISSION_DENIED
1707 assert e.value.details() == "You're not allowed to edit that event."
1709 assert not api.GetEvent(events_pb2.GetEventReq(event_id=event_id)).organizer
1711 with events_session(token1) as api:
1712 api.InviteEventOrganizer(events_pb2.InviteEventOrganizerReq(event_id=event_id, user_id=user2.id))
1714 with events_session(token2) as api:
1715 assert api.GetEvent(events_pb2.GetEventReq(event_id=event_id)).organizer
1718def test_ListEventOccurrences(db):
1719 user1, token1 = generate_user()
1720 user2, token2 = generate_user()
1721 user3, token3 = generate_user()
1723 with session_scope() as session:
1724 c_id = create_community(session, 0, 2, "Community", [user2], [], None).id
1726 start = now()
1728 event_ids = []
1730 with events_session(token1) as api:
1731 res = api.CreateEvent(
1732 events_pb2.CreateEventReq(
1733 title="First occurrence",
1734 content="Dummy content.",
1735 parent_community_id=c_id,
1736 online_information=events_pb2.OnlineEventInformation(
1737 link="https://couchers.org/meet/",
1738 ),
1739 start_time=Timestamp_from_datetime(start + timedelta(hours=1)),
1740 end_time=Timestamp_from_datetime(start + timedelta(hours=1.5)),
1741 timezone="UTC",
1742 )
1743 )
1745 event_ids.append(res.event_id)
1747 for i in range(5):
1748 res = api.ScheduleEvent(
1749 events_pb2.ScheduleEventReq(
1750 event_id=event_ids[-1],
1751 content=f"{i}th occurrence",
1752 online_information=events_pb2.OnlineEventInformation(
1753 link="https://couchers.org/meet/",
1754 ),
1755 start_time=Timestamp_from_datetime(start + timedelta(hours=2 + i)),
1756 end_time=Timestamp_from_datetime(start + timedelta(hours=2.5 + i)),
1757 timezone="UTC",
1758 )
1759 )
1761 event_ids.append(res.event_id)
1763 res = api.ListEventOccurrences(events_pb2.ListEventOccurrencesReq(event_id=event_ids[-1], page_size=2))
1764 assert [event.event_id for event in res.events] == event_ids[:2]
1766 res = api.ListEventOccurrences(
1767 events_pb2.ListEventOccurrencesReq(event_id=event_ids[-1], page_size=2, page_token=res.next_page_token)
1768 )
1769 assert [event.event_id for event in res.events] == event_ids[2:4]
1771 res = api.ListEventOccurrences(
1772 events_pb2.ListEventOccurrencesReq(event_id=event_ids[-1], page_size=2, page_token=res.next_page_token)
1773 )
1774 assert [event.event_id for event in res.events] == event_ids[4:6]
1775 assert not res.next_page_token
1778def test_ListMyEvents(db):
1779 user1, token1 = generate_user()
1780 user2, token2 = generate_user()
1781 user3, token3 = generate_user()
1782 user4, token4 = generate_user()
1783 user5, token5 = generate_user()
1785 with session_scope() as session:
1786 # Create global community first (node_id=1), then a child community (node_id=2)
1787 # This allows testing my_communities_exclude_global
1788 global_community = create_community(session, 0, 100, "Global", [user3], [], None)
1789 c_id = global_community.id
1790 child_community = create_community(session, 0, 50, "Child Community", [user3, user4], [], global_community)
1791 c2_id = child_community.id
1793 start = now()
1795 def new_event(hours_from_now: int, community_id: int, online: bool = True) -> events_pb2.CreateEventReq:
1796 if online:
1797 return events_pb2.CreateEventReq(
1798 title="Dummy Online Title",
1799 content="Dummy content.",
1800 online_information=events_pb2.OnlineEventInformation(
1801 link="https://couchers.org/meet/",
1802 ),
1803 parent_community_id=community_id,
1804 timezone="UTC",
1805 start_time=Timestamp_from_datetime(start + timedelta(hours=hours_from_now)),
1806 end_time=Timestamp_from_datetime(start + timedelta(hours=hours_from_now + 0.5)),
1807 )
1808 else:
1809 return events_pb2.CreateEventReq(
1810 title="Dummy Offline Title",
1811 content="Dummy content.",
1812 offline_information=events_pb2.OfflineEventInformation(
1813 address="Near Null Island",
1814 lat=0.1,
1815 lng=0.2,
1816 ),
1817 parent_community_id=community_id,
1818 timezone="UTC",
1819 start_time=Timestamp_from_datetime(start + timedelta(hours=hours_from_now)),
1820 end_time=Timestamp_from_datetime(start + timedelta(hours=hours_from_now + 0.5)),
1821 )
1823 with events_session(token1) as api:
1824 e2 = api.CreateEvent(new_event(2, c_id, True)).event_id
1826 with events_session(token2) as api:
1827 e1 = api.CreateEvent(new_event(1, c_id, False)).event_id
1829 with events_session(token1) as api:
1830 e3 = api.CreateEvent(new_event(3, c_id, False)).event_id
1832 with events_session(token2) as api:
1833 e5 = api.CreateEvent(new_event(5, c_id, True)).event_id
1835 with events_session(token3) as api:
1836 e4 = api.CreateEvent(new_event(4, c_id, True)).event_id
1838 with events_session(token4) as api:
1839 e6 = api.CreateEvent(new_event(6, c2_id, True)).event_id
1841 with events_session(token1) as api:
1842 api.InviteEventOrganizer(events_pb2.InviteEventOrganizerReq(event_id=e3, user_id=user3.id))
1844 with events_session(token1) as api:
1845 api.SetEventAttendance(
1846 events_pb2.SetEventAttendanceReq(event_id=e1, attendance_state=events_pb2.ATTENDANCE_STATE_MAYBE)
1847 )
1848 api.SetEventSubscription(events_pb2.SetEventSubscriptionReq(event_id=e4, subscribe=True))
1850 with events_session(token2) as api:
1851 api.SetEventAttendance(
1852 events_pb2.SetEventAttendanceReq(event_id=e3, attendance_state=events_pb2.ATTENDANCE_STATE_GOING)
1853 )
1855 with events_session(token3) as api:
1856 api.SetEventSubscription(events_pb2.SetEventSubscriptionReq(event_id=e2, subscribe=True))
1858 with events_session(token1) as api:
1859 # test pagination with token first
1860 res = api.ListMyEvents(events_pb2.ListMyEventsReq(page_size=2))
1861 assert [event.event_id for event in res.events] == [e1, e2]
1862 res = api.ListMyEvents(events_pb2.ListMyEventsReq(page_size=2, page_token=res.next_page_token))
1863 assert [event.event_id for event in res.events] == [e3, e4]
1864 assert not res.next_page_token
1866 res = api.ListMyEvents(
1867 events_pb2.ListMyEventsReq(
1868 subscribed=True,
1869 attending=True,
1870 organizing=True,
1871 )
1872 )
1873 assert [event.event_id for event in res.events] == [e1, e2, e3, e4]
1875 res = api.ListMyEvents(events_pb2.ListMyEventsReq())
1876 assert [event.event_id for event in res.events] == [e1, e2, e3, e4]
1878 res = api.ListMyEvents(events_pb2.ListMyEventsReq(subscribed=True))
1879 assert [event.event_id for event in res.events] == [e2, e3, e4]
1881 res = api.ListMyEvents(events_pb2.ListMyEventsReq(attending=True))
1882 assert [event.event_id for event in res.events] == [e1, e2, e3]
1884 res = api.ListMyEvents(events_pb2.ListMyEventsReq(organizing=True))
1885 assert [event.event_id for event in res.events] == [e2, e3]
1887 with events_session(token1) as api:
1888 # Test pagination with page_number and verify total_items
1889 res = api.ListMyEvents(
1890 events_pb2.ListMyEventsReq(page_size=2, page_number=1, subscribed=True, attending=True, organizing=True)
1891 )
1892 assert [event.event_id for event in res.events] == [e1, e2]
1893 assert res.total_items == 4
1895 res = api.ListMyEvents(
1896 events_pb2.ListMyEventsReq(page_size=2, page_number=2, subscribed=True, attending=True, organizing=True)
1897 )
1898 assert [event.event_id for event in res.events] == [e3, e4]
1899 assert res.total_items == 4
1901 # Verify no more pages
1902 res = api.ListMyEvents(
1903 events_pb2.ListMyEventsReq(page_size=2, page_number=3, subscribed=True, attending=True, organizing=True)
1904 )
1905 assert not res.events
1906 assert res.total_items == 4
1908 with events_session(token2) as api:
1909 res = api.ListMyEvents(events_pb2.ListMyEventsReq())
1910 assert [event.event_id for event in res.events] == [e1, e3, e5]
1912 res = api.ListMyEvents(events_pb2.ListMyEventsReq(subscribed=True))
1913 assert [event.event_id for event in res.events] == [e1, e5]
1915 res = api.ListMyEvents(events_pb2.ListMyEventsReq(attending=True))
1916 assert [event.event_id for event in res.events] == [e1, e3, e5]
1918 res = api.ListMyEvents(events_pb2.ListMyEventsReq(organizing=True))
1919 assert [event.event_id for event in res.events] == [e1, e5]
1921 with events_session(token3) as api:
1922 # user3 is member of both global (c_id) and child (c2_id) communities
1923 res = api.ListMyEvents(events_pb2.ListMyEventsReq())
1924 assert [event.event_id for event in res.events] == [e1, e2, e3, e4, e5, e6]
1926 res = api.ListMyEvents(events_pb2.ListMyEventsReq(subscribed=True))
1927 assert [event.event_id for event in res.events] == [e2, e4]
1929 res = api.ListMyEvents(events_pb2.ListMyEventsReq(attending=True))
1930 assert [event.event_id for event in res.events] == [e4]
1932 res = api.ListMyEvents(events_pb2.ListMyEventsReq(organizing=True))
1933 assert [event.event_id for event in res.events] == [e3, e4]
1935 # my_communities returns events from both communities user3 is a member of
1936 res = api.ListMyEvents(events_pb2.ListMyEventsReq(my_communities=True))
1937 assert [event.event_id for event in res.events] == [e1, e2, e3, e4, e5, e6]
1939 # my_communities_exclude_global filters out events from global community (node_id=1)
1940 res = api.ListMyEvents(events_pb2.ListMyEventsReq(my_communities=True, my_communities_exclude_global=True))
1941 assert [event.event_id for event in res.events] == [e6]
1943 # my_communities_exclude_global works independently of my_communities flag
1944 res = api.ListMyEvents(events_pb2.ListMyEventsReq(my_communities_exclude_global=True))
1945 assert [event.event_id for event in res.events] == [e6]
1947 # my_communities_exclude_global filters organizing results too
1948 res = api.ListMyEvents(events_pb2.ListMyEventsReq(organizing=True, my_communities_exclude_global=True))
1949 assert [event.event_id for event in res.events] == []
1951 # my_communities_exclude_global filters subscribed results too
1952 res = api.ListMyEvents(events_pb2.ListMyEventsReq(subscribed=True, my_communities_exclude_global=True))
1953 assert [event.event_id for event in res.events] == []
1955 with events_session(token5) as api:
1956 res = api.ListAllEvents(events_pb2.ListAllEventsReq())
1957 assert [event.event_id for event in res.events] == [e1, e2, e3, e4, e5, e6]
1960def test_RemoveEventOrganizer(db):
1961 user1, token1 = generate_user()
1962 user2, token2 = generate_user()
1964 with session_scope() as session:
1965 c_id = create_community(session, 0, 2, "Community", [user1], [], None).id
1967 with events_session(token1) as api:
1968 event_id = api.CreateEvent(
1969 events_pb2.CreateEventReq(
1970 title="Dummy Title",
1971 content="Dummy content.",
1972 offline_information=events_pb2.OfflineEventInformation(
1973 address="Near Null Island",
1974 lat=0.1,
1975 lng=0.2,
1976 ),
1977 start_time=Timestamp_from_datetime(now() + timedelta(hours=2)),
1978 end_time=Timestamp_from_datetime(now() + timedelta(hours=5)),
1979 timezone="UTC",
1980 )
1981 ).event_id
1983 with events_session(token2) as api:
1984 assert not api.GetEvent(events_pb2.GetEventReq(event_id=event_id)).organizer
1986 with pytest.raises(grpc.RpcError) as e:
1987 api.RemoveEventOrganizer(events_pb2.RemoveEventOrganizerReq(event_id=event_id))
1988 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
1989 assert e.value.details() == "You're not allowed to edit that event."
1991 assert not api.GetEvent(events_pb2.GetEventReq(event_id=event_id)).organizer
1993 with events_session(token1) as api:
1994 api.InviteEventOrganizer(events_pb2.InviteEventOrganizerReq(event_id=event_id, user_id=user2.id))
1996 with pytest.raises(grpc.RpcError) as e:
1997 api.RemoveEventOrganizer(events_pb2.RemoveEventOrganizerReq(event_id=event_id))
1998 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
1999 assert e.value.details() == "You cannot remove the event owner as an organizer."
2001 with events_session(token2) as api:
2002 res = api.GetEvent(events_pb2.GetEventReq(event_id=event_id))
2003 assert res.organizer
2004 assert res.organizer_count == 2
2005 api.RemoveEventOrganizer(events_pb2.RemoveEventOrganizerReq(event_id=event_id))
2006 assert not api.GetEvent(events_pb2.GetEventReq(event_id=event_id)).organizer
2008 with pytest.raises(grpc.RpcError) as e:
2009 api.RemoveEventOrganizer(events_pb2.RemoveEventOrganizerReq(event_id=event_id))
2010 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
2011 assert e.value.details() == "You're not allowed to edit that event."
2013 res = api.GetEvent(events_pb2.GetEventReq(event_id=event_id))
2014 assert not res.organizer
2015 assert res.organizer_count == 1
2017 # Test that event owner can remove co-organizers
2018 with events_session(token1) as api:
2019 # Add user2 back as organizer
2020 api.InviteEventOrganizer(events_pb2.InviteEventOrganizerReq(event_id=event_id, user_id=user2.id))
2022 # Verify user2 is now an organizer
2023 res = api.GetEvent(events_pb2.GetEventReq(event_id=event_id))
2024 assert res.organizer_count == 2
2026 # Event owner can remove co-organizer
2027 api.RemoveEventOrganizer(
2028 events_pb2.RemoveEventOrganizerReq(event_id=event_id, user_id=wrappers_pb2.Int64Value(value=user2.id))
2029 )
2031 # Verify user2 is no longer an organizer
2032 res = api.GetEvent(events_pb2.GetEventReq(event_id=event_id))
2033 assert res.organizer_count == 1
2035 # Test that non-organizers cannot remove other organizers
2036 with events_session(token2) as api:
2037 # User2 cannot invite themselves as organizer (not the owner)
2038 with pytest.raises(grpc.RpcError) as e:
2039 api.InviteEventOrganizer(events_pb2.InviteEventOrganizerReq(event_id=event_id, user_id=user2.id))
2040 assert e.value.code() == grpc.StatusCode.PERMISSION_DENIED
2041 assert e.value.details() == "You're not allowed to edit that event."
2043 # Test that non-organizers cannot remove other organizers (user1 adds user2 back first)
2044 with events_session(token1) as api:
2045 # Add user2 back as organizer
2046 api.InviteEventOrganizer(events_pb2.InviteEventOrganizerReq(event_id=event_id, user_id=user2.id))
2049def test_ListEventAttendees_regression(db):
2050 # see issue #1617:
2051 #
2052 # 1. Create an event
2053 # 2. Transfer the event to a community (although this step probably not necessarily, only needed for it to show up in UI/`ListEvents` from `communities.proto`
2054 # 3. Change the current user's attendance state to "not going" (with `SetEventAttendance`)
2055 # 4. Change the current user's attendance state to "going" again
2056 #
2057 # **Expected behaviour**
2058 # `ListEventAttendees` should return the current user's ID
2059 #
2060 # **Actual/current behaviour**
2061 # `ListEventAttendees` returns another user's ID. This ID seems to be determined from the row's auto increment ID in `event_occurrence_attendees` in the database
2063 user1, token1 = generate_user()
2064 user2, token2 = generate_user()
2065 user3, token3 = generate_user()
2066 user4, token4 = generate_user()
2067 user5, token5 = generate_user()
2069 with session_scope() as session:
2070 c_id = create_community(session, 0, 2, "Community", [user1], [], None).id
2072 start_time = now() + timedelta(hours=2)
2073 end_time = start_time + timedelta(hours=3)
2075 with events_session(token1) as api:
2076 res = api.CreateEvent(
2077 events_pb2.CreateEventReq(
2078 title="Dummy Title",
2079 content="Dummy content.",
2080 online_information=events_pb2.OnlineEventInformation(
2081 link="https://couchers.org",
2082 ),
2083 parent_community_id=c_id,
2084 start_time=Timestamp_from_datetime(start_time),
2085 end_time=Timestamp_from_datetime(end_time),
2086 timezone="UTC",
2087 )
2088 )
2090 res = api.TransferEvent(
2091 events_pb2.TransferEventReq(
2092 event_id=res.event_id,
2093 new_owner_community_id=c_id,
2094 )
2095 )
2097 event_id = res.event_id
2099 api.SetEventAttendance(
2100 events_pb2.SetEventAttendanceReq(event_id=event_id, attendance_state=events_pb2.ATTENDANCE_STATE_NOT_GOING)
2101 )
2102 api.SetEventAttendance(
2103 events_pb2.SetEventAttendanceReq(event_id=event_id, attendance_state=events_pb2.ATTENDANCE_STATE_GOING)
2104 )
2106 res = api.ListEventAttendees(events_pb2.ListEventAttendeesReq(event_id=event_id))
2107 assert len(res.attendee_user_ids) == 1
2108 assert res.attendee_user_ids[0] == user1.id
2111def test_event_threads(db, push_collector: PushCollector):
2112 user1, token1 = generate_user()
2113 user2, token2 = generate_user()
2114 user3, token3 = generate_user()
2115 user4, token4 = generate_user()
2117 with session_scope() as session:
2118 c = create_community(session, 0, 2, "Community", [user3], [], None)
2119 h = create_group(session, "Group", [user4], [], c)
2120 c_id = c.id
2121 h_id = h.id
2122 user4_id = user4.id
2124 with events_session(token1) as api:
2125 event = api.CreateEvent(
2126 events_pb2.CreateEventReq(
2127 title="Dummy Title",
2128 content="Dummy content.",
2129 offline_information=events_pb2.OfflineEventInformation(
2130 address="Near Null Island",
2131 lat=0.1,
2132 lng=0.2,
2133 ),
2134 start_time=Timestamp_from_datetime(now() + timedelta(hours=2)),
2135 end_time=Timestamp_from_datetime(now() + timedelta(hours=5)),
2136 timezone="UTC",
2137 )
2138 )
2140 with threads_session(token2) as api:
2141 reply_id = api.PostReply(threads_pb2.PostReplyReq(thread_id=event.thread.thread_id, content="hi")).thread_id
2143 with events_session(token3) as api:
2144 res = api.GetEvent(events_pb2.GetEventReq(event_id=event.event_id))
2145 assert res.thread.num_responses == 1
2147 with threads_session(token3) as api:
2148 ret = api.GetThread(threads_pb2.GetThreadReq(thread_id=res.thread.thread_id))
2149 assert len(ret.replies) == 1
2150 assert not ret.next_page_token
2151 assert ret.replies[0].thread_id == reply_id
2152 assert ret.replies[0].content == "hi"
2153 assert ret.replies[0].author_user_id == user2.id
2154 assert ret.replies[0].num_replies == 0
2156 api.PostReply(threads_pb2.PostReplyReq(thread_id=reply_id, content="what a silly comment"))
2158 process_jobs()
2160 assert push_collector.pop_for_user(user1.id, last=True).content.title == f"{user2.name} • Dummy Title"
2161 assert push_collector.pop_for_user(user2.id, last=True).content.title == f"{user3.name} • Dummy Title"
2162 assert push_collector.count_for_user(user4_id) == 0
2165def test_can_overlap_other_events_schedule_regression(db):
2166 # we had a bug where we were checking overlapping for *all* occurrences of *all* events, not just the ones for this event
2167 user, token = generate_user()
2169 with session_scope() as session:
2170 c_id = create_community(session, 0, 2, "Community", [user], [], None).id
2172 start = now()
2174 with events_session(token) as api:
2175 # create another event, should be able to overlap with this one
2176 api.CreateEvent(
2177 events_pb2.CreateEventReq(
2178 title="Dummy Title",
2179 content="Dummy content.",
2180 parent_community_id=c_id,
2181 online_information=events_pb2.OnlineEventInformation(
2182 link="https://couchers.org/meet/",
2183 ),
2184 start_time=Timestamp_from_datetime(start + timedelta(hours=1)),
2185 end_time=Timestamp_from_datetime(start + timedelta(hours=5)),
2186 timezone="UTC",
2187 )
2188 )
2190 # this event
2191 res = api.CreateEvent(
2192 events_pb2.CreateEventReq(
2193 title="Dummy Title",
2194 content="Dummy content.",
2195 parent_community_id=c_id,
2196 online_information=events_pb2.OnlineEventInformation(
2197 link="https://couchers.org/meet/",
2198 ),
2199 start_time=Timestamp_from_datetime(start + timedelta(hours=1)),
2200 end_time=Timestamp_from_datetime(start + timedelta(hours=2)),
2201 timezone="UTC",
2202 )
2203 )
2205 # this doesn't overlap with the just created event, but does overlap with the occurrence from earlier; which should be no problem
2206 api.ScheduleEvent(
2207 events_pb2.ScheduleEventReq(
2208 event_id=res.event_id,
2209 content="New event occurrence",
2210 offline_information=events_pb2.OfflineEventInformation(
2211 address="A bit further but still near Null Island",
2212 lat=0.3,
2213 lng=0.2,
2214 ),
2215 start_time=Timestamp_from_datetime(start + timedelta(hours=3)),
2216 end_time=Timestamp_from_datetime(start + timedelta(hours=6)),
2217 timezone="UTC",
2218 )
2219 )
2222def test_can_overlap_other_events_update_regression(db):
2223 user, token = generate_user()
2225 with session_scope() as session:
2226 c_id = create_community(session, 0, 2, "Community", [user], [], None).id
2228 start = now()
2230 with events_session(token) as api:
2231 # create another event, should be able to overlap with this one
2232 api.CreateEvent(
2233 events_pb2.CreateEventReq(
2234 title="Dummy Title",
2235 content="Dummy content.",
2236 parent_community_id=c_id,
2237 online_information=events_pb2.OnlineEventInformation(
2238 link="https://couchers.org/meet/",
2239 ),
2240 start_time=Timestamp_from_datetime(start + timedelta(hours=1)),
2241 end_time=Timestamp_from_datetime(start + timedelta(hours=3)),
2242 timezone="UTC",
2243 )
2244 )
2246 res = api.CreateEvent(
2247 events_pb2.CreateEventReq(
2248 title="Dummy Title",
2249 content="Dummy content.",
2250 parent_community_id=c_id,
2251 online_information=events_pb2.OnlineEventInformation(
2252 link="https://couchers.org/meet/",
2253 ),
2254 start_time=Timestamp_from_datetime(start + timedelta(hours=7)),
2255 end_time=Timestamp_from_datetime(start + timedelta(hours=8)),
2256 timezone="UTC",
2257 )
2258 )
2260 event_id = api.ScheduleEvent(
2261 events_pb2.ScheduleEventReq(
2262 event_id=res.event_id,
2263 content="New event occurrence",
2264 offline_information=events_pb2.OfflineEventInformation(
2265 address="A bit further but still near Null Island",
2266 lat=0.3,
2267 lng=0.2,
2268 ),
2269 start_time=Timestamp_from_datetime(start + timedelta(hours=4)),
2270 end_time=Timestamp_from_datetime(start + timedelta(hours=6)),
2271 timezone="UTC",
2272 )
2273 ).event_id
2275 # can overlap with this current existing occurrence
2276 api.UpdateEvent(
2277 events_pb2.UpdateEventReq(
2278 event_id=event_id,
2279 start_time=Timestamp_from_datetime(start + timedelta(hours=5)),
2280 end_time=Timestamp_from_datetime(start + timedelta(hours=6)),
2281 )
2282 )
2284 api.UpdateEvent(
2285 events_pb2.UpdateEventReq(
2286 event_id=event_id,
2287 start_time=Timestamp_from_datetime(start + timedelta(hours=2)),
2288 end_time=Timestamp_from_datetime(start + timedelta(hours=4)),
2289 )
2290 )
2293def test_list_past_events_regression(db):
2294 # test for a bug where listing past events didn't work if they didn't have a future occurrence
2295 user, token = generate_user()
2297 with session_scope() as session:
2298 c_id = create_community(session, 0, 2, "Community", [user], [], None).id
2300 start = now()
2302 with events_session(token) as api:
2303 api.CreateEvent(
2304 events_pb2.CreateEventReq(
2305 title="Dummy Title",
2306 content="Dummy content.",
2307 parent_community_id=c_id,
2308 online_information=events_pb2.OnlineEventInformation(
2309 link="https://couchers.org/meet/",
2310 ),
2311 start_time=Timestamp_from_datetime(start + timedelta(hours=3)),
2312 end_time=Timestamp_from_datetime(start + timedelta(hours=4)),
2313 timezone="UTC",
2314 )
2315 )
2317 with session_scope() as session:
2318 session.execute(
2319 update(EventOccurrence).values(
2320 during=DateTimeTZRange(start + timedelta(hours=-5), start + timedelta(hours=-4))
2321 )
2322 )
2324 with events_session(token) as api:
2325 res = api.ListAllEvents(events_pb2.ListAllEventsReq(past=True))
2326 assert len(res.events) == 1
2329def test_community_invite_requests(db):
2330 user1, token1 = generate_user(complete_profile=True)
2331 user2, token2 = generate_user()
2332 user3, token3 = generate_user()
2333 user4, token4 = generate_user()
2334 user5, token5 = generate_user(is_superuser=True)
2336 with session_scope() as session:
2337 w = create_community(session, 0, 2, "World Community", [user5], [], None)
2338 c_id = create_community(session, 0, 2, "Community", [user1, user3, user4], [], w).id
2340 enforce_community_memberships()
2342 with events_session(token1) as api:
2343 res = api.CreateEvent(
2344 events_pb2.CreateEventReq(
2345 title="Dummy Title",
2346 content="Dummy content.",
2347 parent_community_id=c_id,
2348 online_information=events_pb2.OnlineEventInformation(
2349 link="https://couchers.org/meet/",
2350 ),
2351 start_time=Timestamp_from_datetime(now() + timedelta(hours=3)),
2352 end_time=Timestamp_from_datetime(now() + timedelta(hours=4)),
2353 timezone="UTC",
2354 )
2355 )
2356 user_url = f"http://localhost:3000/user/{user1.username}"
2357 event_url = f"http://localhost:3000/event/{res.event_id}/{res.slug}"
2359 event_id = res.event_id
2361 with mock_notification_email() as mock:
2362 api.RequestCommunityInvite(events_pb2.RequestCommunityInviteReq(event_id=event_id))
2363 assert mock.call_count == 1
2364 e = email_fields(mock)
2365 assert e.recipient == "mods@couchers.org.invalid"
2367 assert user_url in e.plain
2368 assert event_url in e.plain
2370 # can't send another req
2371 with pytest.raises(grpc.RpcError) as err:
2372 api.RequestCommunityInvite(events_pb2.RequestCommunityInviteReq(event_id=event_id))
2373 assert err.value.code() == grpc.StatusCode.FAILED_PRECONDITION
2374 assert err.value.details() == "You have already requested a community invite for this event."
2376 # another user can send one though
2377 with events_session(token3) as api:
2378 api.RequestCommunityInvite(events_pb2.RequestCommunityInviteReq(event_id=event_id))
2380 # but not a non-admin
2381 with events_session(token2) as api:
2382 with pytest.raises(grpc.RpcError) as err:
2383 api.RequestCommunityInvite(events_pb2.RequestCommunityInviteReq(event_id=event_id))
2384 assert err.value.code() == grpc.StatusCode.PERMISSION_DENIED
2385 assert err.value.details() == "You're not allowed to edit that event."
2387 with real_editor_session(token5) as editor:
2388 res = editor.ListEventCommunityInviteRequests(editor_pb2.ListEventCommunityInviteRequestsReq())
2389 assert len(res.requests) == 2
2390 assert res.requests[0].user_id == user1.id
2391 assert res.requests[0].approx_users_to_notify == 3
2392 assert res.requests[1].user_id == user3.id
2393 assert res.requests[1].approx_users_to_notify == 3
2395 editor.DecideEventCommunityInviteRequest(
2396 editor_pb2.DecideEventCommunityInviteRequestReq(
2397 event_community_invite_request_id=res.requests[0].event_community_invite_request_id,
2398 approve=False,
2399 )
2400 )
2402 editor.DecideEventCommunityInviteRequest(
2403 editor_pb2.DecideEventCommunityInviteRequestReq(
2404 event_community_invite_request_id=res.requests[1].event_community_invite_request_id,
2405 approve=True,
2406 )
2407 )
2409 # not after approve
2410 with events_session(token4) as api:
2411 with pytest.raises(grpc.RpcError) as err:
2412 api.RequestCommunityInvite(events_pb2.RequestCommunityInviteReq(event_id=event_id))
2413 assert err.value.code() == grpc.StatusCode.FAILED_PRECONDITION
2414 assert err.value.details() == "A community invite has already been sent out for this event."
2417def test_update_event_should_notify_queues_job():
2418 user, token = generate_user()
2419 start = now()
2421 with session_scope() as session:
2422 c_id = create_community(session, 0, 2, "Community", [user], [], None).id
2424 # create an event
2425 with events_session(token) as api:
2426 create_res = api.CreateEvent(
2427 events_pb2.CreateEventReq(
2428 title="Dummy Title",
2429 content="Dummy content.",
2430 parent_community_id=c_id,
2431 offline_information=events_pb2.OfflineEventInformation(
2432 address="https://couchers.org/meet/",
2433 lat=1.0,
2434 lng=2.0,
2435 ),
2436 start_time=Timestamp_from_datetime(start + timedelta(hours=3)),
2437 end_time=Timestamp_from_datetime(start + timedelta(hours=6)),
2438 timezone="UTC",
2439 )
2440 )
2442 event_id = create_res.event_id
2444 # measure initial background job queue length
2445 with session_scope() as session:
2446 jobs = session.query(BackgroundJob).all()
2447 job_length_before_update = len(jobs)
2449 # update with should_notify=False, expect no change in background job queue
2450 api.UpdateEvent(
2451 events_pb2.UpdateEventReq(
2452 event_id=event_id,
2453 start_time=Timestamp_from_datetime(start + timedelta(hours=4)),
2454 should_notify=False,
2455 )
2456 )
2458 with session_scope() as session:
2459 jobs = session.query(BackgroundJob).all()
2460 assert len(jobs) == job_length_before_update
2462 # update with should_notify=True, expect one new background job added
2463 api.UpdateEvent(
2464 events_pb2.UpdateEventReq(
2465 event_id=event_id,
2466 start_time=Timestamp_from_datetime(start + timedelta(hours=4)),
2467 should_notify=True,
2468 )
2469 )
2471 with session_scope() as session:
2472 jobs = session.query(BackgroundJob).all()
2473 assert len(jobs) == job_length_before_update + 1
2476def test_event_photo_key(db):
2477 """Test that events return the photo_key field when a photo is set."""
2478 user, token = generate_user()
2480 start_time = now() + timedelta(hours=2)
2481 end_time = start_time + timedelta(hours=3)
2483 # Create a community and an upload for the event photo
2484 with session_scope() as session:
2485 create_community(session, 0, 2, "Community", [user], [], None)
2486 upload = Upload(
2487 key="test_event_photo_key_123",
2488 filename="test_event_photo_key_123.jpg",
2489 creator_user_id=user.id,
2490 )
2491 session.add(upload)
2493 with events_session(token) as api:
2494 # Create event without photo
2495 res = api.CreateEvent(
2496 events_pb2.CreateEventReq(
2497 title="Event Without Photo",
2498 content="No photo content.",
2499 photo_key=None,
2500 offline_information=events_pb2.OfflineEventInformation(
2501 address="Near Null Island",
2502 lat=0.1,
2503 lng=0.2,
2504 ),
2505 start_time=Timestamp_from_datetime(start_time),
2506 end_time=Timestamp_from_datetime(end_time),
2507 timezone="UTC",
2508 )
2509 )
2511 assert res.photo_key == ""
2512 assert res.photo_url == ""
2514 # Create event with photo
2515 res_with_photo = api.CreateEvent(
2516 events_pb2.CreateEventReq(
2517 title="Event With Photo",
2518 content="Has photo content.",
2519 photo_key="test_event_photo_key_123",
2520 offline_information=events_pb2.OfflineEventInformation(
2521 address="Near Null Island",
2522 lat=0.1,
2523 lng=0.2,
2524 ),
2525 start_time=Timestamp_from_datetime(start_time + timedelta(days=1)),
2526 end_time=Timestamp_from_datetime(end_time + timedelta(days=1)),
2527 timezone="UTC",
2528 )
2529 )
2531 assert res_with_photo.photo_key == "test_event_photo_key_123"
2532 assert "test_event_photo_key_123" in res_with_photo.photo_url
2534 event_id = res_with_photo.event_id
2536 # Verify photo_key is returned when getting the event
2537 get_res = api.GetEvent(events_pb2.GetEventReq(event_id=event_id))
2538 assert get_res.photo_key == "test_event_photo_key_123"
2539 assert "test_event_photo_key_123" in get_res.photo_url