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

1435 statements  

« prev     ^ index     » next       coverage.py v7.16.1, created at 2026-09-19 15:47 +0000

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) 

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

22from couchers.rate_limits.definitions import RATE_LIMIT_DEFINITIONS, RATE_LIMIT_HOURS 

23from couchers.servicers import conversations 

24from couchers.utils import Duration_from_timedelta, now, to_aware_datetime 

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

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

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

28from tests.fixtures.timewarp import Timewarp 

29 

30 

31def test_list_group_chats(db, moderator): 

32 user1, token1 = generate_user() 

33 user2, token2 = generate_user() 

34 user3, token3 = generate_user() 

35 

36 make_friends(user2, user1) 

37 make_friends(user1, user3) 

38 

39 with conversations_session(token1) as c: 

40 # no threads initially 

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

42 assert len(res.group_chats) == 0 

43 

44 # create some group chats with messages 

45 res = c.CreateGroupChat( 

46 conversations_pb2.CreateGroupChatReq( 

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

48 ) 

49 ) 

50 group_chat1_id = res.group_chat_id 

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

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

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

54 group_chat2_id = res.group_chat_id 

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

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

57 

58 moderator.approve_group_chat(group_chat1_id) 

59 moderator.approve_group_chat(group_chat2_id) 

60 

61 with conversations_session(token1) as c: 

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

63 assert len(res.group_chats) == 2 

64 assert res.no_more 

65 

66 with conversations_session(token2) as c: 

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

68 assert len(res.group_chats) == 2 

69 assert res.no_more 

70 

71 with conversations_session(token3) as c: 

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

73 assert len(res.group_chats) == 1 

74 assert res.no_more 

75 

76 # Test archive filtering: archive group_chat1 for user1 

77 with conversations_session(token1) as c: 

78 res = c.SetGroupChatArchiveStatus( 

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

80 ) 

81 assert res.group_chat_id == group_chat1_id 

82 assert res.is_archived 

83 

84 with conversations_session(token1) as c: 

85 # Without filter, returns all chats 

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

87 assert len(res.group_chats) == 2 

88 

89 # only_archived=False returns non-archived chats only 

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

91 assert len(res.group_chats) == 1 

92 

93 # only_archived=True returns archived chats only 

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

95 assert len(res.group_chats) == 1 

96 

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

98 with conversations_session(token2) as c: 

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

100 assert len(res.group_chats) == 2 

101 

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

103 assert len(res.group_chats) == 0 

104 

105 

106def test_list_empty_group_chats(db, moderator): 

107 user1, token1 = generate_user() 

108 user2, token2 = generate_user() 

109 user3, token3 = generate_user() 

110 

111 make_friends(user1, user3) 

112 make_friends(user2, user1) 

113 make_friends(user2, user3) 

114 

115 with conversations_session(token1) as c: 

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

117 assert len(res.group_chats) == 0 

118 

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

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

121 

122 moderator.approve_group_chat(res1.group_chat_id) 

123 moderator.approve_group_chat(res2.group_chat_id) 

124 

125 with conversations_session(token1) as c: 

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

127 assert len(res.group_chats) == 2 

128 assert res.no_more 

129 

130 with conversations_session(token2) as c: 

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

132 assert len(res.group_chats) == 2 

133 assert res.no_more 

134 

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

136 moderator.approve_group_chat(res3.group_chat_id) 

137 

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

139 assert len(res.group_chats) == 3 

140 assert res.no_more 

141 

142 with conversations_session(token3) as c: 

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

144 assert len(res.group_chats) == 2 

145 assert res.no_more 

146 

147 

148def test_list_group_chats_ordering(db, moderator): 

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

150 user, token = generate_user() 

151 user2, token2 = generate_user() 

152 user3, token3 = generate_user() 

153 user4, token4 = generate_user() 

154 

155 make_friends(user2, user) 

156 make_friends(user2, user3) 

157 make_friends(user2, user4) 

158 make_friends(user3, user) 

159 make_friends(user3, user4) 

160 make_friends(user, user4) 

161 

162 chat_ids = [] 

163 

164 with conversations_session(token2) as c: 

165 res = c.CreateGroupChat( 

166 conversations_pb2.CreateGroupChatReq( 

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

168 ) 

169 ) 

170 chat_ids.append(res.group_chat_id) 

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

172 res = c.CreateGroupChat( 

173 conversations_pb2.CreateGroupChatReq( 

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

175 ) 

176 ) 

177 chat_ids.append(res.group_chat_id) 

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

179 res = c.CreateGroupChat( 

180 conversations_pb2.CreateGroupChatReq( 

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

182 ) 

183 ) 

184 chat_ids.append(res.group_chat_id) 

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

186 

187 with conversations_session(token3) as c: 

188 res = c.CreateGroupChat( 

189 conversations_pb2.CreateGroupChatReq( 

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

191 ) 

192 ) 

193 chat_ids.append(res.group_chat_id) 

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

195 

196 for chat_id in chat_ids: 

197 moderator.approve_group_chat(chat_id) 

198 

199 with conversations_session(token) as c: 

200 res = c.CreateGroupChat( 

201 conversations_pb2.CreateGroupChatReq( 

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

203 ) 

204 ) 

205 moderator.approve_group_chat(res.group_chat_id) 

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

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

208 assert len(res.group_chats) == 5 

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

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

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

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

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

214 

215 c.SendMessage( 

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

217 ) 

218 c.SendMessage( 

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

220 ) 

221 

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

223 assert len(res.group_chats) == 5 

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

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

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

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

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

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

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

231 

232 

233def test_list_group_chats_ordering_after_left(db, moderator): 

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

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

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

237 user, token = generate_user() 

238 user2, token2 = generate_user() 

239 user3, token3 = generate_user() 

240 user4, token4 = generate_user() 

241 

242 make_friends(user2, user) 

243 make_friends(user2, user3) 

244 make_friends(user2, user4) 

245 make_friends(user3, user) 

246 make_friends(user3, user4) 

247 make_friends(user, user4) 

248 

249 chat_ids = [] 

250 

251 with conversations_session(token2) as c: 

252 res = c.CreateGroupChat( 

253 conversations_pb2.CreateGroupChatReq( 

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

255 ) 

256 ) 

257 chat_ids.append(res.group_chat_id) 

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

259 res = c.CreateGroupChat( 

260 conversations_pb2.CreateGroupChatReq( 

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

262 ) 

263 ) 

264 left_chat_id = res.group_chat_id 

265 chat_ids.append(left_chat_id) 

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

267 res = c.CreateGroupChat( 

268 conversations_pb2.CreateGroupChatReq( 

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

270 ) 

271 ) 

272 chat2_id = res.group_chat_id 

273 chat_ids.append(chat2_id) 

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

275 

276 with conversations_session(token3) as c: 

277 res = c.CreateGroupChat( 

278 conversations_pb2.CreateGroupChatReq( 

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

280 ) 

281 ) 

282 chat_ids.append(res.group_chat_id) 

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

284 

285 for chat_id in chat_ids: 

286 moderator.approve_group_chat(chat_id) 

287 

288 with conversations_session(token) as c: 

289 res = c.CreateGroupChat( 

290 conversations_pb2.CreateGroupChatReq( 

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

292 ) 

293 ) 

294 moderator.approve_group_chat(res.group_chat_id) 

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

296 

297 # leave chat 

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

299 

300 with conversations_session(token3) as c: 

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

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

303 

304 with conversations_session(token2) as c: 

305 # other user sends a message to that chat 

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

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

308 assert len(res.group_chats) == 5 

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

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

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

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

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

314 

315 with conversations_session(token) as c: 

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

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

318 assert len(res.group_chats) == 5 

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

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

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

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

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

324 

325 

326def test_get_group_chat_messages(db): 

327 user1, token1 = generate_user() 

328 user2, token2 = generate_user() 

329 user3, token3 = generate_user() 

330 

331 make_friends(user1, user2) 

332 

333 with conversations_session(token1) as c: 

334 # create some threads with messages 

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

336 group_chat_id = res.group_chat_id 

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

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

339 

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

341 # created + 2 normal 

342 assert len(res.messages) == 3 

343 assert res.no_more 

344 

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

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

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

348 

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

350 with conversations_session(token3) as c: 

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

352 assert len(res.messages) == 0 

353 

354 

355def test_get_group_chat_messages_pagination(db, moderator): 

356 user1, token1 = generate_user() 

357 user2, token2 = generate_user() 

358 make_friends(user1, user2) 

359 

360 with conversations_session(token1) as c: 

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

362 group_chat_id = res.group_chat_id 

363 for i in range(30): 

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

365 

366 moderator.approve_group_chat(group_chat_id) 

367 

368 with conversations_session(token2) as c: 

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

370 # pagination 

371 assert len(res.messages) == 20 

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

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

374 assert not res.no_more 

375 res = c.GetGroupChatMessages( 

376 conversations_pb2.GetGroupChatMessagesReq( 

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

378 ) 

379 ) 

380 assert len(res.messages) == 11 

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

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

383 assert res.no_more 

384 

385 

386def test_get_group_chat_messages_joined_left(db, moderator): 

387 user1, token1 = generate_user() 

388 user2, token2 = generate_user() 

389 user3, token3 = generate_user() 

390 user4, token4 = generate_user() 

391 make_friends(user1, user2) 

392 make_friends(user1, user3) 

393 make_friends(user1, user4) 

394 

395 with conversations_session(token1) as c: 

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

397 group_chat_id = res.group_chat_id 

398 moderator.approve_group_chat(group_chat_id) 

399 

400 for i in range(10): 

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

402 

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

404 

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

406 

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

408 

409 # created + 10 normal + invited + normal 

410 assert len(res.messages) == 13 

411 

412 with conversations_session(token3) as c: 

413 # can only see last message after invited 

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

415 # joined + normal 

416 assert len(res.messages) == 2 

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

418 

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

420 

421 with conversations_session(token1) as c: 

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

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

424 

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

426 

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

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

429 

430 with conversations_session(token3) as c: 

431 # can only see last message after invited 

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

433 # joined + normal + left + invite + 2 normal 

434 assert len(res.messages) == 6 

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

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

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

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

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

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

441 

442 

443def test_get_group_chat_info(db): 

444 user1, token1 = generate_user() 

445 user2, token2 = generate_user() 

446 user3, token3 = generate_user() 

447 

448 make_friends(user1, user2) 

449 make_friends(user3, user1) 

450 

451 with conversations_session(token1) as c: 

452 # create some threads with messages 

453 res = c.CreateGroupChat( 

454 conversations_pb2.CreateGroupChatReq( 

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

456 ) 

457 ) 

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

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

460 group_chat1_id = res.group_chat_id 

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

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

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

464 group_chat2_id = res.group_chat_id 

465 

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

467 assert res.title == "Test title" 

468 assert user2.id in res.member_user_ids 

469 assert user1.id in res.admin_user_ids 

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

471 assert res.only_admins_invite 

472 assert res.is_dm 

473 assert not res.is_archived 

474 

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

476 assert not res.title 

477 assert user2.id in res.member_user_ids 

478 assert user3.id in res.member_user_ids 

479 assert user1.id in res.admin_user_ids 

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

481 assert res.only_admins_invite 

482 assert not res.is_dm 

483 assert not res.is_archived 

484 

485 

486def test_get_group_chat_info_denied(db): 

487 user1, token1 = generate_user() 

488 user2, token2 = generate_user() 

489 user3, token3 = generate_user() 

490 user4, token4 = generate_user() 

491 

492 make_friends(user1, user2) 

493 make_friends(user3, user1) 

494 

495 with conversations_session(token1) as c: 

496 # create a group chat with messages 

497 res = c.CreateGroupChat( 

498 conversations_pb2.CreateGroupChatReq( 

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

500 ) 

501 ) 

502 group_chat_id = res.group_chat_id 

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

504 

505 with conversations_session(token4) as c: 

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

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

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

509 

510 

511def test_get_group_chat_info_left(db, moderator): 

512 user1, token1 = generate_user() 

513 user2, token2 = generate_user() 

514 user3, token3 = generate_user() 

515 user4, token4 = generate_user() 

516 

517 make_friends(user1, user2) 

518 make_friends(user3, user1) 

519 make_friends(user1, user4) 

520 

521 with conversations_session(token1) as c: 

522 # create a group chat with messages 

523 res = c.CreateGroupChat( 

524 conversations_pb2.CreateGroupChatReq( 

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

526 ) 

527 ) 

528 group_chat_id = res.group_chat_id 

529 moderator.approve_group_chat(group_chat_id) 

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

531 

532 with conversations_session(token3) as c: 

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

534 

535 with conversations_session(token1) as c: 

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

537 

538 with conversations_session(token3) as c: 

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

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

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

542 assert len(res.member_user_ids) == 3 

543 assert user1.id in res.member_user_ids 

544 assert user2.id in res.member_user_ids 

545 assert user3.id in res.member_user_ids 

546 

547 

548def test_remove_group_chat_user(db): 

549 # create 3 uses and connect them 

550 user1, token1 = generate_user() 

551 user2, token2 = generate_user() 

552 user3, token3 = generate_user() 

553 make_friends(user1, user2) 

554 make_friends(user1, user3) 

555 

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

557 with conversations_session(token1) as c: 

558 # create a group chat 

559 res = c.CreateGroupChat( 

560 conversations_pb2.CreateGroupChatReq( 

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

562 ) 

563 ) 

564 group_chat_id = res.group_chat_id 

565 

566 # remove a user from group 

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

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

569 

570 # can't remove the same user twice 

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

572 c.RemoveGroupChatUser( 

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

574 ) 

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

576 

577 

578def test_edit_group_chat(db, moderator): 

579 user1, token1 = generate_user() 

580 user2, token2 = generate_user() 

581 user3, token3 = generate_user() 

582 make_friends(user1, user2) 

583 

584 with conversations_session(token1) as c: 

585 # create some threads with messages 

586 res = c.CreateGroupChat( 

587 conversations_pb2.CreateGroupChatReq( 

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

589 ) 

590 ) 

591 group_chat_id = res.group_chat_id 

592 moderator.approve_group_chat(group_chat_id) 

593 

594 c.EditGroupChat( 

595 conversations_pb2.EditGroupChatReq( 

596 group_chat_id=group_chat_id, 

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

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

599 ) 

600 ) 

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

602 assert res.title == "Modified title" 

603 assert not res.only_admins_invite 

604 

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

606 with conversations_session(token2) as c: 

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

608 c.EditGroupChat( 

609 conversations_pb2.EditGroupChatReq( 

610 group_chat_id=group_chat_id, 

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

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

613 ) 

614 ) 

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

616 

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

618 with conversations_session(token3) as c: 

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

620 c.EditGroupChat( 

621 conversations_pb2.EditGroupChatReq( 

622 group_chat_id=group_chat_id, 

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

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

625 ) 

626 ) 

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

628 

629 

630def test_make_remove_group_chat_admin(db, moderator): 

631 user1, token1 = generate_user() 

632 user2, token2 = generate_user() 

633 user3, token3 = generate_user() 

634 

635 make_friends(user1, user2) 

636 make_friends(user1, user3) 

637 make_friends(user2, user3) 

638 

639 with conversations_session(token1) as c: 

640 # create some threads with messages 

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

642 group_chat_id = res.group_chat_id 

643 moderator.approve_group_chat(group_chat_id) 

644 

645 # shouldn't be able to remove only admin 

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

647 c.RemoveGroupChatAdmin( 

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

649 ) 

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

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

652 

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

654 

655 # shouldn't be able to make admin again 

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

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

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

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

660 

661 with conversations_session(token2) as c: 

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

663 assert user1.id in res.admin_user_ids 

664 assert user2.id in res.admin_user_ids 

665 

666 with conversations_session(token1) as c: 

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

668 

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

670 assert user1.id in res.admin_user_ids 

671 assert user2.id not in res.admin_user_ids 

672 

673 with conversations_session(token2) as c: 

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

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

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

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

678 

679 

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

681 user1, token1 = generate_user() 

682 user2, token2 = generate_user() 

683 user3, token3 = generate_user() 

684 make_friends(user1, user2) 

685 make_friends(user1, user3) 

686 

687 # Let user2 receive email notifications for every chat message 

688 with notifications_session(token2) as n: 

689 n.SetNotificationSettings( 

690 notifications_pb2.SetNotificationSettingsReq( 

691 preferences=[ 

692 notifications_pb2.SingleNotificationPreference( 

693 topic=NotificationTopicAction.chat__message.topic, 

694 action=NotificationTopicAction.chat__message.action, 

695 delivery_method="email", 

696 enabled=True, 

697 ) 

698 ] 

699 ) 

700 ) 

701 

702 group_chat_title = "My group chat" 

703 message1 = "Test message 1" 

704 

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

706 with conversations_session(token1) as c: 

707 res = c.CreateGroupChat( 

708 conversations_pb2.CreateGroupChatReq( 

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

710 ) 

711 ) 

712 group_chat_id = res.group_chat_id 

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

714 

715 # Sender can already see group chat 

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

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

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

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

720 

721 # user2 sees nothing until the group chat is approved 

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

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

724 

725 moderator.approve_group_chat(group_chat_id) 

726 

727 # user2 gets email and push notifications 

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

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

730 assert user1.name in push.content.title 

731 assert group_chat_title in push.content.title 

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

733 assert push.content.ios_subtitle == group_chat_title 

734 assert message1 in push.content.body 

735 

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

737 assert group_chat_title in email.subject 

738 assert message1 in email.plain 

739 assert message1 in email.html 

740 

741 # can't send message if not in chat 

742 with conversations_session(token3) as c: 

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

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

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

746 

747 make_user_block(user2, user1) 

748 with conversations_session(token1) as c: 

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

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

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

752 

753 

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

755 user1, token1 = generate_user(complete_profile=True) 

756 user2, token2 = generate_user(complete_profile=True) 

757 

758 make_friends(user1, user2) 

759 

760 # Let user2 receive email notifications for every chat message 

761 with notifications_session(token2) as n: 

762 n.SetNotificationSettings( 

763 notifications_pb2.SetNotificationSettingsReq( 

764 preferences=[ 

765 notifications_pb2.SingleNotificationPreference( 

766 topic=NotificationTopicAction.chat__message.topic, 

767 action=NotificationTopicAction.chat__message.action, 

768 delivery_method="email", 

769 enabled=True, 

770 ) 

771 ] 

772 ) 

773 ) 

774 

775 # Let user1 send two DM's to user2 

776 message1 = "Hello, user2!" 

777 message2 = "One more message." 

778 

779 with conversations_session(token1) as c1: 

780 # Send a DM from user1 to user2 

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

782 moderator.approve_group_chat(res.group_chat_id) 

783 

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

785 

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

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

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

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

790 assert push.content.body == message1 

791 

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

793 assert user1.name in email.subject 

794 assert message1 in email.plain 

795 assert message1 in email.html 

796 

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

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

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

800 assert push.content.body == message2 

801 

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

803 assert user1.name in email.subject 

804 assert message2 in email.plain 

805 assert message2 in email.html 

806 

807 with conversations_session(token2) as c2: 

808 # Fetch the chat by ID returned from SendDirectMessage 

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

810 

811 assert chat.is_dm 

812 group_chat_id = chat.group_chat_id 

813 

814 # Verify that the messages was received 

815 messages = c2.GetGroupChatMessages( 

816 conversations_pb2.GetGroupChatMessagesReq(group_chat_id=group_chat_id) 

817 ).messages 

818 

819 assert len(messages) == 2 

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

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

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

823 

824 

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

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

827 user, token = generate_user() 

828 rate_limit_definition = RATE_LIMIT_DEFINITIONS[RateLimitAction.chat_initiation] 

829 with conversations_session(token) as c: 

830 # Test warning email 

831 for _ in range(rate_limit_definition.warning_limit): 

832 recipient_user, _ = generate_user() 

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

834 

835 assert email_collector.count_for_reports() == 0 

836 

837 recipient_user, _ = generate_user() 

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

839 

840 email = email_collector.pop_for_reports(last=True) 

841 assert email.plain.startswith( 

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

843 ) 

844 

845 # Test new chat initiations fail after exceeding CHAT_INITIATION_HARD_LIMIT 

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

847 recipient_user, _ = generate_user() 

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

849 

850 assert email_collector.count_for_reports() == 0 

851 

852 recipient_user, _ = generate_user() 

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

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

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

856 assert ( 

857 exc_info.value.details() 

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

859 ) 

860 

861 email = email_collector.pop_for_reports(last=True) 

862 assert email.plain.startswith( 

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

864 ) 

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

866 

867 

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

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

870 user, token = generate_user(complete_profile=True) 

871 rate_limit_definition = RATE_LIMIT_DEFINITIONS[RateLimitAction.chat_initiation] 

872 

873 with conversations_session(token) as c: 

874 for _ in range(rate_limit_definition.warning_limit): 

875 recipient, _ = generate_user() 

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

877 

878 assert email_collector.count_for_reports() == 0 

879 

880 recipient, _ = generate_user() 

881 existing_dm_recipient_id = recipient.id 

882 c.SendDirectMessage( 

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

884 ) 

885 

886 email = email_collector.pop_for_reports(last=True) 

887 assert email.plain.startswith( 

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

889 ) 

890 

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

892 recipient, _ = generate_user() 

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

894 

895 assert email_collector.count_for_reports() == 0 

896 

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

898 c.SendDirectMessage( 

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

900 ) 

901 assert email_collector.count_for_reports() == 0 

902 

903 recipient, _ = generate_user() 

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

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

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

907 assert ( 

908 exc_info.value.details() 

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

910 ) 

911 

912 email = email_collector.pop_for_reports(last=True) 

913 assert email.plain.startswith( 

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

915 ) 

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

917 

918 

919def test_leave_invite_to_group_chat(db, moderator): 

920 user1, token1 = generate_user() 

921 user2, token2 = generate_user() 

922 user3, token3 = generate_user() 

923 user4, token4 = generate_user() 

924 user5, token5 = generate_user() 

925 user6, token6 = generate_user(delete_user=True) 

926 user7, token7 = generate_user() 

927 user8, token8 = generate_user() 

928 

929 make_friends(user1, user2) 

930 make_friends(user1, user3) 

931 make_friends(user1, user5) 

932 make_friends(user1, user7) 

933 make_friends(user1, user8) 

934 make_friends(user2, user3) 

935 make_friends(user4, user3) 

936 make_user_block(user1, user7) 

937 make_user_block(user8, user1) 

938 

939 with conversations_session(token1) as c: 

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

941 group_chat_id = res.group_chat_id 

942 moderator.approve_group_chat(group_chat_id) 

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

944 

945 # other user not in chat 

946 with conversations_session(token3) as c: 

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

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

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

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

951 res = c.InviteToGroupChat( 

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

953 ) 

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

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

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

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

958 

959 with conversations_session(token2) as c: 

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

961 assert user3.id not in res.member_user_ids 

962 

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

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

965 res = c.InviteToGroupChat( 

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

967 ) 

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

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

970 

971 with conversations_session(token1) as c: 

972 # invite invisible user fails 

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

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

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

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

977 # invite fake user fails 

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

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

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

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

982 # invite blocked user fails 

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

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

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

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

987 # invite blocking user fails 

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

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

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

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

992 

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

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

995 assert user1.id in res.member_user_ids 

996 assert user5.id in res.member_user_ids 

997 assert user3.id in res.member_user_ids 

998 

999 # test non-admin inviting 

1000 c.EditGroupChat( 

1001 conversations_pb2.EditGroupChatReq( 

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

1003 ) 

1004 ) 

1005 

1006 with conversations_session(token3) as c: 

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

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

1009 assert user2.id in res.member_user_ids 

1010 

1011 

1012def test_group_chats_with_messages_before_join(db, moderator): 

1013 """ 

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

1015 should still see the group chat! 

1016 """ 

1017 user1, token1 = generate_user() 

1018 user2, token2 = generate_user() 

1019 user3, token3 = generate_user() 

1020 user4, token4 = generate_user() 

1021 

1022 make_friends(user1, user2) 

1023 make_friends(user1, user3) 

1024 make_friends(user2, user3) 

1025 make_friends(user1, user4) 

1026 

1027 with conversations_session(token1) as c: 

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

1029 group_chat_id = res.group_chat_id 

1030 moderator.approve_group_chat(group_chat_id) 

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

1032 

1033 with conversations_session(token2) as c: 

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

1035 

1036 with conversations_session(token1) as c: 

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

1038 

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

1040 

1041 with conversations_session(token3) as c: 

1042 # should work 

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

1044 

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

1046 assert len(res.group_chats) == 1 

1047 

1048 

1049def test_invite_to_dm(db): 

1050 user1, token1 = generate_user() 

1051 user2, token2 = generate_user() 

1052 user3, token3 = generate_user() 

1053 

1054 make_friends(user1, user2) 

1055 make_friends(user1, user3) 

1056 make_friends(user2, user3) 

1057 

1058 with conversations_session(token1) as c: 

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

1060 group_chat_id = res.group_chat_id 

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

1062 

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

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

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

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

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

1068 

1069 

1070def test_sole_admin_leaves(db, moderator): 

1071 user1, token1 = generate_user() 

1072 user2, token2 = generate_user() 

1073 user3, token3 = generate_user() 

1074 

1075 make_friends(user1, user2) 

1076 make_friends(user1, user3) 

1077 make_friends(user2, user3) 

1078 

1079 with conversations_session(token1) as c: 

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

1081 group_chat_id = res.group_chat_id 

1082 moderator.approve_group_chat(group_chat_id) 

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

1084 

1085 # sole admin can't leave group chat 

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

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

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

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

1090 

1091 with conversations_session(token2) as c: 

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

1093 

1094 with conversations_session(token3) as c: 

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

1096 

1097 # sole admin can leave when last in chat 

1098 with conversations_session(token1) as c: 

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

1100 

1101 

1102def test_search_messages(db, moderator): 

1103 user1, token1 = generate_user() 

1104 user2, token2 = generate_user() 

1105 user3, token3 = generate_user() 

1106 

1107 make_friends(user1, user2) 

1108 make_friends(user1, user3) 

1109 

1110 with conversations_session(token1) as c: 

1111 # create some threads with messages 

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

1113 gc1_id = res.group_chat_id 

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

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

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

1117 gc2_id = res.group_chat_id 

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

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

1120 

1121 # Approve group chats so they appear in search results 

1122 moderator.approve_group_chat(gc1_id) 

1123 moderator.approve_group_chat(gc2_id) 

1124 

1125 with conversations_session(token1) as c: 

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

1127 assert len(res.results) == 4 

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

1129 assert len(res.results) == 2 

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

1131 assert len(res.results) == 0 

1132 

1133 # outside user doesn't get results 

1134 with conversations_session(token3) as c: 

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

1136 assert len(res.results) == 0 

1137 

1138 

1139def test_search_messages_left_joined(db, moderator): 

1140 user1, token1 = generate_user() 

1141 user2, token2 = generate_user() 

1142 user3, token3 = generate_user() 

1143 user4, token4 = generate_user() 

1144 make_friends(user1, user2) 

1145 make_friends(user1, user3) 

1146 make_friends(user1, user4) 

1147 

1148 with conversations_session(token1) as c: 

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

1150 group_chat_id = res.group_chat_id 

1151 moderator.approve_group_chat(group_chat_id) 

1152 for i in range(10): 

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

1154 

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

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

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

1158 

1159 assert len(res.results) == 11 

1160 

1161 with conversations_session(token3) as c: 

1162 # can only see last message after invited 

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

1164 

1165 assert len(res.results) == 1 

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

1167 

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

1169 

1170 with conversations_session(token1) as c: 

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

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

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

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

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

1176 

1177 with conversations_session(token3) as c: 

1178 # can only see last message after invited 

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

1180 assert len(res.results) == 3 

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

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

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

1184 

1185 

1186def test_admin_behaviour(db, moderator): 

1187 user1, token1 = generate_user() 

1188 user2, token2 = generate_user() 

1189 user3, token3 = generate_user() 

1190 

1191 make_friends(user1, user2) 

1192 make_friends(user1, user3) 

1193 make_friends(user2, user3) 

1194 

1195 with conversations_session(token1) as c: 

1196 gcid = c.CreateGroupChat( 

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

1198 ).group_chat_id 

1199 moderator.approve_group_chat(gcid) 

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

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

1202 assert len(res.admin_user_ids) == 2 

1203 assert user1.id in res.admin_user_ids 

1204 assert user2.id in res.admin_user_ids 

1205 

1206 with conversations_session(token3) as c: 

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

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

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

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

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

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

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

1214 assert len(res.admin_user_ids) == 2 

1215 assert user1.id in res.admin_user_ids 

1216 assert user2.id in res.admin_user_ids 

1217 

1218 with conversations_session(token2) as c: 

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

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

1221 assert len(res.admin_user_ids) == 3 

1222 assert user1.id in res.admin_user_ids 

1223 assert user2.id in res.admin_user_ids 

1224 assert user3.id in res.admin_user_ids 

1225 

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

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

1228 assert len(res.admin_user_ids) == 2 

1229 assert user2.id in res.admin_user_ids 

1230 assert user3.id in res.admin_user_ids 

1231 

1232 with conversations_session(token1) as c: 

1233 with pytest.raises(grpc.RpcError): 

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

1235 with pytest.raises(grpc.RpcError): 

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

1237 with pytest.raises(grpc.RpcError): 

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

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

1240 assert len(res.admin_user_ids) == 2 

1241 assert user2.id in res.admin_user_ids 

1242 assert user3.id in res.admin_user_ids 

1243 

1244 with conversations_session(token2) as c: 

1245 # can demote self if there are other admins 

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

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

1248 assert len(res.admin_user_ids) == 1 

1249 assert user3.id in res.admin_user_ids 

1250 

1251 with conversations_session(token3) as c: 

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

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

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

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

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

1257 assert len(res.admin_user_ids) == 1 

1258 assert user3.id in res.admin_user_ids 

1259 

1260 # last admin can't leave 

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

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

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

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

1265 

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

1267 

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

1269 

1270 with conversations_session(token2) as c: 

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

1272 

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

1274 with conversations_session(token1) as c: 

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

1276 

1277 

1278def test_add_remove_admin_failures(db): 

1279 user1, token1 = generate_user() 

1280 user2, token2 = generate_user() 

1281 user3, token3 = generate_user() 

1282 user4, token4 = generate_user() 

1283 user5, token5 = generate_user() 

1284 

1285 make_friends(user1, user2) 

1286 make_friends(user1, user3) 

1287 make_friends(user1, user4) 

1288 make_friends(user1, user5) 

1289 

1290 with conversations_session(token1) as c: 

1291 gcid = c.CreateGroupChat( 

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

1293 ).group_chat_id 

1294 

1295 make_user_invisible(user3.id) 

1296 make_user_block(user1, user4) 

1297 make_user_block(user5, user1) 

1298 

1299 # make non-existent user admin 

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

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

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

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

1304 

1305 # make invisible user admin 

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

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

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

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

1310 

1311 # make blocked user admin 

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

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

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

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

1316 

1317 # make blocking user admin 

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

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

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

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

1322 

1323 with session_scope() as session: 

1324 subscriptions = ( 

1325 session.execute( 

1326 select(GroupChatSubscription) 

1327 .where(GroupChatSubscription.group_chat_id == gcid) 

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

1329 ) 

1330 .scalars() 

1331 .all() 

1332 ) 

1333 

1334 for subscription in subscriptions: 

1335 subscription.role = GroupChatRole.admin 

1336 

1337 with conversations_session(token1) as c: 

1338 # remove non-existent user admin 

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

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

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

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

1343 

1344 # remove invisible admin 

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

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

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

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

1349 

1350 # remove blocked admin 

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

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

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

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

1355 

1356 # remove blocking admin 

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

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

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

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

1361 

1362 

1363def test_last_seen(db, moderator): 

1364 user1, token1 = generate_user() 

1365 user2, token2 = generate_user() 

1366 user3, token3 = generate_user() 

1367 

1368 make_friends(user1, user2) 

1369 make_friends(user1, user3) 

1370 make_friends(user2, user3) 

1371 

1372 with conversations_session(token3) as c: 

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

1374 gcid_distraction = c.CreateGroupChat( 

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

1376 ).group_chat_id 

1377 moderator.approve_group_chat(gcid_distraction) 

1378 

1379 with conversations_session(token1) as c: 

1380 gcid = c.CreateGroupChat( 

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

1382 ).group_chat_id 

1383 moderator.approve_group_chat(gcid) 

1384 

1385 message_ids = [] 

1386 

1387 for i in range(6): 

1388 c.SendMessage( 

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

1390 ) 

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

1392 c.SendMessage( 

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

1394 ) 

1395 

1396 message_ids.append( 

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

1398 ) 

1399 

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

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

1402 assert res.unseen_message_count == 0 

1403 

1404 with conversations_session(token2) as c: 

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

1406 # created + 6 normal 

1407 assert res.unseen_message_count == 7 

1408 

1409 backward_offset = 3 

1410 c.MarkLastSeenGroupChat( 

1411 conversations_pb2.MarkLastSeenGroupChatReq( 

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

1413 ) 

1414 ) 

1415 

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

1417 assert res.unseen_message_count == backward_offset 

1418 

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

1420 

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

1422 assert res.unseen_message_count == 0 

1423 

1424 with conversations_session(token3) as c: 

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

1426 # created + 7 normal 

1427 assert res.unseen_message_count == 8 

1428 

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

1430 

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

1432 assert res.unseen_message_count == 0 

1433 

1434 

1435def test_mark_last_seen_clears_notifications(db, moderator): 

1436 user1, token1 = generate_user() 

1437 user2, token2 = generate_user() 

1438 

1439 with conversations_session(token1) as c: 

1440 gcid = c.CreateGroupChat( 

1441 conversations_pb2.CreateGroupChatReq( 

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

1443 ) 

1444 ).group_chat_id 

1445 

1446 moderator.approve_group_chat(gcid) 

1447 

1448 with conversations_session(token1) as c: 

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

1450 

1451 process_jobs() 

1452 

1453 def unseen_notification_count(user_id): 

1454 with session_scope() as session: 

1455 return session.execute( 

1456 select(func.count()) 

1457 .select_from(Notification) 

1458 .where(Notification.user_id == user_id) 

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

1460 .where(Notification.is_seen == False) 

1461 ).scalar_one() 

1462 

1463 assert unseen_notification_count(user2.id) > 0 

1464 

1465 with conversations_session(token2) as c: 

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

1467 

1468 assert unseen_notification_count(user2.id) == 0 

1469 

1470 

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

1472 """ 

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

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

1475 """ 

1476 user1, token1 = generate_user() 

1477 user2, token2 = generate_user() 

1478 

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

1480 with notifications_session(token2) as n: 

1481 n.SetNotificationSettings( 

1482 notifications_pb2.SetNotificationSettingsReq( 

1483 preferences=[ 

1484 notifications_pb2.SingleNotificationPreference( 

1485 topic=NotificationTopicAction.chat__missed_messages.topic, 

1486 action=NotificationTopicAction.chat__missed_messages.action, 

1487 delivery_method="push", 

1488 enabled=True, 

1489 ) 

1490 ] 

1491 ) 

1492 ) 

1493 

1494 with conversations_session(token1) as c: 

1495 gcid = c.CreateGroupChat( 

1496 conversations_pb2.CreateGroupChatReq( 

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

1498 ) 

1499 ).group_chat_id 

1500 

1501 moderator.approve_group_chat(gcid) 

1502 

1503 with conversations_session(token1) as c: 

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

1505 

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

1507 timewarp.advance(MISSED_MESSAGES_DELAY) 

1508 send_message_notifications(empty_pb2.Empty()) 

1509 process_jobs() 

1510 

1511 def unseen_missed_messages(): 

1512 with notifications_session(token2) as n: 

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

1514 return [ 

1515 notification 

1516 for notification in res.notifications 

1517 if notification.topic == NotificationTopicAction.chat__missed_messages.topic 

1518 and notification.action == NotificationTopicAction.chat__missed_messages.action 

1519 ] 

1520 

1521 assert len(unseen_missed_messages()) == 1 

1522 

1523 with conversations_session(token2) as c: 

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

1525 

1526 assert unseen_missed_messages() == [] 

1527 

1528 

1529def test_one_dm_per_pair(db, moderator): 

1530 user1, token1 = generate_user() 

1531 user2, token2 = generate_user() 

1532 user3, token3 = generate_user() 

1533 

1534 make_friends(user1, user2) 

1535 make_friends(user1, user3) 

1536 make_friends(user2, user3) 

1537 

1538 with conversations_session(token1) as c: 

1539 # create DM with user 2 

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

1541 assert res.is_dm 

1542 dm_with_user2 = res.group_chat_id 

1543 

1544 # create DM with user 3 

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

1546 assert res.is_dm 

1547 dm_with_user3 = res.group_chat_id 

1548 

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

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

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

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

1553 

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

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

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

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

1558 

1559 # can create joined group chat 

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

1561 assert not res.is_dm 

1562 

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

1564 moderator.approve_group_chat(dm_with_user2) 

1565 

1566 with conversations_session(token2) as c: 

1567 # can create DM with user 3 

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

1569 assert res.is_dm 

1570 

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

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

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

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

1575 

1576 

1577def test_GetDirectMessage(db): 

1578 user1, token1 = generate_user() 

1579 user2, token2 = generate_user() 

1580 user3, token3 = generate_user() 

1581 

1582 make_friends(user1, user2) 

1583 make_friends(user1, user3) 

1584 make_friends(user2, user3) 

1585 

1586 with conversations_session(token1) as c: 

1587 # no group chat with user 2 

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

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

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

1591 

1592 # no group chat with nor user 3 

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

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

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

1596 

1597 # create DM with user 2 

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

1599 assert res.is_dm 

1600 gcid = res.group_chat_id 

1601 

1602 # now should exist 

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

1604 assert res.group_chat_id == gcid 

1605 assert not res.is_archived 

1606 

1607 # create DM with user 3 

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

1609 assert res.is_dm 

1610 

1611 # can create joined group chat 

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

1613 assert not res.is_dm 

1614 

1615 with conversations_session(token2) as c: 

1616 # can create DM with user 3 

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

1618 assert res.is_dm 

1619 assert res.can_message 

1620 gcid = res.group_chat_id 

1621 

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

1623 make_user_block(user3, user2) 

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

1625 assert res.group_chat_id == gcid 

1626 assert not res.can_message 

1627 

1628 

1629def test_total_unseen(db, moderator): 

1630 user1, token1 = generate_user() 

1631 user2, token2 = generate_user() 

1632 user3, token3 = generate_user() 

1633 

1634 # distractions 

1635 user4, token4 = generate_user() 

1636 

1637 make_friends(user1, user2) 

1638 make_friends(user1, user3) 

1639 make_friends(user2, user3) 

1640 

1641 # distractions 

1642 make_friends(user1, user4) 

1643 

1644 with conversations_session(token1) as c: 

1645 # distractions 

1646 gcid_distraction = c.CreateGroupChat( 

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

1648 ).group_chat_id 

1649 moderator.approve_group_chat(gcid_distraction) 

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

1651 

1652 gcid = c.CreateGroupChat( 

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

1654 ).group_chat_id 

1655 moderator.approve_group_chat(gcid) 

1656 

1657 for i in range(6): 

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

1659 

1660 # distractions 

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

1662 

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

1664 with api_session(token1) as api: 

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

1666 

1667 with api_session(token2) as api: 

1668 # chat created + 6 normal messages 

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

1670 

1671 # now leave chat with user2 

1672 with conversations_session(token2) as c: 

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

1674 

1675 with api_session(token2) as api: 

1676 # seen messages becomes 0 when leaving 

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

1678 

1679 with conversations_session(token1) as c: 

1680 # distractions 

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

1682 

1683 # send more stuff without user 2 

1684 for i in range(3): 

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

1686 

1687 # distractions 

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

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 # add user 2 back 

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

1697 

1698 # send more stuff with user 2 

1699 for i in range(12): 

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

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 # joined + 12 normal 

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

1708 

1709 

1710def test_left_group_chat_unseen_count_excludes_unreachable_messages(db, moderator): 

1711 """ 

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

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

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

1715 """ 

1716 user1, token1 = generate_user() 

1717 user2, token2 = generate_user() 

1718 user3, token3 = generate_user() 

1719 make_friends(user1, user2) 

1720 make_friends(user2, user3) 

1721 

1722 with conversations_session(token2) as c: 

1723 group_chat_id = c.CreateGroupChat( 

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

1725 ).group_chat_id 

1726 moderator.approve_group_chat(group_chat_id) 

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

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

1729 for _ in range(3): 

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

1731 

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

1733 with api_session(token1) as api: 

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

1735 

1736 with conversations_session(token1) as c: 

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

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

1739 assert listed.unseen_message_count == 3 

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

1741 

1742 

1743def test_left_group_chat_can_be_marked_seen(db, moderator): 

1744 """ 

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

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

1747 """ 

1748 user1, token1 = generate_user() 

1749 user2, token2 = generate_user() 

1750 user3, token3 = generate_user() 

1751 make_friends(user1, user2) 

1752 make_friends(user2, user3) 

1753 

1754 with conversations_session(token2) as c: 

1755 group_chat_id = c.CreateGroupChat( 

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

1757 ).group_chat_id 

1758 moderator.approve_group_chat(group_chat_id) 

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

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

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

1762 

1763 with api_session(token1) as api: 

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

1765 

1766 with conversations_session(token1) as c: 

1767 latest_message_id = c.GetGroupChat( 

1768 conversations_pb2.GetGroupChatReq(group_chat_id=group_chat_id) 

1769 ).latest_message.message_id 

1770 c.MarkLastSeenGroupChat( 

1771 conversations_pb2.MarkLastSeenGroupChatReq( 

1772 group_chat_id=group_chat_id, last_seen_message_id=latest_message_id 

1773 ) 

1774 ) 

1775 

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

1777 

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

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

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

1781 

1782 with api_session(token1) as api: 

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

1784 

1785 

1786def test_rejoined_group_chat_ping_reads_newest_subscription(db, moderator): 

1787 """ 

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

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

1790 """ 

1791 user1, token1 = generate_user() 

1792 user2, token2 = generate_user() 

1793 user3, token3 = generate_user() 

1794 make_friends(user1, user2) 

1795 make_friends(user2, user3) 

1796 

1797 with conversations_session(token2) as c: 

1798 group_chat_id = c.CreateGroupChat( 

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

1800 ).group_chat_id 

1801 moderator.approve_group_chat(group_chat_id) 

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

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

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

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

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

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

1808 

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

1810 with api_session(token1) as api: 

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

1812 

1813 with conversations_session(token1) as c: 

1814 latest_message_id = c.GetGroupChat( 

1815 conversations_pb2.GetGroupChatReq(group_chat_id=group_chat_id) 

1816 ).latest_message.message_id 

1817 c.MarkLastSeenGroupChat( 

1818 conversations_pb2.MarkLastSeenGroupChatReq( 

1819 group_chat_id=group_chat_id, last_seen_message_id=latest_message_id 

1820 ) 

1821 ) 

1822 

1823 with api_session(token1) as api: 

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

1825 

1826 

1827def test_rejoined_group_chat_only_unseen_matches_the_badge(db, moderator): 

1828 """ 

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

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

1831 """ 

1832 user1, token1 = generate_user() 

1833 user2, token2 = generate_user() 

1834 user3, token3 = generate_user() 

1835 make_friends(user1, user2) 

1836 make_friends(user2, user3) 

1837 

1838 with conversations_session(token2) as c: 

1839 group_chat_id = c.CreateGroupChat( 

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

1841 ).group_chat_id 

1842 moderator.approve_group_chat(group_chat_id) 

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

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

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

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

1847 

1848 with api_session(token1) as api: 

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

1850 

1851 with conversations_session(token1) as c: 

1852 unseen = c.GetGroupChatMessages( 

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

1854 ).messages 

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

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

1857 

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

1859 all_messages = c.GetGroupChatMessages( 

1860 conversations_pb2.GetGroupChatMessagesReq(group_chat_id=group_chat_id) 

1861 ).messages 

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

1863 

1864 

1865def test_rejoined_group_chat_archived_filter_reads_newest_subscription(db, moderator): 

1866 """ 

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

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

1869 subscription, where nothing could ever mark it seen. 

1870 """ 

1871 user1, token1 = generate_user() 

1872 user2, token2 = generate_user() 

1873 user3, token3 = generate_user() 

1874 make_friends(user1, user2) 

1875 make_friends(user2, user3) 

1876 

1877 with conversations_session(token2) as c: 

1878 group_chat_id = c.CreateGroupChat( 

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

1880 ).group_chat_id 

1881 moderator.approve_group_chat(group_chat_id) 

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

1883 

1884 with conversations_session(token1) as c: 

1885 c.SetGroupChatArchiveStatus( 

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

1887 ) 

1888 

1889 with conversations_session(token2) as c: 

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

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

1892 

1893 with conversations_session(token1) as c: 

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

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

1896 

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

1898 assert listed.group_chat_id == group_chat_id 

1899 assert not listed.is_archived 

1900 

1901 

1902def test_regression_ListGroupChats_pagination(db, moderator, monkeypatch): 

1903 user1, token1 = generate_user() 

1904 user2, token2 = generate_user() 

1905 user3, token3 = generate_user() 

1906 

1907 make_friends(user1, user2) 

1908 make_friends(user1, user3) 

1909 

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

1911 # chat per row of a production-sized page 

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

1913 

1914 with conversations_session(token1) as c: 

1915 # tuples of (group_chat_id, message_id) 

1916 group_chat_and_message_ids = [] 

1917 for i in range(7): 

1918 res1 = c.CreateGroupChat( 

1919 conversations_pb2.CreateGroupChatReq( 

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

1921 ) 

1922 ) 

1923 

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

1925 

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

1927 

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

1929 moderator.approve_group_chat(res1.group_chat_id) 

1930 

1931 seen_group_chat_ids = [] 

1932 

1933 pages = 0 

1934 last_message_id = 0 

1935 more = True 

1936 while more: 

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

1938 last_message_id = res.last_message_id 

1939 more = not res.no_more 

1940 pages += 1 

1941 

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

1943 

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

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

1946 

1947 

1948def test_muting(db, moderator): 

1949 user1, token1 = generate_user() 

1950 user2, token2 = generate_user() 

1951 user3, token3 = generate_user() 

1952 

1953 make_friends(user1, user2) 

1954 make_friends(user1, user3) 

1955 make_friends(user2, user3) 

1956 

1957 with conversations_session(token3) as c: 

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

1959 gcid_distraction = c.CreateGroupChat( 

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

1961 ).group_chat_id 

1962 moderator.approve_group_chat(gcid_distraction) 

1963 

1964 with conversations_session(token1) as c: 

1965 gcid = c.CreateGroupChat( 

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

1967 ).group_chat_id 

1968 moderator.approve_group_chat(gcid) 

1969 

1970 with conversations_session(token2) as c: 

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

1972 assert not res.mute_info.muted 

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

1974 

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

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

1977 assert res.mute_info.muted 

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

1979 

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

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

1982 assert not res.mute_info.muted 

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

1984 

1985 c.MuteGroupChat( 

1986 conversations_pb2.MuteGroupChatReq( 

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

1988 ) 

1989 ) 

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

1991 assert res.mute_info.muted 

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

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

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

1995 

1996 

1997def test_archiving(db, moderator): 

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

1999 user1, token1 = generate_user() 

2000 user2, token2 = generate_user() 

2001 user3, token3 = generate_user() 

2002 

2003 make_friends(user1, user2) 

2004 make_friends(user1, user3) 

2005 make_friends(user2, user3) 

2006 

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

2008 with conversations_session(token3) as c: 

2009 gcid_distraction = c.CreateGroupChat( 

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

2011 ).group_chat_id 

2012 moderator.approve_group_chat(gcid_distraction) 

2013 

2014 with conversations_session(token1) as c: 

2015 gcid = c.CreateGroupChat( 

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

2017 ).group_chat_id 

2018 moderator.approve_group_chat(gcid) 

2019 

2020 # Test basic archive/unarchive functionality 

2021 with conversations_session(token2) as c: 

2022 # Initially not archived 

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

2024 assert not res.is_archived 

2025 

2026 # Archive the chat 

2027 archive_res = c.SetGroupChatArchiveStatus( 

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

2029 ) 

2030 assert archive_res.group_chat_id == gcid 

2031 assert archive_res.is_archived 

2032 

2033 # Verify archived via GetGroupChat 

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

2035 assert res.is_archived 

2036 

2037 # Unarchive the chat 

2038 archive_res = c.SetGroupChatArchiveStatus( 

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

2040 ) 

2041 assert archive_res.group_chat_id == gcid 

2042 assert not archive_res.is_archived 

2043 

2044 # Verify unarchived via GetGroupChat 

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

2046 assert not res.is_archived 

2047 

2048 

2049def test_archiving_per_user(db, moderator): 

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

2051 user1, token1 = generate_user() 

2052 user2, token2 = generate_user() 

2053 

2054 make_friends(user1, user2) 

2055 

2056 with conversations_session(token1) as c: 

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

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

2059 moderator.approve_group_chat(gcid) 

2060 

2061 # User1 archives the chat 

2062 with conversations_session(token1) as c: 

2063 c.SetGroupChatArchiveStatus( 

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

2065 ) 

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

2067 assert res.is_archived 

2068 

2069 # User2 should NOT see it as archived 

2070 with conversations_session(token2) as c: 

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

2072 assert not res.is_archived 

2073 

2074 # User2 archives it too 

2075 with conversations_session(token2) as c: 

2076 c.SetGroupChatArchiveStatus( 

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

2078 ) 

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

2080 assert res.is_archived 

2081 

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

2083 with conversations_session(token1) as c: 

2084 c.SetGroupChatArchiveStatus( 

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

2086 ) 

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

2088 assert not res.is_archived 

2089 

2090 with conversations_session(token2) as c: 

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

2092 assert res.is_archived 

2093 

2094 

2095def test_archiving_with_list_group_chats(db, moderator): 

2096 """Test archive filtering with ListGroupChats endpoint""" 

2097 user1, token1 = generate_user() 

2098 user2, token2 = generate_user() 

2099 user3, token3 = generate_user() 

2100 

2101 make_friends(user1, user2) 

2102 make_friends(user1, user3) 

2103 

2104 with conversations_session(token1) as c: 

2105 # Create 3 chats 

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

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

2108 moderator.approve_group_chat(gcid1) 

2109 

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

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

2112 moderator.approve_group_chat(gcid2) 

2113 

2114 gcid3 = c.CreateGroupChat( 

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

2116 ).group_chat_id 

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

2118 moderator.approve_group_chat(gcid3) 

2119 

2120 with conversations_session(token1) as c: 

2121 # Initially all 3 chats should be visible 

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

2123 assert len(res.group_chats) == 3 

2124 

2125 # All should be non-archived 

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

2127 assert len(res.group_chats) == 3 

2128 

2129 # None should be archived 

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

2131 assert len(res.group_chats) == 0 

2132 

2133 # Archive chat 1 

2134 c.SetGroupChatArchiveStatus( 

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

2136 ) 

2137 

2138 # Without filter, still see all 3 

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

2140 assert len(res.group_chats) == 3 

2141 

2142 # Non-archived should show 2 

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

2144 assert len(res.group_chats) == 2 

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

2146 

2147 # Archived should show 1 

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

2149 assert len(res.group_chats) == 1 

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

2151 assert res.group_chats[0].is_archived 

2152 

2153 # Archive chat 2 as well 

2154 c.SetGroupChatArchiveStatus( 

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

2156 ) 

2157 

2158 # Non-archived should show 1 

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

2160 assert len(res.group_chats) == 1 

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

2162 

2163 # Archived should show 2 

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

2165 assert len(res.group_chats) == 2 

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

2167 assert archived_ids == {gcid1, gcid2} 

2168 

2169 # Unarchive chat 1 

2170 c.SetGroupChatArchiveStatus( 

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

2172 ) 

2173 

2174 # Non-archived should show 2 

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

2176 assert len(res.group_chats) == 2 

2177 

2178 # Archived should show 1 

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

2180 assert len(res.group_chats) == 1 

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

2182 

2183 

2184def test_archiving_chat_not_found(db, moderator): 

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

2186 user1, token1 = generate_user() 

2187 user2, token2 = generate_user() 

2188 user3, token3 = generate_user() 

2189 

2190 make_friends(user1, user2) 

2191 

2192 with conversations_session(token1) as c: 

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

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

2195 moderator.approve_group_chat(gcid) 

2196 

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

2198 with conversations_session(token3) as c: 

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

2200 c.SetGroupChatArchiveStatus( 

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

2202 ) 

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

2204 

2205 # Non-existent chat ID should fail 

2206 with conversations_session(token1) as c: 

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

2208 c.SetGroupChatArchiveStatus( 

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

2210 ) 

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

2212 

2213 

2214def test_archiving_after_leaving_chat(db, moderator): 

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

2216 user1, token1 = generate_user() 

2217 user2, token2 = generate_user() 

2218 user3, token3 = generate_user() 

2219 

2220 make_friends(user1, user2) 

2221 make_friends(user1, user3) 

2222 make_friends(user2, user3) 

2223 

2224 with conversations_session(token1) as c: 

2225 gcid = c.CreateGroupChat( 

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

2227 ).group_chat_id 

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

2229 moderator.approve_group_chat(gcid) 

2230 

2231 # User2 leaves the chat 

2232 with conversations_session(token2) as c: 

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

2234 

2235 # User2 should not be able to archive it now 

2236 with conversations_session(token2) as c: 

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

2238 c.SetGroupChatArchiveStatus( 

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

2240 ) 

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

2242 

2243 

2244def test_archiving_dm(db, moderator): 

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

2246 user1, token1 = generate_user() 

2247 user2, token2 = generate_user() 

2248 

2249 make_friends(user1, user2) 

2250 

2251 with conversations_session(token1) as c: 

2252 # Create a DM 

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

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

2255 moderator.approve_group_chat(gcid) 

2256 

2257 # Verify it's a DM 

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

2259 assert res.is_dm 

2260 assert not res.is_archived 

2261 

2262 # Archive the DM 

2263 c.SetGroupChatArchiveStatus( 

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

2265 ) 

2266 

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

2268 assert res.is_archived 

2269 

2270 # Unarchive it 

2271 c.SetGroupChatArchiveStatus( 

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

2273 ) 

2274 

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

2276 assert not res.is_archived 

2277 

2278 

2279def test_archiving_idempotent(db, moderator): 

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

2281 user1, token1 = generate_user() 

2282 user2, token2 = generate_user() 

2283 

2284 make_friends(user1, user2) 

2285 

2286 with conversations_session(token1) as c: 

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

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

2289 moderator.approve_group_chat(gcid) 

2290 

2291 # Archive twice - should work without error 

2292 c.SetGroupChatArchiveStatus( 

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

2294 ) 

2295 c.SetGroupChatArchiveStatus( 

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

2297 ) 

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

2299 assert res.is_archived 

2300 

2301 # Unarchive twice - should work without error 

2302 c.SetGroupChatArchiveStatus( 

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

2304 ) 

2305 c.SetGroupChatArchiveStatus( 

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

2307 ) 

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

2309 assert not res.is_archived 

2310 

2311 

2312def test_chat_notifications(db, moderator): 

2313 user1, token1 = generate_user() 

2314 user2, token2 = generate_user() 

2315 # notifs off 

2316 user3, token3 = generate_user() 

2317 user4, token4 = generate_user() 

2318 user5, token5 = generate_user() 

2319 user6, token6 = generate_user() 

2320 

2321 make_friends(user1, user2) 

2322 make_friends(user1, user3) 

2323 make_friends(user1, user4) 

2324 make_friends(user4, user5) 

2325 make_friends(user4, user6) 

2326 

2327 # have some of them enable/disable notifs 

2328 topic_action = NotificationTopicAction.chat__message 

2329 for token, enabled in [ 

2330 (token1, True), 

2331 (token2, True), 

2332 (token3, False), 

2333 (token4, True), 

2334 (token5, True), 

2335 (token6, True), 

2336 ]: 

2337 with notifications_session(token) as notifications: 

2338 notifications.SetNotificationSettings( 

2339 notifications_pb2.SetNotificationSettingsReq( 

2340 preferences=[ 

2341 notifications_pb2.SingleNotificationPreference( 

2342 topic=topic_action.topic, 

2343 action=topic_action.action, 

2344 delivery_method=delivery_method, 

2345 enabled=enabled, 

2346 ) 

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

2348 ], 

2349 ) 

2350 ) 

2351 

2352 group_chat_id = None 

2353 

2354 def send_msg(c, i): 

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

2356 

2357 with conversations_session(token1) as c: 

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

2359 group_chat_id = res.group_chat_id 

2360 moderator.approve_group_chat(group_chat_id) 

2361 c.EditGroupChat( 

2362 conversations_pb2.EditGroupChatReq( 

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

2364 ) 

2365 ) 

2366 send_msg(c, i=1) 

2367 send_msg(c, i=2) 

2368 

2369 with conversations_session(token4) as c: 

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

2371 send_msg(c, i=3) 

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

2373 send_msg(c, i=4) 

2374 send_msg(c, i=5) 

2375 

2376 with conversations_session(token3) as c: 

2377 send_msg(c, i=6) 

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

2379 

2380 with conversations_session(token2) as c: 

2381 send_msg(c, i=7) 

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

2383 

2384 with conversations_session(token6) as c: 

2385 send_msg(c, i=8) 

2386 

2387 # go through all bg jobs 

2388 while process_job(): 

2389 pass 

2390 

2391 # now check notifs... 

2392 expected_notifs = [ 

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

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

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

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

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

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

2399 ] 

2400 

2401 with session_scope() as session: 

2402 for user, label, expected_msgs in expected_notifs: 

2403 deliv = ( 

2404 session.execute( 

2405 select(Notification.data) 

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

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

2408 .where(Notification.topic_action == topic_action) 

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

2410 .order_by(Notification.created) 

2411 ) 

2412 .scalars() 

2413 .all() 

2414 ) 

2415 

2416 def parse_message_payload(data): 

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

2418 

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

2420 

2421 print(contents) 

2422 

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

2424 

2425 

2426def test_incomplete_profile(db): 

2427 user1, token1 = generate_user(complete_profile=True) 

2428 user2, token2 = generate_user(complete_profile=False) 

2429 user3, token3 = generate_user(complete_profile=True) 

2430 make_friends(user1, user2) 

2431 make_friends(user1, user3) 

2432 

2433 # user 1 can make a chat 

2434 with conversations_session(token1) as c: 

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

2436 group_chat_id = res.group_chat_id 

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

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

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

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

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

2442 

2443 # user 2 cannot 

2444 with conversations_session(token2) as c: 

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

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

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

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