Coverage for src/tests/test_editor.py: 100%
290 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-12-06 23:17 +0000
« prev ^ index » next coverage.py v7.11.0, created at 2025-12-06 23:17 +0000
1import grpc
2import pytest
3from google.protobuf.wrappers_pb2 import BoolValue, DoubleValue, StringValue
5from couchers.db import session_scope
6from couchers.materialized_views import refresh_materialized_views_rapid
7from couchers.models import (
8 Cluster,
9 Node,
10 Volunteer,
11)
12from couchers.proto import editor_pb2
13from couchers.sql import couchers_select as select
14from tests.test_fixtures import db, generate_user, real_editor_session, testconfig # noqa
17@pytest.fixture(autouse=True)
18def _(testconfig):
19 pass
22VALID_GEOJSON_MULTIPOLYGON = """
23 {
24 "type": "MultiPolygon",
25 "coordinates":
26 [
27 [
28 [
29 [
30 -73.98114904754641,
31 40.7470284264813
32 ],
33 [
34 -73.98314135177611,
35 40.73416844413217
36 ],
37 [
38 -74.00538969848634,
39 40.734314779027144
40 ],
41 [
42 -74.00479214294432,
43 40.75027851544338
44 ],
45 [
46 -73.98114904754641,
47 40.7470284264813
48 ]
49 ]
50 ]
51 ]
52 }
53"""
55POINT_GEOJSON = """
56{ "type": "Point", "coordinates": [100.0, 0.0] }
57"""
60def test_access_by_normal_user(db):
61 """Normal users should not be able to access editor APIs"""
62 normal_user, normal_token = generate_user()
64 with real_editor_session(normal_token) as api:
65 with pytest.raises(grpc.RpcError) as e:
66 api.CreateCommunity(
67 editor_pb2.CreateCommunityReq(
68 name="test community",
69 description="community for testing",
70 admin_ids=[],
71 geojson=VALID_GEOJSON_MULTIPOLYGON,
72 )
73 )
74 assert e.value.code() == grpc.StatusCode.PERMISSION_DENIED
75 assert e.value.details() == "Permission denied"
78def test_access_by_editor_user(db):
79 """Editor users should be able to access editor APIs"""
80 editor_user, editor_token = generate_user(is_editor=True)
82 with session_scope() as session:
83 with real_editor_session(editor_token) as api:
84 api.CreateCommunity(
85 editor_pb2.CreateCommunityReq(
86 name="test community",
87 description="community for testing",
88 admin_ids=[],
89 geojson=VALID_GEOJSON_MULTIPOLYGON,
90 )
91 )
92 community = session.execute(select(Cluster).where(Cluster.name == "test community")).scalar_one()
93 assert community.description == "community for testing"
94 assert community.slug == "test-community"
97def test_access_by_superuser(db):
98 """Superusers (who are also editors) should be able to access editor APIs"""
99 editor_user, editor_token = generate_user(is_editor=True)
101 with session_scope() as session:
102 with real_editor_session(editor_token) as api:
103 api.CreateCommunity(
104 editor_pb2.CreateCommunityReq(
105 name="test community",
106 description="community for testing",
107 admin_ids=[],
108 geojson=VALID_GEOJSON_MULTIPOLYGON,
109 )
110 )
111 community = session.execute(select(Cluster).where(Cluster.name == "test community")).scalar_one()
112 assert community.description == "community for testing"
113 assert community.slug == "test-community"
116def test_CreateCommunity_invalid_geojson(db):
117 """CreateCommunity should reject invalid GeoJSON"""
118 editor_user, editor_token = generate_user(is_editor=True)
120 with real_editor_session(editor_token) as api:
121 with pytest.raises(grpc.RpcError) as e:
122 api.CreateCommunity(
123 editor_pb2.CreateCommunityReq(
124 name="test community",
125 description="community for testing",
126 admin_ids=[],
127 geojson=POINT_GEOJSON,
128 )
129 )
130 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
131 assert e.value.details() == "GeoJson was not of type MultiPolygon."
134def test_UpdateCommunity_invalid_geojson(db):
135 """UpdateCommunity should reject invalid GeoJSON"""
136 editor_user, editor_token = generate_user(is_editor=True)
138 with session_scope() as session:
139 with real_editor_session(editor_token) as api:
140 api.CreateCommunity(
141 editor_pb2.CreateCommunityReq(
142 name="test community",
143 description="community for testing",
144 admin_ids=[],
145 geojson=VALID_GEOJSON_MULTIPOLYGON,
146 )
147 )
148 community = session.execute(select(Cluster).where(Cluster.name == "test community")).scalar_one()
150 with pytest.raises(grpc.RpcError) as e:
151 api.UpdateCommunity(
152 editor_pb2.UpdateCommunityReq(
153 community_id=community.parent_node_id,
154 name="test community 2",
155 description="community for testing 2",
156 geojson=POINT_GEOJSON,
157 )
158 )
159 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
160 assert e.value.details() == "GeoJson was not of type MultiPolygon."
163def test_UpdateCommunity_invalid_id(db):
164 """UpdateCommunity should reject invalid community IDs"""
165 editor_user, editor_token = generate_user(is_editor=True)
167 with real_editor_session(editor_token) as api:
168 api.CreateCommunity(
169 editor_pb2.CreateCommunityReq(
170 name="test community",
171 description="community for testing",
172 admin_ids=[],
173 geojson=VALID_GEOJSON_MULTIPOLYGON,
174 )
175 )
177 with pytest.raises(grpc.RpcError) as e:
178 api.UpdateCommunity(
179 editor_pb2.UpdateCommunityReq(
180 community_id=1000,
181 name="test community 1000",
182 description="community for testing 1000",
183 geojson=VALID_GEOJSON_MULTIPOLYGON,
184 )
185 )
186 assert e.value.code() == grpc.StatusCode.NOT_FOUND
187 assert e.value.details() == "Community not found."
190def test_UpdateCommunity(db):
191 """UpdateCommunity should successfully update a community"""
192 editor_user, editor_token = generate_user(is_editor=True)
194 with session_scope() as session:
195 with real_editor_session(editor_token) as api:
196 api.CreateCommunity(
197 editor_pb2.CreateCommunityReq(
198 name="test community",
199 description="community for testing",
200 admin_ids=[],
201 geojson=VALID_GEOJSON_MULTIPOLYGON,
202 )
203 )
204 community = session.execute(select(Cluster).where(Cluster.name == "test community")).scalar_one()
206 api.UpdateCommunity(
207 editor_pb2.UpdateCommunityReq(
208 community_id=community.parent_node_id,
209 name="test community updated",
210 description="community for testing updated",
211 geojson=VALID_GEOJSON_MULTIPOLYGON,
212 )
213 )
214 session.commit()
216 community_updated = session.execute(select(Cluster).where(Cluster.id == community.id)).scalar_one()
217 assert community_updated.name == "test community updated"
218 assert community_updated.description == "community for testing updated"
219 assert community_updated.slug == "test-community-updated"
222def test_CreateCommunity(db):
223 with session_scope() as session:
224 editor_user, editor_token = generate_user(is_editor=True)
225 normal_user, normal_token = generate_user()
226 with real_editor_session(editor_token) as api:
227 api.CreateCommunity(
228 editor_pb2.CreateCommunityReq(
229 name="test community",
230 description="community for testing",
231 admin_ids=[],
232 geojson=VALID_GEOJSON_MULTIPOLYGON,
233 )
234 )
235 community = session.execute(select(Cluster).where(Cluster.name == "test community")).scalar_one()
236 assert community.description == "community for testing"
237 assert community.slug == "test-community"
240def test_UpdateCommunity2(db):
241 editor_user, editor_token = generate_user(is_editor=True)
243 with session_scope() as session:
244 with real_editor_session(editor_token) as api:
245 api.CreateCommunity(
246 editor_pb2.CreateCommunityReq(
247 name="test community",
248 description="community for testing",
249 admin_ids=[],
250 geojson=VALID_GEOJSON_MULTIPOLYGON,
251 )
252 )
253 community = session.execute(select(Cluster).where(Cluster.name == "test community")).scalar_one()
254 assert community.description == "community for testing"
256 api.CreateCommunity(
257 editor_pb2.CreateCommunityReq(
258 name="test community 2",
259 description="community for testing 2",
260 admin_ids=[],
261 geojson=VALID_GEOJSON_MULTIPOLYGON,
262 )
263 )
264 community_2 = session.execute(select(Cluster).where(Cluster.name == "test community 2")).scalar_one()
266 api.UpdateCommunity(
267 editor_pb2.UpdateCommunityReq(
268 community_id=community.parent_node_id,
269 name="test community 2",
270 description="community for testing 2",
271 geojson=VALID_GEOJSON_MULTIPOLYGON,
272 parent_node_id=community_2.parent_node_id,
273 )
274 )
275 session.commit()
277 community_updated = session.execute(select(Cluster).where(Cluster.id == community.id)).scalar_one()
278 assert community_updated.description == "community for testing 2"
279 assert community_updated.slug == "test-community-2"
281 node_updated = session.execute(select(Node).where(Node.id == community_updated.parent_node_id)).scalar_one()
282 assert node_updated.parent_node_id == community_2.parent_node_id
285def test_MakeUserVolunteer(db):
286 """MakeUserVolunteer should successfully create a volunteer"""
287 editor_user, editor_token = generate_user(is_editor=True)
288 normal_user, normal_token = generate_user()
290 refresh_materialized_views_rapid(None)
291 with session_scope() as session:
292 with real_editor_session(editor_token) as api:
293 res = api.MakeUserVolunteer(
294 editor_pb2.MakeUserVolunteerReq(
295 user_id=normal_user.id,
296 role="Test Volunteer",
297 started_volunteering="2024-01-15",
298 hide_on_team_page=False,
299 )
300 )
302 # Check response
303 assert res.user_id == normal_user.id
304 assert res.role == "Test Volunteer"
305 assert res.started_volunteering == "2024-01-15"
306 assert res.show_on_team_page is True
307 assert res.username == normal_user.username
308 assert res.name == normal_user.name
310 volunteer = session.execute(select(Volunteer).where(Volunteer.user_id == normal_user.id)).scalar_one()
311 assert volunteer.role == "Test Volunteer"
312 assert volunteer.started_volunteering.isoformat() == "2024-01-15"
313 assert volunteer.show_on_team_page is True
316def test_MakeUserVolunteer_default_values(db):
317 """MakeUserVolunteer should use default values when not provided"""
318 editor_user, editor_token = generate_user(is_editor=True)
319 normal_user, normal_token = generate_user()
321 refresh_materialized_views_rapid(None)
322 with session_scope() as session:
323 with real_editor_session(editor_token) as api:
324 api.MakeUserVolunteer(
325 editor_pb2.MakeUserVolunteerReq(
326 user_id=normal_user.id,
327 role="Test Volunteer",
328 )
329 )
331 volunteer = session.execute(select(Volunteer).where(Volunteer.user_id == normal_user.id)).scalar_one()
332 assert volunteer.role == "Test Volunteer"
333 assert volunteer.started_volunteering is not None # defaults to today
334 assert volunteer.show_on_team_page is True # hide_on_team_page defaults to False
337def test_MakeUserVolunteer_hide_on_team_page(db):
338 """MakeUserVolunteer should respect hide_on_team_page=True"""
339 editor_user, editor_token = generate_user(is_editor=True)
340 normal_user, normal_token = generate_user()
342 refresh_materialized_views_rapid(None)
343 with session_scope() as session:
344 with real_editor_session(editor_token) as api:
345 api.MakeUserVolunteer(
346 editor_pb2.MakeUserVolunteerReq(
347 user_id=normal_user.id,
348 role="Test Volunteer",
349 hide_on_team_page=True,
350 )
351 )
353 volunteer = session.execute(select(Volunteer).where(Volunteer.user_id == normal_user.id)).scalar_one()
354 assert volunteer.role == "Test Volunteer"
355 assert volunteer.show_on_team_page is False # hide_on_team_page=True means don't show
358def test_MakeUserVolunteer_user_not_found(db):
359 """MakeUserVolunteer should fail if user doesn't exist"""
360 editor_user, editor_token = generate_user(is_editor=True)
362 with real_editor_session(editor_token) as api:
363 with pytest.raises(grpc.RpcError) as e:
364 api.MakeUserVolunteer(
365 editor_pb2.MakeUserVolunteerReq(
366 user_id=999999,
367 role="Test Volunteer",
368 )
369 )
370 assert e.value.code() == grpc.StatusCode.NOT_FOUND
371 assert e.value.details() == "Couldn't find that user."
374def test_MakeUserVolunteer_already_volunteer(db):
375 """MakeUserVolunteer should fail if user is already a volunteer"""
376 editor_user, editor_token = generate_user(is_editor=True)
377 normal_user, normal_token = generate_user()
379 refresh_materialized_views_rapid(None)
380 with real_editor_session(editor_token) as api:
381 # Create volunteer first time
382 api.MakeUserVolunteer(
383 editor_pb2.MakeUserVolunteerReq(
384 user_id=normal_user.id,
385 role="Test Volunteer",
386 )
387 )
389 # Try to create again
390 with pytest.raises(grpc.RpcError) as e:
391 api.MakeUserVolunteer(
392 editor_pb2.MakeUserVolunteerReq(
393 user_id=normal_user.id,
394 role="Test Volunteer 2",
395 )
396 )
397 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
398 assert e.value.details() == "This user is already a volunteer."
401def test_MakeUserVolunteer_invalid_date(db):
402 """MakeUserVolunteer should fail with invalid date format"""
403 editor_user, editor_token = generate_user(is_editor=True)
404 normal_user, normal_token = generate_user()
406 with real_editor_session(editor_token) as api:
407 with pytest.raises(grpc.RpcError) as e:
408 api.MakeUserVolunteer(
409 editor_pb2.MakeUserVolunteerReq(
410 user_id=normal_user.id,
411 role="Test Volunteer",
412 started_volunteering="invalid-date",
413 )
414 )
415 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
416 assert e.value.details() == "Invalid start date for volunteering."
419def test_UpdateVolunteer(db):
420 """UpdateVolunteer should successfully update volunteer fields"""
421 editor_user, editor_token = generate_user(is_editor=True)
422 normal_user, normal_token = generate_user()
424 refresh_materialized_views_rapid(None)
425 with session_scope() as session:
426 with real_editor_session(editor_token) as api:
427 # Create volunteer first
428 api.MakeUserVolunteer(
429 editor_pb2.MakeUserVolunteerReq(
430 user_id=normal_user.id,
431 role="Test Volunteer",
432 )
433 )
435 # Update volunteer
436 res = api.UpdateVolunteer(
437 editor_pb2.UpdateVolunteerReq(
438 user_id=normal_user.id,
439 role=StringValue(value="Updated Volunteer"),
440 sort_key=DoubleValue(value=10.5),
441 started_volunteering=StringValue(value="2023-06-01"),
442 stopped_volunteering=StringValue(value="2024-12-31"),
443 show_on_team_page=BoolValue(value=False),
444 )
445 )
447 # Check response
448 assert res.user_id == normal_user.id
449 assert res.role == "Updated Volunteer"
450 assert res.sort_key == 10.5
451 assert res.started_volunteering == "2023-06-01"
452 assert res.stopped_volunteering == "2024-12-31"
453 assert res.show_on_team_page is False
454 assert res.username == normal_user.username
456 volunteer = session.execute(select(Volunteer).where(Volunteer.user_id == normal_user.id)).scalar_one()
457 assert volunteer.role == "Updated Volunteer"
458 assert volunteer.sort_key == 10.5
459 assert volunteer.started_volunteering.isoformat() == "2023-06-01"
460 assert volunteer.stopped_volunteering.isoformat() == "2024-12-31"
461 assert volunteer.show_on_team_page is False
464def test_UpdateVolunteer_partial_update(db):
465 """UpdateVolunteer should only update provided fields"""
466 editor_user, editor_token = generate_user(is_editor=True)
467 normal_user, normal_token = generate_user()
469 refresh_materialized_views_rapid(None)
470 with session_scope() as session:
471 with real_editor_session(editor_token) as api:
472 # Create volunteer first
473 api.MakeUserVolunteer(
474 editor_pb2.MakeUserVolunteerReq(
475 user_id=normal_user.id,
476 role="Test Volunteer",
477 started_volunteering="2024-01-01",
478 )
479 )
481 # Update only role
482 api.UpdateVolunteer(
483 editor_pb2.UpdateVolunteerReq(
484 user_id=normal_user.id,
485 role=StringValue(value="Updated Role"),
486 )
487 )
489 volunteer = session.execute(select(Volunteer).where(Volunteer.user_id == normal_user.id)).scalar_one()
490 assert volunteer.role == "Updated Role"
491 assert volunteer.started_volunteering.isoformat() == "2024-01-01" # unchanged
492 assert volunteer.show_on_team_page is True # unchanged
495def test_UpdateVolunteer_not_found(db):
496 """UpdateVolunteer should fail if volunteer doesn't exist"""
497 editor_user, editor_token = generate_user(is_editor=True)
498 normal_user, normal_token = generate_user()
500 with real_editor_session(editor_token) as api:
501 with pytest.raises(grpc.RpcError) as e:
502 api.UpdateVolunteer(
503 editor_pb2.UpdateVolunteerReq(
504 user_id=normal_user.id,
505 role=StringValue(value="Updated Volunteer"),
506 )
507 )
508 assert e.value.code() == grpc.StatusCode.NOT_FOUND
509 assert e.value.details() == "Volunteer not found."
512def test_UpdateVolunteer_invalid_started_date(db):
513 """UpdateVolunteer should fail with invalid started_volunteering date"""
514 editor_user, editor_token = generate_user(is_editor=True)
515 normal_user, normal_token = generate_user()
517 refresh_materialized_views_rapid(None)
518 with real_editor_session(editor_token) as api:
519 # Create volunteer first
520 api.MakeUserVolunteer(
521 editor_pb2.MakeUserVolunteerReq(
522 user_id=normal_user.id,
523 role="Test Volunteer",
524 )
525 )
527 # Try to update with invalid date
528 with pytest.raises(grpc.RpcError) as e:
529 api.UpdateVolunteer(
530 editor_pb2.UpdateVolunteerReq(
531 user_id=normal_user.id,
532 started_volunteering=StringValue(value="invalid-date"),
533 )
534 )
535 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
536 assert e.value.details() == "Invalid start date for volunteering."
539def test_UpdateVolunteer_invalid_stopped_date(db):
540 """UpdateVolunteer should fail with invalid stopped_volunteering date"""
541 editor_user, editor_token = generate_user(is_editor=True)
542 normal_user, normal_token = generate_user()
544 refresh_materialized_views_rapid(None)
545 with real_editor_session(editor_token) as api:
546 # Create volunteer first
547 api.MakeUserVolunteer(
548 editor_pb2.MakeUserVolunteerReq(
549 user_id=normal_user.id,
550 role="Test Volunteer",
551 )
552 )
554 # Try to update with invalid date
555 with pytest.raises(grpc.RpcError) as e:
556 api.UpdateVolunteer(
557 editor_pb2.UpdateVolunteerReq(
558 user_id=normal_user.id,
559 stopped_volunteering=StringValue(value="not-a-date"),
560 )
561 )
562 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
563 assert e.value.details() == "Invalid end date for volunteering."
566def test_ListVolunteers(db):
567 """ListVolunteers should return all current volunteers"""
568 editor_user, editor_token = generate_user(is_editor=True)
569 user1, _ = generate_user()
570 user2, _ = generate_user()
571 user3, _ = generate_user()
573 refresh_materialized_views_rapid(None)
574 with session_scope() as session:
575 with real_editor_session(editor_token) as api:
576 # Create three volunteers
577 api.MakeUserVolunteer(
578 editor_pb2.MakeUserVolunteerReq(
579 user_id=user1.id,
580 role="Volunteer 1",
581 started_volunteering="2024-01-15",
582 )
583 )
584 api.MakeUserVolunteer(
585 editor_pb2.MakeUserVolunteerReq(
586 user_id=user2.id,
587 role="Volunteer 2",
588 started_volunteering="2023-06-01",
589 )
590 )
591 api.MakeUserVolunteer(
592 editor_pb2.MakeUserVolunteerReq(
593 user_id=user3.id,
594 role="Volunteer 3",
595 started_volunteering="2024-03-20",
596 )
597 )
599 # List volunteers (only current ones by default)
600 res = api.ListVolunteers(editor_pb2.ListVolunteersReq(include_past=False))
602 assert len(res.volunteers) == 3
603 user_ids = {v.user_id for v in res.volunteers}
604 assert user_ids == {user1.id, user2.id, user3.id}
606 # Check that all fields are populated
607 for volunteer in res.volunteers:
608 assert volunteer.user_id > 0
609 assert volunteer.role != ""
610 assert volunteer.username != ""
611 assert volunteer.name != ""
612 assert volunteer.started_volunteering != ""
613 assert volunteer.show_on_team_page is True
616def test_ListVolunteers_with_past(db):
617 """ListVolunteers should include past volunteers when requested"""
618 editor_user, editor_token = generate_user(is_editor=True)
619 user1, _ = generate_user()
620 user2, _ = generate_user()
622 refresh_materialized_views_rapid(None)
623 with session_scope() as session:
624 with real_editor_session(editor_token) as api:
625 # Create current volunteer
626 api.MakeUserVolunteer(
627 editor_pb2.MakeUserVolunteerReq(
628 user_id=user1.id,
629 role="Current Volunteer",
630 )
631 )
633 # Create past volunteer
634 api.MakeUserVolunteer(
635 editor_pb2.MakeUserVolunteerReq(
636 user_id=user2.id,
637 role="Past Volunteer",
638 )
639 )
640 api.UpdateVolunteer(
641 editor_pb2.UpdateVolunteerReq(
642 user_id=user2.id,
643 stopped_volunteering=StringValue(value="2024-06-30"),
644 )
645 )
647 # List only current volunteers
648 res = api.ListVolunteers(editor_pb2.ListVolunteersReq(include_past=False))
649 assert len(res.volunteers) == 1
650 assert res.volunteers[0].user_id == user1.id
651 assert not res.volunteers[0].HasField("stopped_volunteering")
653 # List all volunteers (including past)
654 res_with_past = api.ListVolunteers(editor_pb2.ListVolunteersReq(include_past=True))
655 assert len(res_with_past.volunteers) == 2
656 user_ids = {v.user_id for v in res_with_past.volunteers}
657 assert user_ids == {user1.id, user2.id}
659 # Find the past volunteer and verify stopped_volunteering is set
660 past_volunteer = next(v for v in res_with_past.volunteers if v.user_id == user2.id)
661 assert past_volunteer.stopped_volunteering == "2024-06-30"
664def test_ListVolunteers_ordering(db):
665 """ListVolunteers should respect sort_key ordering"""
666 editor_user, editor_token = generate_user(is_editor=True)
667 user1, _ = generate_user()
668 user2, _ = generate_user()
669 user3, _ = generate_user()
671 refresh_materialized_views_rapid(None)
672 with session_scope() as session:
673 with real_editor_session(editor_token) as api:
674 # Create volunteers with different sort keys
675 api.MakeUserVolunteer(editor_pb2.MakeUserVolunteerReq(user_id=user1.id, role="Volunteer 1"))
676 api.UpdateVolunteer(editor_pb2.UpdateVolunteerReq(user_id=user1.id, sort_key=DoubleValue(value=30.0)))
678 api.MakeUserVolunteer(editor_pb2.MakeUserVolunteerReq(user_id=user2.id, role="Volunteer 2"))
679 api.UpdateVolunteer(editor_pb2.UpdateVolunteerReq(user_id=user2.id, sort_key=DoubleValue(value=10.0)))
681 api.MakeUserVolunteer(editor_pb2.MakeUserVolunteerReq(user_id=user3.id, role="Volunteer 3"))
682 api.UpdateVolunteer(editor_pb2.UpdateVolunteerReq(user_id=user3.id, sort_key=DoubleValue(value=20.0)))
684 # List volunteers - should be ordered by sort_key ascending
685 res = api.ListVolunteers(editor_pb2.ListVolunteersReq(include_past=False))
686 assert len(res.volunteers) == 3
687 assert res.volunteers[0].user_id == user2.id # sort_key 10.0
688 assert res.volunteers[1].user_id == user3.id # sort_key 20.0
689 assert res.volunteers[2].user_id == user1.id # sort_key 30.0
692def test_ListVolunteers_empty(db):
693 """ListVolunteers should return empty list when no volunteers exist"""
694 editor_user, editor_token = generate_user(is_editor=True)
696 refresh_materialized_views_rapid(None)
697 with real_editor_session(editor_token) as api:
698 res = api.ListVolunteers(editor_pb2.ListVolunteersReq(include_past=False))
699 assert len(res.volunteers) == 0