Coverage for app/backend/src/tests/test_conversations.py: 100%

1442 statements  

« prev     ^ index     » next       coverage.py v7.17.0, created at 2026-11-03 11:56 +0000

1from datetime import timedelta 

2 

3import grpc 

4import pytest 

5from google.protobuf import empty_pb2, wrappers_pb2 

6from sqlalchemy import func, select 

7 

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 RateLimitViolation, 

21) 

22from couchers.proto import api_pb2, conversations_pb2, notification_data_pb2, notifications_pb2 

23from couchers.rate_limits.definitions import RATE_LIMIT_DEFINITIONS, RATE_LIMIT_HOURS 

24from couchers.servicers import conversations 

25from couchers.utils import Duration_from_timedelta, now, to_aware_datetime 

26from tests.fixtures.db import generate_user, make_friends, make_user_block, make_user_invisible 

27from tests.fixtures.misc import EmailCollector, Moderator, PushCollector, process_jobs 

28from tests.fixtures.sessions import api_session, conversations_session, notifications_session 

29from tests.fixtures.timewarp import Timewarp 

30 

31 

32def test_list_group_chats(db, moderator): 

33 user1, token1 = generate_user() 

34 user2, token2 = generate_user() 

35 user3, token3 = generate_user() 

36 

37 make_friends(user2, user1) 

38 make_friends(user1, user3) 

39 

40 with conversations_session(token1) as c: 

41 # no threads initially 

42 res = c.ListGroupChats(conversations_pb2.ListGroupChatsReq()) 

43 assert len(res.group_chats) == 0 

44 

45 # create some group chats with messages 

46 res = c.CreateGroupChat( 

47 conversations_pb2.CreateGroupChatReq( 

48 recipient_user_ids=[user2.id], title=wrappers_pb2.StringValue(value="Test title") 

49 ) 

50 ) 

51 group_chat1_id = res.group_chat_id 

52 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=res.group_chat_id, text="Test message 1")) 

53 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=res.group_chat_id, text="Test message 2")) 

54 res = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id, user3.id])) 

55 group_chat2_id = res.group_chat_id 

56 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=res.group_chat_id, text="Test group message 1")) 

57 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=res.group_chat_id, text="Test group message 2")) 

58 

59 moderator.approve_group_chat(group_chat1_id) 

60 moderator.approve_group_chat(group_chat2_id) 

61 

62 with conversations_session(token1) as c: 

63 res = c.ListGroupChats(conversations_pb2.ListGroupChatsReq()) 

64 assert len(res.group_chats) == 2 

65 assert res.no_more 

66 

67 with conversations_session(token2) as c: 

68 res = c.ListGroupChats(conversations_pb2.ListGroupChatsReq()) 

69 assert len(res.group_chats) == 2 

70 assert res.no_more 

71 

72 with conversations_session(token3) as c: 

73 res = c.ListGroupChats(conversations_pb2.ListGroupChatsReq()) 

74 assert len(res.group_chats) == 1 

75 assert res.no_more 

76 

77 # Test archive filtering: archive group_chat1 for user1 

78 with conversations_session(token1) as c: 

79 res = c.SetGroupChatArchiveStatus( 

80 conversations_pb2.SetGroupChatArchiveStatusReq(group_chat_id=group_chat1_id, is_archived=True) 

81 ) 

82 assert res.group_chat_id == group_chat1_id 

83 assert res.is_archived 

84 

85 with conversations_session(token1) as c: 

86 # Without filter, returns all chats 

87 res = c.ListGroupChats(conversations_pb2.ListGroupChatsReq()) 

88 assert len(res.group_chats) == 2 

89 

90 # only_archived=False returns non-archived chats only 

91 res = c.ListGroupChats(conversations_pb2.ListGroupChatsReq(only_archived=False)) 

92 assert len(res.group_chats) == 1 

93 

94 # only_archived=True returns archived chats only 

95 res = c.ListGroupChats(conversations_pb2.ListGroupChatsReq(only_archived=True)) 

96 assert len(res.group_chats) == 1 

97 

98 # user2 should still see both as non-archived (archive is per-user) 

99 with conversations_session(token2) as c: 

100 res = c.ListGroupChats(conversations_pb2.ListGroupChatsReq(only_archived=False)) 

101 assert len(res.group_chats) == 2 

102 

103 res = c.ListGroupChats(conversations_pb2.ListGroupChatsReq(only_archived=True)) 

104 assert len(res.group_chats) == 0 

105 

106 

107def test_list_empty_group_chats(db, moderator): 

108 user1, token1 = generate_user() 

109 user2, token2 = generate_user() 

110 user3, token3 = generate_user() 

111 

112 make_friends(user1, user3) 

113 make_friends(user2, user1) 

114 make_friends(user2, user3) 

115 

116 with conversations_session(token1) as c: 

117 res = c.ListGroupChats(conversations_pb2.ListGroupChatsReq()) 

118 assert len(res.group_chats) == 0 

119 

120 res1 = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id])) 

121 res2 = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id, user3.id])) 

122 

123 moderator.approve_group_chat(res1.group_chat_id) 

124 moderator.approve_group_chat(res2.group_chat_id) 

125 

126 with conversations_session(token1) as c: 

127 res = c.ListGroupChats(conversations_pb2.ListGroupChatsReq()) 

128 assert len(res.group_chats) == 2 

129 assert res.no_more 

130 

131 with conversations_session(token2) as c: 

132 res = c.ListGroupChats(conversations_pb2.ListGroupChatsReq()) 

133 assert len(res.group_chats) == 2 

134 assert res.no_more 

135 

136 res3 = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user3.id])) 

137 moderator.approve_group_chat(res3.group_chat_id) 

138 

139 res = c.ListGroupChats(conversations_pb2.ListGroupChatsReq()) 

140 assert len(res.group_chats) == 3 

141 assert res.no_more 

142 

143 with conversations_session(token3) as c: 

144 res = c.ListGroupChats(conversations_pb2.ListGroupChatsReq()) 

145 assert len(res.group_chats) == 2 

146 assert res.no_more 

147 

148 

149def test_list_group_chats_ordering(db, moderator): 

150 # user is member of 5 group chats, order them correctly 

151 user, token = generate_user() 

152 user2, token2 = generate_user() 

153 user3, token3 = generate_user() 

154 user4, token4 = generate_user() 

155 

156 make_friends(user2, user) 

157 make_friends(user2, user3) 

158 make_friends(user2, user4) 

159 make_friends(user3, user) 

160 make_friends(user3, user4) 

161 make_friends(user, user4) 

162 

163 chat_ids = [] 

164 

165 with conversations_session(token2) as c: 

166 res = c.CreateGroupChat( 

167 conversations_pb2.CreateGroupChatReq( 

168 recipient_user_ids=[user.id], title=wrappers_pb2.StringValue(value="Chat 0") 

169 ) 

170 ) 

171 chat_ids.append(res.group_chat_id) 

172 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=res.group_chat_id, text="Test message")) 

173 res = c.CreateGroupChat( 

174 conversations_pb2.CreateGroupChatReq( 

175 recipient_user_ids=[user.id, user3.id], title=wrappers_pb2.StringValue(value="Chat 1") 

176 ) 

177 ) 

178 chat_ids.append(res.group_chat_id) 

179 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=res.group_chat_id, text="Test message")) 

180 res = c.CreateGroupChat( 

181 conversations_pb2.CreateGroupChatReq( 

182 recipient_user_ids=[user.id, user3.id], title=wrappers_pb2.StringValue(value="Chat 2") 

183 ) 

184 ) 

185 chat_ids.append(res.group_chat_id) 

186 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=res.group_chat_id, text="Test message")) 

187 

188 with conversations_session(token3) as c: 

189 res = c.CreateGroupChat( 

190 conversations_pb2.CreateGroupChatReq( 

191 recipient_user_ids=[user.id, user2.id, user4.id], title=wrappers_pb2.StringValue(value="Chat 3") 

192 ) 

193 ) 

194 chat_ids.append(res.group_chat_id) 

195 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=res.group_chat_id, text="Test message")) 

196 

197 for chat_id in chat_ids: 

198 moderator.approve_group_chat(chat_id) 

199 

200 with conversations_session(token) as c: 

201 res = c.CreateGroupChat( 

202 conversations_pb2.CreateGroupChatReq( 

203 recipient_user_ids=[user2.id, user3.id, user4.id], title=wrappers_pb2.StringValue(value="Chat 4") 

204 ) 

205 ) 

206 moderator.approve_group_chat(res.group_chat_id) 

207 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=res.group_chat_id, text="Test message")) 

208 res = c.ListGroupChats(conversations_pb2.ListGroupChatsReq()) 

209 assert len(res.group_chats) == 5 

210 assert res.group_chats[0].title == "Chat 4" 

211 assert res.group_chats[1].title == "Chat 3" 

212 assert res.group_chats[2].title == "Chat 2" 

213 assert res.group_chats[3].title == "Chat 1" 

214 assert res.group_chats[4].title == "Chat 0" 

215 

216 c.SendMessage( 

217 conversations_pb2.SendMessageReq(group_chat_id=res.group_chats[3].group_chat_id, text="Test message 2a") 

218 ) 

219 c.SendMessage( 

220 conversations_pb2.SendMessageReq(group_chat_id=res.group_chats[2].group_chat_id, text="Test message 2b") 

221 ) 

222 

223 res = c.ListGroupChats(conversations_pb2.ListGroupChatsReq()) 

224 assert len(res.group_chats) == 5 

225 assert res.group_chats[0].title == "Chat 2" 

226 assert res.group_chats[0].latest_message.text.text == "Test message 2b" 

227 assert res.group_chats[1].title == "Chat 1" 

228 assert res.group_chats[1].latest_message.text.text == "Test message 2a" 

229 assert res.group_chats[2].title == "Chat 4" 

230 assert res.group_chats[3].title == "Chat 3" 

231 assert res.group_chats[4].title == "Chat 0" 

232 

233 

234def test_list_group_chats_ordering_after_left(db, moderator): 

235 # user is member to 4 group chats, and has left one. 

236 # The one user left has the most recent message, but user left before then, 

237 # this should display as e.g. 3rd most recent depending on last message when they were in the chat 

238 user, token = generate_user() 

239 user2, token2 = generate_user() 

240 user3, token3 = generate_user() 

241 user4, token4 = generate_user() 

242 

243 make_friends(user2, user) 

244 make_friends(user2, user3) 

245 make_friends(user2, user4) 

246 make_friends(user3, user) 

247 make_friends(user3, user4) 

248 make_friends(user, user4) 

249 

250 chat_ids = [] 

251 

252 with conversations_session(token2) as c: 

253 res = c.CreateGroupChat( 

254 conversations_pb2.CreateGroupChatReq( 

255 recipient_user_ids=[user.id], title=wrappers_pb2.StringValue(value="Chat 0") 

256 ) 

257 ) 

258 chat_ids.append(res.group_chat_id) 

259 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=res.group_chat_id, text="Test message")) 

260 res = c.CreateGroupChat( 

261 conversations_pb2.CreateGroupChatReq( 

262 recipient_user_ids=[user.id, user3.id], title=wrappers_pb2.StringValue(value="Left Chat 1") 

263 ) 

264 ) 

265 left_chat_id = res.group_chat_id 

266 chat_ids.append(left_chat_id) 

267 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=left_chat_id, text="Test message")) 

268 res = c.CreateGroupChat( 

269 conversations_pb2.CreateGroupChatReq( 

270 recipient_user_ids=[user.id, user3.id], title=wrappers_pb2.StringValue(value="Chat 2") 

271 ) 

272 ) 

273 chat2_id = res.group_chat_id 

274 chat_ids.append(chat2_id) 

275 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=chat2_id, text="Test message")) 

276 

277 with conversations_session(token3) as c: 

278 res = c.CreateGroupChat( 

279 conversations_pb2.CreateGroupChatReq( 

280 recipient_user_ids=[user.id, user2.id, user4.id], title=wrappers_pb2.StringValue(value="Chat 3") 

281 ) 

282 ) 

283 chat_ids.append(res.group_chat_id) 

284 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=res.group_chat_id, text="Test message")) 

285 

286 for chat_id in chat_ids: 

287 moderator.approve_group_chat(chat_id) 

288 

289 with conversations_session(token) as c: 

290 res = c.CreateGroupChat( 

291 conversations_pb2.CreateGroupChatReq( 

292 recipient_user_ids=[user2.id, user3.id, user4.id], title=wrappers_pb2.StringValue(value="Chat 4") 

293 ) 

294 ) 

295 moderator.approve_group_chat(res.group_chat_id) 

296 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=res.group_chat_id, text="Test message")) 

297 

298 # leave chat 

299 c.LeaveGroupChat(conversations_pb2.LeaveGroupChatReq(group_chat_id=left_chat_id)) 

300 

301 with conversations_session(token3) as c: 

302 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=chat2_id, text="Test message")) 

303 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=left_chat_id, text="Test message")) 

304 

305 with conversations_session(token2) as c: 

306 # other user sends a message to that chat 

307 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=left_chat_id, text="Another test message")) 

308 res = c.ListGroupChats(conversations_pb2.ListGroupChatsReq()) 

309 assert len(res.group_chats) == 5 

310 assert res.group_chats[0].title == "Left Chat 1" 

311 assert res.group_chats[1].title == "Chat 2" 

312 assert res.group_chats[2].title == "Chat 4" 

313 assert res.group_chats[3].title == "Chat 3" 

314 assert res.group_chats[4].title == "Chat 0" 

315 

316 with conversations_session(token) as c: 

317 # we can't see the new message since we left before it was sent 

318 res = c.ListGroupChats(conversations_pb2.ListGroupChatsReq()) 

319 assert len(res.group_chats) == 5 

320 assert res.group_chats[0].title == "Chat 2" 

321 assert res.group_chats[1].title == "Left Chat 1" 

322 assert res.group_chats[2].title == "Chat 4" 

323 assert res.group_chats[3].title == "Chat 3" 

324 assert res.group_chats[4].title == "Chat 0" 

325 

326 

327def test_get_group_chat_messages(db): 

328 user1, token1 = generate_user() 

329 user2, token2 = generate_user() 

330 user3, token3 = generate_user() 

331 

332 make_friends(user1, user2) 

333 

334 with conversations_session(token1) as c: 

335 # create some threads with messages 

336 res = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id])) 

337 group_chat_id = res.group_chat_id 

338 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=res.group_chat_id, text="Test message 1")) 

339 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=res.group_chat_id, text="Test message 2")) 

340 

341 res = c.GetGroupChatMessages(conversations_pb2.GetGroupChatMessagesReq(group_chat_id=group_chat_id)) 

342 # created + 2 normal 

343 assert len(res.messages) == 3 

344 assert res.no_more 

345 

346 assert res.messages[0].text.text == "Test message 2" 

347 assert res.messages[1].text.text == "Test message 1" 

348 assert res.messages[2].WhichOneof("content") == "chat_created" 

349 

350 # test that another user can't access the thread 

351 with conversations_session(token3) as c: 

352 res = c.GetGroupChatMessages(conversations_pb2.GetGroupChatMessagesReq(group_chat_id=group_chat_id)) 

353 assert len(res.messages) == 0 

354 

355 

356def test_get_group_chat_messages_pagination(db, moderator): 

357 user1, token1 = generate_user() 

358 user2, token2 = generate_user() 

359 make_friends(user1, user2) 

360 

361 with conversations_session(token1) as c: 

362 res = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id])) 

363 group_chat_id = res.group_chat_id 

364 for i in range(30): 

365 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=group_chat_id, text=str(i))) 

366 

367 moderator.approve_group_chat(group_chat_id) 

368 

369 with conversations_session(token2) as c: 

370 res = c.GetGroupChatMessages(conversations_pb2.GetGroupChatMessagesReq(group_chat_id=group_chat_id)) 

371 # pagination 

372 assert len(res.messages) == 20 

373 assert res.messages[0].text.text == "29" 

374 assert res.messages[19].text.text == "10" 

375 assert not res.no_more 

376 res = c.GetGroupChatMessages( 

377 conversations_pb2.GetGroupChatMessagesReq( 

378 group_chat_id=group_chat_id, last_message_id=res.messages[19].message_id 

379 ) 

380 ) 

381 assert len(res.messages) == 11 

382 assert res.messages[0].text.text == "9" 

383 assert res.messages[9].text.text == "0" 

384 assert res.no_more 

385 

386 

387def test_get_group_chat_messages_joined_left(db, moderator): 

388 user1, token1 = generate_user() 

389 user2, token2 = generate_user() 

390 user3, token3 = generate_user() 

391 user4, token4 = generate_user() 

392 make_friends(user1, user2) 

393 make_friends(user1, user3) 

394 make_friends(user1, user4) 

395 

396 with conversations_session(token1) as c: 

397 res = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id, user4.id])) 

398 group_chat_id = res.group_chat_id 

399 moderator.approve_group_chat(group_chat_id) 

400 

401 for i in range(10): 

402 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=group_chat_id, text=str(i))) 

403 

404 c.InviteToGroupChat(conversations_pb2.InviteToGroupChatReq(group_chat_id=group_chat_id, user_id=user3.id)) 

405 

406 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=group_chat_id, text="10")) 

407 

408 res = c.GetGroupChatMessages(conversations_pb2.GetGroupChatMessagesReq(group_chat_id=group_chat_id)) 

409 

410 # created + 10 normal + invited + normal 

411 assert len(res.messages) == 13 

412 

413 with conversations_session(token3) as c: 

414 # can only see last message after invited 

415 res = c.GetGroupChatMessages(conversations_pb2.GetGroupChatMessagesReq(group_chat_id=group_chat_id)) 

416 # joined + normal 

417 assert len(res.messages) == 2 

418 assert res.messages[0].text.text == "10" 

419 

420 c.LeaveGroupChat(conversations_pb2.LeaveGroupChatReq(group_chat_id=group_chat_id)) 

421 

422 with conversations_session(token1) as c: 

423 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=group_chat_id, text="11")) 

424 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=group_chat_id, text="12")) 

425 

426 c.InviteToGroupChat(conversations_pb2.InviteToGroupChatReq(group_chat_id=group_chat_id, user_id=user3.id)) 

427 

428 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=group_chat_id, text="13")) 

429 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=group_chat_id, text="14")) 

430 

431 with conversations_session(token3) as c: 

432 # can only see last message after invited 

433 res = c.GetGroupChatMessages(conversations_pb2.GetGroupChatMessagesReq(group_chat_id=group_chat_id)) 

434 # joined + normal + left + invite + 2 normal 

435 assert len(res.messages) == 6 

436 assert res.messages[0].text.text == "14" 

437 assert res.messages[1].text.text == "13" 

438 assert res.messages[2].WhichOneof("content") == "user_invited" 

439 assert res.messages[3].WhichOneof("content") == "user_left" 

440 assert res.messages[4].text.text == "10" 

441 assert res.messages[5].WhichOneof("content") == "user_invited" 

442 

443 

444def test_get_group_chat_info(db): 

445 user1, token1 = generate_user() 

446 user2, token2 = generate_user() 

447 user3, token3 = generate_user() 

448 

449 make_friends(user1, user2) 

450 make_friends(user3, user1) 

451 

452 with conversations_session(token1) as c: 

453 # create some threads with messages 

454 res = c.CreateGroupChat( 

455 conversations_pb2.CreateGroupChatReq( 

456 recipient_user_ids=[user2.id], title=wrappers_pb2.StringValue(value="Test title") 

457 ) 

458 ) 

459 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=res.group_chat_id, text="Test message 1")) 

460 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=res.group_chat_id, text="Test message 2")) 

461 group_chat1_id = res.group_chat_id 

462 res = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id, user3.id])) 

463 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=res.group_chat_id, text="Test group message 1")) 

464 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=res.group_chat_id, text="Test group message 2")) 

465 group_chat2_id = res.group_chat_id 

466 

467 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=group_chat1_id)) 

468 assert res.title == "Test title" 

469 assert user2.id in res.member_user_ids 

470 assert user1.id in res.admin_user_ids 

471 assert to_aware_datetime(res.created) <= now() 

472 assert res.only_admins_invite 

473 assert res.is_dm 

474 assert not res.is_archived 

475 

476 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=group_chat2_id)) 

477 assert not res.title 

478 assert user2.id in res.member_user_ids 

479 assert user3.id in res.member_user_ids 

480 assert user1.id in res.admin_user_ids 

481 assert to_aware_datetime(res.created) <= now() 

482 assert res.only_admins_invite 

483 assert not res.is_dm 

484 assert not res.is_archived 

485 

486 

487def test_get_group_chat_info_denied(db): 

488 user1, token1 = generate_user() 

489 user2, token2 = generate_user() 

490 user3, token3 = generate_user() 

491 user4, token4 = generate_user() 

492 

493 make_friends(user1, user2) 

494 make_friends(user3, user1) 

495 

496 with conversations_session(token1) as c: 

497 # create a group chat with messages 

498 res = c.CreateGroupChat( 

499 conversations_pb2.CreateGroupChatReq( 

500 recipient_user_ids=[user2.id, user3.id], title=wrappers_pb2.StringValue(value="Test title") 

501 ) 

502 ) 

503 group_chat_id = res.group_chat_id 

504 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=group_chat_id, text="Test message 1")) 

505 

506 with conversations_session(token4) as c: 

507 with pytest.raises(grpc.RpcError) as e: 

508 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=group_chat_id)) 

509 assert e.value.code() == grpc.StatusCode.NOT_FOUND 

510 

511 

512def test_get_group_chat_info_left(db, moderator): 

513 user1, token1 = generate_user() 

514 user2, token2 = generate_user() 

515 user3, token3 = generate_user() 

516 user4, token4 = generate_user() 

517 

518 make_friends(user1, user2) 

519 make_friends(user3, user1) 

520 make_friends(user1, user4) 

521 

522 with conversations_session(token1) as c: 

523 # create a group chat with messages 

524 res = c.CreateGroupChat( 

525 conversations_pb2.CreateGroupChatReq( 

526 recipient_user_ids=[user2.id, user3.id], title=wrappers_pb2.StringValue(value="Test title") 

527 ) 

528 ) 

529 group_chat_id = res.group_chat_id 

530 moderator.approve_group_chat(group_chat_id) 

531 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=group_chat_id, text="Test message 1")) 

532 

533 with conversations_session(token3) as c: 

534 c.LeaveGroupChat(conversations_pb2.LeaveGroupChatReq(group_chat_id=group_chat_id)) 

535 

536 with conversations_session(token1) as c: 

537 c.InviteToGroupChat(conversations_pb2.InviteToGroupChatReq(group_chat_id=group_chat_id, user_id=user4.id)) 

538 

539 with conversations_session(token3) as c: 

540 # this user left when user4 wasn't a member, 

541 # so the returned members should be user1, user2, and user3 only 

542 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=group_chat_id)) 

543 assert len(res.member_user_ids) == 3 

544 assert user1.id in res.member_user_ids 

545 assert user2.id in res.member_user_ids 

546 assert user3.id in res.member_user_ids 

547 

548 

549def test_remove_group_chat_user(db): 

550 # create 3 uses and connect them 

551 user1, token1 = generate_user() 

552 user2, token2 = generate_user() 

553 user3, token3 = generate_user() 

554 make_friends(user1, user2) 

555 make_friends(user1, user3) 

556 

557 # using user token, create a Conversations API for testing 

558 with conversations_session(token1) as c: 

559 # create a group chat 

560 res = c.CreateGroupChat( 

561 conversations_pb2.CreateGroupChatReq( 

562 recipient_user_ids=[user2.id, user3.id], title=wrappers_pb2.StringValue(value="Test title") 

563 ) 

564 ) 

565 group_chat_id = res.group_chat_id 

566 

567 # remove a user from group 

568 c.RemoveGroupChatUser(conversations_pb2.RemoveGroupChatUserReq(group_chat_id=group_chat_id, user_id=user2.id)) 

569 assert user3.id in res.member_user_ids # other users are still in the group 

570 

571 # can't remove the same user twice 

572 with pytest.raises(grpc.RpcError) as e: 

573 c.RemoveGroupChatUser( 

574 conversations_pb2.RemoveGroupChatUserReq(group_chat_id=group_chat_id, user_id=user2.id) 

575 ) 

576 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION 

577 

578 

579def test_edit_group_chat(db, moderator): 

580 user1, token1 = generate_user() 

581 user2, token2 = generate_user() 

582 user3, token3 = generate_user() 

583 make_friends(user1, user2) 

584 

585 with conversations_session(token1) as c: 

586 # create some threads with messages 

587 res = c.CreateGroupChat( 

588 conversations_pb2.CreateGroupChatReq( 

589 recipient_user_ids=[user2.id], title=wrappers_pb2.StringValue(value="Test title") 

590 ) 

591 ) 

592 group_chat_id = res.group_chat_id 

593 moderator.approve_group_chat(group_chat_id) 

594 

595 c.EditGroupChat( 

596 conversations_pb2.EditGroupChatReq( 

597 group_chat_id=group_chat_id, 

598 title=wrappers_pb2.StringValue(value="Modified title"), 

599 only_admins_invite=wrappers_pb2.BoolValue(value=False), 

600 ) 

601 ) 

602 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=group_chat_id)) 

603 assert res.title == "Modified title" 

604 assert not res.only_admins_invite 

605 

606 # make sure non-admin is not allowed to modify 

607 with conversations_session(token2) as c: 

608 with pytest.raises(grpc.RpcError) as e: 

609 c.EditGroupChat( 

610 conversations_pb2.EditGroupChatReq( 

611 group_chat_id=group_chat_id, 

612 title=wrappers_pb2.StringValue(value="Other title"), 

613 only_admins_invite=wrappers_pb2.BoolValue(value=True), 

614 ) 

615 ) 

616 assert e.value.code() == grpc.StatusCode.PERMISSION_DENIED 

617 

618 # make sure non-recipient is not allowed to modify 

619 with conversations_session(token3) as c: 

620 with pytest.raises(grpc.RpcError) as e: 

621 c.EditGroupChat( 

622 conversations_pb2.EditGroupChatReq( 

623 group_chat_id=group_chat_id, 

624 title=wrappers_pb2.StringValue(value="Other title"), 

625 only_admins_invite=wrappers_pb2.BoolValue(value=True), 

626 ) 

627 ) 

628 assert e.value.code() == grpc.StatusCode.NOT_FOUND 

629 

630 

631def test_make_remove_group_chat_admin(db, moderator): 

632 user1, token1 = generate_user() 

633 user2, token2 = generate_user() 

634 user3, token3 = generate_user() 

635 

636 make_friends(user1, user2) 

637 make_friends(user1, user3) 

638 make_friends(user2, user3) 

639 

640 with conversations_session(token1) as c: 

641 # create some threads with messages 

642 res = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id, user3.id])) 

643 group_chat_id = res.group_chat_id 

644 moderator.approve_group_chat(group_chat_id) 

645 

646 # shouldn't be able to remove only admin 

647 with pytest.raises(grpc.RpcError) as e: 

648 c.RemoveGroupChatAdmin( 

649 conversations_pb2.RemoveGroupChatAdminReq(group_chat_id=group_chat_id, user_id=user1.id) 

650 ) 

651 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION 

652 assert e.value.details() == "You can't remove the last admin." 

653 

654 c.MakeGroupChatAdmin(conversations_pb2.MakeGroupChatAdminReq(group_chat_id=group_chat_id, user_id=user2.id)) 

655 

656 # shouldn't be able to make admin again 

657 with pytest.raises(grpc.RpcError) as e: 

658 c.MakeGroupChatAdmin(conversations_pb2.MakeGroupChatAdminReq(group_chat_id=group_chat_id, user_id=user2.id)) 

659 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION 

660 assert e.value.details() == "That user is already an admin." 

661 

662 with conversations_session(token2) as c: 

663 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=group_chat_id)) 

664 assert user1.id in res.admin_user_ids 

665 assert user2.id in res.admin_user_ids 

666 

667 with conversations_session(token1) as c: 

668 c.RemoveGroupChatAdmin(conversations_pb2.RemoveGroupChatAdminReq(group_chat_id=group_chat_id, user_id=user2.id)) 

669 

670 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=group_chat_id)) 

671 assert user1.id in res.admin_user_ids 

672 assert user2.id not in res.admin_user_ids 

673 

674 with conversations_session(token2) as c: 

675 # shouldn't be able to make admin if not admin 

676 with pytest.raises(grpc.RpcError) as e: 

677 c.MakeGroupChatAdmin(conversations_pb2.MakeGroupChatAdminReq(group_chat_id=group_chat_id, user_id=user3.id)) 

678 assert e.value.code() == grpc.StatusCode.PERMISSION_DENIED 

679 

680 

681def test_send_message(db, moderator: Moderator, push_collector: PushCollector, email_collector: EmailCollector): 

682 user1, token1 = generate_user() 

683 user2, token2 = generate_user() 

684 user3, token3 = generate_user() 

685 make_friends(user1, user2) 

686 make_friends(user1, user3) 

687 

688 # Let user2 receive email notifications for every chat message 

689 with notifications_session(token2) as n: 

690 n.SetNotificationSettings( 

691 notifications_pb2.SetNotificationSettingsReq( 

692 preferences=[ 

693 notifications_pb2.SingleNotificationPreference( 

694 topic=NotificationTopicAction.chat__message.topic, 

695 action=NotificationTopicAction.chat__message.action, 

696 delivery_method="email", 

697 enabled=True, 

698 ) 

699 ] 

700 ) 

701 ) 

702 

703 group_chat_title = "My group chat" 

704 message1 = "Test message 1" 

705 

706 # Let user1 create a group chat with user2 and send a message 

707 with conversations_session(token1) as c: 

708 res = c.CreateGroupChat( 

709 conversations_pb2.CreateGroupChatReq( 

710 title=wrappers_pb2.StringValue(value=group_chat_title), recipient_user_ids=[user2.id] 

711 ) 

712 ) 

713 group_chat_id = res.group_chat_id 

714 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=group_chat_id, text=message1)) 

715 

716 # Sender can already see group chat 

717 res = c.GetGroupChatMessages(conversations_pb2.GetGroupChatMessagesReq(group_chat_id=group_chat_id)) 

718 assert res.messages[0].text.text == message1 

719 assert to_aware_datetime(res.messages[0].time) <= now() 

720 assert res.messages[0].author_user_id == user1.id 

721 

722 # user2 sees nothing until the group chat is approved 

723 assert push_collector.count_for_user(user2.id) == 0 

724 assert email_collector.count_for_recipient(user2.email) == 0 

725 

726 moderator.approve_group_chat(group_chat_id) 

727 

728 # user2 gets email and push notifications 

729 push = push_collector.pop_for_user(user2.id, last=True) 

730 assert push.topic_action == NotificationTopicAction.chat__message.display 

731 assert user1.name in push.content.title 

732 assert group_chat_title in push.content.title 

733 assert push.content.ios_title == user1.name 

734 assert push.content.ios_subtitle == group_chat_title 

735 assert message1 in push.content.body 

736 

737 email = email_collector.pop_for_recipient(user2.email, last=True) 

738 assert group_chat_title in email.subject 

739 assert message1 in email.plain 

740 assert message1 in email.html 

741 

742 # can't send message if not in chat 

743 with conversations_session(token3) as c: 

744 with pytest.raises(grpc.RpcError) as e: 

745 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=group_chat_id, text="Test message 2")) 

746 assert e.value.code() == grpc.StatusCode.NOT_FOUND 

747 

748 make_user_block(user2, user1) 

749 with conversations_session(token1) as c: 

750 with pytest.raises(grpc.RpcError) as e: 

751 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=group_chat_id, text="Message after block")) 

752 assert e.value.details() == "You can't send a message in this chat." 

753 

754 

755def test_send_direct_message(db, moderator: Moderator, push_collector: PushCollector, email_collector: EmailCollector): 

756 user1, token1 = generate_user(complete_profile=True) 

757 user2, token2 = generate_user(complete_profile=True) 

758 

759 make_friends(user1, user2) 

760 

761 # Let user2 receive email notifications for every chat message 

762 with notifications_session(token2) as n: 

763 n.SetNotificationSettings( 

764 notifications_pb2.SetNotificationSettingsReq( 

765 preferences=[ 

766 notifications_pb2.SingleNotificationPreference( 

767 topic=NotificationTopicAction.chat__message.topic, 

768 action=NotificationTopicAction.chat__message.action, 

769 delivery_method="email", 

770 enabled=True, 

771 ) 

772 ] 

773 ) 

774 ) 

775 

776 # Let user1 send two DM's to user2 

777 message1 = "Hello, user2!" 

778 message2 = "One more message." 

779 

780 with conversations_session(token1) as c1: 

781 # Send a DM from user1 to user2 

782 res = c1.SendDirectMessage(conversations_pb2.SendDirectMessageReq(recipient_user_id=user2.id, text=message1)) 

783 moderator.approve_group_chat(res.group_chat_id) 

784 

785 c1.SendDirectMessage(conversations_pb2.SendDirectMessageReq(recipient_user_id=user2.id, text=message2)) 

786 

787 # user2 should have received push and email notifications for both messages 

788 push = push_collector.pop_for_user(user2.id, last=False) 

789 assert push.topic_action == NotificationTopicAction.chat__message.display 

790 assert push.content.title == user1.name 

791 assert push.content.body == message1 

792 

793 email = email_collector.pop_for_recipient(user2.email, last=False) 

794 assert user1.name in email.subject 

795 assert message1 in email.plain 

796 assert message1 in email.html 

797 

798 push = push_collector.pop_for_user(user2.id, last=True) 

799 assert push.topic_action == NotificationTopicAction.chat__message.display 

800 assert push.content.title == user1.name 

801 assert push.content.body == message2 

802 

803 email = email_collector.pop_for_recipient(user2.email, last=True) 

804 assert user1.name in email.subject 

805 assert message2 in email.plain 

806 assert message2 in email.html 

807 

808 with conversations_session(token2) as c2: 

809 # Fetch the chat by ID returned from SendDirectMessage 

810 chat = c2.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=res.group_chat_id)) 

811 

812 assert chat.is_dm 

813 group_chat_id = chat.group_chat_id 

814 

815 # Verify that the messages was received 

816 messages = c2.GetGroupChatMessages( 

817 conversations_pb2.GetGroupChatMessagesReq(group_chat_id=group_chat_id) 

818 ).messages 

819 

820 assert len(messages) == 2 

821 assert messages[0].text.text == message2 

822 assert messages[1].text.text == message1 

823 assert messages[0].author_user_id == user1.id 

824 

825 

826def test_excessive_chat_initiations_are_reported(db, low_rate_limits, email_collector: EmailCollector): 

827 """Test that excessive chat initiations are first reported in a warning email and finally lead blocking of further contacting other users.""" 

828 user, token = generate_user() 

829 rate_limit_definition = RATE_LIMIT_DEFINITIONS[RateLimitAction.chat_initiation] 

830 with conversations_session(token) as c: 

831 # Test warning email 

832 for _ in range(rate_limit_definition.warning_limit): 

833 recipient_user, _ = generate_user() 

834 _ = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[recipient_user.id])) 

835 

836 assert email_collector.count_for_reports() == 0 

837 

838 recipient_user, _ = generate_user() 

839 _ = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[recipient_user.id])) 

840 

841 email = email_collector.pop_for_reports(last=True) 

842 assert email.plain.startswith( 

843 f"User {user.username} has sent {rate_limit_definition.warning_limit} chat initiations in the past {RATE_LIMIT_HOURS} hours." 

844 ) 

845 

846 # Test new chat initiations fail after exceeding CHAT_INITIATION_HARD_LIMIT 

847 for _ in range(rate_limit_definition.hard_limit - rate_limit_definition.warning_limit - 1): 

848 recipient_user, _ = generate_user() 

849 _ = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[recipient_user.id])) 

850 

851 assert email_collector.count_for_reports() == 0 

852 

853 recipient_user, _ = generate_user() 

854 with pytest.raises(grpc.RpcError) as exc_info: 

855 _ = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[recipient_user.id])) 

856 assert exc_info.value.code() == grpc.StatusCode.RESOURCE_EXHAUSTED 

857 assert ( 

858 exc_info.value.details() 

859 == "You have messaged a lot of users in the past 24 hours. To avoid spam, you can't contact any more users for now." 

860 ) 

861 

862 email = email_collector.pop_for_reports(last=True) 

863 assert email.plain.startswith( 

864 f"User {user.username} has sent {rate_limit_definition.hard_limit} chat initiations in the past {RATE_LIMIT_HOURS} hours." 

865 ) 

866 assert "The user has been blocked from sending further chat initiations for now." in email.plain 

867 

868 with session_scope() as session: 

869 assert session.execute( 

870 select(RateLimitViolation.is_hard_limit) 

871 .where(RateLimitViolation.user_id == user.id) 

872 .order_by(RateLimitViolation.id) 

873 ).scalars().all() == [False, True] 

874 

875 # still blocked once the hard violation is on record, without reporting again 

876 recipient_user, _ = generate_user() 

877 with pytest.raises(grpc.RpcError) as exc_info: 

878 _ = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[recipient_user.id])) 

879 assert exc_info.value.code() == grpc.StatusCode.RESOURCE_EXHAUSTED 

880 assert email_collector.count_for_reports() == 0 

881 

882 

883def test_send_direct_message_rate_limit(db, low_rate_limits, moderator, email_collector: EmailCollector): 

884 """SendDirectMessage should enforce the chat_initiation rate limit when creating a new DM, but not when sending into an existing one.""" 

885 user, token = generate_user(complete_profile=True) 

886 rate_limit_definition = RATE_LIMIT_DEFINITIONS[RateLimitAction.chat_initiation] 

887 

888 with conversations_session(token) as c: 

889 for _ in range(rate_limit_definition.warning_limit): 

890 recipient, _ = generate_user() 

891 c.SendDirectMessage(conversations_pb2.SendDirectMessageReq(recipient_user_id=recipient.id, text="hi")) 

892 

893 assert email_collector.count_for_reports() == 0 

894 

895 recipient, _ = generate_user() 

896 existing_dm_recipient_id = recipient.id 

897 c.SendDirectMessage( 

898 conversations_pb2.SendDirectMessageReq(recipient_user_id=existing_dm_recipient_id, text="hi") 

899 ) 

900 

901 email = email_collector.pop_for_reports(last=True) 

902 assert email.plain.startswith( 

903 f"User {user.username} has sent {rate_limit_definition.warning_limit} chat initiations in the past {RATE_LIMIT_HOURS} hours." 

904 ) 

905 

906 for _ in range(rate_limit_definition.hard_limit - rate_limit_definition.warning_limit - 1): 

907 recipient, _ = generate_user() 

908 c.SendDirectMessage(conversations_pb2.SendDirectMessageReq(recipient_user_id=recipient.id, text="hi")) 

909 

910 assert email_collector.count_for_reports() == 0 

911 

912 # follow-up into an existing DM must not count as a new initiation 

913 c.SendDirectMessage( 

914 conversations_pb2.SendDirectMessageReq(recipient_user_id=existing_dm_recipient_id, text="follow-up") 

915 ) 

916 assert email_collector.count_for_reports() == 0 

917 

918 recipient, _ = generate_user() 

919 with pytest.raises(grpc.RpcError) as exc_info: 

920 c.SendDirectMessage(conversations_pb2.SendDirectMessageReq(recipient_user_id=recipient.id, text="hi")) 

921 assert exc_info.value.code() == grpc.StatusCode.RESOURCE_EXHAUSTED 

922 assert ( 

923 exc_info.value.details() 

924 == "You have messaged a lot of users in the past 24 hours. To avoid spam, you can't contact any more users for now." 

925 ) 

926 

927 email = email_collector.pop_for_reports(last=True) 

928 assert email.plain.startswith( 

929 f"User {user.username} has sent {rate_limit_definition.hard_limit} chat initiations in the past {RATE_LIMIT_HOURS} hours." 

930 ) 

931 assert "The user has been blocked from sending further chat initiations for now." in email.plain 

932 

933 

934def test_leave_invite_to_group_chat(db, moderator): 

935 user1, token1 = generate_user() 

936 user2, token2 = generate_user() 

937 user3, token3 = generate_user() 

938 user4, token4 = generate_user() 

939 user5, token5 = generate_user() 

940 user6, token6 = generate_user(delete_user=True) 

941 user7, token7 = generate_user() 

942 user8, token8 = generate_user() 

943 

944 make_friends(user1, user2) 

945 make_friends(user1, user3) 

946 make_friends(user1, user5) 

947 make_friends(user1, user7) 

948 make_friends(user1, user8) 

949 make_friends(user2, user3) 

950 make_friends(user4, user3) 

951 make_user_block(user1, user7) 

952 make_user_block(user8, user1) 

953 

954 with conversations_session(token1) as c: 

955 res = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id, user5.id])) 

956 group_chat_id = res.group_chat_id 

957 moderator.approve_group_chat(group_chat_id) 

958 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=group_chat_id, text="Test message 1")) 

959 

960 # other user not in chat 

961 with conversations_session(token3) as c: 

962 with pytest.raises(grpc.RpcError) as e: 

963 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=group_chat_id)) 

964 assert e.value.code() == grpc.StatusCode.NOT_FOUND 

965 with pytest.raises(grpc.RpcError) as e: 

966 res = c.InviteToGroupChat( 

967 conversations_pb2.InviteToGroupChatReq(group_chat_id=group_chat_id, user_id=user4.id) 

968 ) 

969 assert e.value.code() == grpc.StatusCode.NOT_FOUND 

970 with pytest.raises(grpc.RpcError) as e: 

971 res = c.LeaveGroupChat(conversations_pb2.LeaveGroupChatReq(group_chat_id=group_chat_id)) 

972 assert e.value.code() == grpc.StatusCode.NOT_FOUND 

973 

974 with conversations_session(token2) as c: 

975 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=group_chat_id)) 

976 assert user3.id not in res.member_user_ids 

977 

978 # only_admins_invite defaults to true so shouldn't be able to invite 

979 with pytest.raises(grpc.RpcError) as e: 

980 res = c.InviteToGroupChat( 

981 conversations_pb2.InviteToGroupChatReq(group_chat_id=group_chat_id, user_id=user3.id) 

982 ) 

983 assert e.value.code() == grpc.StatusCode.PERMISSION_DENIED 

984 c.LeaveGroupChat(conversations_pb2.LeaveGroupChatReq(group_chat_id=group_chat_id)) 

985 

986 with conversations_session(token1) as c: 

987 # invite invisible user fails 

988 with pytest.raises(grpc.RpcError) as e: 

989 c.InviteToGroupChat(conversations_pb2.InviteToGroupChatReq(group_chat_id=group_chat_id, user_id=user6.id)) 

990 assert e.value.code() == grpc.StatusCode.NOT_FOUND 

991 assert e.value.details() == "Couldn't find that user." 

992 # invite fake user fails 

993 with pytest.raises(grpc.RpcError) as e: 

994 c.InviteToGroupChat(conversations_pb2.InviteToGroupChatReq(group_chat_id=group_chat_id, user_id=999)) 

995 assert e.value.code() == grpc.StatusCode.NOT_FOUND 

996 assert e.value.details() == "Couldn't find that user." 

997 # invite blocked user fails 

998 with pytest.raises(grpc.RpcError) as e: 

999 c.InviteToGroupChat(conversations_pb2.InviteToGroupChatReq(group_chat_id=group_chat_id, user_id=user7.id)) 

1000 assert e.value.code() == grpc.StatusCode.NOT_FOUND 

1001 assert e.value.details() == "Couldn't find that user." 

1002 # invite blocking user fails 

1003 with pytest.raises(grpc.RpcError) as e: 

1004 c.InviteToGroupChat(conversations_pb2.InviteToGroupChatReq(group_chat_id=group_chat_id, user_id=user8.id)) 

1005 assert e.value.code() == grpc.StatusCode.NOT_FOUND 

1006 assert e.value.details() == "Couldn't find that user." 

1007 

1008 c.InviteToGroupChat(conversations_pb2.InviteToGroupChatReq(group_chat_id=group_chat_id, user_id=user3.id)) 

1009 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=group_chat_id)) 

1010 assert user1.id in res.member_user_ids 

1011 assert user5.id in res.member_user_ids 

1012 assert user3.id in res.member_user_ids 

1013 

1014 # test non-admin inviting 

1015 c.EditGroupChat( 

1016 conversations_pb2.EditGroupChatReq( 

1017 group_chat_id=group_chat_id, only_admins_invite=wrappers_pb2.BoolValue(value=False) 

1018 ) 

1019 ) 

1020 

1021 with conversations_session(token3) as c: 

1022 c.InviteToGroupChat(conversations_pb2.InviteToGroupChatReq(group_chat_id=group_chat_id, user_id=user2.id)) 

1023 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=group_chat_id)) 

1024 assert user2.id in res.member_user_ids 

1025 

1026 

1027def test_group_chats_with_messages_before_join(db, moderator): 

1028 """ 

1029 If user 1 and 2 have a group chat and send messages, then add user 3; user 3 

1030 should still see the group chat! 

1031 """ 

1032 user1, token1 = generate_user() 

1033 user2, token2 = generate_user() 

1034 user3, token3 = generate_user() 

1035 user4, token4 = generate_user() 

1036 

1037 make_friends(user1, user2) 

1038 make_friends(user1, user3) 

1039 make_friends(user2, user3) 

1040 make_friends(user1, user4) 

1041 

1042 with conversations_session(token1) as c: 

1043 res = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id, user4.id])) 

1044 group_chat_id = res.group_chat_id 

1045 moderator.approve_group_chat(group_chat_id) 

1046 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=group_chat_id, text="Test message 1")) 

1047 

1048 with conversations_session(token2) as c: 

1049 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=group_chat_id, text="Test message 2")) 

1050 

1051 with conversations_session(token1) as c: 

1052 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=group_chat_id, text="Test message 3")) 

1053 

1054 c.InviteToGroupChat(conversations_pb2.InviteToGroupChatReq(group_chat_id=group_chat_id, user_id=user3.id)) 

1055 

1056 with conversations_session(token3) as c: 

1057 # should work 

1058 c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=group_chat_id)) 

1059 

1060 res = c.ListGroupChats(conversations_pb2.ListGroupChatsReq()) 

1061 assert len(res.group_chats) == 1 

1062 

1063 

1064def test_invite_to_dm(db): 

1065 user1, token1 = generate_user() 

1066 user2, token2 = generate_user() 

1067 user3, token3 = generate_user() 

1068 

1069 make_friends(user1, user2) 

1070 make_friends(user1, user3) 

1071 make_friends(user2, user3) 

1072 

1073 with conversations_session(token1) as c: 

1074 res = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id])) 

1075 group_chat_id = res.group_chat_id 

1076 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=group_chat_id, text="Test message 1")) 

1077 

1078 # dm, shou;dn't be able to invite someone else 

1079 with pytest.raises(grpc.RpcError) as e: 

1080 c.InviteToGroupChat(conversations_pb2.InviteToGroupChatReq(group_chat_id=group_chat_id, user_id=user3.id)) 

1081 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION 

1082 assert e.value.details() == "You can't invite other users to a direct message." 

1083 

1084 

1085def test_sole_admin_leaves(db, moderator): 

1086 user1, token1 = generate_user() 

1087 user2, token2 = generate_user() 

1088 user3, token3 = generate_user() 

1089 

1090 make_friends(user1, user2) 

1091 make_friends(user1, user3) 

1092 make_friends(user2, user3) 

1093 

1094 with conversations_session(token1) as c: 

1095 res = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id, user3.id])) 

1096 group_chat_id = res.group_chat_id 

1097 moderator.approve_group_chat(group_chat_id) 

1098 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=group_chat_id, text="Test message 1")) 

1099 

1100 # sole admin can't leave group chat 

1101 with pytest.raises(grpc.RpcError) as e: 

1102 c.LeaveGroupChat(conversations_pb2.LeaveGroupChatReq(group_chat_id=group_chat_id)) 

1103 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION 

1104 assert e.value.details() == "The last admin can't leave a group chat." 

1105 

1106 with conversations_session(token2) as c: 

1107 c.LeaveGroupChat(conversations_pb2.LeaveGroupChatReq(group_chat_id=group_chat_id)) 

1108 

1109 with conversations_session(token3) as c: 

1110 c.LeaveGroupChat(conversations_pb2.LeaveGroupChatReq(group_chat_id=group_chat_id)) 

1111 

1112 # sole admin can leave when last in chat 

1113 with conversations_session(token1) as c: 

1114 c.LeaveGroupChat(conversations_pb2.LeaveGroupChatReq(group_chat_id=group_chat_id)) 

1115 

1116 

1117def test_search_messages(db, moderator): 

1118 user1, token1 = generate_user() 

1119 user2, token2 = generate_user() 

1120 user3, token3 = generate_user() 

1121 

1122 make_friends(user1, user2) 

1123 make_friends(user1, user3) 

1124 

1125 with conversations_session(token1) as c: 

1126 # create some threads with messages 

1127 res = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id])) 

1128 gc1_id = res.group_chat_id 

1129 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=gc1_id, text="Test message 1")) 

1130 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=gc1_id, text="Test message 2")) 

1131 res = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id, user3.id])) 

1132 gc2_id = res.group_chat_id 

1133 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=gc2_id, text="Test group message 3")) 

1134 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=gc2_id, text="Test group message 4")) 

1135 

1136 # Approve group chats so they appear in search results 

1137 moderator.approve_group_chat(gc1_id) 

1138 moderator.approve_group_chat(gc2_id) 

1139 

1140 with conversations_session(token1) as c: 

1141 res = c.SearchMessages(conversations_pb2.SearchMessagesReq(query="message ")) 

1142 assert len(res.results) == 4 

1143 res = c.SearchMessages(conversations_pb2.SearchMessagesReq(query="group ")) 

1144 assert len(res.results) == 2 

1145 res = c.SearchMessages(conversations_pb2.SearchMessagesReq(query="message 5")) 

1146 assert len(res.results) == 0 

1147 

1148 # outside user doesn't get results 

1149 with conversations_session(token3) as c: 

1150 res = c.SearchMessages(conversations_pb2.SearchMessagesReq(query="Test message")) 

1151 assert len(res.results) == 0 

1152 

1153 

1154def test_search_messages_left_joined(db, moderator): 

1155 user1, token1 = generate_user() 

1156 user2, token2 = generate_user() 

1157 user3, token3 = generate_user() 

1158 user4, token4 = generate_user() 

1159 make_friends(user1, user2) 

1160 make_friends(user1, user3) 

1161 make_friends(user1, user4) 

1162 

1163 with conversations_session(token1) as c: 

1164 res = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id, user4.id])) 

1165 group_chat_id = res.group_chat_id 

1166 moderator.approve_group_chat(group_chat_id) 

1167 for i in range(10): 

1168 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=group_chat_id, text="Test message " + str(i))) 

1169 

1170 c.InviteToGroupChat(conversations_pb2.InviteToGroupChatReq(group_chat_id=group_chat_id, user_id=user3.id)) 

1171 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=group_chat_id, text="Test message 10")) 

1172 res = c.SearchMessages(conversations_pb2.SearchMessagesReq(query="Test message")) 

1173 

1174 assert len(res.results) == 11 

1175 

1176 with conversations_session(token3) as c: 

1177 # can only see last message after invited 

1178 res = c.SearchMessages(conversations_pb2.SearchMessagesReq(query="Test message")) 

1179 

1180 assert len(res.results) == 1 

1181 assert res.results[0].message.text.text == "Test message 10" 

1182 

1183 c.LeaveGroupChat(conversations_pb2.LeaveGroupChatReq(group_chat_id=group_chat_id)) 

1184 

1185 with conversations_session(token1) as c: 

1186 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=group_chat_id, text="Test message 11")) 

1187 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=group_chat_id, text="Test message 12")) 

1188 c.InviteToGroupChat(conversations_pb2.InviteToGroupChatReq(group_chat_id=group_chat_id, user_id=user3.id)) 

1189 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=group_chat_id, text="Test message 13")) 

1190 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=group_chat_id, text="Test message 14")) 

1191 

1192 with conversations_session(token3) as c: 

1193 # can only see last message after invited 

1194 res = c.SearchMessages(conversations_pb2.SearchMessagesReq(query="Test message")) 

1195 assert len(res.results) == 3 

1196 assert res.results[0].message.text.text == "Test message 14" 

1197 assert res.results[1].message.text.text == "Test message 13" 

1198 assert res.results[2].message.text.text == "Test message 10" 

1199 

1200 

1201def test_admin_behaviour(db, moderator): 

1202 user1, token1 = generate_user() 

1203 user2, token2 = generate_user() 

1204 user3, token3 = generate_user() 

1205 

1206 make_friends(user1, user2) 

1207 make_friends(user1, user3) 

1208 make_friends(user2, user3) 

1209 

1210 with conversations_session(token1) as c: 

1211 gcid = c.CreateGroupChat( 

1212 conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id, user3.id]) 

1213 ).group_chat_id 

1214 moderator.approve_group_chat(gcid) 

1215 c.MakeGroupChatAdmin(conversations_pb2.MakeGroupChatAdminReq(group_chat_id=gcid, user_id=user2.id)) 

1216 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=gcid)) 

1217 assert len(res.admin_user_ids) == 2 

1218 assert user1.id in res.admin_user_ids 

1219 assert user2.id in res.admin_user_ids 

1220 

1221 with conversations_session(token3) as c: 

1222 with pytest.raises(grpc.RpcError) as e: 

1223 c.MakeGroupChatAdmin(conversations_pb2.MakeGroupChatAdminReq(group_chat_id=gcid, user_id=user3.id)) 

1224 assert e.value.code() == grpc.StatusCode.PERMISSION_DENIED 

1225 with pytest.raises(grpc.RpcError) as e: 

1226 c.RemoveGroupChatAdmin(conversations_pb2.RemoveGroupChatAdminReq(group_chat_id=gcid, user_id=user1.id)) 

1227 assert e.value.code() == grpc.StatusCode.PERMISSION_DENIED 

1228 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=gcid)) 

1229 assert len(res.admin_user_ids) == 2 

1230 assert user1.id in res.admin_user_ids 

1231 assert user2.id in res.admin_user_ids 

1232 

1233 with conversations_session(token2) as c: 

1234 c.MakeGroupChatAdmin(conversations_pb2.MakeGroupChatAdminReq(group_chat_id=gcid, user_id=user3.id)) 

1235 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=gcid)) 

1236 assert len(res.admin_user_ids) == 3 

1237 assert user1.id in res.admin_user_ids 

1238 assert user2.id in res.admin_user_ids 

1239 assert user3.id in res.admin_user_ids 

1240 

1241 c.RemoveGroupChatAdmin(conversations_pb2.RemoveGroupChatAdminReq(group_chat_id=gcid, user_id=user1.id)) 

1242 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=gcid)) 

1243 assert len(res.admin_user_ids) == 2 

1244 assert user2.id in res.admin_user_ids 

1245 assert user3.id in res.admin_user_ids 

1246 

1247 with conversations_session(token1) as c: 

1248 with pytest.raises(grpc.RpcError): 

1249 c.MakeGroupChatAdmin(conversations_pb2.MakeGroupChatAdminReq(group_chat_id=gcid, user_id=user1.id)) 

1250 with pytest.raises(grpc.RpcError): 

1251 c.MakeGroupChatAdmin(conversations_pb2.MakeGroupChatAdminReq(group_chat_id=gcid, user_id=user3.id)) 

1252 with pytest.raises(grpc.RpcError): 

1253 c.RemoveGroupChatAdmin(conversations_pb2.RemoveGroupChatAdminReq(group_chat_id=gcid, user_id=user2.id)) 

1254 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=gcid)) 

1255 assert len(res.admin_user_ids) == 2 

1256 assert user2.id in res.admin_user_ids 

1257 assert user3.id in res.admin_user_ids 

1258 

1259 with conversations_session(token2) as c: 

1260 # can demote self if there are other admins 

1261 c.RemoveGroupChatAdmin(conversations_pb2.RemoveGroupChatAdminReq(group_chat_id=gcid, user_id=user2.id)) 

1262 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=gcid)) 

1263 assert len(res.admin_user_ids) == 1 

1264 assert user3.id in res.admin_user_ids 

1265 

1266 with conversations_session(token3) as c: 

1267 with pytest.raises(grpc.RpcError) as e: 

1268 c.RemoveGroupChatAdmin(conversations_pb2.RemoveGroupChatAdminReq(group_chat_id=gcid, user_id=user3.id)) 

1269 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION 

1270 assert e.value.details() == "You can't remove the last admin." 

1271 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=gcid)) 

1272 assert len(res.admin_user_ids) == 1 

1273 assert user3.id in res.admin_user_ids 

1274 

1275 # last admin can't leave 

1276 with pytest.raises(grpc.RpcError) as e: 

1277 c.LeaveGroupChat(conversations_pb2.LeaveGroupChatReq(group_chat_id=gcid)) 

1278 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION 

1279 assert e.value.details() == "The last admin can't leave a group chat." 

1280 

1281 c.MakeGroupChatAdmin(conversations_pb2.MakeGroupChatAdminReq(group_chat_id=gcid, user_id=user1.id)) 

1282 

1283 c.LeaveGroupChat(conversations_pb2.LeaveGroupChatReq(group_chat_id=gcid)) 

1284 

1285 with conversations_session(token2) as c: 

1286 c.LeaveGroupChat(conversations_pb2.LeaveGroupChatReq(group_chat_id=gcid)) 

1287 

1288 # last participant must be admin but can leave to orphan chat 

1289 with conversations_session(token1) as c: 

1290 c.LeaveGroupChat(conversations_pb2.LeaveGroupChatReq(group_chat_id=gcid)) 

1291 

1292 

1293def test_add_remove_admin_failures(db): 

1294 user1, token1 = generate_user() 

1295 user2, token2 = generate_user() 

1296 user3, token3 = generate_user() 

1297 user4, token4 = generate_user() 

1298 user5, token5 = generate_user() 

1299 

1300 make_friends(user1, user2) 

1301 make_friends(user1, user3) 

1302 make_friends(user1, user4) 

1303 make_friends(user1, user5) 

1304 

1305 with conversations_session(token1) as c: 

1306 gcid = c.CreateGroupChat( 

1307 conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id, user3.id, user4.id, user5.id]) 

1308 ).group_chat_id 

1309 

1310 make_user_invisible(user3.id) 

1311 make_user_block(user1, user4) 

1312 make_user_block(user5, user1) 

1313 

1314 # make non-existent user admin 

1315 with pytest.raises(grpc.RpcError) as e: 

1316 c.MakeGroupChatAdmin(conversations_pb2.MakeGroupChatAdminReq(group_chat_id=gcid, user_id=999)) 

1317 assert e.value.code() == grpc.StatusCode.NOT_FOUND 

1318 assert e.value.details() == "Couldn't find that user." 

1319 

1320 # make invisible user admin 

1321 with pytest.raises(grpc.RpcError) as e: 

1322 c.MakeGroupChatAdmin(conversations_pb2.MakeGroupChatAdminReq(group_chat_id=gcid, user_id=user3.id)) 

1323 assert e.value.code() == grpc.StatusCode.NOT_FOUND 

1324 assert e.value.details() == "Couldn't find that user." 

1325 

1326 # make blocked user admin 

1327 with pytest.raises(grpc.RpcError) as e: 

1328 c.MakeGroupChatAdmin(conversations_pb2.MakeGroupChatAdminReq(group_chat_id=gcid, user_id=user4.id)) 

1329 assert e.value.code() == grpc.StatusCode.NOT_FOUND 

1330 assert e.value.details() == "Couldn't find that user." 

1331 

1332 # make blocking user admin 

1333 with pytest.raises(grpc.RpcError) as e: 

1334 c.MakeGroupChatAdmin(conversations_pb2.MakeGroupChatAdminReq(group_chat_id=gcid, user_id=user5.id)) 

1335 assert e.value.code() == grpc.StatusCode.NOT_FOUND 

1336 assert e.value.details() == "Couldn't find that user." 

1337 

1338 with session_scope() as session: 

1339 subscriptions = ( 

1340 session.execute( 

1341 select(GroupChatSubscription) 

1342 .where(GroupChatSubscription.group_chat_id == gcid) 

1343 .where(GroupChatSubscription.role == GroupChatRole.participant) 

1344 ) 

1345 .scalars() 

1346 .all() 

1347 ) 

1348 

1349 for subscription in subscriptions: 

1350 subscription.role = GroupChatRole.admin 

1351 

1352 with conversations_session(token1) as c: 

1353 # remove non-existent user admin 

1354 with pytest.raises(grpc.RpcError) as e: 

1355 c.RemoveGroupChatAdmin(conversations_pb2.RemoveGroupChatAdminReq(group_chat_id=gcid, user_id=999)) 

1356 assert e.value.code() == grpc.StatusCode.NOT_FOUND 

1357 assert e.value.details() == "Couldn't find that user." 

1358 

1359 # remove invisible admin 

1360 with pytest.raises(grpc.RpcError) as e: 

1361 c.RemoveGroupChatAdmin(conversations_pb2.RemoveGroupChatAdminReq(group_chat_id=gcid, user_id=user3.id)) 

1362 assert e.value.code() == grpc.StatusCode.NOT_FOUND 

1363 assert e.value.details() == "Couldn't find that user." 

1364 

1365 # remove blocked admin 

1366 with pytest.raises(grpc.RpcError) as e: 

1367 c.RemoveGroupChatAdmin(conversations_pb2.RemoveGroupChatAdminReq(group_chat_id=gcid, user_id=user4.id)) 

1368 assert e.value.code() == grpc.StatusCode.NOT_FOUND 

1369 assert e.value.details() == "Couldn't find that user." 

1370 

1371 # remove blocking admin 

1372 with pytest.raises(grpc.RpcError) as e: 

1373 c.RemoveGroupChatAdmin(conversations_pb2.RemoveGroupChatAdminReq(group_chat_id=gcid, user_id=user5.id)) 

1374 assert e.value.code() == grpc.StatusCode.NOT_FOUND 

1375 assert e.value.details() == "Couldn't find that user." 

1376 

1377 

1378def test_last_seen(db, moderator): 

1379 user1, token1 = generate_user() 

1380 user2, token2 = generate_user() 

1381 user3, token3 = generate_user() 

1382 

1383 make_friends(user1, user2) 

1384 make_friends(user1, user3) 

1385 make_friends(user2, user3) 

1386 

1387 with conversations_session(token3) as c: 

1388 # this is just here to mess up any issues we get if we pretend there's only one group chat ever 

1389 gcid_distraction = c.CreateGroupChat( 

1390 conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id, user1.id]) 

1391 ).group_chat_id 

1392 moderator.approve_group_chat(gcid_distraction) 

1393 

1394 with conversations_session(token1) as c: 

1395 gcid = c.CreateGroupChat( 

1396 conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id, user3.id]) 

1397 ).group_chat_id 

1398 moderator.approve_group_chat(gcid) 

1399 

1400 message_ids = [] 

1401 

1402 for i in range(6): 

1403 c.SendMessage( 

1404 conversations_pb2.SendMessageReq(group_chat_id=gcid_distraction, text=f"gibberish message... {i}") 

1405 ) 

1406 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=gcid, text=f"test message {i}")) 

1407 c.SendMessage( 

1408 conversations_pb2.SendMessageReq(group_chat_id=gcid_distraction, text=f"gibberish message {i}") 

1409 ) 

1410 

1411 message_ids.append( 

1412 c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=gcid)).latest_message.message_id 

1413 ) 

1414 

1415 # messages are automatically marked as seen when you send a new message 

1416 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=gcid)) 

1417 assert res.unseen_message_count == 0 

1418 

1419 with conversations_session(token2) as c: 

1420 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=gcid)) 

1421 # created + 6 normal 

1422 assert res.unseen_message_count == 7 

1423 

1424 backward_offset = 3 

1425 c.MarkLastSeenGroupChat( 

1426 conversations_pb2.MarkLastSeenGroupChatReq( 

1427 group_chat_id=gcid, last_seen_message_id=message_ids[-backward_offset - 1] 

1428 ) 

1429 ) 

1430 

1431 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=gcid)) 

1432 assert res.unseen_message_count == backward_offset 

1433 

1434 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=gcid, text="test message ...")) 

1435 

1436 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=gcid)) 

1437 assert res.unseen_message_count == 0 

1438 

1439 with conversations_session(token3) as c: 

1440 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=gcid)) 

1441 # created + 7 normal 

1442 assert res.unseen_message_count == 8 

1443 

1444 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=gcid, text="test message ...")) 

1445 

1446 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=gcid)) 

1447 assert res.unseen_message_count == 0 

1448 

1449 

1450def test_mark_last_seen_clears_notifications(db, moderator): 

1451 user1, token1 = generate_user() 

1452 user2, token2 = generate_user() 

1453 

1454 with conversations_session(token1) as c: 

1455 gcid = c.CreateGroupChat( 

1456 conversations_pb2.CreateGroupChatReq( 

1457 recipient_user_ids=[user2.id], title=wrappers_pb2.StringValue(value="Test chat") 

1458 ) 

1459 ).group_chat_id 

1460 

1461 moderator.approve_group_chat(gcid) 

1462 

1463 with conversations_session(token1) as c: 

1464 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=gcid, text="Hello")) 

1465 

1466 process_jobs() 

1467 

1468 def unseen_notification_count(user_id): 

1469 with session_scope() as session: 

1470 return session.execute( 

1471 select(func.count()) 

1472 .select_from(Notification) 

1473 .where(Notification.user_id == user_id) 

1474 .where(Notification.key == str(gcid)) 

1475 .where(Notification.is_seen == False) 

1476 ).scalar_one() 

1477 

1478 assert unseen_notification_count(user2.id) > 0 

1479 

1480 with conversations_session(token2) as c: 

1481 c.MarkLastSeenGroupChat(conversations_pb2.MarkLastSeenGroupChatReq(group_chat_id=gcid, last_seen_message_id=1)) 

1482 

1483 assert unseen_notification_count(user2.id) == 0 

1484 

1485 

1486def test_mark_last_seen_clears_missed_messages_notification(db, moderator, timewarp: Timewarp): 

1487 """ 

1488 Regression test: chat__missed_messages is a summary keyed with "" rather than a chat id, so it 

1489 needs to be marked seen separately from the per-chat chat__message notifications. 

1490 """ 

1491 user1, token1 = generate_user() 

1492 user2, token2 = generate_user() 

1493 

1494 # this notification is email-only by default, and the in-app feed only shows push-enabled ones 

1495 with notifications_session(token2) as n: 

1496 n.SetNotificationSettings( 

1497 notifications_pb2.SetNotificationSettingsReq( 

1498 preferences=[ 

1499 notifications_pb2.SingleNotificationPreference( 

1500 topic=NotificationTopicAction.chat__missed_messages.topic, 

1501 action=NotificationTopicAction.chat__missed_messages.action, 

1502 delivery_method="push", 

1503 enabled=True, 

1504 ) 

1505 ] 

1506 ) 

1507 ) 

1508 

1509 with conversations_session(token1) as c: 

1510 gcid = c.CreateGroupChat( 

1511 conversations_pb2.CreateGroupChatReq( 

1512 recipient_user_ids=[user2.id], title=wrappers_pb2.StringValue(value="Test chat") 

1513 ) 

1514 ).group_chat_id 

1515 

1516 moderator.approve_group_chat(gcid) 

1517 

1518 with conversations_session(token1) as c: 

1519 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=gcid, text="Hello!")) 

1520 

1521 # the job only picks up messages that have been unseen for five minutes 

1522 timewarp.advance(MISSED_MESSAGES_DELAY) 

1523 send_message_notifications(empty_pb2.Empty()) 

1524 process_jobs() 

1525 

1526 def unseen_missed_messages(): 

1527 with notifications_session(token2) as n: 

1528 res = n.ListNotifications(notifications_pb2.ListNotificationsReq(only_unread=True)) 

1529 return [ 

1530 notification 

1531 for notification in res.notifications 

1532 if notification.topic == NotificationTopicAction.chat__missed_messages.topic 

1533 and notification.action == NotificationTopicAction.chat__missed_messages.action 

1534 ] 

1535 

1536 assert len(unseen_missed_messages()) == 1 

1537 

1538 with conversations_session(token2) as c: 

1539 c.MarkLastSeenGroupChat(conversations_pb2.MarkLastSeenGroupChatReq(group_chat_id=gcid, last_seen_message_id=1)) 

1540 

1541 assert unseen_missed_messages() == [] 

1542 

1543 

1544def test_one_dm_per_pair(db, moderator): 

1545 user1, token1 = generate_user() 

1546 user2, token2 = generate_user() 

1547 user3, token3 = generate_user() 

1548 

1549 make_friends(user1, user2) 

1550 make_friends(user1, user3) 

1551 make_friends(user2, user3) 

1552 

1553 with conversations_session(token1) as c: 

1554 # create DM with user 2 

1555 res = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id])) 

1556 assert res.is_dm 

1557 dm_with_user2 = res.group_chat_id 

1558 

1559 # create DM with user 3 

1560 res = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user3.id])) 

1561 assert res.is_dm 

1562 dm_with_user3 = res.group_chat_id 

1563 

1564 # can't create another group chat with just user 2 

1565 with pytest.raises(grpc.RpcError) as e: 

1566 res = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id])) 

1567 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION 

1568 

1569 # can't create another group chat with just user 3 

1570 with pytest.raises(grpc.RpcError) as e: 

1571 res = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user3.id])) 

1572 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION 

1573 

1574 # can create joined group chat 

1575 res = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id, user3.id])) 

1576 assert not res.is_dm 

1577 

1578 # Approve the DMs so user2 can see them (otherwise they're SHADOWED and only visible to creator) 

1579 moderator.approve_group_chat(dm_with_user2) 

1580 

1581 with conversations_session(token2) as c: 

1582 # can create DM with user 3 

1583 res = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user3.id])) 

1584 assert res.is_dm 

1585 

1586 # can't create another group chat with just user 1 (DM was approved, so user2 can see it) 

1587 with pytest.raises(grpc.RpcError) as e: 

1588 res = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user1.id])) 

1589 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION 

1590 

1591 

1592def test_GetDirectMessage(db): 

1593 user1, token1 = generate_user() 

1594 user2, token2 = generate_user() 

1595 user3, token3 = generate_user() 

1596 

1597 make_friends(user1, user2) 

1598 make_friends(user1, user3) 

1599 make_friends(user2, user3) 

1600 

1601 with conversations_session(token1) as c: 

1602 # no group chat with user 2 

1603 with pytest.raises(grpc.RpcError) as e: 

1604 res = c.GetDirectMessage(conversations_pb2.GetDirectMessageReq(user_id=user2.id)) 

1605 assert e.value.code() == grpc.StatusCode.NOT_FOUND 

1606 

1607 # no group chat with nor user 3 

1608 with pytest.raises(grpc.RpcError) as e: 

1609 res = c.GetDirectMessage(conversations_pb2.GetDirectMessageReq(user_id=user3.id)) 

1610 assert e.value.code() == grpc.StatusCode.NOT_FOUND 

1611 

1612 # create DM with user 2 

1613 res = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id])) 

1614 assert res.is_dm 

1615 gcid = res.group_chat_id 

1616 

1617 # now should exist 

1618 res = c.GetDirectMessage(conversations_pb2.GetDirectMessageReq(user_id=user2.id)) 

1619 assert res.group_chat_id == gcid 

1620 assert not res.is_archived 

1621 

1622 # create DM with user 3 

1623 res = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user3.id])) 

1624 assert res.is_dm 

1625 

1626 # can create joined group chat 

1627 res = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id, user3.id])) 

1628 assert not res.is_dm 

1629 

1630 with conversations_session(token2) as c: 

1631 # can create DM with user 3 

1632 res = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user3.id])) 

1633 assert res.is_dm 

1634 assert res.can_message 

1635 gcid = res.group_chat_id 

1636 

1637 # DM with 3 should exist, but can't message after being blocked 

1638 make_user_block(user3, user2) 

1639 res = c.GetDirectMessage(conversations_pb2.GetDirectMessageReq(user_id=user3.id)) 

1640 assert res.group_chat_id == gcid 

1641 assert not res.can_message 

1642 

1643 

1644def test_total_unseen(db, moderator): 

1645 user1, token1 = generate_user() 

1646 user2, token2 = generate_user() 

1647 user3, token3 = generate_user() 

1648 

1649 # distractions 

1650 user4, token4 = generate_user() 

1651 

1652 make_friends(user1, user2) 

1653 make_friends(user1, user3) 

1654 make_friends(user2, user3) 

1655 

1656 # distractions 

1657 make_friends(user1, user4) 

1658 

1659 with conversations_session(token1) as c: 

1660 # distractions 

1661 gcid_distraction = c.CreateGroupChat( 

1662 conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user4.id]) 

1663 ).group_chat_id 

1664 moderator.approve_group_chat(gcid_distraction) 

1665 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=gcid_distraction, text="distraction...")) 

1666 

1667 gcid = c.CreateGroupChat( 

1668 conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id, user3.id]) 

1669 ).group_chat_id 

1670 moderator.approve_group_chat(gcid) 

1671 

1672 for i in range(6): 

1673 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=gcid, text=f"test message {i}")) 

1674 

1675 # distractions 

1676 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=gcid_distraction, text="distraction...")) 

1677 

1678 # messages are automatically marked as seen when you send a new message 

1679 with api_session(token1) as api: 

1680 assert api.Ping(api_pb2.PingReq()).unseen_message_count == 0 

1681 

1682 with api_session(token2) as api: 

1683 # chat created + 6 normal messages 

1684 assert api.Ping(api_pb2.PingReq()).unseen_message_count == 7 

1685 

1686 # now leave chat with user2 

1687 with conversations_session(token2) as c: 

1688 c.LeaveGroupChat(conversations_pb2.LeaveGroupChatReq(group_chat_id=gcid)) 

1689 

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 

1693 

1694 with conversations_session(token1) as c: 

1695 # distractions 

1696 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=gcid_distraction, text="distraction...")) 

1697 

1698 # send more stuff without user 2 

1699 for i in range(3): 

1700 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=gcid, text=f"test message {i}")) 

1701 

1702 # distractions 

1703 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=gcid_distraction, text="distraction...")) 

1704 

1705 with api_session(token2) as api: 

1706 # seen messages becomes 0 when leaving 

1707 assert api.Ping(api_pb2.PingReq()).unseen_message_count == 0 

1708 

1709 with conversations_session(token1) as c: 

1710 # add user 2 back 

1711 c.InviteToGroupChat(conversations_pb2.InviteToGroupChatReq(group_chat_id=gcid, user_id=user2.id)) 

1712 

1713 # send more stuff with user 2 

1714 for i in range(12): 

1715 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=gcid, text=f"test message {i}")) 

1716 

1717 # distractions 

1718 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=gcid_distraction, text="distraction...")) 

1719 

1720 with api_session(token2) as api: 

1721 # joined + 12 normal 

1722 assert api.Ping(api_pb2.PingReq()).unseen_message_count == 13 

1723 

1724 

1725def test_left_group_chat_unseen_count_excludes_unreachable_messages(db, moderator): 

1726 """ 

1727 ListGroupChats and GetGroupChat used to count messages sent after the viewer left the chat, which 

1728 they can never open. The viewer is removed rather than leaving, because leaving posts a message of 

1729 your own, which marks everything up to it seen. 

1730 """ 

1731 user1, token1 = generate_user() 

1732 user2, token2 = generate_user() 

1733 user3, token3 = generate_user() 

1734 make_friends(user1, user2) 

1735 make_friends(user2, user3) 

1736 

1737 with conversations_session(token2) as c: 

1738 group_chat_id = c.CreateGroupChat( 

1739 conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user1.id, user3.id]) 

1740 ).group_chat_id 

1741 moderator.approve_group_chat(group_chat_id) 

1742 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=group_chat_id, text="hi")) 

1743 c.RemoveGroupChatUser(conversations_pb2.RemoveGroupChatUserReq(group_chat_id=group_chat_id, user_id=user1.id)) 

1744 for _ in range(3): 

1745 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=group_chat_id, text="after you left")) 

1746 

1747 # chat created, "hi", and the removal notice; the three messages sent afterwards are out of reach 

1748 with api_session(token1) as api: 

1749 assert api.Ping(api_pb2.PingReq()).unseen_message_count == 3 

1750 

1751 with conversations_session(token1) as c: 

1752 res = c.ListGroupChats(conversations_pb2.ListGroupChatsReq()) 

1753 (listed,) = [gc for gc in res.group_chats if gc.group_chat_id == group_chat_id] 

1754 assert listed.unseen_message_count == 3 

1755 assert c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=group_chat_id)).unseen_message_count == 3 

1756 

1757 

1758def test_left_group_chat_can_be_marked_seen(db, moderator): 

1759 """ 

1760 MarkLastSeenGroupChat used to require a subscription with left == None, so a chat you were removed 

1761 from kept contributing to the unread badge with no way to clear it. 

1762 """ 

1763 user1, token1 = generate_user() 

1764 user2, token2 = generate_user() 

1765 user3, token3 = generate_user() 

1766 make_friends(user1, user2) 

1767 make_friends(user2, user3) 

1768 

1769 with conversations_session(token2) as c: 

1770 group_chat_id = c.CreateGroupChat( 

1771 conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user1.id, user3.id]) 

1772 ).group_chat_id 

1773 moderator.approve_group_chat(group_chat_id) 

1774 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=group_chat_id, text="hi")) 

1775 c.RemoveGroupChatUser(conversations_pb2.RemoveGroupChatUserReq(group_chat_id=group_chat_id, user_id=user1.id)) 

1776 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=group_chat_id, text="after you left")) 

1777 

1778 with api_session(token1) as api: 

1779 assert api.Ping(api_pb2.PingReq()).unseen_message_count == 3 

1780 

1781 with conversations_session(token1) as c: 

1782 latest_message_id = c.GetGroupChat( 

1783 conversations_pb2.GetGroupChatReq(group_chat_id=group_chat_id) 

1784 ).latest_message.message_id 

1785 c.MarkLastSeenGroupChat( 

1786 conversations_pb2.MarkLastSeenGroupChatReq( 

1787 group_chat_id=group_chat_id, last_seen_message_id=latest_message_id 

1788 ) 

1789 ) 

1790 

1791 assert c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=group_chat_id)).unseen_message_count == 0 

1792 

1793 with pytest.raises(grpc.RpcError) as e: 

1794 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=group_chat_id, text="let me back in")) 

1795 assert e.value.code() == grpc.StatusCode.NOT_FOUND 

1796 

1797 with api_session(token1) as api: 

1798 assert api.Ping(api_pb2.PingReq()).unseen_message_count == 0 

1799 

1800 

1801def test_rejoined_group_chat_ping_reads_newest_subscription(db, moderator): 

1802 """ 

1803 Rejoining a chat leaves the earlier subscription behind with its own last-seen state. Ping used to 

1804 match on any of the viewer's subscriptions, so the stale one kept the badge up forever. 

1805 """ 

1806 user1, token1 = generate_user() 

1807 user2, token2 = generate_user() 

1808 user3, token3 = generate_user() 

1809 make_friends(user1, user2) 

1810 make_friends(user2, user3) 

1811 

1812 with conversations_session(token2) as c: 

1813 group_chat_id = c.CreateGroupChat( 

1814 conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user1.id, user3.id]) 

1815 ).group_chat_id 

1816 moderator.approve_group_chat(group_chat_id) 

1817 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=group_chat_id, text="hi")) 

1818 # removed rather than leaving, so user1's first subscription is left behind with unread messages 

1819 c.RemoveGroupChatUser(conversations_pb2.RemoveGroupChatUserReq(group_chat_id=group_chat_id, user_id=user1.id)) 

1820 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=group_chat_id, text="while you were away")) 

1821 c.InviteToGroupChat(conversations_pb2.InviteToGroupChatReq(group_chat_id=group_chat_id, user_id=user1.id)) 

1822 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=group_chat_id, text="welcome back")) 

1823 

1824 # only the invite notice and "welcome back" are in reach of the newest subscription 

1825 with api_session(token1) as api: 

1826 assert api.Ping(api_pb2.PingReq()).unseen_message_count == 2 

1827 

1828 with conversations_session(token1) as c: 

1829 latest_message_id = c.GetGroupChat( 

1830 conversations_pb2.GetGroupChatReq(group_chat_id=group_chat_id) 

1831 ).latest_message.message_id 

1832 c.MarkLastSeenGroupChat( 

1833 conversations_pb2.MarkLastSeenGroupChatReq( 

1834 group_chat_id=group_chat_id, last_seen_message_id=latest_message_id 

1835 ) 

1836 ) 

1837 

1838 with api_session(token1) as api: 

1839 assert api.Ping(api_pb2.PingReq()).unseen_message_count == 0 

1840 

1841 

1842def test_rejoined_group_chat_only_unseen_matches_the_badge(db, moderator): 

1843 """ 

1844 GetGroupChatMessages matched only_unseen against every one of the viewer's subscriptions, so the 

1845 abandoned one served up its unread messages even though the badge no longer counts them. 

1846 """ 

1847 user1, token1 = generate_user() 

1848 user2, token2 = generate_user() 

1849 user3, token3 = generate_user() 

1850 make_friends(user1, user2) 

1851 make_friends(user2, user3) 

1852 

1853 with conversations_session(token2) as c: 

1854 group_chat_id = c.CreateGroupChat( 

1855 conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user1.id, user3.id]) 

1856 ).group_chat_id 

1857 moderator.approve_group_chat(group_chat_id) 

1858 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=group_chat_id, text="hi")) 

1859 c.RemoveGroupChatUser(conversations_pb2.RemoveGroupChatUserReq(group_chat_id=group_chat_id, user_id=user1.id)) 

1860 c.InviteToGroupChat(conversations_pb2.InviteToGroupChatReq(group_chat_id=group_chat_id, user_id=user1.id)) 

1861 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=group_chat_id, text="welcome back")) 

1862 

1863 with api_session(token1) as api: 

1864 assert api.Ping(api_pb2.PingReq()).unseen_message_count == 2 

1865 

1866 with conversations_session(token1) as c: 

1867 unseen = c.GetGroupChatMessages( 

1868 conversations_pb2.GetGroupChatMessagesReq(group_chat_id=group_chat_id, only_unseen=True) 

1869 ).messages 

1870 assert [m.WhichOneof("content") for m in unseen] == ["text", "user_invited"] 

1871 assert unseen[0].text.text == "welcome back" 

1872 

1873 # the earlier stint is still readable, it just isn't unread 

1874 all_messages = c.GetGroupChatMessages( 

1875 conversations_pb2.GetGroupChatMessagesReq(group_chat_id=group_chat_id) 

1876 ).messages 

1877 assert [m.text.text for m in all_messages if m.WhichOneof("content") == "text"] == ["welcome back", "hi"] 

1878 

1879 

1880def test_rejoined_group_chat_archived_filter_reads_newest_subscription(db, moderator): 

1881 """ 

1882 ListGroupChats applied the archived filter before picking the newest subscription, so a chat 

1883 archived before the viewer was removed stayed in their archived list off the abandoned 

1884 subscription, where nothing could ever mark it seen. 

1885 """ 

1886 user1, token1 = generate_user() 

1887 user2, token2 = generate_user() 

1888 user3, token3 = generate_user() 

1889 make_friends(user1, user2) 

1890 make_friends(user2, user3) 

1891 

1892 with conversations_session(token2) as c: 

1893 group_chat_id = c.CreateGroupChat( 

1894 conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user1.id, user3.id]) 

1895 ).group_chat_id 

1896 moderator.approve_group_chat(group_chat_id) 

1897 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=group_chat_id, text="hi")) 

1898 

1899 with conversations_session(token1) as c: 

1900 c.SetGroupChatArchiveStatus( 

1901 conversations_pb2.SetGroupChatArchiveStatusReq(group_chat_id=group_chat_id, is_archived=True) 

1902 ) 

1903 

1904 with conversations_session(token2) as c: 

1905 c.RemoveGroupChatUser(conversations_pb2.RemoveGroupChatUserReq(group_chat_id=group_chat_id, user_id=user1.id)) 

1906 c.InviteToGroupChat(conversations_pb2.InviteToGroupChatReq(group_chat_id=group_chat_id, user_id=user1.id)) 

1907 

1908 with conversations_session(token1) as c: 

1909 archived = c.ListGroupChats(conversations_pb2.ListGroupChatsReq(only_archived=True)).group_chats 

1910 assert [gc.group_chat_id for gc in archived] == [] 

1911 

1912 (listed,) = c.ListGroupChats(conversations_pb2.ListGroupChatsReq(only_archived=False)).group_chats 

1913 assert listed.group_chat_id == group_chat_id 

1914 assert not listed.is_archived 

1915 

1916 

1917def test_regression_ListGroupChats_pagination(db, moderator, monkeypatch): 

1918 user1, token1 = generate_user() 

1919 user2, token2 = generate_user() 

1920 user3, token3 = generate_user() 

1921 

1922 make_friends(user1, user2) 

1923 make_friends(user1, user3) 

1924 

1925 # two full pages and a partial one, which is the shape that lost chats, without paying for a 

1926 # chat per row of a production-sized page 

1927 monkeypatch.setattr(conversations, "DEFAULT_PAGINATION_LENGTH", 3) 

1928 

1929 with conversations_session(token1) as c: 

1930 # tuples of (group_chat_id, message_id) 

1931 group_chat_and_message_ids = [] 

1932 for i in range(7): 

1933 res1 = c.CreateGroupChat( 

1934 conversations_pb2.CreateGroupChatReq( 

1935 recipient_user_ids=[user2.id, user3.id], title=wrappers_pb2.StringValue(value=f"Chat {i}") 

1936 ) 

1937 ) 

1938 

1939 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=res1.group_chat_id, text=f"Test message {i}")) 

1940 

1941 res2 = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=res1.group_chat_id)) 

1942 

1943 group_chat_and_message_ids.append((res2.group_chat_id, res2.latest_message.message_id)) 

1944 moderator.approve_group_chat(res1.group_chat_id) 

1945 

1946 seen_group_chat_ids = [] 

1947 

1948 pages = 0 

1949 last_message_id = 0 

1950 more = True 

1951 while more: 

1952 res = c.ListGroupChats(conversations_pb2.ListGroupChatsReq(last_message_id=last_message_id)) 

1953 last_message_id = res.last_message_id 

1954 more = not res.no_more 

1955 pages += 1 

1956 

1957 seen_group_chat_ids.extend([chat.group_chat_id for chat in res.group_chats]) 

1958 

1959 assert pages == 3, "The chats didn't come back over several pages, so nothing was paginated" 

1960 assert set(seen_group_chat_ids) == {x[0] for x in group_chat_and_message_ids}, "Not all group chats returned" 

1961 

1962 

1963def test_muting(db, moderator): 

1964 user1, token1 = generate_user() 

1965 user2, token2 = generate_user() 

1966 user3, token3 = generate_user() 

1967 

1968 make_friends(user1, user2) 

1969 make_friends(user1, user3) 

1970 make_friends(user2, user3) 

1971 

1972 with conversations_session(token3) as c: 

1973 # this is just here to mess up any issues we get if we pretend there's only one group chat ever 

1974 gcid_distraction = c.CreateGroupChat( 

1975 conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id, user1.id]) 

1976 ).group_chat_id 

1977 moderator.approve_group_chat(gcid_distraction) 

1978 

1979 with conversations_session(token1) as c: 

1980 gcid = c.CreateGroupChat( 

1981 conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id, user3.id]) 

1982 ).group_chat_id 

1983 moderator.approve_group_chat(gcid) 

1984 

1985 with conversations_session(token2) as c: 

1986 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=gcid)) 

1987 assert not res.mute_info.muted 

1988 assert not res.mute_info.HasField("muted_until") 

1989 

1990 c.MuteGroupChat(conversations_pb2.MuteGroupChatReq(group_chat_id=gcid, forever=True)) 

1991 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=gcid)) 

1992 assert res.mute_info.muted 

1993 assert not res.mute_info.HasField("muted_until") 

1994 

1995 c.MuteGroupChat(conversations_pb2.MuteGroupChatReq(group_chat_id=gcid, unmute=True)) 

1996 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=gcid)) 

1997 assert not res.mute_info.muted 

1998 assert not res.mute_info.HasField("muted_until") 

1999 

2000 c.MuteGroupChat( 

2001 conversations_pb2.MuteGroupChatReq( 

2002 group_chat_id=gcid, for_duration=Duration_from_timedelta(timedelta(hours=2)) 

2003 ) 

2004 ) 

2005 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=gcid)) 

2006 assert res.mute_info.muted 

2007 assert res.mute_info.HasField("muted_until") 

2008 assert to_aware_datetime(res.mute_info.muted_until) >= now() + timedelta(hours=1, minutes=59) 

2009 assert to_aware_datetime(res.mute_info.muted_until) <= now() + timedelta(hours=2, minutes=1) 

2010 

2011 

2012def test_archiving(db, moderator): 

2013 """Test SetGroupChatArchiveStatus RPC with GetGroupChat and ListGroupChats""" 

2014 user1, token1 = generate_user() 

2015 user2, token2 = generate_user() 

2016 user3, token3 = generate_user() 

2017 

2018 make_friends(user1, user2) 

2019 make_friends(user1, user3) 

2020 make_friends(user2, user3) 

2021 

2022 # Create a distraction chat to ensure we're testing the right one 

2023 with conversations_session(token3) as c: 

2024 gcid_distraction = c.CreateGroupChat( 

2025 conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id, user1.id]) 

2026 ).group_chat_id 

2027 moderator.approve_group_chat(gcid_distraction) 

2028 

2029 with conversations_session(token1) as c: 

2030 gcid = c.CreateGroupChat( 

2031 conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id, user3.id]) 

2032 ).group_chat_id 

2033 moderator.approve_group_chat(gcid) 

2034 

2035 # Test basic archive/unarchive functionality 

2036 with conversations_session(token2) as c: 

2037 # Initially not archived 

2038 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=gcid)) 

2039 assert not res.is_archived 

2040 

2041 # Archive the chat 

2042 archive_res = c.SetGroupChatArchiveStatus( 

2043 conversations_pb2.SetGroupChatArchiveStatusReq(group_chat_id=gcid, is_archived=True) 

2044 ) 

2045 assert archive_res.group_chat_id == gcid 

2046 assert archive_res.is_archived 

2047 

2048 # Verify archived via GetGroupChat 

2049 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=gcid)) 

2050 assert res.is_archived 

2051 

2052 # Unarchive the chat 

2053 archive_res = c.SetGroupChatArchiveStatus( 

2054 conversations_pb2.SetGroupChatArchiveStatusReq(group_chat_id=gcid, is_archived=False) 

2055 ) 

2056 assert archive_res.group_chat_id == gcid 

2057 assert not archive_res.is_archived 

2058 

2059 # Verify unarchived via GetGroupChat 

2060 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=gcid)) 

2061 assert not res.is_archived 

2062 

2063 

2064def test_archiving_per_user(db, moderator): 

2065 """Test that archiving is per-user - one user archiving doesn't affect others""" 

2066 user1, token1 = generate_user() 

2067 user2, token2 = generate_user() 

2068 

2069 make_friends(user1, user2) 

2070 

2071 with conversations_session(token1) as c: 

2072 gcid = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id])).group_chat_id 

2073 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=gcid, text="Hello")) 

2074 moderator.approve_group_chat(gcid) 

2075 

2076 # User1 archives the chat 

2077 with conversations_session(token1) as c: 

2078 c.SetGroupChatArchiveStatus( 

2079 conversations_pb2.SetGroupChatArchiveStatusReq(group_chat_id=gcid, is_archived=True) 

2080 ) 

2081 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=gcid)) 

2082 assert res.is_archived 

2083 

2084 # User2 should NOT see it as archived 

2085 with conversations_session(token2) as c: 

2086 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=gcid)) 

2087 assert not res.is_archived 

2088 

2089 # User2 archives it too 

2090 with conversations_session(token2) as c: 

2091 c.SetGroupChatArchiveStatus( 

2092 conversations_pb2.SetGroupChatArchiveStatusReq(group_chat_id=gcid, is_archived=True) 

2093 ) 

2094 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=gcid)) 

2095 assert res.is_archived 

2096 

2097 # User1 unarchives - user2 should still see it as archived 

2098 with conversations_session(token1) as c: 

2099 c.SetGroupChatArchiveStatus( 

2100 conversations_pb2.SetGroupChatArchiveStatusReq(group_chat_id=gcid, is_archived=False) 

2101 ) 

2102 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=gcid)) 

2103 assert not res.is_archived 

2104 

2105 with conversations_session(token2) as c: 

2106 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=gcid)) 

2107 assert res.is_archived 

2108 

2109 

2110def test_archiving_with_list_group_chats(db, moderator): 

2111 """Test archive filtering with ListGroupChats endpoint""" 

2112 user1, token1 = generate_user() 

2113 user2, token2 = generate_user() 

2114 user3, token3 = generate_user() 

2115 

2116 make_friends(user1, user2) 

2117 make_friends(user1, user3) 

2118 

2119 with conversations_session(token1) as c: 

2120 # Create 3 chats 

2121 gcid1 = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id])).group_chat_id 

2122 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=gcid1, text="Chat 1")) 

2123 moderator.approve_group_chat(gcid1) 

2124 

2125 gcid2 = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user3.id])).group_chat_id 

2126 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=gcid2, text="Chat 2")) 

2127 moderator.approve_group_chat(gcid2) 

2128 

2129 gcid3 = c.CreateGroupChat( 

2130 conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id, user3.id]) 

2131 ).group_chat_id 

2132 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=gcid3, text="Chat 3")) 

2133 moderator.approve_group_chat(gcid3) 

2134 

2135 with conversations_session(token1) as c: 

2136 # Initially all 3 chats should be visible 

2137 res = c.ListGroupChats(conversations_pb2.ListGroupChatsReq()) 

2138 assert len(res.group_chats) == 3 

2139 

2140 # All should be non-archived 

2141 res = c.ListGroupChats(conversations_pb2.ListGroupChatsReq(only_archived=False)) 

2142 assert len(res.group_chats) == 3 

2143 

2144 # None should be archived 

2145 res = c.ListGroupChats(conversations_pb2.ListGroupChatsReq(only_archived=True)) 

2146 assert len(res.group_chats) == 0 

2147 

2148 # Archive chat 1 

2149 c.SetGroupChatArchiveStatus( 

2150 conversations_pb2.SetGroupChatArchiveStatusReq(group_chat_id=gcid1, is_archived=True) 

2151 ) 

2152 

2153 # Without filter, still see all 3 

2154 res = c.ListGroupChats(conversations_pb2.ListGroupChatsReq()) 

2155 assert len(res.group_chats) == 3 

2156 

2157 # Non-archived should show 2 

2158 res = c.ListGroupChats(conversations_pb2.ListGroupChatsReq(only_archived=False)) 

2159 assert len(res.group_chats) == 2 

2160 assert gcid1 not in [gc.group_chat_id for gc in res.group_chats] 

2161 

2162 # Archived should show 1 

2163 res = c.ListGroupChats(conversations_pb2.ListGroupChatsReq(only_archived=True)) 

2164 assert len(res.group_chats) == 1 

2165 assert res.group_chats[0].group_chat_id == gcid1 

2166 assert res.group_chats[0].is_archived 

2167 

2168 # Archive chat 2 as well 

2169 c.SetGroupChatArchiveStatus( 

2170 conversations_pb2.SetGroupChatArchiveStatusReq(group_chat_id=gcid2, is_archived=True) 

2171 ) 

2172 

2173 # Non-archived should show 1 

2174 res = c.ListGroupChats(conversations_pb2.ListGroupChatsReq(only_archived=False)) 

2175 assert len(res.group_chats) == 1 

2176 assert res.group_chats[0].group_chat_id == gcid3 

2177 

2178 # Archived should show 2 

2179 res = c.ListGroupChats(conversations_pb2.ListGroupChatsReq(only_archived=True)) 

2180 assert len(res.group_chats) == 2 

2181 archived_ids = {gc.group_chat_id for gc in res.group_chats} 

2182 assert archived_ids == {gcid1, gcid2} 

2183 

2184 # Unarchive chat 1 

2185 c.SetGroupChatArchiveStatus( 

2186 conversations_pb2.SetGroupChatArchiveStatusReq(group_chat_id=gcid1, is_archived=False) 

2187 ) 

2188 

2189 # Non-archived should show 2 

2190 res = c.ListGroupChats(conversations_pb2.ListGroupChatsReq(only_archived=False)) 

2191 assert len(res.group_chats) == 2 

2192 

2193 # Archived should show 1 

2194 res = c.ListGroupChats(conversations_pb2.ListGroupChatsReq(only_archived=True)) 

2195 assert len(res.group_chats) == 1 

2196 assert res.group_chats[0].group_chat_id == gcid2 

2197 

2198 

2199def test_archiving_chat_not_found(db, moderator): 

2200 """Test that archiving a non-existent or non-accessible chat fails""" 

2201 user1, token1 = generate_user() 

2202 user2, token2 = generate_user() 

2203 user3, token3 = generate_user() 

2204 

2205 make_friends(user1, user2) 

2206 

2207 with conversations_session(token1) as c: 

2208 gcid = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id])).group_chat_id 

2209 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=gcid, text="Hello")) 

2210 moderator.approve_group_chat(gcid) 

2211 

2212 # User3 is not in the chat - should get NOT_FOUND 

2213 with conversations_session(token3) as c: 

2214 with pytest.raises(grpc.RpcError) as e: 

2215 c.SetGroupChatArchiveStatus( 

2216 conversations_pb2.SetGroupChatArchiveStatusReq(group_chat_id=gcid, is_archived=True) 

2217 ) 

2218 assert e.value.code() == grpc.StatusCode.NOT_FOUND 

2219 

2220 # Non-existent chat ID should fail 

2221 with conversations_session(token1) as c: 

2222 with pytest.raises(grpc.RpcError) as e: 

2223 c.SetGroupChatArchiveStatus( 

2224 conversations_pb2.SetGroupChatArchiveStatusReq(group_chat_id=99999, is_archived=True) 

2225 ) 

2226 assert e.value.code() == grpc.StatusCode.NOT_FOUND 

2227 

2228 

2229def test_archiving_after_leaving_chat(db, moderator): 

2230 """Test that you can't archive a chat after leaving it""" 

2231 user1, token1 = generate_user() 

2232 user2, token2 = generate_user() 

2233 user3, token3 = generate_user() 

2234 

2235 make_friends(user1, user2) 

2236 make_friends(user1, user3) 

2237 make_friends(user2, user3) 

2238 

2239 with conversations_session(token1) as c: 

2240 gcid = c.CreateGroupChat( 

2241 conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id, user3.id]) 

2242 ).group_chat_id 

2243 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=gcid, text="Hello")) 

2244 moderator.approve_group_chat(gcid) 

2245 

2246 # User2 leaves the chat 

2247 with conversations_session(token2) as c: 

2248 c.LeaveGroupChat(conversations_pb2.LeaveGroupChatReq(group_chat_id=gcid)) 

2249 

2250 # User2 should not be able to archive it now 

2251 with conversations_session(token2) as c: 

2252 with pytest.raises(grpc.RpcError) as e: 

2253 c.SetGroupChatArchiveStatus( 

2254 conversations_pb2.SetGroupChatArchiveStatusReq(group_chat_id=gcid, is_archived=True) 

2255 ) 

2256 assert e.value.code() == grpc.StatusCode.NOT_FOUND 

2257 

2258 

2259def test_archiving_dm(db, moderator): 

2260 """Test that archiving works for DMs as well as group chats""" 

2261 user1, token1 = generate_user() 

2262 user2, token2 = generate_user() 

2263 

2264 make_friends(user1, user2) 

2265 

2266 with conversations_session(token1) as c: 

2267 # Create a DM 

2268 gcid = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id])).group_chat_id 

2269 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=gcid, text="Hello")) 

2270 moderator.approve_group_chat(gcid) 

2271 

2272 # Verify it's a DM 

2273 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=gcid)) 

2274 assert res.is_dm 

2275 assert not res.is_archived 

2276 

2277 # Archive the DM 

2278 c.SetGroupChatArchiveStatus( 

2279 conversations_pb2.SetGroupChatArchiveStatusReq(group_chat_id=gcid, is_archived=True) 

2280 ) 

2281 

2282 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=gcid)) 

2283 assert res.is_archived 

2284 

2285 # Unarchive it 

2286 c.SetGroupChatArchiveStatus( 

2287 conversations_pb2.SetGroupChatArchiveStatusReq(group_chat_id=gcid, is_archived=False) 

2288 ) 

2289 

2290 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=gcid)) 

2291 assert not res.is_archived 

2292 

2293 

2294def test_archiving_idempotent(db, moderator): 

2295 """Test that archiving/unarchiving is idempotent""" 

2296 user1, token1 = generate_user() 

2297 user2, token2 = generate_user() 

2298 

2299 make_friends(user1, user2) 

2300 

2301 with conversations_session(token1) as c: 

2302 gcid = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id])).group_chat_id 

2303 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=gcid, text="Hello")) 

2304 moderator.approve_group_chat(gcid) 

2305 

2306 # Archive twice - should work without error 

2307 c.SetGroupChatArchiveStatus( 

2308 conversations_pb2.SetGroupChatArchiveStatusReq(group_chat_id=gcid, is_archived=True) 

2309 ) 

2310 c.SetGroupChatArchiveStatus( 

2311 conversations_pb2.SetGroupChatArchiveStatusReq(group_chat_id=gcid, is_archived=True) 

2312 ) 

2313 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=gcid)) 

2314 assert res.is_archived 

2315 

2316 # Unarchive twice - should work without error 

2317 c.SetGroupChatArchiveStatus( 

2318 conversations_pb2.SetGroupChatArchiveStatusReq(group_chat_id=gcid, is_archived=False) 

2319 ) 

2320 c.SetGroupChatArchiveStatus( 

2321 conversations_pb2.SetGroupChatArchiveStatusReq(group_chat_id=gcid, is_archived=False) 

2322 ) 

2323 res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=gcid)) 

2324 assert not res.is_archived 

2325 

2326 

2327def test_chat_notifications(db, moderator): 

2328 user1, token1 = generate_user() 

2329 user2, token2 = generate_user() 

2330 # notifs off 

2331 user3, token3 = generate_user() 

2332 user4, token4 = generate_user() 

2333 user5, token5 = generate_user() 

2334 user6, token6 = generate_user() 

2335 

2336 make_friends(user1, user2) 

2337 make_friends(user1, user3) 

2338 make_friends(user1, user4) 

2339 make_friends(user4, user5) 

2340 make_friends(user4, user6) 

2341 

2342 # have some of them enable/disable notifs 

2343 topic_action = NotificationTopicAction.chat__message 

2344 for token, enabled in [ 

2345 (token1, True), 

2346 (token2, True), 

2347 (token3, False), 

2348 (token4, True), 

2349 (token5, True), 

2350 (token6, True), 

2351 ]: 

2352 with notifications_session(token) as notifications: 

2353 notifications.SetNotificationSettings( 

2354 notifications_pb2.SetNotificationSettingsReq( 

2355 preferences=[ 

2356 notifications_pb2.SingleNotificationPreference( 

2357 topic=topic_action.topic, 

2358 action=topic_action.action, 

2359 delivery_method=delivery_method, 

2360 enabled=enabled, 

2361 ) 

2362 for delivery_method in ["push", "email", "digest"] 

2363 ], 

2364 ) 

2365 ) 

2366 

2367 group_chat_id = None 

2368 

2369 def send_msg(c, i): 

2370 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=group_chat_id, text=f"Test message {i}")) 

2371 

2372 with conversations_session(token1) as c: 

2373 res = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id, user3.id, user4.id])) 

2374 group_chat_id = res.group_chat_id 

2375 moderator.approve_group_chat(group_chat_id) 

2376 c.EditGroupChat( 

2377 conversations_pb2.EditGroupChatReq( 

2378 group_chat_id=group_chat_id, only_admins_invite=wrappers_pb2.BoolValue(value=False) 

2379 ) 

2380 ) 

2381 send_msg(c, i=1) 

2382 send_msg(c, i=2) 

2383 

2384 with conversations_session(token4) as c: 

2385 c.InviteToGroupChat(conversations_pb2.InviteToGroupChatReq(group_chat_id=group_chat_id, user_id=user5.id)) 

2386 send_msg(c, i=3) 

2387 c.InviteToGroupChat(conversations_pb2.InviteToGroupChatReq(group_chat_id=group_chat_id, user_id=user6.id)) 

2388 send_msg(c, i=4) 

2389 send_msg(c, i=5) 

2390 

2391 with conversations_session(token3) as c: 

2392 send_msg(c, i=6) 

2393 c.LeaveGroupChat(conversations_pb2.LeaveGroupChatReq(group_chat_id=group_chat_id)) 

2394 

2395 with conversations_session(token2) as c: 

2396 send_msg(c, i=7) 

2397 c.LeaveGroupChat(conversations_pb2.LeaveGroupChatReq(group_chat_id=group_chat_id)) 

2398 

2399 with conversations_session(token6) as c: 

2400 send_msg(c, i=8) 

2401 

2402 # go through all bg jobs 

2403 while process_job(): 

2404 pass 

2405 

2406 # now check notifs... 

2407 expected_notifs = [ 

2408 (user1, "user1", [3, 4, 5, 6, 7, 8]), 

2409 (user2, "user2", [1, 2, 3, 4, 5, 6]), 

2410 (user3, "user3", []), # notifs off 

2411 (user4, "user4", [1, 2, 6, 7, 8]), 

2412 (user5, "user5", [3, 4, 5, 6, 7, 8]), 

2413 (user6, "user6", [4, 5, 6, 7]), 

2414 ] 

2415 

2416 with session_scope() as session: 

2417 for user, label, expected_msgs in expected_notifs: 

2418 deliv = ( 

2419 session.execute( 

2420 select(Notification.data) 

2421 .join(NotificationDelivery, NotificationDelivery.notification_id == Notification.id) 

2422 .where(Notification.user_id == user.id) 

2423 .where(Notification.topic_action == topic_action) 

2424 .where(NotificationDelivery.delivery_type == NotificationDeliveryType.push) 

2425 .order_by(Notification.created) 

2426 ) 

2427 .scalars() 

2428 .all() 

2429 ) 

2430 

2431 def parse_message_payload(data): 

2432 return notification_data_pb2.ChatMessage.FromString(data).text 

2433 

2434 contents = [parse_message_payload(d) for d in deliv] 

2435 

2436 print(contents) 

2437 

2438 assert [f"Test message {i}" for i in expected_msgs] == contents, f"Wrong messages for {label}" 

2439 

2440 

2441def test_incomplete_profile(db): 

2442 user1, token1 = generate_user(complete_profile=True) 

2443 user2, token2 = generate_user(complete_profile=False) 

2444 user3, token3 = generate_user(complete_profile=True) 

2445 make_friends(user1, user2) 

2446 make_friends(user1, user3) 

2447 

2448 # user 1 can make a chat 

2449 with conversations_session(token1) as c: 

2450 res = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id])) 

2451 group_chat_id = res.group_chat_id 

2452 c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=group_chat_id, text="Test message 1")) 

2453 res = c.GetGroupChatMessages(conversations_pb2.GetGroupChatMessagesReq(group_chat_id=group_chat_id)) 

2454 assert res.messages[0].text.text == "Test message 1" 

2455 assert to_aware_datetime(res.messages[0].time) <= now() 

2456 assert res.messages[0].author_user_id == user1.id 

2457 

2458 # user 2 cannot 

2459 with conversations_session(token2) as c: 

2460 with pytest.raises(grpc.RpcError) as e: 

2461 c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user3.id])) 

2462 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION 

2463 assert e.value.details() == "You have to complete your profile before you can send a message."