Coverage for app/backend/src/tests/test_event_log.py: 100%
405 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-07-25 18:54 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-07-25 18:54 +0000
1from datetime import timedelta
2from typing import cast
4import grpc
5import pytest
6from google.protobuf import empty_pb2, wrappers_pb2
7from sqlalchemy import select
9from couchers.context import make_interactive_context
10from couchers.crypto import hash_password
11from couchers.db import session_scope
12from couchers.event_log import log_event
13from couchers.i18n import LocalizationContext
14from couchers.models import FriendRelationship, SignupFlow
15from couchers.models.logging import EventLog
16from couchers.proto import (
17 account_pb2,
18 api_pb2,
19 auth_pb2,
20 conversations_pb2,
21 events_pb2,
22 messages_pb2,
23 references_pb2,
24 reporting_pb2,
25 requests_pb2,
26 search_pb2,
27)
28from couchers.utils import create_coordinate, datetime_to_iso8601_local, now, today
29from tests.fixtures.db import generate_user, make_friends
30from tests.fixtures.sessions import (
31 MockGrpcContext,
32 account_session,
33 api_session,
34 auth_api_session,
35 conversations_session,
36 events_session,
37 references_session,
38 reporting_session,
39 requests_session,
40 search_session,
41)
42from tests.test_communities import create_community
45@pytest.fixture(autouse=True)
46def _(testconfig, fast_passwords):
47 pass
50def _get_events(session, event_type=None):
51 """Helper to query EventLog entries, optionally filtered by event_type."""
52 stmt = select(EventLog).order_by(EventLog.id)
53 if event_type:
54 stmt = stmt.where(EventLog.event_type == event_type)
55 return session.execute(stmt).scalars().all()
58# ===== Unit tests for log_event function =====
61def test_log_event_authenticated_context(db):
62 """log_event stores event with user_id from context."""
63 user, token = generate_user()
65 with session_scope() as session:
66 context = make_interactive_context(
67 grpc_context=cast(grpc.ServicerContext, MockGrpcContext()),
68 user_id=user.id,
69 is_api_key=False,
70 token=token,
71 localization=LocalizationContext.en_utc(),
72 sofa="test-sofa-123",
73 )
74 log_event(context, session, "test.event", {"key": "value"})
76 with session_scope() as session:
77 events = _get_events(session, "test.event")
78 assert len(events) == 1
79 assert events[0].user_id == user.id
80 assert events[0].event_type == "test.event"
81 assert events[0].properties == {"key": "value"}
82 assert events[0].sofa == "test-sofa-123"
83 assert events[0].created is not None
86def test_log_event_with_override_user_id(db):
87 """log_event uses _override_user_id to set user_id."""
88 user, token = generate_user()
90 with session_scope() as session:
91 context = make_interactive_context(
92 grpc_context=cast(grpc.ServicerContext, MockGrpcContext()),
93 user_id=None,
94 is_api_key=False,
95 token=None,
96 localization=LocalizationContext.en_utc(),
97 sofa="sofa-456",
98 )
99 log_event(context, session, "account.signup_completed", {"gender": "Woman"}, _override_user_id=user.id)
101 with session_scope() as session:
102 events = _get_events(session, "account.signup_completed")
103 assert len(events) == 1
104 assert events[0].user_id == user.id
105 assert events[0].properties == {"gender": "Woman"}
106 assert events[0].sofa == "sofa-456"
109def test_log_event_anonymous(db):
110 """log_event stores event with user_id=None when context has no user."""
111 with session_scope() as session:
112 context = make_interactive_context(
113 grpc_context=cast(grpc.ServicerContext, MockGrpcContext()),
114 user_id=None,
115 is_api_key=False,
116 token=None,
117 localization=LocalizationContext.en_utc(),
118 )
119 log_event(context, session, "account.signup_initiated", {"has_invite_code": False})
121 with session_scope() as session:
122 events = _get_events(session, "account.signup_initiated")
123 assert len(events) == 1
124 assert events[0].user_id is None
125 assert events[0].properties == {"has_invite_code": False}
128def test_log_event_complex_properties(db):
129 """Properties dict with various types is stored as JSONB correctly."""
130 user, token = generate_user()
132 props = {
133 "string_val": "hello",
134 "int_val": 42,
135 "float_val": 3.14,
136 "bool_val": True,
137 "none_val": None,
138 "list_val": [1, 2, 3],
139 "nested": {"a": 1, "b": "two"},
140 }
142 with session_scope() as session:
143 context = make_interactive_context(
144 grpc_context=cast(grpc.ServicerContext, MockGrpcContext()),
145 user_id=user.id,
146 is_api_key=False,
147 token=token,
148 localization=LocalizationContext.en_utc(),
149 )
150 log_event(context, session, "test.complex", props)
152 with session_scope() as session:
153 events = _get_events(session, "test.complex")
154 assert len(events) == 1
155 assert events[0].properties == props
158def test_log_event_empty_properties(db):
159 """Empty properties dict is stored correctly."""
160 user, token = generate_user()
162 with session_scope() as session:
163 context = make_interactive_context(
164 grpc_context=cast(grpc.ServicerContext, MockGrpcContext()),
165 user_id=user.id,
166 is_api_key=False,
167 token=token,
168 localization=LocalizationContext.en_utc(),
169 )
170 log_event(context, session, "account.logout", {})
172 with session_scope() as session:
173 events = _get_events(session, "account.logout")
174 assert len(events) == 1
175 assert events[0].properties == {}
178def test_log_event_multiple_events(db):
179 """Multiple events are stored independently."""
180 user, token = generate_user()
182 with session_scope() as session:
183 context = make_interactive_context(
184 grpc_context=cast(grpc.ServicerContext, MockGrpcContext()),
185 user_id=user.id,
186 is_api_key=False,
187 token=token,
188 localization=LocalizationContext.en_utc(),
189 )
190 log_event(context, session, "test.first", {"n": 1})
191 log_event(context, session, "test.second", {"n": 2})
192 log_event(context, session, "test.first", {"n": 3})
194 with session_scope() as session:
195 all_events = _get_events(session)
196 assert len(all_events) == 3
198 first_events = _get_events(session, "test.first")
199 assert len(first_events) == 2
200 assert first_events[0].properties == {"n": 1}
201 assert first_events[1].properties == {"n": 3}
203 second_events = _get_events(session, "test.second")
204 assert len(second_events) == 1
205 assert second_events[0].properties == {"n": 2}
208# ===== Integration tests: auth events =====
211def test_signup_flow_creates_events(db):
212 """Full signup flow creates account.signup_initiated and account.signup_completed events."""
213 with auth_api_session() as (auth_api, metadata_interceptor):
214 res = auth_api.SignupFlow(
215 auth_pb2.SignupFlowReq(
216 basic=auth_pb2.SignupBasic(name="testing", email="email@couchers.org.invalid"),
217 )
218 )
220 flow_token = res.flow_token
222 with session_scope() as session:
223 events = _get_events(session, "account.signup_initiated")
224 assert len(events) == 1
225 assert events[0].properties["has_invite_code"] is False
227 # complete signup: get email token, verify, fill account, etc.
228 with session_scope() as session:
229 flow = session.execute(select(SignupFlow).where(SignupFlow.flow_token == flow_token)).scalar_one()
230 email_token = flow.email_token
232 with auth_api_session() as (auth_api, metadata_interceptor):
233 auth_api.SignupFlow(
234 auth_pb2.SignupFlowReq(
235 flow_token=flow_token,
236 email_token=email_token,
237 )
238 )
240 with auth_api_session() as (auth_api, metadata_interceptor):
241 auth_api.SignupFlow(
242 auth_pb2.SignupFlowReq(
243 flow_token=flow_token,
244 accept_community_guidelines=wrappers_pb2.BoolValue(value=True),
245 )
246 )
248 with auth_api_session() as (auth_api, metadata_interceptor):
249 auth_api.SignupFlow(
250 auth_pb2.SignupFlowReq(
251 flow_token=flow_token,
252 account=auth_pb2.SignupAccount(
253 username="frodo",
254 password="a very insecure password",
255 birthdate="1970-01-01",
256 gender="Bot",
257 hosting_status=api_pb2.HOSTING_STATUS_MAYBE,
258 city="New York City",
259 lat=40.7331,
260 lng=-73.9778,
261 radius=500,
262 accept_tos=True,
263 ),
264 )
265 )
267 with auth_api_session() as (auth_api, metadata_interceptor):
268 res = auth_api.SignupFlow(
269 auth_pb2.SignupFlowReq(
270 flow_token=flow_token,
271 motivations=auth_pb2.SignupMotivations(motivations=["surfing"]),
272 )
273 )
275 assert res.HasField("auth_res")
276 user_id = res.auth_res.user_id
278 with session_scope() as session:
279 events = _get_events(session, "account.signup_completed")
280 assert len(events) == 1
281 e = events[0]
282 assert e.user_id == user_id
283 assert e.properties["gender"] == "Bot"
284 assert e.properties["hosting_status"] is not None
285 assert e.properties["city"] == "New York City"
286 assert e.properties["has_invite_code"] is False
287 assert isinstance(e.properties["signup_duration_s"], (int, float))
288 assert "filled_contributor_form" in e.properties
291def test_login_creates_event(db):
292 """Login creates account.login event with gender and remember_device."""
293 user, token = generate_user(hashed_password=hash_password("password123"))
295 with auth_api_session() as (auth_api, metadata_interceptor):
296 auth_api.Authenticate(
297 auth_pb2.AuthReq(
298 user=user.username,
299 password="password123",
300 remember_device=True,
301 )
302 )
304 with session_scope() as session:
305 events = _get_events(session, "account.login")
306 assert len(events) == 1
307 e = events[0]
308 assert e.user_id == user.id
309 assert e.properties["gender"] == user.gender
310 assert e.properties["remember_device"] is True
313def test_logout_creates_event(db):
314 """Logout creates account.logout event."""
315 user, token = generate_user()
317 with auth_api_session() as (auth_api, metadata_interceptor):
318 auth_api.Deauthenticate(empty_pb2.Empty(), metadata=(("cookie", f"couchers-sesh={token}"),))
320 with session_scope() as session:
321 events = _get_events(session, "account.logout")
322 assert len(events) == 1
323 assert events[0].user_id == user.id
324 assert events[0].properties == {}
327# ===== Integration tests: host request events =====
330def test_host_request_created_event(db, moderator):
331 """Creating a host request logs host_request.created with full context."""
332 user1, token1 = generate_user()
333 user2, token2 = generate_user(
334 city="Berlin",
335 geom=create_coordinate(52.5200, 13.4050),
336 geom_radius=200,
337 )
339 from_date = today() + timedelta(days=2)
340 to_date = today() + timedelta(days=5)
342 with requests_session(token1) as api:
343 res = api.CreateHostRequest(
344 requests_pb2.CreateHostRequestReq(
345 host_user_id=user2.id,
346 from_date=from_date.isoformat(),
347 to_date=to_date.isoformat(),
348 text="a]" * 200 + "Hello! I would love to stay with you.",
349 )
350 )
352 host_request_id = res.host_request_id
354 with session_scope() as session:
355 events = _get_events(session, "host_request.created")
356 assert len(events) == 1
357 e = events[0]
358 assert e.user_id == user1.id
359 assert e.properties["host_request_id"] == host_request_id
360 assert e.properties["host_id"] == user2.id
361 assert e.properties["surfer_gender"] == user1.gender
362 assert e.properties["host_gender"] == user2.gender
363 assert e.properties["city"] == "Berlin"
364 assert e.properties["from_date"] == str(from_date)
365 assert e.properties["to_date"] == str(to_date)
366 assert e.properties["nights"] == 3
369def test_host_request_status_change_events(db, moderator):
370 """Accepting a host request logs event with both parties' info."""
371 user1, token1 = generate_user()
372 user2, token2 = generate_user(
373 city="Berlin",
374 geom=create_coordinate(52.5200, 13.4050),
375 geom_radius=200,
376 )
378 from_date = today() + timedelta(days=2)
379 to_date = today() + timedelta(days=5)
381 with requests_session(token1) as api:
382 res = api.CreateHostRequest(
383 requests_pb2.CreateHostRequestReq(
384 host_user_id=user2.id,
385 from_date=from_date.isoformat(),
386 to_date=to_date.isoformat(),
387 text="a]" * 200 + "Hello! I would love to stay with you.",
388 )
389 )
390 host_request_id = res.host_request_id
391 moderator.approve_host_request(host_request_id)
393 # Host accepts
394 with requests_session(token2) as api:
395 api.RespondHostRequest(
396 requests_pb2.RespondHostRequestReq(
397 host_request_id=host_request_id,
398 status=messages_pb2.HOST_REQUEST_STATUS_ACCEPTED,
399 )
400 )
402 with session_scope() as session:
403 events = _get_events(session, "host_request.accepted")
404 assert len(events) == 1
405 e = events[0]
406 assert e.user_id == user2.id
407 assert e.properties["host_request_id"] == host_request_id
408 assert e.properties["surfer_id"] == user1.id
409 assert e.properties["host_id"] == user2.id
410 assert e.properties["surfer_gender"] == user1.gender
411 assert e.properties["host_gender"] == user2.gender
412 assert e.properties["from_date"] == str(from_date)
413 assert e.properties["to_date"] == str(to_date)
414 assert e.properties["host_city"] == "Berlin"
416 # Surfer confirms
417 with requests_session(token1) as api:
418 api.RespondHostRequest(
419 requests_pb2.RespondHostRequestReq(
420 host_request_id=host_request_id,
421 status=messages_pb2.HOST_REQUEST_STATUS_CONFIRMED,
422 )
423 )
425 with session_scope() as session:
426 events = _get_events(session, "host_request.confirmed")
427 assert len(events) == 1
428 e = events[0]
429 assert e.user_id == user1.id
430 assert e.properties["surfer_id"] == user1.id
431 assert e.properties["host_id"] == user2.id
432 assert e.properties["surfer_gender"] == user1.gender
433 assert e.properties["host_gender"] == user2.gender
436def test_host_request_rejected_event(db, moderator):
437 """Rejecting a host request logs event."""
438 user1, token1 = generate_user()
439 user2, token2 = generate_user(
440 city="Paris",
441 geom=create_coordinate(48.8566, 2.3522),
442 geom_radius=200,
443 )
445 with requests_session(token1) as api:
446 res = api.CreateHostRequest(
447 requests_pb2.CreateHostRequestReq(
448 host_user_id=user2.id,
449 from_date=(today() + timedelta(days=2)).isoformat(),
450 to_date=(today() + timedelta(days=4)).isoformat(),
451 text="a]" * 200 + "Would love to visit!",
452 )
453 )
454 moderator.approve_host_request(res.host_request_id)
456 with requests_session(token2) as api:
457 api.RespondHostRequest(
458 requests_pb2.RespondHostRequestReq(
459 host_request_id=res.host_request_id,
460 status=messages_pb2.HOST_REQUEST_STATUS_REJECTED,
461 )
462 )
464 with session_scope() as session:
465 events = _get_events(session, "host_request.rejected")
466 assert len(events) == 1
467 e = events[0]
468 assert e.user_id == user2.id
469 assert e.properties["surfer_id"] == user1.id
470 assert e.properties["host_id"] == user2.id
471 assert e.properties["host_city"] == "Paris"
474def test_host_request_cancelled_event(db, moderator):
475 """Cancelling a host request logs event."""
476 user1, token1 = generate_user()
477 user2, token2 = generate_user(
478 geom=create_coordinate(52.5200, 13.4050),
479 geom_radius=200,
480 )
482 with requests_session(token1) as api:
483 res = api.CreateHostRequest(
484 requests_pb2.CreateHostRequestReq(
485 host_user_id=user2.id,
486 from_date=(today() + timedelta(days=2)).isoformat(),
487 to_date=(today() + timedelta(days=4)).isoformat(),
488 text="a]" * 200 + "Would love to visit!",
489 )
490 )
491 moderator.approve_host_request(res.host_request_id)
493 with requests_session(token1) as api:
494 api.RespondHostRequest(
495 requests_pb2.RespondHostRequestReq(
496 host_request_id=res.host_request_id,
497 status=messages_pb2.HOST_REQUEST_STATUS_CANCELLED,
498 )
499 )
501 with session_scope() as session:
502 events = _get_events(session, "host_request.cancelled")
503 assert len(events) == 1
504 e = events[0]
505 assert e.user_id == user1.id
506 assert e.properties["surfer_id"] == user1.id
507 assert e.properties["host_id"] == user2.id
510def test_host_request_message_event(db, moderator):
511 """Sending a message in a host request logs event with role."""
512 user1, token1 = generate_user()
513 user2, token2 = generate_user(
514 geom=create_coordinate(52.5200, 13.4050),
515 geom_radius=200,
516 )
518 with requests_session(token1) as api:
519 res = api.CreateHostRequest(
520 requests_pb2.CreateHostRequestReq(
521 host_user_id=user2.id,
522 from_date=(today() + timedelta(days=2)).isoformat(),
523 to_date=(today() + timedelta(days=4)).isoformat(),
524 text="a]" * 200 + "Hello!",
525 )
526 )
527 host_request_id = res.host_request_id
528 moderator.approve_host_request(host_request_id)
530 # Host sends a message
531 with requests_session(token2) as api:
532 api.SendHostRequestMessage(
533 requests_pb2.SendHostRequestMessageReq(
534 host_request_id=host_request_id,
535 text="Welcome!",
536 )
537 )
539 with session_scope() as session:
540 events = _get_events(session, "host_request.message_sent")
541 assert len(events) == 1
542 e = events[0]
543 assert e.user_id == user2.id
544 assert e.properties["host_request_id"] == host_request_id
545 assert e.properties["role"] == "host"
546 assert e.properties["surfer_id"] == user1.id
547 assert e.properties["host_id"] == user2.id
549 # Surfer sends a message
550 with requests_session(token1) as api:
551 api.SendHostRequestMessage(
552 requests_pb2.SendHostRequestMessageReq(
553 host_request_id=host_request_id,
554 text="Thanks!",
555 )
556 )
558 with session_scope() as session:
559 events = _get_events(session, "host_request.message_sent")
560 assert len(events) == 2
561 e = events[1]
562 assert e.user_id == user1.id
563 assert e.properties["role"] == "surfer"
566# ===== Integration tests: messaging events =====
569def test_send_message_creates_event(db):
570 """Sending a direct message creates a message.sent event."""
571 user1, token1 = generate_user()
572 user2, token2 = generate_user()
573 make_friends(user1, user2)
575 with conversations_session(token1) as api:
576 res = api.SendDirectMessage(
577 conversations_pb2.SendDirectMessageReq(
578 recipient_user_id=user2.id,
579 text="Hello friend!",
580 )
581 )
583 with session_scope() as session:
584 events = _get_events(session, "message.sent")
585 assert len(events) == 1
586 e = events[0]
587 assert e.user_id == user1.id
588 assert e.properties["group_chat_id"] == res.group_chat_id
589 assert e.properties["is_dm"] is True
590 assert e.properties["recipient_id"] == user2.id
593def test_create_group_chat_event(db):
594 """Creating a group chat creates a group_chat.created event."""
595 user1, token1 = generate_user()
596 user2, token2 = generate_user()
597 user3, token3 = generate_user()
598 make_friends(user1, user2)
599 make_friends(user1, user3)
601 with conversations_session(token1) as api:
602 res = api.CreateGroupChat(
603 conversations_pb2.CreateGroupChatReq(
604 recipient_user_ids=[user2.id, user3.id],
605 title=wrappers_pb2.StringValue(value="Test Group"),
606 )
607 )
609 with session_scope() as session:
610 events = _get_events(session, "group_chat.created")
611 assert len(events) == 1
612 e = events[0]
613 assert e.user_id == user1.id
614 assert e.properties["group_chat_id"] == res.group_chat_id
615 assert e.properties["is_dm"] is False
616 assert e.properties["recipient_count"] == 2
619# ===== Integration tests: friendship events =====
622def test_friendship_request_events(db, moderator):
623 """Friend request lifecycle creates appropriate events."""
624 user1, token1 = generate_user()
625 user2, token2 = generate_user()
627 # Send friend request
628 with api_session(token1) as api:
629 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
631 with session_scope() as session:
632 events = _get_events(session, "friendship.request_sent")
633 assert len(events) == 1
634 assert events[0].user_id == user1.id
635 assert events[0].properties["to_user_id"] == user2.id
637 # Approve and accept friend request
638 with session_scope() as session:
639 fr = session.execute(select(FriendRelationship)).scalar_one()
640 fr_id = fr.id
642 moderator.approve_friend_request(fr_id)
644 with api_session(token2) as api:
645 api.RespondFriendRequest(api_pb2.RespondFriendRequestReq(friend_request_id=fr_id, accept=True))
647 with session_scope() as session:
648 events = _get_events(session, "friendship.request_responded")
649 assert len(events) == 1
650 e = events[0]
651 assert e.user_id == user2.id
652 assert e.properties["from_user_id"] == user1.id
653 assert e.properties["accepted"] is True
655 # Remove friend
656 with api_session(token1) as api:
657 api.RemoveFriend(api_pb2.RemoveFriendReq(user_id=user2.id))
659 with session_scope() as session:
660 events = _get_events(session, "friendship.removed")
661 assert len(events) == 1
662 assert events[0].user_id == user1.id
663 assert events[0].properties["other_user_id"] == user2.id
666def test_friendship_cancel_event(db, moderator):
667 """Cancelling a friend request creates event."""
668 user1, token1 = generate_user()
669 user2, token2 = generate_user()
671 with api_session(token1) as api:
672 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
674 with session_scope() as session:
675 fr = session.execute(select(FriendRelationship)).scalar_one()
676 fr_id = fr.id
678 with api_session(token1) as api:
679 api.CancelFriendRequest(api_pb2.CancelFriendRequestReq(friend_request_id=fr_id))
681 with session_scope() as session:
682 events = _get_events(session, "friendship.request_cancelled")
683 assert len(events) == 1
684 assert events[0].properties["to_user_id"] == user2.id
687# ===== Integration tests: reporting events =====
690def test_report_creates_event(db):
691 """Reporting content creates content.reported event with full context."""
692 user1, token1 = generate_user()
693 user2, token2 = generate_user()
695 with reporting_session(token1) as api:
696 api.Report(
697 reporting_pb2.ReportReq(
698 reason="spam",
699 description="This is spam",
700 content_ref="comment/456",
701 author_user=user2.username,
702 user_agent="TestAgent/1.0",
703 page="https://couchers.org/profile/123",
704 )
705 )
707 with session_scope() as session:
708 events = _get_events(session, "content.reported")
709 assert len(events) == 1
710 e = events[0]
711 assert e.user_id == user1.id
712 assert e.properties["author_user_id"] == user2.id
713 assert e.properties["reason"] == "spam"
714 assert e.properties["content_ref"] == "comment/456"
715 assert e.properties["page"] == "https://couchers.org/profile/123"
718# ===== Integration tests: search events =====
721def test_search_creates_event(db):
722 """User search creates search.performed event with search parameters."""
723 user, token = generate_user()
725 with search_session(token) as api:
726 api.UserSearch(search_pb2.UserSearchReq())
728 with session_scope() as session:
729 events = _get_events(session, "search.performed")
730 assert len(events) == 1
731 e = events[0]
732 assert e.user_id == user.id
733 assert e.properties["has_query"] is False
734 assert e.properties["has_filters"] is False
735 assert "total_items" in e.properties
736 assert e.properties["search_in"] is None
739# ===== Integration tests: reference events =====
742def test_friend_reference_event(db):
743 """Writing a friend reference creates reference.friend_written event."""
744 user1, token1 = generate_user()
745 user2, token2 = generate_user()
746 make_friends(user1, user2)
748 with references_session(token1) as api:
749 api.WriteFriendReference(
750 references_pb2.WriteFriendReferenceReq(
751 to_user_id=user2.id,
752 text="Great person!",
753 private_text="",
754 rating=0.9,
755 was_appropriate=True,
756 )
757 )
759 with session_scope() as session:
760 events = _get_events(session, "reference.friend_written")
761 assert len(events) == 1
762 e = events[0]
763 assert e.user_id == user1.id
764 assert e.properties["to_user_id"] == user2.id
765 assert e.properties["rating"] == pytest.approx(0.9)
766 assert e.properties["was_appropriate"] is True
769# ===== Integration tests: event (calendar) events =====
772def test_event_created_event(db):
773 """Creating an event logs event.created with community info."""
774 user, token = generate_user()
776 with session_scope() as session:
777 create_community(session, 0, 2, "Community", [user], [], None)
779 start_time = now() + timedelta(days=1)
780 end_time = start_time + timedelta(hours=2)
782 with events_session(token) as api:
783 res = api.CreateEvent(
784 events_pb2.CreateEventReq(
785 title="Test Meetup",
786 content="Let's hang out",
787 location=events_pb2.EventLocation(
788 address="123 Main St",
789 lat=0.1,
790 lng=0.2,
791 ),
792 start_datetime_iso8601_local=datetime_to_iso8601_local(start_time),
793 end_datetime_iso8601_local=datetime_to_iso8601_local(end_time),
794 )
795 )
797 with session_scope() as session:
798 events = _get_events(session, "event.created")
799 assert len(events) == 1
800 e = events[0]
801 assert e.user_id == user.id
802 assert e.properties["event_id"] is not None
803 assert e.properties["occurrence_id"] is not None
804 assert e.properties["parent_community_id"] is not None
805 assert e.properties["parent_community_name"] is not None
808# ===== Integration tests: password change =====
811def test_password_change_event(db):
812 """Changing password creates account.password_changed event."""
813 user, token = generate_user(hashed_password=hash_password("oldpassword"))
815 with account_session(token) as api:
816 api.ChangePasswordV2(
817 account_pb2.ChangePasswordV2Req(
818 old_password="oldpassword",
819 new_password="a new very secure password",
820 )
821 )
823 with session_scope() as session:
824 events = _get_events(session, "account.password_changed")
825 assert len(events) == 1
826 assert events[0].user_id == user.id
827 assert events[0].properties == {}
830# ===== Test that events don't leak across tests =====
833def test_no_stale_events(db):
834 """Verify the database is clean - no events from previous tests."""
835 with session_scope() as session:
836 events = _get_events(session)
837 assert len(events) == 0