Coverage for app / backend / src / tests / fixtures / misc.py: 100%
56 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 collections.abc import Generator
2from contextlib import contextmanager
3from dataclasses import dataclass
4from typing import Any
5from unittest.mock import Mock, patch
7from sqlalchemy.orm import Session
9from couchers.jobs.worker import process_job
10from couchers.models import User
11from couchers.notifications.push import PushNotificationContent
12from couchers.proto import moderation_pb2
13from tests.fixtures.sessions import real_moderation_session
16def process_jobs() -> None:
17 while process_job():
18 pass
21@contextmanager
22def mock_notification_email() -> Generator[Mock]:
23 with patch("couchers.email._queue_email") as mock:
24 yield mock
25 process_jobs()
28@dataclass
29class EmailData:
30 sender_name: str
31 sender_email: str
32 recipient: str
33 subject: str
34 plain: str
35 html: str
36 source_data: str
37 list_unsubscribe_header: str
40def email_fields(mock: Mock, call_ix: int = 0) -> EmailData:
41 _, kw = mock.call_args_list[call_ix]
42 return EmailData(
43 sender_name=kw.get("sender_name"),
44 sender_email=kw.get("sender_email"),
45 recipient=kw.get("recipient"),
46 subject=kw.get("subject"),
47 plain=kw.get("plain"),
48 html=kw.get("html"),
49 source_data=kw.get("source_data"),
50 list_unsubscribe_header=kw.get("list_unsubscribe_header"),
51 )
54@dataclass(frozen=True, slots=True, kw_only=True)
55class Push:
56 topic_action: str
57 content: PushNotificationContent
58 key: str | None = None
59 ttl: int | None = None
62class PushCollector:
63 def __init__(self):
64 self.by_user: dict[int, list[Push]] = {}
65 """Collected notifications by user id, chronologically."""
67 def push_to_user(self, session: Session, user_id: int, **kwargs: Any) -> None:
68 if user_id not in self.by_user:
69 self.by_user[user_id] = []
70 self.by_user[user_id].append(Push(**kwargs))
72 def count_for_user(self, user_id: int) -> int:
73 return len(self.by_user.get(user_id, []))
75 def pop_for_user(self, user_id: int, last: bool = False) -> Push:
76 """
77 Removes and returns the oldest push notification received by the given user,
78 optionally asserting that it is the last one.
79 """
80 pushes = self.by_user.get(user_id)
81 assert pushes, f"No notifications to pop for user {user_id}."
82 if last:
83 assert len(pushes) == 1, f"Expected a single notification for user {user_id}."
84 return pushes.pop(0)
87class Moderator:
88 """
89 A test fixture that provides a moderator user and methods to exercise the moderation API.
91 Usage:
92 def test_example(db, moderator):
93 user, token = generate_user()
94 # ... create a host request ...
95 moderator.approve_host_request(host_request_id)
96 """
98 def __init__(self, user: User, token: str):
99 self.user = user
100 self.token = token
102 def approve_host_request(self, host_request_id: int, reason: str = "Test approval") -> None:
103 """
104 Approve a host request using the moderation API.
106 Args:
107 host_request_id: The conversation_id of the host request
108 reason: Optional reason for approval
109 """
110 with real_moderation_session(self.token) as api:
111 state_res = api.GetModerationState(
112 moderation_pb2.GetModerationStateReq(
113 object_type=moderation_pb2.MODERATION_OBJECT_TYPE_HOST_REQUEST,
114 object_id=host_request_id,
115 )
116 )
117 api.ModerateContent(
118 moderation_pb2.ModerateContentReq(
119 moderation_state_id=state_res.moderation_state.moderation_state_id,
120 action=moderation_pb2.MODERATION_ACTION_APPROVE,
121 visibility=moderation_pb2.MODERATION_VISIBILITY_VISIBLE,
122 reason=reason,
123 )
124 )
126 def approve_group_chat(self, group_chat_id: int, reason: str = "Test approval") -> None:
127 """
128 Approve a group chat using the moderation API.
130 Args:
131 group_chat_id: The conversation_id of the group chat
132 reason: Optional reason for approval
133 """
134 with real_moderation_session(self.token) as api:
135 state_res = api.GetModerationState(
136 moderation_pb2.GetModerationStateReq(
137 object_type=moderation_pb2.MODERATION_OBJECT_TYPE_GROUP_CHAT,
138 object_id=group_chat_id,
139 )
140 )
141 api.ModerateContent(
142 moderation_pb2.ModerateContentReq(
143 moderation_state_id=state_res.moderation_state.moderation_state_id,
144 action=moderation_pb2.MODERATION_ACTION_APPROVE,
145 visibility=moderation_pb2.MODERATION_VISIBILITY_VISIBLE,
146 reason=reason,
147 )
148 )