Coverage for app/backend/src/tests/test_conversations.py: 100%
1435 statements
« prev ^ index » next coverage.py v7.16.1, created at 2026-09-19 15:47 +0000
« prev ^ index » next coverage.py v7.16.1, created at 2026-09-19 15:47 +0000
1from datetime import timedelta
3import grpc
4import pytest
5from google.protobuf import empty_pb2, wrappers_pb2
6from sqlalchemy import func, select
8from couchers.constants import MISSED_MESSAGES_DELAY
9from couchers.db import session_scope
10from couchers.jobs.handlers import send_message_notifications
11from couchers.jobs.worker import process_job
12from couchers.models import (
13 GroupChatRole,
14 GroupChatSubscription,
15 Notification,
16 NotificationDelivery,
17 NotificationDeliveryType,
18 NotificationTopicAction,
19 RateLimitAction,
20)
21from couchers.proto import api_pb2, conversations_pb2, notification_data_pb2, notifications_pb2
22from couchers.rate_limits.definitions import RATE_LIMIT_DEFINITIONS, RATE_LIMIT_HOURS
23from couchers.servicers import conversations
24from couchers.utils import Duration_from_timedelta, now, to_aware_datetime
25from tests.fixtures.db import generate_user, make_friends, make_user_block, make_user_invisible
26from tests.fixtures.misc import EmailCollector, Moderator, PushCollector, process_jobs
27from tests.fixtures.sessions import api_session, conversations_session, notifications_session
28from tests.fixtures.timewarp import Timewarp
31def test_list_group_chats(db, moderator):
32 user1, token1 = generate_user()
33 user2, token2 = generate_user()
34 user3, token3 = generate_user()
36 make_friends(user2, user1)
37 make_friends(user1, user3)
39 with conversations_session(token1) as c:
40 # no threads initially
41 res = c.ListGroupChats(conversations_pb2.ListGroupChatsReq())
42 assert len(res.group_chats) == 0
44 # create some group chats with messages
45 res = c.CreateGroupChat(
46 conversations_pb2.CreateGroupChatReq(
47 recipient_user_ids=[user2.id], title=wrappers_pb2.StringValue(value="Test title")
48 )
49 )
50 group_chat1_id = res.group_chat_id
51 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=res.group_chat_id, text="Test message 1"))
52 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=res.group_chat_id, text="Test message 2"))
53 res = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id, user3.id]))
54 group_chat2_id = res.group_chat_id
55 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=res.group_chat_id, text="Test group message 1"))
56 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=res.group_chat_id, text="Test group message 2"))
58 moderator.approve_group_chat(group_chat1_id)
59 moderator.approve_group_chat(group_chat2_id)
61 with conversations_session(token1) as c:
62 res = c.ListGroupChats(conversations_pb2.ListGroupChatsReq())
63 assert len(res.group_chats) == 2
64 assert res.no_more
66 with conversations_session(token2) as c:
67 res = c.ListGroupChats(conversations_pb2.ListGroupChatsReq())
68 assert len(res.group_chats) == 2
69 assert res.no_more
71 with conversations_session(token3) as c:
72 res = c.ListGroupChats(conversations_pb2.ListGroupChatsReq())
73 assert len(res.group_chats) == 1
74 assert res.no_more
76 # Test archive filtering: archive group_chat1 for user1
77 with conversations_session(token1) as c:
78 res = c.SetGroupChatArchiveStatus(
79 conversations_pb2.SetGroupChatArchiveStatusReq(group_chat_id=group_chat1_id, is_archived=True)
80 )
81 assert res.group_chat_id == group_chat1_id
82 assert res.is_archived
84 with conversations_session(token1) as c:
85 # Without filter, returns all chats
86 res = c.ListGroupChats(conversations_pb2.ListGroupChatsReq())
87 assert len(res.group_chats) == 2
89 # only_archived=False returns non-archived chats only
90 res = c.ListGroupChats(conversations_pb2.ListGroupChatsReq(only_archived=False))
91 assert len(res.group_chats) == 1
93 # only_archived=True returns archived chats only
94 res = c.ListGroupChats(conversations_pb2.ListGroupChatsReq(only_archived=True))
95 assert len(res.group_chats) == 1
97 # user2 should still see both as non-archived (archive is per-user)
98 with conversations_session(token2) as c:
99 res = c.ListGroupChats(conversations_pb2.ListGroupChatsReq(only_archived=False))
100 assert len(res.group_chats) == 2
102 res = c.ListGroupChats(conversations_pb2.ListGroupChatsReq(only_archived=True))
103 assert len(res.group_chats) == 0
106def test_list_empty_group_chats(db, moderator):
107 user1, token1 = generate_user()
108 user2, token2 = generate_user()
109 user3, token3 = generate_user()
111 make_friends(user1, user3)
112 make_friends(user2, user1)
113 make_friends(user2, user3)
115 with conversations_session(token1) as c:
116 res = c.ListGroupChats(conversations_pb2.ListGroupChatsReq())
117 assert len(res.group_chats) == 0
119 res1 = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id]))
120 res2 = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id, user3.id]))
122 moderator.approve_group_chat(res1.group_chat_id)
123 moderator.approve_group_chat(res2.group_chat_id)
125 with conversations_session(token1) as c:
126 res = c.ListGroupChats(conversations_pb2.ListGroupChatsReq())
127 assert len(res.group_chats) == 2
128 assert res.no_more
130 with conversations_session(token2) as c:
131 res = c.ListGroupChats(conversations_pb2.ListGroupChatsReq())
132 assert len(res.group_chats) == 2
133 assert res.no_more
135 res3 = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user3.id]))
136 moderator.approve_group_chat(res3.group_chat_id)
138 res = c.ListGroupChats(conversations_pb2.ListGroupChatsReq())
139 assert len(res.group_chats) == 3
140 assert res.no_more
142 with conversations_session(token3) as c:
143 res = c.ListGroupChats(conversations_pb2.ListGroupChatsReq())
144 assert len(res.group_chats) == 2
145 assert res.no_more
148def test_list_group_chats_ordering(db, moderator):
149 # user is member of 5 group chats, order them correctly
150 user, token = generate_user()
151 user2, token2 = generate_user()
152 user3, token3 = generate_user()
153 user4, token4 = generate_user()
155 make_friends(user2, user)
156 make_friends(user2, user3)
157 make_friends(user2, user4)
158 make_friends(user3, user)
159 make_friends(user3, user4)
160 make_friends(user, user4)
162 chat_ids = []
164 with conversations_session(token2) as c:
165 res = c.CreateGroupChat(
166 conversations_pb2.CreateGroupChatReq(
167 recipient_user_ids=[user.id], title=wrappers_pb2.StringValue(value="Chat 0")
168 )
169 )
170 chat_ids.append(res.group_chat_id)
171 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=res.group_chat_id, text="Test message"))
172 res = c.CreateGroupChat(
173 conversations_pb2.CreateGroupChatReq(
174 recipient_user_ids=[user.id, user3.id], title=wrappers_pb2.StringValue(value="Chat 1")
175 )
176 )
177 chat_ids.append(res.group_chat_id)
178 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=res.group_chat_id, text="Test message"))
179 res = c.CreateGroupChat(
180 conversations_pb2.CreateGroupChatReq(
181 recipient_user_ids=[user.id, user3.id], title=wrappers_pb2.StringValue(value="Chat 2")
182 )
183 )
184 chat_ids.append(res.group_chat_id)
185 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=res.group_chat_id, text="Test message"))
187 with conversations_session(token3) as c:
188 res = c.CreateGroupChat(
189 conversations_pb2.CreateGroupChatReq(
190 recipient_user_ids=[user.id, user2.id, user4.id], title=wrappers_pb2.StringValue(value="Chat 3")
191 )
192 )
193 chat_ids.append(res.group_chat_id)
194 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=res.group_chat_id, text="Test message"))
196 for chat_id in chat_ids:
197 moderator.approve_group_chat(chat_id)
199 with conversations_session(token) as c:
200 res = c.CreateGroupChat(
201 conversations_pb2.CreateGroupChatReq(
202 recipient_user_ids=[user2.id, user3.id, user4.id], title=wrappers_pb2.StringValue(value="Chat 4")
203 )
204 )
205 moderator.approve_group_chat(res.group_chat_id)
206 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=res.group_chat_id, text="Test message"))
207 res = c.ListGroupChats(conversations_pb2.ListGroupChatsReq())
208 assert len(res.group_chats) == 5
209 assert res.group_chats[0].title == "Chat 4"
210 assert res.group_chats[1].title == "Chat 3"
211 assert res.group_chats[2].title == "Chat 2"
212 assert res.group_chats[3].title == "Chat 1"
213 assert res.group_chats[4].title == "Chat 0"
215 c.SendMessage(
216 conversations_pb2.SendMessageReq(group_chat_id=res.group_chats[3].group_chat_id, text="Test message 2a")
217 )
218 c.SendMessage(
219 conversations_pb2.SendMessageReq(group_chat_id=res.group_chats[2].group_chat_id, text="Test message 2b")
220 )
222 res = c.ListGroupChats(conversations_pb2.ListGroupChatsReq())
223 assert len(res.group_chats) == 5
224 assert res.group_chats[0].title == "Chat 2"
225 assert res.group_chats[0].latest_message.text.text == "Test message 2b"
226 assert res.group_chats[1].title == "Chat 1"
227 assert res.group_chats[1].latest_message.text.text == "Test message 2a"
228 assert res.group_chats[2].title == "Chat 4"
229 assert res.group_chats[3].title == "Chat 3"
230 assert res.group_chats[4].title == "Chat 0"
233def test_list_group_chats_ordering_after_left(db, moderator):
234 # user is member to 4 group chats, and has left one.
235 # The one user left has the most recent message, but user left before then,
236 # this should display as e.g. 3rd most recent depending on last message when they were in the chat
237 user, token = generate_user()
238 user2, token2 = generate_user()
239 user3, token3 = generate_user()
240 user4, token4 = generate_user()
242 make_friends(user2, user)
243 make_friends(user2, user3)
244 make_friends(user2, user4)
245 make_friends(user3, user)
246 make_friends(user3, user4)
247 make_friends(user, user4)
249 chat_ids = []
251 with conversations_session(token2) as c:
252 res = c.CreateGroupChat(
253 conversations_pb2.CreateGroupChatReq(
254 recipient_user_ids=[user.id], title=wrappers_pb2.StringValue(value="Chat 0")
255 )
256 )
257 chat_ids.append(res.group_chat_id)
258 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=res.group_chat_id, text="Test message"))
259 res = c.CreateGroupChat(
260 conversations_pb2.CreateGroupChatReq(
261 recipient_user_ids=[user.id, user3.id], title=wrappers_pb2.StringValue(value="Left Chat 1")
262 )
263 )
264 left_chat_id = res.group_chat_id
265 chat_ids.append(left_chat_id)
266 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=left_chat_id, text="Test message"))
267 res = c.CreateGroupChat(
268 conversations_pb2.CreateGroupChatReq(
269 recipient_user_ids=[user.id, user3.id], title=wrappers_pb2.StringValue(value="Chat 2")
270 )
271 )
272 chat2_id = res.group_chat_id
273 chat_ids.append(chat2_id)
274 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=chat2_id, text="Test message"))
276 with conversations_session(token3) as c:
277 res = c.CreateGroupChat(
278 conversations_pb2.CreateGroupChatReq(
279 recipient_user_ids=[user.id, user2.id, user4.id], title=wrappers_pb2.StringValue(value="Chat 3")
280 )
281 )
282 chat_ids.append(res.group_chat_id)
283 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=res.group_chat_id, text="Test message"))
285 for chat_id in chat_ids:
286 moderator.approve_group_chat(chat_id)
288 with conversations_session(token) as c:
289 res = c.CreateGroupChat(
290 conversations_pb2.CreateGroupChatReq(
291 recipient_user_ids=[user2.id, user3.id, user4.id], title=wrappers_pb2.StringValue(value="Chat 4")
292 )
293 )
294 moderator.approve_group_chat(res.group_chat_id)
295 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=res.group_chat_id, text="Test message"))
297 # leave chat
298 c.LeaveGroupChat(conversations_pb2.LeaveGroupChatReq(group_chat_id=left_chat_id))
300 with conversations_session(token3) as c:
301 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=chat2_id, text="Test message"))
302 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=left_chat_id, text="Test message"))
304 with conversations_session(token2) as c:
305 # other user sends a message to that chat
306 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=left_chat_id, text="Another test message"))
307 res = c.ListGroupChats(conversations_pb2.ListGroupChatsReq())
308 assert len(res.group_chats) == 5
309 assert res.group_chats[0].title == "Left Chat 1"
310 assert res.group_chats[1].title == "Chat 2"
311 assert res.group_chats[2].title == "Chat 4"
312 assert res.group_chats[3].title == "Chat 3"
313 assert res.group_chats[4].title == "Chat 0"
315 with conversations_session(token) as c:
316 # we can't see the new message since we left before it was sent
317 res = c.ListGroupChats(conversations_pb2.ListGroupChatsReq())
318 assert len(res.group_chats) == 5
319 assert res.group_chats[0].title == "Chat 2"
320 assert res.group_chats[1].title == "Left Chat 1"
321 assert res.group_chats[2].title == "Chat 4"
322 assert res.group_chats[3].title == "Chat 3"
323 assert res.group_chats[4].title == "Chat 0"
326def test_get_group_chat_messages(db):
327 user1, token1 = generate_user()
328 user2, token2 = generate_user()
329 user3, token3 = generate_user()
331 make_friends(user1, user2)
333 with conversations_session(token1) as c:
334 # create some threads with messages
335 res = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id]))
336 group_chat_id = res.group_chat_id
337 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=res.group_chat_id, text="Test message 1"))
338 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=res.group_chat_id, text="Test message 2"))
340 res = c.GetGroupChatMessages(conversations_pb2.GetGroupChatMessagesReq(group_chat_id=group_chat_id))
341 # created + 2 normal
342 assert len(res.messages) == 3
343 assert res.no_more
345 assert res.messages[0].text.text == "Test message 2"
346 assert res.messages[1].text.text == "Test message 1"
347 assert res.messages[2].WhichOneof("content") == "chat_created"
349 # test that another user can't access the thread
350 with conversations_session(token3) as c:
351 res = c.GetGroupChatMessages(conversations_pb2.GetGroupChatMessagesReq(group_chat_id=group_chat_id))
352 assert len(res.messages) == 0
355def test_get_group_chat_messages_pagination(db, moderator):
356 user1, token1 = generate_user()
357 user2, token2 = generate_user()
358 make_friends(user1, user2)
360 with conversations_session(token1) as c:
361 res = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id]))
362 group_chat_id = res.group_chat_id
363 for i in range(30):
364 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=group_chat_id, text=str(i)))
366 moderator.approve_group_chat(group_chat_id)
368 with conversations_session(token2) as c:
369 res = c.GetGroupChatMessages(conversations_pb2.GetGroupChatMessagesReq(group_chat_id=group_chat_id))
370 # pagination
371 assert len(res.messages) == 20
372 assert res.messages[0].text.text == "29"
373 assert res.messages[19].text.text == "10"
374 assert not res.no_more
375 res = c.GetGroupChatMessages(
376 conversations_pb2.GetGroupChatMessagesReq(
377 group_chat_id=group_chat_id, last_message_id=res.messages[19].message_id
378 )
379 )
380 assert len(res.messages) == 11
381 assert res.messages[0].text.text == "9"
382 assert res.messages[9].text.text == "0"
383 assert res.no_more
386def test_get_group_chat_messages_joined_left(db, moderator):
387 user1, token1 = generate_user()
388 user2, token2 = generate_user()
389 user3, token3 = generate_user()
390 user4, token4 = generate_user()
391 make_friends(user1, user2)
392 make_friends(user1, user3)
393 make_friends(user1, user4)
395 with conversations_session(token1) as c:
396 res = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id, user4.id]))
397 group_chat_id = res.group_chat_id
398 moderator.approve_group_chat(group_chat_id)
400 for i in range(10):
401 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=group_chat_id, text=str(i)))
403 c.InviteToGroupChat(conversations_pb2.InviteToGroupChatReq(group_chat_id=group_chat_id, user_id=user3.id))
405 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=group_chat_id, text="10"))
407 res = c.GetGroupChatMessages(conversations_pb2.GetGroupChatMessagesReq(group_chat_id=group_chat_id))
409 # created + 10 normal + invited + normal
410 assert len(res.messages) == 13
412 with conversations_session(token3) as c:
413 # can only see last message after invited
414 res = c.GetGroupChatMessages(conversations_pb2.GetGroupChatMessagesReq(group_chat_id=group_chat_id))
415 # joined + normal
416 assert len(res.messages) == 2
417 assert res.messages[0].text.text == "10"
419 c.LeaveGroupChat(conversations_pb2.LeaveGroupChatReq(group_chat_id=group_chat_id))
421 with conversations_session(token1) as c:
422 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=group_chat_id, text="11"))
423 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=group_chat_id, text="12"))
425 c.InviteToGroupChat(conversations_pb2.InviteToGroupChatReq(group_chat_id=group_chat_id, user_id=user3.id))
427 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=group_chat_id, text="13"))
428 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=group_chat_id, text="14"))
430 with conversations_session(token3) as c:
431 # can only see last message after invited
432 res = c.GetGroupChatMessages(conversations_pb2.GetGroupChatMessagesReq(group_chat_id=group_chat_id))
433 # joined + normal + left + invite + 2 normal
434 assert len(res.messages) == 6
435 assert res.messages[0].text.text == "14"
436 assert res.messages[1].text.text == "13"
437 assert res.messages[2].WhichOneof("content") == "user_invited"
438 assert res.messages[3].WhichOneof("content") == "user_left"
439 assert res.messages[4].text.text == "10"
440 assert res.messages[5].WhichOneof("content") == "user_invited"
443def test_get_group_chat_info(db):
444 user1, token1 = generate_user()
445 user2, token2 = generate_user()
446 user3, token3 = generate_user()
448 make_friends(user1, user2)
449 make_friends(user3, user1)
451 with conversations_session(token1) as c:
452 # create some threads with messages
453 res = c.CreateGroupChat(
454 conversations_pb2.CreateGroupChatReq(
455 recipient_user_ids=[user2.id], title=wrappers_pb2.StringValue(value="Test title")
456 )
457 )
458 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=res.group_chat_id, text="Test message 1"))
459 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=res.group_chat_id, text="Test message 2"))
460 group_chat1_id = res.group_chat_id
461 res = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id, user3.id]))
462 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=res.group_chat_id, text="Test group message 1"))
463 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=res.group_chat_id, text="Test group message 2"))
464 group_chat2_id = res.group_chat_id
466 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=group_chat1_id))
467 assert res.title == "Test title"
468 assert user2.id in res.member_user_ids
469 assert user1.id in res.admin_user_ids
470 assert to_aware_datetime(res.created) <= now()
471 assert res.only_admins_invite
472 assert res.is_dm
473 assert not res.is_archived
475 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=group_chat2_id))
476 assert not res.title
477 assert user2.id in res.member_user_ids
478 assert user3.id in res.member_user_ids
479 assert user1.id in res.admin_user_ids
480 assert to_aware_datetime(res.created) <= now()
481 assert res.only_admins_invite
482 assert not res.is_dm
483 assert not res.is_archived
486def test_get_group_chat_info_denied(db):
487 user1, token1 = generate_user()
488 user2, token2 = generate_user()
489 user3, token3 = generate_user()
490 user4, token4 = generate_user()
492 make_friends(user1, user2)
493 make_friends(user3, user1)
495 with conversations_session(token1) as c:
496 # create a group chat with messages
497 res = c.CreateGroupChat(
498 conversations_pb2.CreateGroupChatReq(
499 recipient_user_ids=[user2.id, user3.id], title=wrappers_pb2.StringValue(value="Test title")
500 )
501 )
502 group_chat_id = res.group_chat_id
503 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=group_chat_id, text="Test message 1"))
505 with conversations_session(token4) as c:
506 with pytest.raises(grpc.RpcError) as e:
507 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=group_chat_id))
508 assert e.value.code() == grpc.StatusCode.NOT_FOUND
511def test_get_group_chat_info_left(db, moderator):
512 user1, token1 = generate_user()
513 user2, token2 = generate_user()
514 user3, token3 = generate_user()
515 user4, token4 = generate_user()
517 make_friends(user1, user2)
518 make_friends(user3, user1)
519 make_friends(user1, user4)
521 with conversations_session(token1) as c:
522 # create a group chat with messages
523 res = c.CreateGroupChat(
524 conversations_pb2.CreateGroupChatReq(
525 recipient_user_ids=[user2.id, user3.id], title=wrappers_pb2.StringValue(value="Test title")
526 )
527 )
528 group_chat_id = res.group_chat_id
529 moderator.approve_group_chat(group_chat_id)
530 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=group_chat_id, text="Test message 1"))
532 with conversations_session(token3) as c:
533 c.LeaveGroupChat(conversations_pb2.LeaveGroupChatReq(group_chat_id=group_chat_id))
535 with conversations_session(token1) as c:
536 c.InviteToGroupChat(conversations_pb2.InviteToGroupChatReq(group_chat_id=group_chat_id, user_id=user4.id))
538 with conversations_session(token3) as c:
539 # this user left when user4 wasn't a member,
540 # so the returned members should be user1, user2, and user3 only
541 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=group_chat_id))
542 assert len(res.member_user_ids) == 3
543 assert user1.id in res.member_user_ids
544 assert user2.id in res.member_user_ids
545 assert user3.id in res.member_user_ids
548def test_remove_group_chat_user(db):
549 # create 3 uses and connect them
550 user1, token1 = generate_user()
551 user2, token2 = generate_user()
552 user3, token3 = generate_user()
553 make_friends(user1, user2)
554 make_friends(user1, user3)
556 # using user token, create a Conversations API for testing
557 with conversations_session(token1) as c:
558 # create a group chat
559 res = c.CreateGroupChat(
560 conversations_pb2.CreateGroupChatReq(
561 recipient_user_ids=[user2.id, user3.id], title=wrappers_pb2.StringValue(value="Test title")
562 )
563 )
564 group_chat_id = res.group_chat_id
566 # remove a user from group
567 c.RemoveGroupChatUser(conversations_pb2.RemoveGroupChatUserReq(group_chat_id=group_chat_id, user_id=user2.id))
568 assert user3.id in res.member_user_ids # other users are still in the group
570 # can't remove the same user twice
571 with pytest.raises(grpc.RpcError) as e:
572 c.RemoveGroupChatUser(
573 conversations_pb2.RemoveGroupChatUserReq(group_chat_id=group_chat_id, user_id=user2.id)
574 )
575 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
578def test_edit_group_chat(db, moderator):
579 user1, token1 = generate_user()
580 user2, token2 = generate_user()
581 user3, token3 = generate_user()
582 make_friends(user1, user2)
584 with conversations_session(token1) as c:
585 # create some threads with messages
586 res = c.CreateGroupChat(
587 conversations_pb2.CreateGroupChatReq(
588 recipient_user_ids=[user2.id], title=wrappers_pb2.StringValue(value="Test title")
589 )
590 )
591 group_chat_id = res.group_chat_id
592 moderator.approve_group_chat(group_chat_id)
594 c.EditGroupChat(
595 conversations_pb2.EditGroupChatReq(
596 group_chat_id=group_chat_id,
597 title=wrappers_pb2.StringValue(value="Modified title"),
598 only_admins_invite=wrappers_pb2.BoolValue(value=False),
599 )
600 )
601 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=group_chat_id))
602 assert res.title == "Modified title"
603 assert not res.only_admins_invite
605 # make sure non-admin is not allowed to modify
606 with conversations_session(token2) as c:
607 with pytest.raises(grpc.RpcError) as e:
608 c.EditGroupChat(
609 conversations_pb2.EditGroupChatReq(
610 group_chat_id=group_chat_id,
611 title=wrappers_pb2.StringValue(value="Other title"),
612 only_admins_invite=wrappers_pb2.BoolValue(value=True),
613 )
614 )
615 assert e.value.code() == grpc.StatusCode.PERMISSION_DENIED
617 # make sure non-recipient is not allowed to modify
618 with conversations_session(token3) as c:
619 with pytest.raises(grpc.RpcError) as e:
620 c.EditGroupChat(
621 conversations_pb2.EditGroupChatReq(
622 group_chat_id=group_chat_id,
623 title=wrappers_pb2.StringValue(value="Other title"),
624 only_admins_invite=wrappers_pb2.BoolValue(value=True),
625 )
626 )
627 assert e.value.code() == grpc.StatusCode.NOT_FOUND
630def test_make_remove_group_chat_admin(db, moderator):
631 user1, token1 = generate_user()
632 user2, token2 = generate_user()
633 user3, token3 = generate_user()
635 make_friends(user1, user2)
636 make_friends(user1, user3)
637 make_friends(user2, user3)
639 with conversations_session(token1) as c:
640 # create some threads with messages
641 res = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id, user3.id]))
642 group_chat_id = res.group_chat_id
643 moderator.approve_group_chat(group_chat_id)
645 # shouldn't be able to remove only admin
646 with pytest.raises(grpc.RpcError) as e:
647 c.RemoveGroupChatAdmin(
648 conversations_pb2.RemoveGroupChatAdminReq(group_chat_id=group_chat_id, user_id=user1.id)
649 )
650 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
651 assert e.value.details() == "You can't remove the last admin."
653 c.MakeGroupChatAdmin(conversations_pb2.MakeGroupChatAdminReq(group_chat_id=group_chat_id, user_id=user2.id))
655 # shouldn't be able to make admin again
656 with pytest.raises(grpc.RpcError) as e:
657 c.MakeGroupChatAdmin(conversations_pb2.MakeGroupChatAdminReq(group_chat_id=group_chat_id, user_id=user2.id))
658 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
659 assert e.value.details() == "That user is already an admin."
661 with conversations_session(token2) as c:
662 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=group_chat_id))
663 assert user1.id in res.admin_user_ids
664 assert user2.id in res.admin_user_ids
666 with conversations_session(token1) as c:
667 c.RemoveGroupChatAdmin(conversations_pb2.RemoveGroupChatAdminReq(group_chat_id=group_chat_id, user_id=user2.id))
669 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=group_chat_id))
670 assert user1.id in res.admin_user_ids
671 assert user2.id not in res.admin_user_ids
673 with conversations_session(token2) as c:
674 # shouldn't be able to make admin if not admin
675 with pytest.raises(grpc.RpcError) as e:
676 c.MakeGroupChatAdmin(conversations_pb2.MakeGroupChatAdminReq(group_chat_id=group_chat_id, user_id=user3.id))
677 assert e.value.code() == grpc.StatusCode.PERMISSION_DENIED
680def test_send_message(db, moderator: Moderator, push_collector: PushCollector, email_collector: EmailCollector):
681 user1, token1 = generate_user()
682 user2, token2 = generate_user()
683 user3, token3 = generate_user()
684 make_friends(user1, user2)
685 make_friends(user1, user3)
687 # Let user2 receive email notifications for every chat message
688 with notifications_session(token2) as n:
689 n.SetNotificationSettings(
690 notifications_pb2.SetNotificationSettingsReq(
691 preferences=[
692 notifications_pb2.SingleNotificationPreference(
693 topic=NotificationTopicAction.chat__message.topic,
694 action=NotificationTopicAction.chat__message.action,
695 delivery_method="email",
696 enabled=True,
697 )
698 ]
699 )
700 )
702 group_chat_title = "My group chat"
703 message1 = "Test message 1"
705 # Let user1 create a group chat with user2 and send a message
706 with conversations_session(token1) as c:
707 res = c.CreateGroupChat(
708 conversations_pb2.CreateGroupChatReq(
709 title=wrappers_pb2.StringValue(value=group_chat_title), recipient_user_ids=[user2.id]
710 )
711 )
712 group_chat_id = res.group_chat_id
713 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=group_chat_id, text=message1))
715 # Sender can already see group chat
716 res = c.GetGroupChatMessages(conversations_pb2.GetGroupChatMessagesReq(group_chat_id=group_chat_id))
717 assert res.messages[0].text.text == message1
718 assert to_aware_datetime(res.messages[0].time) <= now()
719 assert res.messages[0].author_user_id == user1.id
721 # user2 sees nothing until the group chat is approved
722 assert push_collector.count_for_user(user2.id) == 0
723 assert email_collector.count_for_recipient(user2.email) == 0
725 moderator.approve_group_chat(group_chat_id)
727 # user2 gets email and push notifications
728 push = push_collector.pop_for_user(user2.id, last=True)
729 assert push.topic_action == NotificationTopicAction.chat__message.display
730 assert user1.name in push.content.title
731 assert group_chat_title in push.content.title
732 assert push.content.ios_title == user1.name
733 assert push.content.ios_subtitle == group_chat_title
734 assert message1 in push.content.body
736 email = email_collector.pop_for_recipient(user2.email, last=True)
737 assert group_chat_title in email.subject
738 assert message1 in email.plain
739 assert message1 in email.html
741 # can't send message if not in chat
742 with conversations_session(token3) as c:
743 with pytest.raises(grpc.RpcError) as e:
744 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=group_chat_id, text="Test message 2"))
745 assert e.value.code() == grpc.StatusCode.NOT_FOUND
747 make_user_block(user2, user1)
748 with conversations_session(token1) as c:
749 with pytest.raises(grpc.RpcError) as e:
750 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=group_chat_id, text="Message after block"))
751 assert e.value.details() == "You can't send a message in this chat."
754def test_send_direct_message(db, moderator: Moderator, push_collector: PushCollector, email_collector: EmailCollector):
755 user1, token1 = generate_user(complete_profile=True)
756 user2, token2 = generate_user(complete_profile=True)
758 make_friends(user1, user2)
760 # Let user2 receive email notifications for every chat message
761 with notifications_session(token2) as n:
762 n.SetNotificationSettings(
763 notifications_pb2.SetNotificationSettingsReq(
764 preferences=[
765 notifications_pb2.SingleNotificationPreference(
766 topic=NotificationTopicAction.chat__message.topic,
767 action=NotificationTopicAction.chat__message.action,
768 delivery_method="email",
769 enabled=True,
770 )
771 ]
772 )
773 )
775 # Let user1 send two DM's to user2
776 message1 = "Hello, user2!"
777 message2 = "One more message."
779 with conversations_session(token1) as c1:
780 # Send a DM from user1 to user2
781 res = c1.SendDirectMessage(conversations_pb2.SendDirectMessageReq(recipient_user_id=user2.id, text=message1))
782 moderator.approve_group_chat(res.group_chat_id)
784 c1.SendDirectMessage(conversations_pb2.SendDirectMessageReq(recipient_user_id=user2.id, text=message2))
786 # user2 should have received push and email notifications for both messages
787 push = push_collector.pop_for_user(user2.id, last=False)
788 assert push.topic_action == NotificationTopicAction.chat__message.display
789 assert push.content.title == user1.name
790 assert push.content.body == message1
792 email = email_collector.pop_for_recipient(user2.email, last=False)
793 assert user1.name in email.subject
794 assert message1 in email.plain
795 assert message1 in email.html
797 push = push_collector.pop_for_user(user2.id, last=True)
798 assert push.topic_action == NotificationTopicAction.chat__message.display
799 assert push.content.title == user1.name
800 assert push.content.body == message2
802 email = email_collector.pop_for_recipient(user2.email, last=True)
803 assert user1.name in email.subject
804 assert message2 in email.plain
805 assert message2 in email.html
807 with conversations_session(token2) as c2:
808 # Fetch the chat by ID returned from SendDirectMessage
809 chat = c2.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=res.group_chat_id))
811 assert chat.is_dm
812 group_chat_id = chat.group_chat_id
814 # Verify that the messages was received
815 messages = c2.GetGroupChatMessages(
816 conversations_pb2.GetGroupChatMessagesReq(group_chat_id=group_chat_id)
817 ).messages
819 assert len(messages) == 2
820 assert messages[0].text.text == message2
821 assert messages[1].text.text == message1
822 assert messages[0].author_user_id == user1.id
825def test_excessive_chat_initiations_are_reported(db, low_rate_limits, email_collector: EmailCollector):
826 """Test that excessive chat initiations are first reported in a warning email and finally lead blocking of further contacting other users."""
827 user, token = generate_user()
828 rate_limit_definition = RATE_LIMIT_DEFINITIONS[RateLimitAction.chat_initiation]
829 with conversations_session(token) as c:
830 # Test warning email
831 for _ in range(rate_limit_definition.warning_limit):
832 recipient_user, _ = generate_user()
833 _ = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[recipient_user.id]))
835 assert email_collector.count_for_reports() == 0
837 recipient_user, _ = generate_user()
838 _ = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[recipient_user.id]))
840 email = email_collector.pop_for_reports(last=True)
841 assert email.plain.startswith(
842 f"User {user.username} has sent {rate_limit_definition.warning_limit} chat initiations in the past {RATE_LIMIT_HOURS} hours."
843 )
845 # Test new chat initiations fail after exceeding CHAT_INITIATION_HARD_LIMIT
846 for _ in range(rate_limit_definition.hard_limit - rate_limit_definition.warning_limit - 1):
847 recipient_user, _ = generate_user()
848 _ = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[recipient_user.id]))
850 assert email_collector.count_for_reports() == 0
852 recipient_user, _ = generate_user()
853 with pytest.raises(grpc.RpcError) as exc_info:
854 _ = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[recipient_user.id]))
855 assert exc_info.value.code() == grpc.StatusCode.RESOURCE_EXHAUSTED
856 assert (
857 exc_info.value.details()
858 == "You have messaged a lot of users in the past 24 hours. To avoid spam, you can't contact any more users for now."
859 )
861 email = email_collector.pop_for_reports(last=True)
862 assert email.plain.startswith(
863 f"User {user.username} has sent {rate_limit_definition.hard_limit} chat initiations in the past {RATE_LIMIT_HOURS} hours."
864 )
865 assert "The user has been blocked from sending further chat initiations for now." in email.plain
868def test_send_direct_message_rate_limit(db, low_rate_limits, moderator, email_collector: EmailCollector):
869 """SendDirectMessage should enforce the chat_initiation rate limit when creating a new DM, but not when sending into an existing one."""
870 user, token = generate_user(complete_profile=True)
871 rate_limit_definition = RATE_LIMIT_DEFINITIONS[RateLimitAction.chat_initiation]
873 with conversations_session(token) as c:
874 for _ in range(rate_limit_definition.warning_limit):
875 recipient, _ = generate_user()
876 c.SendDirectMessage(conversations_pb2.SendDirectMessageReq(recipient_user_id=recipient.id, text="hi"))
878 assert email_collector.count_for_reports() == 0
880 recipient, _ = generate_user()
881 existing_dm_recipient_id = recipient.id
882 c.SendDirectMessage(
883 conversations_pb2.SendDirectMessageReq(recipient_user_id=existing_dm_recipient_id, text="hi")
884 )
886 email = email_collector.pop_for_reports(last=True)
887 assert email.plain.startswith(
888 f"User {user.username} has sent {rate_limit_definition.warning_limit} chat initiations in the past {RATE_LIMIT_HOURS} hours."
889 )
891 for _ in range(rate_limit_definition.hard_limit - rate_limit_definition.warning_limit - 1):
892 recipient, _ = generate_user()
893 c.SendDirectMessage(conversations_pb2.SendDirectMessageReq(recipient_user_id=recipient.id, text="hi"))
895 assert email_collector.count_for_reports() == 0
897 # follow-up into an existing DM must not count as a new initiation
898 c.SendDirectMessage(
899 conversations_pb2.SendDirectMessageReq(recipient_user_id=existing_dm_recipient_id, text="follow-up")
900 )
901 assert email_collector.count_for_reports() == 0
903 recipient, _ = generate_user()
904 with pytest.raises(grpc.RpcError) as exc_info:
905 c.SendDirectMessage(conversations_pb2.SendDirectMessageReq(recipient_user_id=recipient.id, text="hi"))
906 assert exc_info.value.code() == grpc.StatusCode.RESOURCE_EXHAUSTED
907 assert (
908 exc_info.value.details()
909 == "You have messaged a lot of users in the past 24 hours. To avoid spam, you can't contact any more users for now."
910 )
912 email = email_collector.pop_for_reports(last=True)
913 assert email.plain.startswith(
914 f"User {user.username} has sent {rate_limit_definition.hard_limit} chat initiations in the past {RATE_LIMIT_HOURS} hours."
915 )
916 assert "The user has been blocked from sending further chat initiations for now." in email.plain
919def test_leave_invite_to_group_chat(db, moderator):
920 user1, token1 = generate_user()
921 user2, token2 = generate_user()
922 user3, token3 = generate_user()
923 user4, token4 = generate_user()
924 user5, token5 = generate_user()
925 user6, token6 = generate_user(delete_user=True)
926 user7, token7 = generate_user()
927 user8, token8 = generate_user()
929 make_friends(user1, user2)
930 make_friends(user1, user3)
931 make_friends(user1, user5)
932 make_friends(user1, user7)
933 make_friends(user1, user8)
934 make_friends(user2, user3)
935 make_friends(user4, user3)
936 make_user_block(user1, user7)
937 make_user_block(user8, user1)
939 with conversations_session(token1) as c:
940 res = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id, user5.id]))
941 group_chat_id = res.group_chat_id
942 moderator.approve_group_chat(group_chat_id)
943 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=group_chat_id, text="Test message 1"))
945 # other user not in chat
946 with conversations_session(token3) as c:
947 with pytest.raises(grpc.RpcError) as e:
948 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=group_chat_id))
949 assert e.value.code() == grpc.StatusCode.NOT_FOUND
950 with pytest.raises(grpc.RpcError) as e:
951 res = c.InviteToGroupChat(
952 conversations_pb2.InviteToGroupChatReq(group_chat_id=group_chat_id, user_id=user4.id)
953 )
954 assert e.value.code() == grpc.StatusCode.NOT_FOUND
955 with pytest.raises(grpc.RpcError) as e:
956 res = c.LeaveGroupChat(conversations_pb2.LeaveGroupChatReq(group_chat_id=group_chat_id))
957 assert e.value.code() == grpc.StatusCode.NOT_FOUND
959 with conversations_session(token2) as c:
960 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=group_chat_id))
961 assert user3.id not in res.member_user_ids
963 # only_admins_invite defaults to true so shouldn't be able to invite
964 with pytest.raises(grpc.RpcError) as e:
965 res = c.InviteToGroupChat(
966 conversations_pb2.InviteToGroupChatReq(group_chat_id=group_chat_id, user_id=user3.id)
967 )
968 assert e.value.code() == grpc.StatusCode.PERMISSION_DENIED
969 c.LeaveGroupChat(conversations_pb2.LeaveGroupChatReq(group_chat_id=group_chat_id))
971 with conversations_session(token1) as c:
972 # invite invisible user fails
973 with pytest.raises(grpc.RpcError) as e:
974 c.InviteToGroupChat(conversations_pb2.InviteToGroupChatReq(group_chat_id=group_chat_id, user_id=user6.id))
975 assert e.value.code() == grpc.StatusCode.NOT_FOUND
976 assert e.value.details() == "Couldn't find that user."
977 # invite fake user fails
978 with pytest.raises(grpc.RpcError) as e:
979 c.InviteToGroupChat(conversations_pb2.InviteToGroupChatReq(group_chat_id=group_chat_id, user_id=999))
980 assert e.value.code() == grpc.StatusCode.NOT_FOUND
981 assert e.value.details() == "Couldn't find that user."
982 # invite blocked user fails
983 with pytest.raises(grpc.RpcError) as e:
984 c.InviteToGroupChat(conversations_pb2.InviteToGroupChatReq(group_chat_id=group_chat_id, user_id=user7.id))
985 assert e.value.code() == grpc.StatusCode.NOT_FOUND
986 assert e.value.details() == "Couldn't find that user."
987 # invite blocking user fails
988 with pytest.raises(grpc.RpcError) as e:
989 c.InviteToGroupChat(conversations_pb2.InviteToGroupChatReq(group_chat_id=group_chat_id, user_id=user8.id))
990 assert e.value.code() == grpc.StatusCode.NOT_FOUND
991 assert e.value.details() == "Couldn't find that user."
993 c.InviteToGroupChat(conversations_pb2.InviteToGroupChatReq(group_chat_id=group_chat_id, user_id=user3.id))
994 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=group_chat_id))
995 assert user1.id in res.member_user_ids
996 assert user5.id in res.member_user_ids
997 assert user3.id in res.member_user_ids
999 # test non-admin inviting
1000 c.EditGroupChat(
1001 conversations_pb2.EditGroupChatReq(
1002 group_chat_id=group_chat_id, only_admins_invite=wrappers_pb2.BoolValue(value=False)
1003 )
1004 )
1006 with conversations_session(token3) as c:
1007 c.InviteToGroupChat(conversations_pb2.InviteToGroupChatReq(group_chat_id=group_chat_id, user_id=user2.id))
1008 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=group_chat_id))
1009 assert user2.id in res.member_user_ids
1012def test_group_chats_with_messages_before_join(db, moderator):
1013 """
1014 If user 1 and 2 have a group chat and send messages, then add user 3; user 3
1015 should still see the group chat!
1016 """
1017 user1, token1 = generate_user()
1018 user2, token2 = generate_user()
1019 user3, token3 = generate_user()
1020 user4, token4 = generate_user()
1022 make_friends(user1, user2)
1023 make_friends(user1, user3)
1024 make_friends(user2, user3)
1025 make_friends(user1, user4)
1027 with conversations_session(token1) as c:
1028 res = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id, user4.id]))
1029 group_chat_id = res.group_chat_id
1030 moderator.approve_group_chat(group_chat_id)
1031 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=group_chat_id, text="Test message 1"))
1033 with conversations_session(token2) as c:
1034 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=group_chat_id, text="Test message 2"))
1036 with conversations_session(token1) as c:
1037 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=group_chat_id, text="Test message 3"))
1039 c.InviteToGroupChat(conversations_pb2.InviteToGroupChatReq(group_chat_id=group_chat_id, user_id=user3.id))
1041 with conversations_session(token3) as c:
1042 # should work
1043 c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=group_chat_id))
1045 res = c.ListGroupChats(conversations_pb2.ListGroupChatsReq())
1046 assert len(res.group_chats) == 1
1049def test_invite_to_dm(db):
1050 user1, token1 = generate_user()
1051 user2, token2 = generate_user()
1052 user3, token3 = generate_user()
1054 make_friends(user1, user2)
1055 make_friends(user1, user3)
1056 make_friends(user2, user3)
1058 with conversations_session(token1) as c:
1059 res = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id]))
1060 group_chat_id = res.group_chat_id
1061 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=group_chat_id, text="Test message 1"))
1063 # dm, shou;dn't be able to invite someone else
1064 with pytest.raises(grpc.RpcError) as e:
1065 c.InviteToGroupChat(conversations_pb2.InviteToGroupChatReq(group_chat_id=group_chat_id, user_id=user3.id))
1066 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
1067 assert e.value.details() == "You can't invite other users to a direct message."
1070def test_sole_admin_leaves(db, moderator):
1071 user1, token1 = generate_user()
1072 user2, token2 = generate_user()
1073 user3, token3 = generate_user()
1075 make_friends(user1, user2)
1076 make_friends(user1, user3)
1077 make_friends(user2, user3)
1079 with conversations_session(token1) as c:
1080 res = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id, user3.id]))
1081 group_chat_id = res.group_chat_id
1082 moderator.approve_group_chat(group_chat_id)
1083 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=group_chat_id, text="Test message 1"))
1085 # sole admin can't leave group chat
1086 with pytest.raises(grpc.RpcError) as e:
1087 c.LeaveGroupChat(conversations_pb2.LeaveGroupChatReq(group_chat_id=group_chat_id))
1088 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
1089 assert e.value.details() == "The last admin can't leave a group chat."
1091 with conversations_session(token2) as c:
1092 c.LeaveGroupChat(conversations_pb2.LeaveGroupChatReq(group_chat_id=group_chat_id))
1094 with conversations_session(token3) as c:
1095 c.LeaveGroupChat(conversations_pb2.LeaveGroupChatReq(group_chat_id=group_chat_id))
1097 # sole admin can leave when last in chat
1098 with conversations_session(token1) as c:
1099 c.LeaveGroupChat(conversations_pb2.LeaveGroupChatReq(group_chat_id=group_chat_id))
1102def test_search_messages(db, moderator):
1103 user1, token1 = generate_user()
1104 user2, token2 = generate_user()
1105 user3, token3 = generate_user()
1107 make_friends(user1, user2)
1108 make_friends(user1, user3)
1110 with conversations_session(token1) as c:
1111 # create some threads with messages
1112 res = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id]))
1113 gc1_id = res.group_chat_id
1114 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=gc1_id, text="Test message 1"))
1115 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=gc1_id, text="Test message 2"))
1116 res = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id, user3.id]))
1117 gc2_id = res.group_chat_id
1118 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=gc2_id, text="Test group message 3"))
1119 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=gc2_id, text="Test group message 4"))
1121 # Approve group chats so they appear in search results
1122 moderator.approve_group_chat(gc1_id)
1123 moderator.approve_group_chat(gc2_id)
1125 with conversations_session(token1) as c:
1126 res = c.SearchMessages(conversations_pb2.SearchMessagesReq(query="message "))
1127 assert len(res.results) == 4
1128 res = c.SearchMessages(conversations_pb2.SearchMessagesReq(query="group "))
1129 assert len(res.results) == 2
1130 res = c.SearchMessages(conversations_pb2.SearchMessagesReq(query="message 5"))
1131 assert len(res.results) == 0
1133 # outside user doesn't get results
1134 with conversations_session(token3) as c:
1135 res = c.SearchMessages(conversations_pb2.SearchMessagesReq(query="Test message"))
1136 assert len(res.results) == 0
1139def test_search_messages_left_joined(db, moderator):
1140 user1, token1 = generate_user()
1141 user2, token2 = generate_user()
1142 user3, token3 = generate_user()
1143 user4, token4 = generate_user()
1144 make_friends(user1, user2)
1145 make_friends(user1, user3)
1146 make_friends(user1, user4)
1148 with conversations_session(token1) as c:
1149 res = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id, user4.id]))
1150 group_chat_id = res.group_chat_id
1151 moderator.approve_group_chat(group_chat_id)
1152 for i in range(10):
1153 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=group_chat_id, text="Test message " + str(i)))
1155 c.InviteToGroupChat(conversations_pb2.InviteToGroupChatReq(group_chat_id=group_chat_id, user_id=user3.id))
1156 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=group_chat_id, text="Test message 10"))
1157 res = c.SearchMessages(conversations_pb2.SearchMessagesReq(query="Test message"))
1159 assert len(res.results) == 11
1161 with conversations_session(token3) as c:
1162 # can only see last message after invited
1163 res = c.SearchMessages(conversations_pb2.SearchMessagesReq(query="Test message"))
1165 assert len(res.results) == 1
1166 assert res.results[0].message.text.text == "Test message 10"
1168 c.LeaveGroupChat(conversations_pb2.LeaveGroupChatReq(group_chat_id=group_chat_id))
1170 with conversations_session(token1) as c:
1171 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=group_chat_id, text="Test message 11"))
1172 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=group_chat_id, text="Test message 12"))
1173 c.InviteToGroupChat(conversations_pb2.InviteToGroupChatReq(group_chat_id=group_chat_id, user_id=user3.id))
1174 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=group_chat_id, text="Test message 13"))
1175 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=group_chat_id, text="Test message 14"))
1177 with conversations_session(token3) as c:
1178 # can only see last message after invited
1179 res = c.SearchMessages(conversations_pb2.SearchMessagesReq(query="Test message"))
1180 assert len(res.results) == 3
1181 assert res.results[0].message.text.text == "Test message 14"
1182 assert res.results[1].message.text.text == "Test message 13"
1183 assert res.results[2].message.text.text == "Test message 10"
1186def test_admin_behaviour(db, moderator):
1187 user1, token1 = generate_user()
1188 user2, token2 = generate_user()
1189 user3, token3 = generate_user()
1191 make_friends(user1, user2)
1192 make_friends(user1, user3)
1193 make_friends(user2, user3)
1195 with conversations_session(token1) as c:
1196 gcid = c.CreateGroupChat(
1197 conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id, user3.id])
1198 ).group_chat_id
1199 moderator.approve_group_chat(gcid)
1200 c.MakeGroupChatAdmin(conversations_pb2.MakeGroupChatAdminReq(group_chat_id=gcid, user_id=user2.id))
1201 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=gcid))
1202 assert len(res.admin_user_ids) == 2
1203 assert user1.id in res.admin_user_ids
1204 assert user2.id in res.admin_user_ids
1206 with conversations_session(token3) as c:
1207 with pytest.raises(grpc.RpcError) as e:
1208 c.MakeGroupChatAdmin(conversations_pb2.MakeGroupChatAdminReq(group_chat_id=gcid, user_id=user3.id))
1209 assert e.value.code() == grpc.StatusCode.PERMISSION_DENIED
1210 with pytest.raises(grpc.RpcError) as e:
1211 c.RemoveGroupChatAdmin(conversations_pb2.RemoveGroupChatAdminReq(group_chat_id=gcid, user_id=user1.id))
1212 assert e.value.code() == grpc.StatusCode.PERMISSION_DENIED
1213 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=gcid))
1214 assert len(res.admin_user_ids) == 2
1215 assert user1.id in res.admin_user_ids
1216 assert user2.id in res.admin_user_ids
1218 with conversations_session(token2) as c:
1219 c.MakeGroupChatAdmin(conversations_pb2.MakeGroupChatAdminReq(group_chat_id=gcid, user_id=user3.id))
1220 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=gcid))
1221 assert len(res.admin_user_ids) == 3
1222 assert user1.id in res.admin_user_ids
1223 assert user2.id in res.admin_user_ids
1224 assert user3.id in res.admin_user_ids
1226 c.RemoveGroupChatAdmin(conversations_pb2.RemoveGroupChatAdminReq(group_chat_id=gcid, user_id=user1.id))
1227 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=gcid))
1228 assert len(res.admin_user_ids) == 2
1229 assert user2.id in res.admin_user_ids
1230 assert user3.id in res.admin_user_ids
1232 with conversations_session(token1) as c:
1233 with pytest.raises(grpc.RpcError):
1234 c.MakeGroupChatAdmin(conversations_pb2.MakeGroupChatAdminReq(group_chat_id=gcid, user_id=user1.id))
1235 with pytest.raises(grpc.RpcError):
1236 c.MakeGroupChatAdmin(conversations_pb2.MakeGroupChatAdminReq(group_chat_id=gcid, user_id=user3.id))
1237 with pytest.raises(grpc.RpcError):
1238 c.RemoveGroupChatAdmin(conversations_pb2.RemoveGroupChatAdminReq(group_chat_id=gcid, user_id=user2.id))
1239 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=gcid))
1240 assert len(res.admin_user_ids) == 2
1241 assert user2.id in res.admin_user_ids
1242 assert user3.id in res.admin_user_ids
1244 with conversations_session(token2) as c:
1245 # can demote self if there are other admins
1246 c.RemoveGroupChatAdmin(conversations_pb2.RemoveGroupChatAdminReq(group_chat_id=gcid, user_id=user2.id))
1247 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=gcid))
1248 assert len(res.admin_user_ids) == 1
1249 assert user3.id in res.admin_user_ids
1251 with conversations_session(token3) as c:
1252 with pytest.raises(grpc.RpcError) as e:
1253 c.RemoveGroupChatAdmin(conversations_pb2.RemoveGroupChatAdminReq(group_chat_id=gcid, user_id=user3.id))
1254 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
1255 assert e.value.details() == "You can't remove the last admin."
1256 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=gcid))
1257 assert len(res.admin_user_ids) == 1
1258 assert user3.id in res.admin_user_ids
1260 # last admin can't leave
1261 with pytest.raises(grpc.RpcError) as e:
1262 c.LeaveGroupChat(conversations_pb2.LeaveGroupChatReq(group_chat_id=gcid))
1263 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
1264 assert e.value.details() == "The last admin can't leave a group chat."
1266 c.MakeGroupChatAdmin(conversations_pb2.MakeGroupChatAdminReq(group_chat_id=gcid, user_id=user1.id))
1268 c.LeaveGroupChat(conversations_pb2.LeaveGroupChatReq(group_chat_id=gcid))
1270 with conversations_session(token2) as c:
1271 c.LeaveGroupChat(conversations_pb2.LeaveGroupChatReq(group_chat_id=gcid))
1273 # last participant must be admin but can leave to orphan chat
1274 with conversations_session(token1) as c:
1275 c.LeaveGroupChat(conversations_pb2.LeaveGroupChatReq(group_chat_id=gcid))
1278def test_add_remove_admin_failures(db):
1279 user1, token1 = generate_user()
1280 user2, token2 = generate_user()
1281 user3, token3 = generate_user()
1282 user4, token4 = generate_user()
1283 user5, token5 = generate_user()
1285 make_friends(user1, user2)
1286 make_friends(user1, user3)
1287 make_friends(user1, user4)
1288 make_friends(user1, user5)
1290 with conversations_session(token1) as c:
1291 gcid = c.CreateGroupChat(
1292 conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id, user3.id, user4.id, user5.id])
1293 ).group_chat_id
1295 make_user_invisible(user3.id)
1296 make_user_block(user1, user4)
1297 make_user_block(user5, user1)
1299 # make non-existent user admin
1300 with pytest.raises(grpc.RpcError) as e:
1301 c.MakeGroupChatAdmin(conversations_pb2.MakeGroupChatAdminReq(group_chat_id=gcid, user_id=999))
1302 assert e.value.code() == grpc.StatusCode.NOT_FOUND
1303 assert e.value.details() == "Couldn't find that user."
1305 # make invisible user admin
1306 with pytest.raises(grpc.RpcError) as e:
1307 c.MakeGroupChatAdmin(conversations_pb2.MakeGroupChatAdminReq(group_chat_id=gcid, user_id=user3.id))
1308 assert e.value.code() == grpc.StatusCode.NOT_FOUND
1309 assert e.value.details() == "Couldn't find that user."
1311 # make blocked user admin
1312 with pytest.raises(grpc.RpcError) as e:
1313 c.MakeGroupChatAdmin(conversations_pb2.MakeGroupChatAdminReq(group_chat_id=gcid, user_id=user4.id))
1314 assert e.value.code() == grpc.StatusCode.NOT_FOUND
1315 assert e.value.details() == "Couldn't find that user."
1317 # make blocking user admin
1318 with pytest.raises(grpc.RpcError) as e:
1319 c.MakeGroupChatAdmin(conversations_pb2.MakeGroupChatAdminReq(group_chat_id=gcid, user_id=user5.id))
1320 assert e.value.code() == grpc.StatusCode.NOT_FOUND
1321 assert e.value.details() == "Couldn't find that user."
1323 with session_scope() as session:
1324 subscriptions = (
1325 session.execute(
1326 select(GroupChatSubscription)
1327 .where(GroupChatSubscription.group_chat_id == gcid)
1328 .where(GroupChatSubscription.role == GroupChatRole.participant)
1329 )
1330 .scalars()
1331 .all()
1332 )
1334 for subscription in subscriptions:
1335 subscription.role = GroupChatRole.admin
1337 with conversations_session(token1) as c:
1338 # remove non-existent user admin
1339 with pytest.raises(grpc.RpcError) as e:
1340 c.RemoveGroupChatAdmin(conversations_pb2.RemoveGroupChatAdminReq(group_chat_id=gcid, user_id=999))
1341 assert e.value.code() == grpc.StatusCode.NOT_FOUND
1342 assert e.value.details() == "Couldn't find that user."
1344 # remove invisible admin
1345 with pytest.raises(grpc.RpcError) as e:
1346 c.RemoveGroupChatAdmin(conversations_pb2.RemoveGroupChatAdminReq(group_chat_id=gcid, user_id=user3.id))
1347 assert e.value.code() == grpc.StatusCode.NOT_FOUND
1348 assert e.value.details() == "Couldn't find that user."
1350 # remove blocked admin
1351 with pytest.raises(grpc.RpcError) as e:
1352 c.RemoveGroupChatAdmin(conversations_pb2.RemoveGroupChatAdminReq(group_chat_id=gcid, user_id=user4.id))
1353 assert e.value.code() == grpc.StatusCode.NOT_FOUND
1354 assert e.value.details() == "Couldn't find that user."
1356 # remove blocking admin
1357 with pytest.raises(grpc.RpcError) as e:
1358 c.RemoveGroupChatAdmin(conversations_pb2.RemoveGroupChatAdminReq(group_chat_id=gcid, user_id=user5.id))
1359 assert e.value.code() == grpc.StatusCode.NOT_FOUND
1360 assert e.value.details() == "Couldn't find that user."
1363def test_last_seen(db, moderator):
1364 user1, token1 = generate_user()
1365 user2, token2 = generate_user()
1366 user3, token3 = generate_user()
1368 make_friends(user1, user2)
1369 make_friends(user1, user3)
1370 make_friends(user2, user3)
1372 with conversations_session(token3) as c:
1373 # this is just here to mess up any issues we get if we pretend there's only one group chat ever
1374 gcid_distraction = c.CreateGroupChat(
1375 conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id, user1.id])
1376 ).group_chat_id
1377 moderator.approve_group_chat(gcid_distraction)
1379 with conversations_session(token1) as c:
1380 gcid = c.CreateGroupChat(
1381 conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id, user3.id])
1382 ).group_chat_id
1383 moderator.approve_group_chat(gcid)
1385 message_ids = []
1387 for i in range(6):
1388 c.SendMessage(
1389 conversations_pb2.SendMessageReq(group_chat_id=gcid_distraction, text=f"gibberish message... {i}")
1390 )
1391 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=gcid, text=f"test message {i}"))
1392 c.SendMessage(
1393 conversations_pb2.SendMessageReq(group_chat_id=gcid_distraction, text=f"gibberish message {i}")
1394 )
1396 message_ids.append(
1397 c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=gcid)).latest_message.message_id
1398 )
1400 # messages are automatically marked as seen when you send a new message
1401 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=gcid))
1402 assert res.unseen_message_count == 0
1404 with conversations_session(token2) as c:
1405 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=gcid))
1406 # created + 6 normal
1407 assert res.unseen_message_count == 7
1409 backward_offset = 3
1410 c.MarkLastSeenGroupChat(
1411 conversations_pb2.MarkLastSeenGroupChatReq(
1412 group_chat_id=gcid, last_seen_message_id=message_ids[-backward_offset - 1]
1413 )
1414 )
1416 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=gcid))
1417 assert res.unseen_message_count == backward_offset
1419 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=gcid, text="test message ..."))
1421 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=gcid))
1422 assert res.unseen_message_count == 0
1424 with conversations_session(token3) as c:
1425 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=gcid))
1426 # created + 7 normal
1427 assert res.unseen_message_count == 8
1429 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=gcid, text="test message ..."))
1431 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=gcid))
1432 assert res.unseen_message_count == 0
1435def test_mark_last_seen_clears_notifications(db, moderator):
1436 user1, token1 = generate_user()
1437 user2, token2 = generate_user()
1439 with conversations_session(token1) as c:
1440 gcid = c.CreateGroupChat(
1441 conversations_pb2.CreateGroupChatReq(
1442 recipient_user_ids=[user2.id], title=wrappers_pb2.StringValue(value="Test chat")
1443 )
1444 ).group_chat_id
1446 moderator.approve_group_chat(gcid)
1448 with conversations_session(token1) as c:
1449 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=gcid, text="Hello"))
1451 process_jobs()
1453 def unseen_notification_count(user_id):
1454 with session_scope() as session:
1455 return session.execute(
1456 select(func.count())
1457 .select_from(Notification)
1458 .where(Notification.user_id == user_id)
1459 .where(Notification.key == str(gcid))
1460 .where(Notification.is_seen == False)
1461 ).scalar_one()
1463 assert unseen_notification_count(user2.id) > 0
1465 with conversations_session(token2) as c:
1466 c.MarkLastSeenGroupChat(conversations_pb2.MarkLastSeenGroupChatReq(group_chat_id=gcid, last_seen_message_id=1))
1468 assert unseen_notification_count(user2.id) == 0
1471def test_mark_last_seen_clears_missed_messages_notification(db, moderator, timewarp: Timewarp):
1472 """
1473 Regression test: chat__missed_messages is a summary keyed with "" rather than a chat id, so it
1474 needs to be marked seen separately from the per-chat chat__message notifications.
1475 """
1476 user1, token1 = generate_user()
1477 user2, token2 = generate_user()
1479 # this notification is email-only by default, and the in-app feed only shows push-enabled ones
1480 with notifications_session(token2) as n:
1481 n.SetNotificationSettings(
1482 notifications_pb2.SetNotificationSettingsReq(
1483 preferences=[
1484 notifications_pb2.SingleNotificationPreference(
1485 topic=NotificationTopicAction.chat__missed_messages.topic,
1486 action=NotificationTopicAction.chat__missed_messages.action,
1487 delivery_method="push",
1488 enabled=True,
1489 )
1490 ]
1491 )
1492 )
1494 with conversations_session(token1) as c:
1495 gcid = c.CreateGroupChat(
1496 conversations_pb2.CreateGroupChatReq(
1497 recipient_user_ids=[user2.id], title=wrappers_pb2.StringValue(value="Test chat")
1498 )
1499 ).group_chat_id
1501 moderator.approve_group_chat(gcid)
1503 with conversations_session(token1) as c:
1504 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=gcid, text="Hello!"))
1506 # the job only picks up messages that have been unseen for five minutes
1507 timewarp.advance(MISSED_MESSAGES_DELAY)
1508 send_message_notifications(empty_pb2.Empty())
1509 process_jobs()
1511 def unseen_missed_messages():
1512 with notifications_session(token2) as n:
1513 res = n.ListNotifications(notifications_pb2.ListNotificationsReq(only_unread=True))
1514 return [
1515 notification
1516 for notification in res.notifications
1517 if notification.topic == NotificationTopicAction.chat__missed_messages.topic
1518 and notification.action == NotificationTopicAction.chat__missed_messages.action
1519 ]
1521 assert len(unseen_missed_messages()) == 1
1523 with conversations_session(token2) as c:
1524 c.MarkLastSeenGroupChat(conversations_pb2.MarkLastSeenGroupChatReq(group_chat_id=gcid, last_seen_message_id=1))
1526 assert unseen_missed_messages() == []
1529def test_one_dm_per_pair(db, moderator):
1530 user1, token1 = generate_user()
1531 user2, token2 = generate_user()
1532 user3, token3 = generate_user()
1534 make_friends(user1, user2)
1535 make_friends(user1, user3)
1536 make_friends(user2, user3)
1538 with conversations_session(token1) as c:
1539 # create DM with user 2
1540 res = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id]))
1541 assert res.is_dm
1542 dm_with_user2 = res.group_chat_id
1544 # create DM with user 3
1545 res = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user3.id]))
1546 assert res.is_dm
1547 dm_with_user3 = res.group_chat_id
1549 # can't create another group chat with just user 2
1550 with pytest.raises(grpc.RpcError) as e:
1551 res = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id]))
1552 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
1554 # can't create another group chat with just user 3
1555 with pytest.raises(grpc.RpcError) as e:
1556 res = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user3.id]))
1557 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
1559 # can create joined group chat
1560 res = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id, user3.id]))
1561 assert not res.is_dm
1563 # Approve the DMs so user2 can see them (otherwise they're SHADOWED and only visible to creator)
1564 moderator.approve_group_chat(dm_with_user2)
1566 with conversations_session(token2) as c:
1567 # can create DM with user 3
1568 res = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user3.id]))
1569 assert res.is_dm
1571 # can't create another group chat with just user 1 (DM was approved, so user2 can see it)
1572 with pytest.raises(grpc.RpcError) as e:
1573 res = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user1.id]))
1574 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
1577def test_GetDirectMessage(db):
1578 user1, token1 = generate_user()
1579 user2, token2 = generate_user()
1580 user3, token3 = generate_user()
1582 make_friends(user1, user2)
1583 make_friends(user1, user3)
1584 make_friends(user2, user3)
1586 with conversations_session(token1) as c:
1587 # no group chat with user 2
1588 with pytest.raises(grpc.RpcError) as e:
1589 res = c.GetDirectMessage(conversations_pb2.GetDirectMessageReq(user_id=user2.id))
1590 assert e.value.code() == grpc.StatusCode.NOT_FOUND
1592 # no group chat with nor user 3
1593 with pytest.raises(grpc.RpcError) as e:
1594 res = c.GetDirectMessage(conversations_pb2.GetDirectMessageReq(user_id=user3.id))
1595 assert e.value.code() == grpc.StatusCode.NOT_FOUND
1597 # create DM with user 2
1598 res = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id]))
1599 assert res.is_dm
1600 gcid = res.group_chat_id
1602 # now should exist
1603 res = c.GetDirectMessage(conversations_pb2.GetDirectMessageReq(user_id=user2.id))
1604 assert res.group_chat_id == gcid
1605 assert not res.is_archived
1607 # create DM with user 3
1608 res = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user3.id]))
1609 assert res.is_dm
1611 # can create joined group chat
1612 res = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id, user3.id]))
1613 assert not res.is_dm
1615 with conversations_session(token2) as c:
1616 # can create DM with user 3
1617 res = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user3.id]))
1618 assert res.is_dm
1619 assert res.can_message
1620 gcid = res.group_chat_id
1622 # DM with 3 should exist, but can't message after being blocked
1623 make_user_block(user3, user2)
1624 res = c.GetDirectMessage(conversations_pb2.GetDirectMessageReq(user_id=user3.id))
1625 assert res.group_chat_id == gcid
1626 assert not res.can_message
1629def test_total_unseen(db, moderator):
1630 user1, token1 = generate_user()
1631 user2, token2 = generate_user()
1632 user3, token3 = generate_user()
1634 # distractions
1635 user4, token4 = generate_user()
1637 make_friends(user1, user2)
1638 make_friends(user1, user3)
1639 make_friends(user2, user3)
1641 # distractions
1642 make_friends(user1, user4)
1644 with conversations_session(token1) as c:
1645 # distractions
1646 gcid_distraction = c.CreateGroupChat(
1647 conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user4.id])
1648 ).group_chat_id
1649 moderator.approve_group_chat(gcid_distraction)
1650 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=gcid_distraction, text="distraction..."))
1652 gcid = c.CreateGroupChat(
1653 conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id, user3.id])
1654 ).group_chat_id
1655 moderator.approve_group_chat(gcid)
1657 for i in range(6):
1658 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=gcid, text=f"test message {i}"))
1660 # distractions
1661 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=gcid_distraction, text="distraction..."))
1663 # messages are automatically marked as seen when you send a new message
1664 with api_session(token1) as api:
1665 assert api.Ping(api_pb2.PingReq()).unseen_message_count == 0
1667 with api_session(token2) as api:
1668 # chat created + 6 normal messages
1669 assert api.Ping(api_pb2.PingReq()).unseen_message_count == 7
1671 # now leave chat with user2
1672 with conversations_session(token2) as c:
1673 c.LeaveGroupChat(conversations_pb2.LeaveGroupChatReq(group_chat_id=gcid))
1675 with api_session(token2) as api:
1676 # seen messages becomes 0 when leaving
1677 assert api.Ping(api_pb2.PingReq()).unseen_message_count == 0
1679 with conversations_session(token1) as c:
1680 # distractions
1681 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=gcid_distraction, text="distraction..."))
1683 # send more stuff without user 2
1684 for i in range(3):
1685 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=gcid, text=f"test message {i}"))
1687 # distractions
1688 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=gcid_distraction, text="distraction..."))
1690 with api_session(token2) as api:
1691 # seen messages becomes 0 when leaving
1692 assert api.Ping(api_pb2.PingReq()).unseen_message_count == 0
1694 with conversations_session(token1) as c:
1695 # add user 2 back
1696 c.InviteToGroupChat(conversations_pb2.InviteToGroupChatReq(group_chat_id=gcid, user_id=user2.id))
1698 # send more stuff with user 2
1699 for i in range(12):
1700 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=gcid, text=f"test message {i}"))
1702 # distractions
1703 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=gcid_distraction, text="distraction..."))
1705 with api_session(token2) as api:
1706 # joined + 12 normal
1707 assert api.Ping(api_pb2.PingReq()).unseen_message_count == 13
1710def test_left_group_chat_unseen_count_excludes_unreachable_messages(db, moderator):
1711 """
1712 ListGroupChats and GetGroupChat used to count messages sent after the viewer left the chat, which
1713 they can never open. The viewer is removed rather than leaving, because leaving posts a message of
1714 your own, which marks everything up to it seen.
1715 """
1716 user1, token1 = generate_user()
1717 user2, token2 = generate_user()
1718 user3, token3 = generate_user()
1719 make_friends(user1, user2)
1720 make_friends(user2, user3)
1722 with conversations_session(token2) as c:
1723 group_chat_id = c.CreateGroupChat(
1724 conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user1.id, user3.id])
1725 ).group_chat_id
1726 moderator.approve_group_chat(group_chat_id)
1727 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=group_chat_id, text="hi"))
1728 c.RemoveGroupChatUser(conversations_pb2.RemoveGroupChatUserReq(group_chat_id=group_chat_id, user_id=user1.id))
1729 for _ in range(3):
1730 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=group_chat_id, text="after you left"))
1732 # chat created, "hi", and the removal notice; the three messages sent afterwards are out of reach
1733 with api_session(token1) as api:
1734 assert api.Ping(api_pb2.PingReq()).unseen_message_count == 3
1736 with conversations_session(token1) as c:
1737 res = c.ListGroupChats(conversations_pb2.ListGroupChatsReq())
1738 (listed,) = [gc for gc in res.group_chats if gc.group_chat_id == group_chat_id]
1739 assert listed.unseen_message_count == 3
1740 assert c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=group_chat_id)).unseen_message_count == 3
1743def test_left_group_chat_can_be_marked_seen(db, moderator):
1744 """
1745 MarkLastSeenGroupChat used to require a subscription with left == None, so a chat you were removed
1746 from kept contributing to the unread badge with no way to clear it.
1747 """
1748 user1, token1 = generate_user()
1749 user2, token2 = generate_user()
1750 user3, token3 = generate_user()
1751 make_friends(user1, user2)
1752 make_friends(user2, user3)
1754 with conversations_session(token2) as c:
1755 group_chat_id = c.CreateGroupChat(
1756 conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user1.id, user3.id])
1757 ).group_chat_id
1758 moderator.approve_group_chat(group_chat_id)
1759 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=group_chat_id, text="hi"))
1760 c.RemoveGroupChatUser(conversations_pb2.RemoveGroupChatUserReq(group_chat_id=group_chat_id, user_id=user1.id))
1761 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=group_chat_id, text="after you left"))
1763 with api_session(token1) as api:
1764 assert api.Ping(api_pb2.PingReq()).unseen_message_count == 3
1766 with conversations_session(token1) as c:
1767 latest_message_id = c.GetGroupChat(
1768 conversations_pb2.GetGroupChatReq(group_chat_id=group_chat_id)
1769 ).latest_message.message_id
1770 c.MarkLastSeenGroupChat(
1771 conversations_pb2.MarkLastSeenGroupChatReq(
1772 group_chat_id=group_chat_id, last_seen_message_id=latest_message_id
1773 )
1774 )
1776 assert c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=group_chat_id)).unseen_message_count == 0
1778 with pytest.raises(grpc.RpcError) as e:
1779 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=group_chat_id, text="let me back in"))
1780 assert e.value.code() == grpc.StatusCode.NOT_FOUND
1782 with api_session(token1) as api:
1783 assert api.Ping(api_pb2.PingReq()).unseen_message_count == 0
1786def test_rejoined_group_chat_ping_reads_newest_subscription(db, moderator):
1787 """
1788 Rejoining a chat leaves the earlier subscription behind with its own last-seen state. Ping used to
1789 match on any of the viewer's subscriptions, so the stale one kept the badge up forever.
1790 """
1791 user1, token1 = generate_user()
1792 user2, token2 = generate_user()
1793 user3, token3 = generate_user()
1794 make_friends(user1, user2)
1795 make_friends(user2, user3)
1797 with conversations_session(token2) as c:
1798 group_chat_id = c.CreateGroupChat(
1799 conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user1.id, user3.id])
1800 ).group_chat_id
1801 moderator.approve_group_chat(group_chat_id)
1802 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=group_chat_id, text="hi"))
1803 # removed rather than leaving, so user1's first subscription is left behind with unread messages
1804 c.RemoveGroupChatUser(conversations_pb2.RemoveGroupChatUserReq(group_chat_id=group_chat_id, user_id=user1.id))
1805 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=group_chat_id, text="while you were away"))
1806 c.InviteToGroupChat(conversations_pb2.InviteToGroupChatReq(group_chat_id=group_chat_id, user_id=user1.id))
1807 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=group_chat_id, text="welcome back"))
1809 # only the invite notice and "welcome back" are in reach of the newest subscription
1810 with api_session(token1) as api:
1811 assert api.Ping(api_pb2.PingReq()).unseen_message_count == 2
1813 with conversations_session(token1) as c:
1814 latest_message_id = c.GetGroupChat(
1815 conversations_pb2.GetGroupChatReq(group_chat_id=group_chat_id)
1816 ).latest_message.message_id
1817 c.MarkLastSeenGroupChat(
1818 conversations_pb2.MarkLastSeenGroupChatReq(
1819 group_chat_id=group_chat_id, last_seen_message_id=latest_message_id
1820 )
1821 )
1823 with api_session(token1) as api:
1824 assert api.Ping(api_pb2.PingReq()).unseen_message_count == 0
1827def test_rejoined_group_chat_only_unseen_matches_the_badge(db, moderator):
1828 """
1829 GetGroupChatMessages matched only_unseen against every one of the viewer's subscriptions, so the
1830 abandoned one served up its unread messages even though the badge no longer counts them.
1831 """
1832 user1, token1 = generate_user()
1833 user2, token2 = generate_user()
1834 user3, token3 = generate_user()
1835 make_friends(user1, user2)
1836 make_friends(user2, user3)
1838 with conversations_session(token2) as c:
1839 group_chat_id = c.CreateGroupChat(
1840 conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user1.id, user3.id])
1841 ).group_chat_id
1842 moderator.approve_group_chat(group_chat_id)
1843 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=group_chat_id, text="hi"))
1844 c.RemoveGroupChatUser(conversations_pb2.RemoveGroupChatUserReq(group_chat_id=group_chat_id, user_id=user1.id))
1845 c.InviteToGroupChat(conversations_pb2.InviteToGroupChatReq(group_chat_id=group_chat_id, user_id=user1.id))
1846 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=group_chat_id, text="welcome back"))
1848 with api_session(token1) as api:
1849 assert api.Ping(api_pb2.PingReq()).unseen_message_count == 2
1851 with conversations_session(token1) as c:
1852 unseen = c.GetGroupChatMessages(
1853 conversations_pb2.GetGroupChatMessagesReq(group_chat_id=group_chat_id, only_unseen=True)
1854 ).messages
1855 assert [m.WhichOneof("content") for m in unseen] == ["text", "user_invited"]
1856 assert unseen[0].text.text == "welcome back"
1858 # the earlier stint is still readable, it just isn't unread
1859 all_messages = c.GetGroupChatMessages(
1860 conversations_pb2.GetGroupChatMessagesReq(group_chat_id=group_chat_id)
1861 ).messages
1862 assert [m.text.text for m in all_messages if m.WhichOneof("content") == "text"] == ["welcome back", "hi"]
1865def test_rejoined_group_chat_archived_filter_reads_newest_subscription(db, moderator):
1866 """
1867 ListGroupChats applied the archived filter before picking the newest subscription, so a chat
1868 archived before the viewer was removed stayed in their archived list off the abandoned
1869 subscription, where nothing could ever mark it seen.
1870 """
1871 user1, token1 = generate_user()
1872 user2, token2 = generate_user()
1873 user3, token3 = generate_user()
1874 make_friends(user1, user2)
1875 make_friends(user2, user3)
1877 with conversations_session(token2) as c:
1878 group_chat_id = c.CreateGroupChat(
1879 conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user1.id, user3.id])
1880 ).group_chat_id
1881 moderator.approve_group_chat(group_chat_id)
1882 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=group_chat_id, text="hi"))
1884 with conversations_session(token1) as c:
1885 c.SetGroupChatArchiveStatus(
1886 conversations_pb2.SetGroupChatArchiveStatusReq(group_chat_id=group_chat_id, is_archived=True)
1887 )
1889 with conversations_session(token2) as c:
1890 c.RemoveGroupChatUser(conversations_pb2.RemoveGroupChatUserReq(group_chat_id=group_chat_id, user_id=user1.id))
1891 c.InviteToGroupChat(conversations_pb2.InviteToGroupChatReq(group_chat_id=group_chat_id, user_id=user1.id))
1893 with conversations_session(token1) as c:
1894 archived = c.ListGroupChats(conversations_pb2.ListGroupChatsReq(only_archived=True)).group_chats
1895 assert [gc.group_chat_id for gc in archived] == []
1897 (listed,) = c.ListGroupChats(conversations_pb2.ListGroupChatsReq(only_archived=False)).group_chats
1898 assert listed.group_chat_id == group_chat_id
1899 assert not listed.is_archived
1902def test_regression_ListGroupChats_pagination(db, moderator, monkeypatch):
1903 user1, token1 = generate_user()
1904 user2, token2 = generate_user()
1905 user3, token3 = generate_user()
1907 make_friends(user1, user2)
1908 make_friends(user1, user3)
1910 # two full pages and a partial one, which is the shape that lost chats, without paying for a
1911 # chat per row of a production-sized page
1912 monkeypatch.setattr(conversations, "DEFAULT_PAGINATION_LENGTH", 3)
1914 with conversations_session(token1) as c:
1915 # tuples of (group_chat_id, message_id)
1916 group_chat_and_message_ids = []
1917 for i in range(7):
1918 res1 = c.CreateGroupChat(
1919 conversations_pb2.CreateGroupChatReq(
1920 recipient_user_ids=[user2.id, user3.id], title=wrappers_pb2.StringValue(value=f"Chat {i}")
1921 )
1922 )
1924 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=res1.group_chat_id, text=f"Test message {i}"))
1926 res2 = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=res1.group_chat_id))
1928 group_chat_and_message_ids.append((res2.group_chat_id, res2.latest_message.message_id))
1929 moderator.approve_group_chat(res1.group_chat_id)
1931 seen_group_chat_ids = []
1933 pages = 0
1934 last_message_id = 0
1935 more = True
1936 while more:
1937 res = c.ListGroupChats(conversations_pb2.ListGroupChatsReq(last_message_id=last_message_id))
1938 last_message_id = res.last_message_id
1939 more = not res.no_more
1940 pages += 1
1942 seen_group_chat_ids.extend([chat.group_chat_id for chat in res.group_chats])
1944 assert pages == 3, "The chats didn't come back over several pages, so nothing was paginated"
1945 assert set(seen_group_chat_ids) == {x[0] for x in group_chat_and_message_ids}, "Not all group chats returned"
1948def test_muting(db, moderator):
1949 user1, token1 = generate_user()
1950 user2, token2 = generate_user()
1951 user3, token3 = generate_user()
1953 make_friends(user1, user2)
1954 make_friends(user1, user3)
1955 make_friends(user2, user3)
1957 with conversations_session(token3) as c:
1958 # this is just here to mess up any issues we get if we pretend there's only one group chat ever
1959 gcid_distraction = c.CreateGroupChat(
1960 conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id, user1.id])
1961 ).group_chat_id
1962 moderator.approve_group_chat(gcid_distraction)
1964 with conversations_session(token1) as c:
1965 gcid = c.CreateGroupChat(
1966 conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id, user3.id])
1967 ).group_chat_id
1968 moderator.approve_group_chat(gcid)
1970 with conversations_session(token2) as c:
1971 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=gcid))
1972 assert not res.mute_info.muted
1973 assert not res.mute_info.HasField("muted_until")
1975 c.MuteGroupChat(conversations_pb2.MuteGroupChatReq(group_chat_id=gcid, forever=True))
1976 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=gcid))
1977 assert res.mute_info.muted
1978 assert not res.mute_info.HasField("muted_until")
1980 c.MuteGroupChat(conversations_pb2.MuteGroupChatReq(group_chat_id=gcid, unmute=True))
1981 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=gcid))
1982 assert not res.mute_info.muted
1983 assert not res.mute_info.HasField("muted_until")
1985 c.MuteGroupChat(
1986 conversations_pb2.MuteGroupChatReq(
1987 group_chat_id=gcid, for_duration=Duration_from_timedelta(timedelta(hours=2))
1988 )
1989 )
1990 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=gcid))
1991 assert res.mute_info.muted
1992 assert res.mute_info.HasField("muted_until")
1993 assert to_aware_datetime(res.mute_info.muted_until) >= now() + timedelta(hours=1, minutes=59)
1994 assert to_aware_datetime(res.mute_info.muted_until) <= now() + timedelta(hours=2, minutes=1)
1997def test_archiving(db, moderator):
1998 """Test SetGroupChatArchiveStatus RPC with GetGroupChat and ListGroupChats"""
1999 user1, token1 = generate_user()
2000 user2, token2 = generate_user()
2001 user3, token3 = generate_user()
2003 make_friends(user1, user2)
2004 make_friends(user1, user3)
2005 make_friends(user2, user3)
2007 # Create a distraction chat to ensure we're testing the right one
2008 with conversations_session(token3) as c:
2009 gcid_distraction = c.CreateGroupChat(
2010 conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id, user1.id])
2011 ).group_chat_id
2012 moderator.approve_group_chat(gcid_distraction)
2014 with conversations_session(token1) as c:
2015 gcid = c.CreateGroupChat(
2016 conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id, user3.id])
2017 ).group_chat_id
2018 moderator.approve_group_chat(gcid)
2020 # Test basic archive/unarchive functionality
2021 with conversations_session(token2) as c:
2022 # Initially not archived
2023 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=gcid))
2024 assert not res.is_archived
2026 # Archive the chat
2027 archive_res = c.SetGroupChatArchiveStatus(
2028 conversations_pb2.SetGroupChatArchiveStatusReq(group_chat_id=gcid, is_archived=True)
2029 )
2030 assert archive_res.group_chat_id == gcid
2031 assert archive_res.is_archived
2033 # Verify archived via GetGroupChat
2034 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=gcid))
2035 assert res.is_archived
2037 # Unarchive the chat
2038 archive_res = c.SetGroupChatArchiveStatus(
2039 conversations_pb2.SetGroupChatArchiveStatusReq(group_chat_id=gcid, is_archived=False)
2040 )
2041 assert archive_res.group_chat_id == gcid
2042 assert not archive_res.is_archived
2044 # Verify unarchived via GetGroupChat
2045 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=gcid))
2046 assert not res.is_archived
2049def test_archiving_per_user(db, moderator):
2050 """Test that archiving is per-user - one user archiving doesn't affect others"""
2051 user1, token1 = generate_user()
2052 user2, token2 = generate_user()
2054 make_friends(user1, user2)
2056 with conversations_session(token1) as c:
2057 gcid = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id])).group_chat_id
2058 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=gcid, text="Hello"))
2059 moderator.approve_group_chat(gcid)
2061 # User1 archives the chat
2062 with conversations_session(token1) as c:
2063 c.SetGroupChatArchiveStatus(
2064 conversations_pb2.SetGroupChatArchiveStatusReq(group_chat_id=gcid, is_archived=True)
2065 )
2066 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=gcid))
2067 assert res.is_archived
2069 # User2 should NOT see it as archived
2070 with conversations_session(token2) as c:
2071 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=gcid))
2072 assert not res.is_archived
2074 # User2 archives it too
2075 with conversations_session(token2) as c:
2076 c.SetGroupChatArchiveStatus(
2077 conversations_pb2.SetGroupChatArchiveStatusReq(group_chat_id=gcid, is_archived=True)
2078 )
2079 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=gcid))
2080 assert res.is_archived
2082 # User1 unarchives - user2 should still see it as archived
2083 with conversations_session(token1) as c:
2084 c.SetGroupChatArchiveStatus(
2085 conversations_pb2.SetGroupChatArchiveStatusReq(group_chat_id=gcid, is_archived=False)
2086 )
2087 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=gcid))
2088 assert not res.is_archived
2090 with conversations_session(token2) as c:
2091 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=gcid))
2092 assert res.is_archived
2095def test_archiving_with_list_group_chats(db, moderator):
2096 """Test archive filtering with ListGroupChats endpoint"""
2097 user1, token1 = generate_user()
2098 user2, token2 = generate_user()
2099 user3, token3 = generate_user()
2101 make_friends(user1, user2)
2102 make_friends(user1, user3)
2104 with conversations_session(token1) as c:
2105 # Create 3 chats
2106 gcid1 = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id])).group_chat_id
2107 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=gcid1, text="Chat 1"))
2108 moderator.approve_group_chat(gcid1)
2110 gcid2 = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user3.id])).group_chat_id
2111 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=gcid2, text="Chat 2"))
2112 moderator.approve_group_chat(gcid2)
2114 gcid3 = c.CreateGroupChat(
2115 conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id, user3.id])
2116 ).group_chat_id
2117 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=gcid3, text="Chat 3"))
2118 moderator.approve_group_chat(gcid3)
2120 with conversations_session(token1) as c:
2121 # Initially all 3 chats should be visible
2122 res = c.ListGroupChats(conversations_pb2.ListGroupChatsReq())
2123 assert len(res.group_chats) == 3
2125 # All should be non-archived
2126 res = c.ListGroupChats(conversations_pb2.ListGroupChatsReq(only_archived=False))
2127 assert len(res.group_chats) == 3
2129 # None should be archived
2130 res = c.ListGroupChats(conversations_pb2.ListGroupChatsReq(only_archived=True))
2131 assert len(res.group_chats) == 0
2133 # Archive chat 1
2134 c.SetGroupChatArchiveStatus(
2135 conversations_pb2.SetGroupChatArchiveStatusReq(group_chat_id=gcid1, is_archived=True)
2136 )
2138 # Without filter, still see all 3
2139 res = c.ListGroupChats(conversations_pb2.ListGroupChatsReq())
2140 assert len(res.group_chats) == 3
2142 # Non-archived should show 2
2143 res = c.ListGroupChats(conversations_pb2.ListGroupChatsReq(only_archived=False))
2144 assert len(res.group_chats) == 2
2145 assert gcid1 not in [gc.group_chat_id for gc in res.group_chats]
2147 # Archived should show 1
2148 res = c.ListGroupChats(conversations_pb2.ListGroupChatsReq(only_archived=True))
2149 assert len(res.group_chats) == 1
2150 assert res.group_chats[0].group_chat_id == gcid1
2151 assert res.group_chats[0].is_archived
2153 # Archive chat 2 as well
2154 c.SetGroupChatArchiveStatus(
2155 conversations_pb2.SetGroupChatArchiveStatusReq(group_chat_id=gcid2, is_archived=True)
2156 )
2158 # Non-archived should show 1
2159 res = c.ListGroupChats(conversations_pb2.ListGroupChatsReq(only_archived=False))
2160 assert len(res.group_chats) == 1
2161 assert res.group_chats[0].group_chat_id == gcid3
2163 # Archived should show 2
2164 res = c.ListGroupChats(conversations_pb2.ListGroupChatsReq(only_archived=True))
2165 assert len(res.group_chats) == 2
2166 archived_ids = {gc.group_chat_id for gc in res.group_chats}
2167 assert archived_ids == {gcid1, gcid2}
2169 # Unarchive chat 1
2170 c.SetGroupChatArchiveStatus(
2171 conversations_pb2.SetGroupChatArchiveStatusReq(group_chat_id=gcid1, is_archived=False)
2172 )
2174 # Non-archived should show 2
2175 res = c.ListGroupChats(conversations_pb2.ListGroupChatsReq(only_archived=False))
2176 assert len(res.group_chats) == 2
2178 # Archived should show 1
2179 res = c.ListGroupChats(conversations_pb2.ListGroupChatsReq(only_archived=True))
2180 assert len(res.group_chats) == 1
2181 assert res.group_chats[0].group_chat_id == gcid2
2184def test_archiving_chat_not_found(db, moderator):
2185 """Test that archiving a non-existent or non-accessible chat fails"""
2186 user1, token1 = generate_user()
2187 user2, token2 = generate_user()
2188 user3, token3 = generate_user()
2190 make_friends(user1, user2)
2192 with conversations_session(token1) as c:
2193 gcid = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id])).group_chat_id
2194 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=gcid, text="Hello"))
2195 moderator.approve_group_chat(gcid)
2197 # User3 is not in the chat - should get NOT_FOUND
2198 with conversations_session(token3) as c:
2199 with pytest.raises(grpc.RpcError) as e:
2200 c.SetGroupChatArchiveStatus(
2201 conversations_pb2.SetGroupChatArchiveStatusReq(group_chat_id=gcid, is_archived=True)
2202 )
2203 assert e.value.code() == grpc.StatusCode.NOT_FOUND
2205 # Non-existent chat ID should fail
2206 with conversations_session(token1) as c:
2207 with pytest.raises(grpc.RpcError) as e:
2208 c.SetGroupChatArchiveStatus(
2209 conversations_pb2.SetGroupChatArchiveStatusReq(group_chat_id=99999, is_archived=True)
2210 )
2211 assert e.value.code() == grpc.StatusCode.NOT_FOUND
2214def test_archiving_after_leaving_chat(db, moderator):
2215 """Test that you can't archive a chat after leaving it"""
2216 user1, token1 = generate_user()
2217 user2, token2 = generate_user()
2218 user3, token3 = generate_user()
2220 make_friends(user1, user2)
2221 make_friends(user1, user3)
2222 make_friends(user2, user3)
2224 with conversations_session(token1) as c:
2225 gcid = c.CreateGroupChat(
2226 conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id, user3.id])
2227 ).group_chat_id
2228 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=gcid, text="Hello"))
2229 moderator.approve_group_chat(gcid)
2231 # User2 leaves the chat
2232 with conversations_session(token2) as c:
2233 c.LeaveGroupChat(conversations_pb2.LeaveGroupChatReq(group_chat_id=gcid))
2235 # User2 should not be able to archive it now
2236 with conversations_session(token2) as c:
2237 with pytest.raises(grpc.RpcError) as e:
2238 c.SetGroupChatArchiveStatus(
2239 conversations_pb2.SetGroupChatArchiveStatusReq(group_chat_id=gcid, is_archived=True)
2240 )
2241 assert e.value.code() == grpc.StatusCode.NOT_FOUND
2244def test_archiving_dm(db, moderator):
2245 """Test that archiving works for DMs as well as group chats"""
2246 user1, token1 = generate_user()
2247 user2, token2 = generate_user()
2249 make_friends(user1, user2)
2251 with conversations_session(token1) as c:
2252 # Create a DM
2253 gcid = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id])).group_chat_id
2254 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=gcid, text="Hello"))
2255 moderator.approve_group_chat(gcid)
2257 # Verify it's a DM
2258 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=gcid))
2259 assert res.is_dm
2260 assert not res.is_archived
2262 # Archive the DM
2263 c.SetGroupChatArchiveStatus(
2264 conversations_pb2.SetGroupChatArchiveStatusReq(group_chat_id=gcid, is_archived=True)
2265 )
2267 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=gcid))
2268 assert res.is_archived
2270 # Unarchive it
2271 c.SetGroupChatArchiveStatus(
2272 conversations_pb2.SetGroupChatArchiveStatusReq(group_chat_id=gcid, is_archived=False)
2273 )
2275 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=gcid))
2276 assert not res.is_archived
2279def test_archiving_idempotent(db, moderator):
2280 """Test that archiving/unarchiving is idempotent"""
2281 user1, token1 = generate_user()
2282 user2, token2 = generate_user()
2284 make_friends(user1, user2)
2286 with conversations_session(token1) as c:
2287 gcid = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id])).group_chat_id
2288 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=gcid, text="Hello"))
2289 moderator.approve_group_chat(gcid)
2291 # Archive twice - should work without error
2292 c.SetGroupChatArchiveStatus(
2293 conversations_pb2.SetGroupChatArchiveStatusReq(group_chat_id=gcid, is_archived=True)
2294 )
2295 c.SetGroupChatArchiveStatus(
2296 conversations_pb2.SetGroupChatArchiveStatusReq(group_chat_id=gcid, is_archived=True)
2297 )
2298 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=gcid))
2299 assert res.is_archived
2301 # Unarchive twice - should work without error
2302 c.SetGroupChatArchiveStatus(
2303 conversations_pb2.SetGroupChatArchiveStatusReq(group_chat_id=gcid, is_archived=False)
2304 )
2305 c.SetGroupChatArchiveStatus(
2306 conversations_pb2.SetGroupChatArchiveStatusReq(group_chat_id=gcid, is_archived=False)
2307 )
2308 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=gcid))
2309 assert not res.is_archived
2312def test_chat_notifications(db, moderator):
2313 user1, token1 = generate_user()
2314 user2, token2 = generate_user()
2315 # notifs off
2316 user3, token3 = generate_user()
2317 user4, token4 = generate_user()
2318 user5, token5 = generate_user()
2319 user6, token6 = generate_user()
2321 make_friends(user1, user2)
2322 make_friends(user1, user3)
2323 make_friends(user1, user4)
2324 make_friends(user4, user5)
2325 make_friends(user4, user6)
2327 # have some of them enable/disable notifs
2328 topic_action = NotificationTopicAction.chat__message
2329 for token, enabled in [
2330 (token1, True),
2331 (token2, True),
2332 (token3, False),
2333 (token4, True),
2334 (token5, True),
2335 (token6, True),
2336 ]:
2337 with notifications_session(token) as notifications:
2338 notifications.SetNotificationSettings(
2339 notifications_pb2.SetNotificationSettingsReq(
2340 preferences=[
2341 notifications_pb2.SingleNotificationPreference(
2342 topic=topic_action.topic,
2343 action=topic_action.action,
2344 delivery_method=delivery_method,
2345 enabled=enabled,
2346 )
2347 for delivery_method in ["push", "email", "digest"]
2348 ],
2349 )
2350 )
2352 group_chat_id = None
2354 def send_msg(c, i):
2355 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=group_chat_id, text=f"Test message {i}"))
2357 with conversations_session(token1) as c:
2358 res = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id, user3.id, user4.id]))
2359 group_chat_id = res.group_chat_id
2360 moderator.approve_group_chat(group_chat_id)
2361 c.EditGroupChat(
2362 conversations_pb2.EditGroupChatReq(
2363 group_chat_id=group_chat_id, only_admins_invite=wrappers_pb2.BoolValue(value=False)
2364 )
2365 )
2366 send_msg(c, i=1)
2367 send_msg(c, i=2)
2369 with conversations_session(token4) as c:
2370 c.InviteToGroupChat(conversations_pb2.InviteToGroupChatReq(group_chat_id=group_chat_id, user_id=user5.id))
2371 send_msg(c, i=3)
2372 c.InviteToGroupChat(conversations_pb2.InviteToGroupChatReq(group_chat_id=group_chat_id, user_id=user6.id))
2373 send_msg(c, i=4)
2374 send_msg(c, i=5)
2376 with conversations_session(token3) as c:
2377 send_msg(c, i=6)
2378 c.LeaveGroupChat(conversations_pb2.LeaveGroupChatReq(group_chat_id=group_chat_id))
2380 with conversations_session(token2) as c:
2381 send_msg(c, i=7)
2382 c.LeaveGroupChat(conversations_pb2.LeaveGroupChatReq(group_chat_id=group_chat_id))
2384 with conversations_session(token6) as c:
2385 send_msg(c, i=8)
2387 # go through all bg jobs
2388 while process_job():
2389 pass
2391 # now check notifs...
2392 expected_notifs = [
2393 (user1, "user1", [3, 4, 5, 6, 7, 8]),
2394 (user2, "user2", [1, 2, 3, 4, 5, 6]),
2395 (user3, "user3", []), # notifs off
2396 (user4, "user4", [1, 2, 6, 7, 8]),
2397 (user5, "user5", [3, 4, 5, 6, 7, 8]),
2398 (user6, "user6", [4, 5, 6, 7]),
2399 ]
2401 with session_scope() as session:
2402 for user, label, expected_msgs in expected_notifs:
2403 deliv = (
2404 session.execute(
2405 select(Notification.data)
2406 .join(NotificationDelivery, NotificationDelivery.notification_id == Notification.id)
2407 .where(Notification.user_id == user.id)
2408 .where(Notification.topic_action == topic_action)
2409 .where(NotificationDelivery.delivery_type == NotificationDeliveryType.push)
2410 .order_by(Notification.created)
2411 )
2412 .scalars()
2413 .all()
2414 )
2416 def parse_message_payload(data):
2417 return notification_data_pb2.ChatMessage.FromString(data).text
2419 contents = [parse_message_payload(d) for d in deliv]
2421 print(contents)
2423 assert [f"Test message {i}" for i in expected_msgs] == contents, f"Wrong messages for {label}"
2426def test_incomplete_profile(db):
2427 user1, token1 = generate_user(complete_profile=True)
2428 user2, token2 = generate_user(complete_profile=False)
2429 user3, token3 = generate_user(complete_profile=True)
2430 make_friends(user1, user2)
2431 make_friends(user1, user3)
2433 # user 1 can make a chat
2434 with conversations_session(token1) as c:
2435 res = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id]))
2436 group_chat_id = res.group_chat_id
2437 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=group_chat_id, text="Test message 1"))
2438 res = c.GetGroupChatMessages(conversations_pb2.GetGroupChatMessagesReq(group_chat_id=group_chat_id))
2439 assert res.messages[0].text.text == "Test message 1"
2440 assert to_aware_datetime(res.messages[0].time) <= now()
2441 assert res.messages[0].author_user_id == user1.id
2443 # user 2 cannot
2444 with conversations_session(token2) as c:
2445 with pytest.raises(grpc.RpcError) as e:
2446 c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user3.id]))
2447 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
2448 assert e.value.details() == "You have to complete your profile before you can send a message."