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

1147 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-03-19 14:14 +0000

1from datetime import timedelta 

2 

3import grpc 

4import pytest 

5from google.protobuf import empty_pb2, wrappers_pb2 

6from sqlalchemy import func, select, update 

7 

8from couchers.db import session_scope 

9from couchers.jobs.handlers import update_badges 

10from couchers.materialized_views import refresh_materialized_views_rapid 

11from couchers.models import ( 

12 FriendRelationship, 

13 FriendStatus, 

14 LanguageFluency, 

15 ModerationObjectType, 

16 ModerationState, 

17 ModerationVisibility, 

18 RateLimitAction, 

19 User, 

20) 

21from couchers.proto import admin_pb2, api_pb2, blocking_pb2, jail_pb2, notifications_pb2 

22from couchers.rate_limits.definitions import RATE_LIMIT_DEFINITIONS, RATE_LIMIT_HOURS 

23from couchers.resources import get_badge_dict 

24from couchers.utils import create_coordinate, now, to_aware_datetime 

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

26from tests.fixtures.misc import PushCollector, email_fields, mock_notification_email 

27from tests.fixtures.sessions import ( 

28 api_session, 

29 blocking_session, 

30 notifications_session, 

31 real_api_session, 

32 real_jail_session, 

33) 

34from tests.fixtures.sessions import ( 

35 real_admin_session as admin_session, 

36) 

37 

38 

39@pytest.fixture(autouse=True) 

40def _(testconfig): 

41 pass 

42 

43 

44def test_ping(db): 

45 user, token = generate_user( 

46 regions_lived=["ESP", "FRA", "EST"], 

47 regions_visited=["CHE", "REU", "FIN"], 

48 language_abilities=[ 

49 ("fin", LanguageFluency.fluent), 

50 ("fra", LanguageFluency.beginner), 

51 ], 

52 ) 

53 

54 with real_api_session(token) as api: 

55 res = api.Ping(api_pb2.PingReq()) 

56 

57 assert res.user.user_id == user.id 

58 assert res.user.username == user.username 

59 assert res.user.name == user.name 

60 assert res.user.city == user.city 

61 assert res.user.hometown == user.hometown 

62 assert res.user.verification == 0.0 

63 assert res.user.community_standing == user.community_standing 

64 assert res.user.num_references == 0 

65 assert res.user.gender == user.gender 

66 assert res.user.pronouns == user.pronouns 

67 assert res.user.age == user.age 

68 

69 assert (res.user.lat, res.user.lng) == user.coordinates 

70 

71 # the joined time is fuzzed 

72 # but shouldn't be before actual joined time, or more than one hour behind 

73 assert user.joined - timedelta(hours=1) <= to_aware_datetime(res.user.joined) <= user.joined 

74 # same for last_active 

75 assert user.last_active - timedelta(hours=1) <= to_aware_datetime(res.user.last_active) <= user.last_active 

76 

77 assert res.user.hosting_status == api_pb2.HOSTING_STATUS_CANT_HOST 

78 assert res.user.meetup_status == api_pb2.MEETUP_STATUS_OPEN_TO_MEETUP 

79 

80 assert res.user.occupation == user.occupation 

81 assert res.user.education == user.education 

82 assert res.user.about_me == user.about_me 

83 assert res.user.things_i_like == user.things_i_like 

84 assert {language_ability.code for language_ability in res.user.language_abilities} == {"fin", "fra"} 

85 assert res.user.about_place == user.about_place 

86 assert res.user.regions_visited == ["FIN", "REU", "CHE"] # Tests alphabetization by region name 

87 assert res.user.regions_lived == ["EST", "FRA", "ESP"] # Ditto 

88 assert res.user.additional_information == user.additional_information 

89 

90 assert res.user.friends == api_pb2.User.FriendshipStatus.NA 

91 assert not res.user.HasField("pending_friend_request") 

92 

93 

94def test_coords(db): 

95 # make them need to update location 

96 user1, token1 = generate_user(geom=create_coordinate(1, 0), geom_radius=2000, needs_to_update_location=True) 

97 user2, token2 = generate_user() 

98 

99 with api_session(token2) as api: 

100 res = api.Ping(api_pb2.PingReq()) 

101 assert res.user.city == user2.city 

102 lat, lng = user2.coordinates 

103 assert res.user.lat == lat 

104 assert res.user.lng == lng 

105 assert res.user.radius == user2.geom_radius 

106 

107 with api_session(token2) as api: 

108 res = api.GetUser(api_pb2.GetUserReq(user=user1.username)) 

109 assert res.city == user1.city 

110 assert res.lat == 1.0 

111 assert res.lng == 0.0 

112 assert res.radius == 2000.0 

113 

114 # Check coordinate wrapping 

115 user3, token3 = generate_user(geom=create_coordinate(40.0, -180.5)) 

116 user4, token4 = generate_user(geom=create_coordinate(40.0, 20.0)) 

117 user5, token5 = generate_user(geom=create_coordinate(90.5, 20.0)) 

118 

119 with api_session(token3) as api: 

120 res = api.GetUser(api_pb2.GetUserReq(user=user3.username)) 

121 assert res.lat == 40.0 

122 assert res.lng == 179.5 

123 

124 with api_session(token4) as api: 

125 res = api.GetUser(api_pb2.GetUserReq(user=user4.username)) 

126 assert res.lat == 40.0 

127 assert res.lng == 20.0 

128 

129 # PostGIS does not wrap longitude for latitude overflow 

130 with api_session(token5) as api: 

131 res = api.GetUser(api_pb2.GetUserReq(user=user5.username)) 

132 assert res.lat == 89.5 

133 assert res.lng == 20.0 

134 

135 with real_jail_session(token1) as jail: 

136 res = jail.JailInfo(empty_pb2.Empty()) 

137 assert res.jailed 

138 assert res.needs_to_update_location 

139 

140 res = jail.SetLocation( 

141 jail_pb2.SetLocationReq( 

142 city="New York City", 

143 lat=40.7812, 

144 lng=-73.9647, 

145 radius=250, 

146 ) 

147 ) 

148 

149 assert not res.jailed 

150 assert not res.needs_to_update_location 

151 

152 res = jail.JailInfo(empty_pb2.Empty()) 

153 assert not res.jailed 

154 assert not res.needs_to_update_location 

155 

156 with api_session(token2) as api: 

157 res = api.GetUser(api_pb2.GetUserReq(user=user1.username)) 

158 assert res.city == "New York City" 

159 assert res.lat == 40.7812 

160 assert res.lng == -73.9647 

161 assert res.radius == 250 

162 

163 

164def test_get_user(db): 

165 user1, token1 = generate_user() 

166 user2, token2 = generate_user() 

167 

168 with api_session(token1) as api: 

169 res = api.GetUser(api_pb2.GetUserReq(user=user2.username)) 

170 assert res.user_id == user2.id 

171 assert res.username == user2.username 

172 assert res.name == user2.name 

173 

174 with api_session(token1) as api: 

175 res = api.GetUser(api_pb2.GetUserReq(user=str(user2.id))) 

176 assert res.user_id == user2.id 

177 assert res.username == user2.username 

178 assert res.name == user2.name 

179 

180 

181@pytest.mark.parametrize("flag", ["deleted_at", "banned_at"]) 

182def test_user_model_to_pb_ghost_user(db, flag): 

183 user1, token1 = generate_user() 

184 user2, _ = generate_user() 

185 

186 with session_scope() as session: 

187 session.execute(update(User).where(User.id == user2.id).values(**{flag: func.now()})) 

188 

189 refresh_materialized_views_rapid(empty_pb2.Empty()) 

190 

191 with api_session(token1) as api: 

192 user_pb = api.GetUser(api_pb2.GetUserReq(user=user2.username)) 

193 

194 assert user_pb.user_id == user2.id 

195 assert user_pb.is_ghost 

196 assert user_pb.username == "ghost" 

197 assert user_pb.name == "Deactivated Account" 

198 assert ( 

199 user_pb.about_me 

200 == "This user is no longer on the platform. They may have deleted their account, been blocked, or been banned. We recommend exercising caution with any further interaction with this user off the platform. You can always reach out to support if you need any help." 

201 ) 

202 

203 assert user_pb.lat == 0 

204 assert user_pb.lng == 0 

205 assert user_pb.radius == 0 

206 assert user_pb.verification == 0.0 

207 assert user_pb.community_standing == 0.0 

208 assert user_pb.num_references == 0 

209 assert user_pb.age == 0 

210 assert user_pb.hosting_status == 0 

211 assert user_pb.meetup_status == 0 

212 assert user_pb.city == "" 

213 assert user_pb.hometown == "" 

214 assert user_pb.timezone == "" 

215 assert user_pb.gender == "" 

216 assert user_pb.pronouns == "" 

217 assert user_pb.occupation == "" 

218 assert user_pb.education == "" 

219 assert user_pb.things_i_like == "" 

220 assert user_pb.about_place == "" 

221 assert user_pb.additional_information == "" 

222 assert list(user_pb.language_abilities) == [] 

223 assert list(user_pb.regions_visited) == [] 

224 assert list(user_pb.regions_lived) == [] 

225 assert list(user_pb.badges) == [] 

226 assert user_pb.friends == api_pb2.User.FriendshipStatus.NOT_FRIENDS 

227 assert user_pb.avatar_url == "" 

228 assert user_pb.avatar_thumbnail_url == "" 

229 assert not user_pb.has_strong_verification 

230 

231 with api_session(token1) as api: 

232 lite_user_pb = api.GetLiteUser(api_pb2.GetLiteUserReq(user=user2.username)) 

233 

234 assert lite_user_pb.user_id == user2.id 

235 assert lite_user_pb.is_ghost 

236 assert lite_user_pb.username == "ghost" 

237 assert lite_user_pb.name == "Deactivated Account" 

238 assert lite_user_pb.city == "" 

239 assert lite_user_pb.age == 0 

240 assert lite_user_pb.avatar_url == "" 

241 assert lite_user_pb.avatar_thumbnail_url == "" 

242 assert lite_user_pb.lat == 0 

243 assert lite_user_pb.lng == 0 

244 assert lite_user_pb.radius == 0 

245 assert not lite_user_pb.has_strong_verification 

246 

247 

248def test_user_model_to_pb_ghost_user_blocked(db): 

249 user1, token1 = generate_user() 

250 user2, _ = generate_user() 

251 

252 with blocking_session(token1) as user_blocks: 

253 user_blocks.BlockUser(blocking_pb2.BlockUserReq(username=user2.username)) 

254 

255 refresh_materialized_views_rapid(empty_pb2.Empty()) 

256 

257 with api_session(token1) as api: 

258 user_pb = api.GetUser(api_pb2.GetUserReq(user=user2.username)) 

259 

260 assert user_pb.user_id == user2.id 

261 assert user_pb.is_ghost 

262 assert user_pb.username == "ghost" 

263 assert user_pb.name == "Deactivated Account" 

264 assert ( 

265 user_pb.about_me 

266 == "This user is no longer on the platform. They may have deleted their account, been blocked, or been banned. We recommend exercising caution with any further interaction with this user off the platform. You can always reach out to support if you need any help." 

267 ) 

268 

269 assert user_pb.lat == 0 

270 assert user_pb.lng == 0 

271 assert user_pb.radius == 0 

272 assert user_pb.verification == 0.0 

273 assert user_pb.community_standing == 0.0 

274 assert user_pb.num_references == 0 

275 assert user_pb.age == 0 

276 assert user_pb.hosting_status == 0 

277 assert user_pb.meetup_status == 0 

278 assert user_pb.city == "" 

279 assert user_pb.hometown == "" 

280 assert user_pb.timezone == "" 

281 assert user_pb.gender == "" 

282 assert user_pb.pronouns == "" 

283 assert user_pb.occupation == "" 

284 assert user_pb.education == "" 

285 assert user_pb.things_i_like == "" 

286 assert user_pb.about_place == "" 

287 assert user_pb.additional_information == "" 

288 assert list(user_pb.language_abilities) == [] 

289 assert list(user_pb.regions_visited) == [] 

290 assert list(user_pb.regions_lived) == [] 

291 assert list(user_pb.badges) == [] 

292 assert user_pb.friends == api_pb2.User.FriendshipStatus.NOT_FRIENDS 

293 assert user_pb.avatar_url == "" 

294 assert user_pb.avatar_thumbnail_url == "" 

295 assert not user_pb.has_strong_verification 

296 

297 with api_session(token1) as api: 

298 lite_user_pb = api.GetLiteUser(api_pb2.GetLiteUserReq(user=user2.username)) 

299 

300 assert lite_user_pb.user_id == user2.id 

301 assert lite_user_pb.is_ghost 

302 assert lite_user_pb.username == "ghost" 

303 assert lite_user_pb.name == "Deactivated Account" 

304 assert lite_user_pb.city == "" 

305 assert lite_user_pb.age == 0 

306 assert lite_user_pb.avatar_url == "" 

307 assert lite_user_pb.avatar_thumbnail_url == "" 

308 assert lite_user_pb.lat == 0 

309 assert lite_user_pb.lng == 0 

310 assert lite_user_pb.radius == 0 

311 assert not lite_user_pb.has_strong_verification 

312 

313 

314@pytest.mark.parametrize("flag", ["deleted_at", "banned_at"]) 

315def test_admin_viewing_ghost_users_sees_full_profile(db, flag): 

316 admin, token_admin = generate_user(is_superuser=True) 

317 user, _ = generate_user() 

318 

319 with session_scope() as session: 

320 session.execute(update(User).where(User.id == user.id).values(**{flag: func.now()})) 

321 

322 with admin_session(token_admin) as api: 

323 user_pb = api.GetUser(admin_pb2.GetUserReq(user=user.username)) 

324 

325 assert user_pb.user_id == user.id 

326 assert user_pb.username == user.username 

327 assert user_pb.name == user.name 

328 assert user_pb.city == user.city 

329 assert user_pb.name != "Deactivated Account" 

330 assert user_pb.username != "ghost" 

331 assert user_pb.hosting_status in ( 

332 api_pb2.HOSTING_STATUS_UNKNOWN, 

333 api_pb2.HOSTING_STATUS_CAN_HOST, 

334 api_pb2.HOSTING_STATUS_MAYBE, 

335 api_pb2.HOSTING_STATUS_CANT_HOST, 

336 ) 

337 

338 

339def test_lite_coords(db): 

340 # make them need to update location 

341 user1, token1 = generate_user(geom=create_coordinate(0, 0), geom_radius=0, needs_to_update_location=True) 

342 user2, token2 = generate_user() 

343 

344 refresh_materialized_views_rapid(empty_pb2.Empty()) 

345 

346 with api_session(token2) as api: 

347 res = api.Ping(api_pb2.PingReq()) 

348 assert res.user.city == user2.city 

349 lat, lng = user2.coordinates 

350 assert res.user.lat == lat 

351 assert res.user.lng == lng 

352 assert res.user.radius == user2.geom_radius 

353 

354 with api_session(token2) as api: 

355 res = api.GetLiteUser(api_pb2.GetLiteUserReq(user=user1.username)) 

356 assert res.city == user1.city 

357 assert res.lat == 0.0 

358 assert res.lng == 0.0 

359 assert res.radius == 0.0 

360 

361 # Check coordinate wrapping 

362 user3, token3 = generate_user(geom=create_coordinate(40.0, -180.5)) 

363 user4, token4 = generate_user(geom=create_coordinate(40.0, 20.0)) 

364 user5, token5 = generate_user(geom=create_coordinate(90.5, 20.0)) 

365 

366 refresh_materialized_views_rapid(empty_pb2.Empty()) 

367 

368 with api_session(token3) as api: 

369 res = api.GetLiteUser(api_pb2.GetLiteUserReq(user=user3.username)) 

370 assert res.lat == 40.0 

371 assert res.lng == 179.5 

372 

373 with api_session(token4) as api: 

374 res = api.GetLiteUser(api_pb2.GetLiteUserReq(user=user4.username)) 

375 assert res.lat == 40.0 

376 assert res.lng == 20.0 

377 

378 # PostGIS does not wrap longitude for latitude overflow 

379 with api_session(token5) as api: 

380 res = api.GetLiteUser(api_pb2.GetLiteUserReq(user=user5.username)) 

381 assert res.lat == 89.5 

382 assert res.lng == 20.0 

383 

384 with real_jail_session(token1) as jail: 

385 res = jail.JailInfo(empty_pb2.Empty()) 

386 assert res.jailed 

387 assert res.needs_to_update_location 

388 

389 res = jail.SetLocation( 

390 jail_pb2.SetLocationReq( 

391 city="New York City", 

392 lat=40.7812, 

393 lng=-73.9647, 

394 radius=250, 

395 ) 

396 ) 

397 

398 assert not res.jailed 

399 assert not res.needs_to_update_location 

400 

401 res = jail.JailInfo(empty_pb2.Empty()) 

402 assert not res.jailed 

403 assert not res.needs_to_update_location 

404 

405 refresh_materialized_views_rapid(empty_pb2.Empty()) 

406 

407 with api_session(token2) as api: 

408 res = api.GetLiteUser(api_pb2.GetLiteUserReq(user=user1.username)) 

409 assert res.city == "New York City" 

410 assert res.lat == 40.7812 

411 assert res.lng == -73.9647 

412 assert res.radius == 250 

413 

414 

415def test_lite_get_user(db): 

416 user1, token1 = generate_user() 

417 user2, token2 = generate_user() 

418 

419 refresh_materialized_views_rapid(empty_pb2.Empty()) 

420 

421 with api_session(token1) as api: 

422 res = api.GetLiteUser(api_pb2.GetLiteUserReq(user=user2.username)) 

423 assert res.user_id == user2.id 

424 assert res.username == user2.username 

425 assert res.name == user2.name 

426 

427 with api_session(token1) as api: 

428 res = api.GetLiteUser(api_pb2.GetLiteUserReq(user=str(user2.id))) 

429 assert res.user_id == user2.id 

430 assert res.username == user2.username 

431 assert res.name == user2.name 

432 

433 

434def test_GetLiteUsers(db): 

435 user1, token1 = generate_user() 

436 user2, _ = generate_user() 

437 user3, _ = generate_user() 

438 user4, _ = generate_user() 

439 user5, _ = generate_user() 

440 user6, _ = generate_user() 

441 

442 make_user_block(user4, user1) 

443 

444 refresh_materialized_views_rapid(empty_pb2.Empty()) 

445 

446 with api_session(token1) as api: 

447 res = api.GetLiteUsers( 

448 api_pb2.GetLiteUsersReq( 

449 users=[ 

450 user1.username, 

451 str(user1.id), 

452 "nonexistent", 

453 str(user2.id), 

454 "9994", 

455 user6.username, 

456 str(user5.id), 

457 "notreal", 

458 user4.username, 

459 ] 

460 ) 

461 ) 

462 

463 assert len(res.responses) == 9 

464 assert res.responses[0].query == user1.username 

465 assert res.responses[0].user.user_id == user1.id 

466 

467 assert res.responses[1].query == str(user1.id) 

468 assert res.responses[1].user.user_id == user1.id 

469 

470 assert res.responses[2].query == "nonexistent" 

471 assert res.responses[2].not_found 

472 

473 assert res.responses[3].query == str(user2.id) 

474 assert res.responses[3].user.user_id == user2.id 

475 

476 assert res.responses[4].query == "9994" 

477 assert res.responses[4].not_found 

478 

479 assert res.responses[5].query == user6.username 

480 assert res.responses[5].user.user_id == user6.id 

481 

482 assert res.responses[6].query == str(user5.id) 

483 assert res.responses[6].user.user_id == user5.id 

484 

485 assert res.responses[7].query == "notreal" 

486 assert res.responses[7].not_found 

487 

488 # blocked - should return ghost profile 

489 assert res.responses[8].query == user4.username 

490 assert not res.responses[8].not_found 

491 assert res.responses[8].user.user_id == user4.id 

492 assert res.responses[8].user.is_ghost 

493 assert res.responses[8].user.username == "ghost" 

494 assert res.responses[8].user.name == "Deactivated Account" 

495 

496 with api_session(token1) as api: 

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

498 api.GetLiteUsers(api_pb2.GetLiteUsersReq(users=201 * [user1.username])) 

499 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT 

500 assert e.value.details() == "You can't request that many users at a time." 

501 

502 

503def test_update_profile(db): 

504 user, token = generate_user() 

505 

506 with api_session(token) as api: 

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

508 api.UpdateProfile(api_pb2.UpdateProfileReq(name=wrappers_pb2.StringValue(value=" "))) 

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

510 assert e.value.details() == "Name not supported." 

511 

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

513 api.UpdateProfile( 

514 api_pb2.UpdateProfileReq(lat=wrappers_pb2.DoubleValue(value=0), lng=wrappers_pb2.DoubleValue(value=0)) 

515 ) 

516 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT 

517 assert e.value.details() == "Invalid coordinate." 

518 

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

520 api.UpdateProfile( 

521 api_pb2.UpdateProfileReq(regions_visited=api_pb2.RepeatedStringValue(value=["United States"])) 

522 ) 

523 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT 

524 assert e.value.details() == "Invalid region." 

525 

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

527 api.UpdateProfile( 

528 api_pb2.UpdateProfileReq(regions_lived=api_pb2.RepeatedStringValue(value=["United Kingdom"])) 

529 ) 

530 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT 

531 assert e.value.details() == "Invalid region." 

532 

533 api.UpdateProfile( 

534 api_pb2.UpdateProfileReq( 

535 name=wrappers_pb2.StringValue(value="New name"), 

536 city=wrappers_pb2.StringValue(value="Timbuktu"), 

537 hometown=api_pb2.NullableStringValue(value="Walla Walla"), 

538 lat=wrappers_pb2.DoubleValue(value=0.01), 

539 lng=wrappers_pb2.DoubleValue(value=-2), 

540 radius=wrappers_pb2.DoubleValue(value=321), 

541 pronouns=api_pb2.NullableStringValue(value="Ro, Robo, Robots"), 

542 occupation=api_pb2.NullableStringValue(value="Testing"), 

543 education=api_pb2.NullableStringValue(value="Couchers U"), 

544 about_me=api_pb2.NullableStringValue(value="I rule"), 

545 things_i_like=api_pb2.NullableStringValue(value="Couchers"), 

546 about_place=api_pb2.NullableStringValue(value="My place"), 

547 hosting_status=api_pb2.HOSTING_STATUS_CAN_HOST, 

548 meetup_status=api_pb2.MEETUP_STATUS_WANTS_TO_MEETUP, 

549 language_abilities=api_pb2.RepeatedLanguageAbilityValue( 

550 value=[ 

551 api_pb2.LanguageAbility( 

552 code="eng", 

553 fluency=api_pb2.LanguageAbility.Fluency.FLUENCY_FLUENT, 

554 ) 

555 ], 

556 ), 

557 regions_visited=api_pb2.RepeatedStringValue(value=["CXR", "FIN"]), 

558 regions_lived=api_pb2.RepeatedStringValue(value=["USA", "EST"]), 

559 additional_information=api_pb2.NullableStringValue(value="I <3 Couchers"), 

560 ) 

561 ) 

562 

563 user_details = api.GetUser(api_pb2.GetUserReq(user=user.username)) 

564 assert user_details.name == "New name" 

565 assert user_details.city == "Timbuktu" 

566 assert user_details.hometown == "Walla Walla" 

567 assert user_details.pronouns == "Ro, Robo, Robots" 

568 assert user_details.education == "Couchers U" 

569 assert user_details.things_i_like == "Couchers" 

570 assert user_details.lat == 0.01 

571 assert user_details.lng == -2 

572 assert user_details.radius == 321 

573 assert user_details.occupation == "Testing" 

574 assert user_details.about_me == "I rule" 

575 assert user_details.about_place == "My place" 

576 assert user_details.hosting_status == api_pb2.HOSTING_STATUS_CAN_HOST 

577 assert user_details.meetup_status == api_pb2.MEETUP_STATUS_WANTS_TO_MEETUP 

578 assert user_details.language_abilities[0].code == "eng" 

579 assert user_details.language_abilities[0].fluency == api_pb2.LanguageAbility.Fluency.FLUENCY_FLUENT 

580 assert user_details.additional_information == "I <3 Couchers" 

581 assert user_details.regions_visited == ["CXR", "FIN"] 

582 assert user_details.regions_lived == ["EST", "USA"] 

583 

584 # Test unset values 

585 api.UpdateProfile( 

586 api_pb2.UpdateProfileReq( 

587 hometown=api_pb2.NullableStringValue(is_null=True), 

588 radius=wrappers_pb2.DoubleValue(value=0), 

589 pronouns=api_pb2.NullableStringValue(is_null=True), 

590 occupation=api_pb2.NullableStringValue(is_null=True), 

591 education=api_pb2.NullableStringValue(is_null=True), 

592 about_me=api_pb2.NullableStringValue(is_null=True), 

593 things_i_like=api_pb2.NullableStringValue(is_null=True), 

594 about_place=api_pb2.NullableStringValue(is_null=True), 

595 hosting_status=api_pb2.HOSTING_STATUS_CAN_HOST, 

596 meetup_status=api_pb2.MEETUP_STATUS_WANTS_TO_MEETUP, 

597 language_abilities=api_pb2.RepeatedLanguageAbilityValue(value=[]), 

598 regions_visited=api_pb2.RepeatedStringValue(value=[]), 

599 regions_lived=api_pb2.RepeatedStringValue(value=[]), 

600 additional_information=api_pb2.NullableStringValue(is_null=True), 

601 ) 

602 ) 

603 

604 user_details = api.GetUser(api_pb2.GetUserReq(user=user.username)) 

605 assert not user_details.hometown 

606 assert not user_details.radius 

607 assert not user_details.pronouns 

608 assert not user_details.occupation 

609 assert not user_details.education 

610 assert not user_details.about_me 

611 assert not user_details.things_i_like 

612 assert not user_details.about_place 

613 assert user_details.hosting_status == api_pb2.HOSTING_STATUS_CAN_HOST 

614 assert user_details.meetup_status == api_pb2.MEETUP_STATUS_WANTS_TO_MEETUP 

615 assert not user_details.language_abilities 

616 assert not user_details.regions_visited 

617 assert not user_details.regions_lived 

618 assert not user_details.additional_information 

619 

620 

621def test_update_profile_do_not_email(db): 

622 user, token = generate_user() 

623 

624 with notifications_session(token) as notifications: 

625 notifications.SetNotificationSettings(notifications_pb2.SetNotificationSettingsReq(enable_do_not_email=True)) 

626 

627 with api_session(token) as api: 

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

629 api.UpdateProfile(api_pb2.UpdateProfileReq(hosting_status=api_pb2.HOSTING_STATUS_CAN_HOST)) 

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

631 assert e.value.details() == "You cannot enable hosting while you have emails turned off in your settings." 

632 

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

634 api.UpdateProfile(api_pb2.UpdateProfileReq(meetup_status=api_pb2.MEETUP_STATUS_OPEN_TO_MEETUP)) 

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

636 assert e.value.details() == "You cannot enable meeting up while you have emails turned off in your settings." 

637 

638 

639def test_language_abilities(db): 

640 user, token = generate_user( 

641 language_abilities=[ 

642 ("fin", LanguageFluency.fluent), 

643 ("fra", LanguageFluency.beginner), 

644 ], 

645 ) 

646 

647 with api_session(token) as api: 

648 res = api.GetUser(api_pb2.GetUserReq(user=user.username)) 

649 assert len(res.language_abilities) == 2 

650 

651 # can't add non-existent languages 

652 with pytest.raises(grpc.RpcError) as err: 

653 api.UpdateProfile( 

654 api_pb2.UpdateProfileReq( 

655 language_abilities=api_pb2.RepeatedLanguageAbilityValue( 

656 value=[ 

657 api_pb2.LanguageAbility( 

658 code="QQQ", 

659 fluency=api_pb2.LanguageAbility.Fluency.FLUENCY_FLUENT, 

660 ) 

661 ], 

662 ), 

663 ) 

664 ) 

665 assert err.value.code() == grpc.StatusCode.INVALID_ARGUMENT 

666 assert err.value.details() == "Invalid language." 

667 

668 # can't have multiple languages of the same type 

669 with pytest.raises(Exception) as err2: 

670 api.UpdateProfile( 

671 api_pb2.UpdateProfileReq( 

672 language_abilities=api_pb2.RepeatedLanguageAbilityValue( 

673 value=[ 

674 api_pb2.LanguageAbility( 

675 code="eng", 

676 fluency=api_pb2.LanguageAbility.Fluency.FLUENCY_FLUENT, 

677 ), 

678 api_pb2.LanguageAbility( 

679 code="eng", 

680 fluency=api_pb2.LanguageAbility.Fluency.FLUENCY_FLUENT, 

681 ), 

682 ], 

683 ), 

684 ) 

685 ) 

686 assert "violates unique constraint" in str(err2.value) 

687 

688 # nothing changed 

689 res = api.GetUser(api_pb2.GetUserReq(user=user.username)) 

690 assert len(res.language_abilities) == 2 

691 

692 # now actually add a value 

693 api.UpdateProfile( 

694 api_pb2.UpdateProfileReq( 

695 language_abilities=api_pb2.RepeatedLanguageAbilityValue( 

696 value=[ 

697 api_pb2.LanguageAbility( 

698 code="eng", 

699 fluency=api_pb2.LanguageAbility.Fluency.FLUENCY_FLUENT, 

700 ) 

701 ], 

702 ), 

703 ) 

704 ) 

705 

706 res = api.GetUser(api_pb2.GetUserReq(user=user.username)) 

707 assert len(res.language_abilities) == 1 

708 assert res.language_abilities[0].code == "eng" 

709 assert res.language_abilities[0].fluency == api_pb2.LanguageAbility.Fluency.FLUENCY_FLUENT 

710 

711 # change the value to a new one 

712 api.UpdateProfile( 

713 api_pb2.UpdateProfileReq( 

714 language_abilities=api_pb2.RepeatedLanguageAbilityValue( 

715 value=[ 

716 api_pb2.LanguageAbility( 

717 code="fin", 

718 fluency=api_pb2.LanguageAbility.Fluency.FLUENCY_BEGINNER, 

719 ) 

720 ], 

721 ), 

722 ) 

723 ) 

724 

725 res = api.GetUser(api_pb2.GetUserReq(user=user.username)) 

726 assert len(res.language_abilities) == 1 

727 assert res.language_abilities[0].code == "fin" 

728 assert res.language_abilities[0].fluency == api_pb2.LanguageAbility.Fluency.FLUENCY_BEGINNER 

729 

730 # should be able to set to same value still 

731 api.UpdateProfile( 

732 api_pb2.UpdateProfileReq( 

733 language_abilities=api_pb2.RepeatedLanguageAbilityValue( 

734 value=[ 

735 api_pb2.LanguageAbility( 

736 code="fin", 

737 fluency=api_pb2.LanguageAbility.Fluency.FLUENCY_BEGINNER, 

738 ) 

739 ], 

740 ), 

741 ) 

742 ) 

743 

744 res = api.GetUser(api_pb2.GetUserReq(user=user.username)) 

745 assert len(res.language_abilities) == 1 

746 assert res.language_abilities[0].code == "fin" 

747 assert res.language_abilities[0].fluency == api_pb2.LanguageAbility.Fluency.FLUENCY_BEGINNER 

748 

749 # don't change it 

750 api.UpdateProfile(api_pb2.UpdateProfileReq()) 

751 

752 res = api.GetUser(api_pb2.GetUserReq(user=user.username)) 

753 assert len(res.language_abilities) == 1 

754 assert res.language_abilities[0].code == "fin" 

755 assert res.language_abilities[0].fluency == api_pb2.LanguageAbility.Fluency.FLUENCY_BEGINNER 

756 

757 # remove value 

758 api.UpdateProfile( 

759 api_pb2.UpdateProfileReq( 

760 language_abilities=api_pb2.RepeatedLanguageAbilityValue( 

761 value=[], 

762 ), 

763 ) 

764 ) 

765 

766 res = api.GetUser(api_pb2.GetUserReq(user=user.username)) 

767 assert len(res.language_abilities) == 0 

768 

769 

770def test_pending_friend_request_count(db, moderator): 

771 user1, token1 = generate_user() 

772 user2, token2 = generate_user() 

773 user3, token3 = generate_user() 

774 

775 with api_session(token2) as api: 

776 res = api.Ping(api_pb2.PingReq()) 

777 assert res.pending_friend_request_count == 0 

778 

779 with api_session(token1) as api: 

780 res = api.Ping(api_pb2.PingReq()) 

781 assert res.pending_friend_request_count == 0 

782 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id)) 

783 # Sender can still see their own sent requests (even while SHADOWED) 

784 res = api.Ping(api_pb2.PingReq()) 

785 assert res.pending_friend_request_count == 0 

786 

787 # Get friend request ID from sender's view (author can see SHADOWED) 

788 with api_session(token1) as api: 

789 res = api.ListFriendRequests(empty_pb2.Empty()) 

790 assert len(res.sent) == 1 

791 fr_id = res.sent[0].friend_request_id 

792 

793 # Recipient cannot see SHADOWED friend requests before mod approval 

794 with api_session(token2) as api: 

795 res = api.Ping(api_pb2.PingReq()) 

796 assert res.pending_friend_request_count == 0 

797 

798 # Moderator approves the friend request 

799 moderator.approve_friend_request(fr_id) 

800 

801 # Now recipient can see the approved friend request 

802 with api_session(token2) as api: 

803 res = api.Ping(api_pb2.PingReq()) 

804 assert res.pending_friend_request_count == 1 

805 

806 with api_session(token2) as api: 

807 # check it's there 

808 res = api.ListFriendRequests(empty_pb2.Empty()) 

809 assert len(res.sent) == 0 

810 assert len(res.received) == 1 

811 

812 assert res.received[0].state == api_pb2.FriendRequest.FriendRequestStatus.PENDING 

813 assert res.received[0].user_id == user1.id 

814 

815 fr_id = res.received[0].friend_request_id 

816 

817 # accept it 

818 api.RespondFriendRequest(api_pb2.RespondFriendRequestReq(friend_request_id=fr_id, accept=True)) 

819 

820 res = api.Ping(api_pb2.PingReq()) 

821 assert res.pending_friend_request_count == 0 

822 

823 

824def test_friend_request_flow(db, push_collector: PushCollector, moderator): 

825 user1, token1 = generate_user(complete_profile=True) 

826 user2, token2 = generate_user(complete_profile=True) 

827 user3, token3 = generate_user() 

828 

829 # send a friend request from user1 to user2 

830 with api_session(token1) as api: 

831 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id)) 

832 

833 with session_scope() as session: 

834 friend_request = session.execute( 

835 select(FriendRelationship).where( 

836 FriendRelationship.from_user_id == user1.id, FriendRelationship.to_user_id == user2.id 

837 ) 

838 ).scalar_one() 

839 friend_request_id = friend_request.id 

840 

841 # Notification is deferred while content is SHADOWED 

842 # No push notification sent yet 

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

844 

845 with api_session(token1) as api: 

846 # Sender can see their own sent requests (even while SHADOWED) 

847 res = api.ListFriendRequests(empty_pb2.Empty()) 

848 assert len(res.sent) == 1 

849 assert len(res.received) == 0 

850 

851 assert res.sent[0].state == api_pb2.FriendRequest.FriendRequestStatus.PENDING 

852 assert res.sent[0].user_id == user2.id 

853 assert res.sent[0].friend_request_id == friend_request_id 

854 

855 # Recipient cannot see SHADOWED friend requests 

856 with api_session(token2) as api: 

857 res = api.ListFriendRequests(empty_pb2.Empty()) 

858 assert len(res.sent) == 0 

859 assert len(res.received) == 0 

860 

861 # Moderator approves the friend request - this triggers the notification 

862 with mock_notification_email() as mock: 

863 moderator.approve_friend_request(friend_request_id) 

864 

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

866 assert push.content.title == f"Friend request from {user1.name}" 

867 assert push.content.body == f"{user1.name} wants to be your friend." 

868 

869 mock.assert_called_once() 

870 e = email_fields(mock) 

871 assert e.recipient == user2.email 

872 assert e.subject == f"[TEST] {user1.name} wants to be your friend on Couchers.org!" 

873 assert user2.name in e.plain 

874 assert user2.name in e.html 

875 assert user1.name in e.plain 

876 assert user1.name in e.html 

877 assert "http://localhost:5001/img/thumbnail/" not in e.plain 

878 assert "http://localhost:5001/img/thumbnail/" in e.html 

879 assert "http://localhost:3000/connections/friends/" in e.plain 

880 assert "http://localhost:3000/connections/friends/" in e.html 

881 

882 # Now recipient can see the approved friend request 

883 with api_session(token2) as api: 

884 # check it's there 

885 res = api.ListFriendRequests(empty_pb2.Empty()) 

886 assert len(res.sent) == 0 

887 assert len(res.received) == 1 

888 

889 assert res.received[0].state == api_pb2.FriendRequest.FriendRequestStatus.PENDING 

890 assert res.received[0].user_id == user1.id 

891 

892 fr_id = res.received[0].friend_request_id 

893 

894 # accept it 

895 with mock_notification_email() as mock: 

896 api.RespondFriendRequest(api_pb2.RespondFriendRequestReq(friend_request_id=fr_id, accept=True)) 

897 

898 # check it's gone 

899 res = api.ListFriendRequests(empty_pb2.Empty()) 

900 assert len(res.sent) == 0 

901 assert len(res.received) == 0 

902 

903 # check we're friends now 

904 res = api.ListFriends(empty_pb2.Empty()) 

905 assert len(res.user_ids) == 1 

906 assert res.user_ids[0] == user1.id 

907 

908 # user2 got one push (from the friend request creation) 

909 # user1 should now have one push (from the friend request acceptance) 

910 push = push_collector.pop_for_user(user1.id, last=True) 

911 assert push.content.title == f"{user2.name} accepted your friend request" 

912 assert push.content.body == f"You are now friends with {user2.name}." 

913 

914 mock.assert_called_once() 

915 e = email_fields(mock) 

916 assert e.recipient == user1.email 

917 assert e.subject == f"[TEST] {user2.name} accepted your friend request!" 

918 assert user1.name in e.plain 

919 assert user1.name in e.html 

920 assert user2.name in e.plain 

921 assert user2.name in e.html 

922 assert "http://localhost:5001/img/thumbnail/" not in e.plain 

923 assert "http://localhost:5001/img/thumbnail/" in e.html 

924 assert f"http://localhost:3000/user/{user2.username}" in e.plain 

925 assert f"http://localhost:3000/user/{user2.username}" in e.html 

926 

927 with api_session(token1) as api: 

928 # check it's gone 

929 res = api.ListFriendRequests(empty_pb2.Empty()) 

930 assert len(res.sent) == 0 

931 assert len(res.received) == 0 

932 

933 # check we're friends now 

934 res = api.ListFriends(empty_pb2.Empty()) 

935 assert len(res.user_ids) == 1 

936 assert res.user_ids[0] == user2.id 

937 

938 with api_session(token1) as api: 

939 # we can't unfriend if we aren't friends 

940 with pytest.raises(grpc.RpcError) as err: 

941 api.RemoveFriend(api_pb2.RemoveFriendReq(user_id=user3.id)) 

942 assert err.value.code() == grpc.StatusCode.FAILED_PRECONDITION 

943 assert err.value.details() == "You aren't friends with that user!" 

944 

945 # we can unfriend 

946 res = api.RemoveFriend(api_pb2.RemoveFriendReq(user_id=user2.id)) 

947 

948 res = api.ListFriends(empty_pb2.Empty()) 

949 assert len(res.user_ids) == 0 

950 

951 

952def test_RemoveFriend_regression(db, push_collector: PushCollector, moderator): 

953 user1, token1 = generate_user(complete_profile=True) 

954 user2, token2 = generate_user(complete_profile=True) 

955 user3, token3 = generate_user() 

956 user4, token4 = generate_user() 

957 user5, token5 = generate_user() 

958 user6, token6 = generate_user() 

959 

960 # Send friend requests 

961 with api_session(token4) as api: 

962 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user1.id)) 

963 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id)) 

964 

965 with api_session(token5) as api: 

966 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user1.id)) 

967 

968 with api_session(token1) as api: 

969 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id)) 

970 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user3.id)) 

971 

972 # Approve all friend requests via moderation 

973 with session_scope() as session: 

974 friend_requests = session.execute(select(FriendRelationship)).scalars().all() 

975 for fr in friend_requests: 

976 moderator.approve_friend_request(fr.id) 

977 

978 # Now recipients can respond 

979 with api_session(token1) as api: 

980 api.RespondFriendRequest( 

981 api_pb2.RespondFriendRequestReq( 

982 friend_request_id=api.ListFriendRequests(empty_pb2.Empty()).received[0].friend_request_id, accept=True 

983 ) 

984 ) 

985 

986 with api_session(token2) as api: 

987 for fr in api.ListFriendRequests(empty_pb2.Empty()).received: 

988 api.RespondFriendRequest( 

989 api_pb2.RespondFriendRequestReq(friend_request_id=fr.friend_request_id, accept=True) 

990 ) 

991 

992 with api_session(token1) as api: 

993 res = api.ListFriends(empty_pb2.Empty()) 

994 assert sorted(res.user_ids) == sorted([user2.id, user4.id]) 

995 

996 api.RemoveFriend(api_pb2.RemoveFriendReq(user_id=user2.id)) 

997 

998 res = api.ListFriends(empty_pb2.Empty()) 

999 assert sorted(res.user_ids) == [user4.id] 

1000 

1001 api.RemoveFriend(api_pb2.RemoveFriendReq(user_id=user4.id)) 

1002 

1003 res = api.ListFriends(empty_pb2.Empty()) 

1004 assert not res.user_ids 

1005 

1006 

1007def test_cant_friend_request_twice(db): 

1008 user1, token1 = generate_user() 

1009 user2, token2 = generate_user() 

1010 

1011 # send friend request from user1 to user2 

1012 with api_session(token1) as api: 

1013 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id)) 

1014 

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

1016 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id)) 

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

1018 assert e.value.details() == "You are already friends with or have sent a friend request to that user." 

1019 

1020 

1021def test_cant_friend_request_pending(db): 

1022 user1, token1 = generate_user() 

1023 user2, token2 = generate_user() 

1024 

1025 # send friend request from user1 to user2 

1026 with api_session(token1) as api: 

1027 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id)) 

1028 

1029 with api_session(token2) as api: 

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

1031 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user1.id)) 

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

1033 assert e.value.details() == "You are already friends with or have sent a friend request to that user." 

1034 

1035 

1036def test_cant_friend_request_already_friends(db): 

1037 user1, token1 = generate_user() 

1038 user2, token2 = generate_user() 

1039 make_friends(user1, user2) 

1040 

1041 with api_session(token1) as api: 

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

1043 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id)) 

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

1045 assert e.value.details() == "You are already friends with or have sent a friend request to that user." 

1046 

1047 with api_session(token2) as api: 

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

1049 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user1.id)) 

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

1051 assert e.value.details() == "You are already friends with or have sent a friend request to that user." 

1052 

1053 

1054def test_excessive_friend_requests_are_reported(db): 

1055 """Test that excessive friend requests are first reported in a warning email and finally lead blocking of further requests.""" 

1056 user, token = generate_user() 

1057 rate_limit_definition = RATE_LIMIT_DEFINITIONS[RateLimitAction.friend_request] 

1058 with api_session(token) as api: 

1059 # Test warning email 

1060 with mock_notification_email() as mock_email: 

1061 for _ in range(rate_limit_definition.warning_limit): 

1062 friend_user, _ = generate_user() 

1063 _ = api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=friend_user.id)) 

1064 

1065 assert mock_email.call_count == 0 

1066 friend_user, _ = generate_user() 

1067 _ = api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=friend_user.id)) 

1068 

1069 assert mock_email.call_count == 1 

1070 email = email_fields(mock_email).plain 

1071 assert email.startswith( 

1072 f"User {user.username} has sent {rate_limit_definition.warning_limit} friend requests in the past {RATE_LIMIT_HOURS} hours." 

1073 ) 

1074 

1075 # Test ban after exceeding FRIEND_REQUEST_HARD_LIMIT 

1076 with mock_notification_email() as mock_email: 

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

1078 friend_user, _ = generate_user() 

1079 _ = api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=friend_user.id)) 

1080 

1081 assert mock_email.call_count == 0 

1082 friend_user, _ = generate_user() 

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

1084 _ = api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=friend_user.id)) 

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

1086 assert ( 

1087 exc_info.value.details() 

1088 == "You have sent a lot of friend requests in the past 24 hours. To avoid spam, you can't send any more for now." 

1089 ) 

1090 

1091 assert mock_email.call_count == 1 

1092 email = email_fields(mock_email).plain 

1093 assert email.startswith( 

1094 f"User {user.username} has sent {rate_limit_definition.hard_limit} friend requests in the past {RATE_LIMIT_HOURS} hours." 

1095 ) 

1096 assert "The user has been blocked from sending further friend requests for now." in email 

1097 

1098 

1099def test_ListFriends(db, moderator): 

1100 user1, token1 = generate_user() 

1101 user2, token2 = generate_user() 

1102 user3, token3 = generate_user() 

1103 

1104 # send friend request from user1 to user2 and user3 

1105 with api_session(token1) as api: 

1106 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id)) 

1107 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user3.id)) 

1108 # sender can see their sent requests (they are the author) 

1109 res = api.ListFriendRequests(empty_pb2.Empty()) 

1110 assert len(res.sent) == 2 

1111 user1_to_user2_id = [req for req in res.sent if req.user_id == user2.id][0].friend_request_id 

1112 user1_to_user3_id = [req for req in res.sent if req.user_id == user3.id][0].friend_request_id 

1113 

1114 with api_session(token3) as api: 

1115 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id)) 

1116 res = api.ListFriendRequests(empty_pb2.Empty()) 

1117 user3_to_user2_id = res.sent[0].friend_request_id 

1118 

1119 # Moderator approves the friend requests so recipients can see them 

1120 moderator.approve_friend_request(user1_to_user2_id) 

1121 moderator.approve_friend_request(user3_to_user2_id) 

1122 

1123 with api_session(token2) as api: 

1124 res = api.ListFriendRequests(empty_pb2.Empty()) 

1125 assert len(res.received) == 2 

1126 

1127 # order is an implementation detail 

1128 user1_req = [req for req in res.received if req.user_id == user1.id][0] 

1129 user3_req = [req for req in res.received if req.user_id == user3.id][0] 

1130 

1131 assert user1_req.state == api_pb2.FriendRequest.FriendRequestStatus.PENDING 

1132 assert user1_req.user_id == user1.id 

1133 api.RespondFriendRequest( 

1134 api_pb2.RespondFriendRequestReq(friend_request_id=user1_req.friend_request_id, accept=True) 

1135 ) 

1136 

1137 assert user3_req.state == api_pb2.FriendRequest.FriendRequestStatus.PENDING 

1138 assert user3_req.user_id == user3.id 

1139 api.RespondFriendRequest( 

1140 api_pb2.RespondFriendRequestReq(friend_request_id=user3_req.friend_request_id, accept=True) 

1141 ) 

1142 

1143 # check we now have two friends 

1144 res = api.ListFriends(empty_pb2.Empty()) 

1145 assert len(res.user_ids) == 2 

1146 assert user1.id in res.user_ids 

1147 assert user3.id in res.user_ids 

1148 

1149 # Moderator approves user1's friend request to user3 so user3 can see it 

1150 moderator.approve_friend_request(user1_to_user3_id) 

1151 

1152 with api_session(token3) as api: 

1153 res = api.ListFriends(empty_pb2.Empty()) 

1154 assert len(res.user_ids) == 1 

1155 assert user2.id in res.user_ids 

1156 

1157 res = api.ListFriendRequests(empty_pb2.Empty()) 

1158 assert len(res.received) == 1 

1159 assert res.received[0].state == api_pb2.FriendRequest.FriendRequestStatus.PENDING 

1160 assert res.received[0].user_id == user1.id 

1161 fr_id = res.received[0].friend_request_id 

1162 api.RespondFriendRequest(api_pb2.RespondFriendRequestReq(friend_request_id=fr_id, accept=True)) 

1163 

1164 res = api.ListFriends(empty_pb2.Empty()) 

1165 assert len(res.user_ids) == 2 

1166 assert user1.id in res.user_ids 

1167 assert user2.id in res.user_ids 

1168 

1169 with api_session(token1) as api: 

1170 res = api.ListFriends(empty_pb2.Empty()) 

1171 assert len(res.user_ids) == 2 

1172 assert user2.id in res.user_ids 

1173 assert user3.id in res.user_ids 

1174 

1175 

1176def test_ListMutualFriends(db): 

1177 user1, token1 = generate_user() 

1178 user2, token2 = generate_user() 

1179 user3, token3 = generate_user() 

1180 user4, token4 = generate_user() 

1181 user5, token5 = generate_user() 

1182 

1183 # arrange friends like this: 1<->2, 1<->3, 1<->4, 1<->5, 3<->2, 3<->4, 

1184 # so 1 and 2 should have mutual friend 3 only 

1185 make_friends(user1, user2) 

1186 make_friends(user1, user3) 

1187 make_friends(user1, user4) 

1188 make_friends(user1, user5) 

1189 make_friends(user3, user2) 

1190 make_friends(user3, user4) 

1191 

1192 with api_session(token1) as api: 

1193 mutual_friends = api.ListMutualFriends(api_pb2.ListMutualFriendsReq(user_id=user2.id)).mutual_friends 

1194 assert len(mutual_friends) == 1 

1195 assert mutual_friends[0].user_id == user3.id 

1196 

1197 # and other way around same 

1198 with api_session(token2) as api: 

1199 mutual_friends = api.ListMutualFriends(api_pb2.ListMutualFriendsReq(user_id=user1.id)).mutual_friends 

1200 assert len(mutual_friends) == 1 

1201 assert mutual_friends[0].user_id == user3.id 

1202 

1203 # Check pending request doesn't have effect 

1204 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user5.id)) 

1205 

1206 mutual_friends = api.ListMutualFriends(api_pb2.ListMutualFriendsReq(user_id=user1.id)).mutual_friends 

1207 assert len(mutual_friends) == 1 

1208 assert mutual_friends[0].user_id == user3.id 

1209 

1210 # both ways 

1211 with api_session(token1) as api: 

1212 mutual_friends = api.ListMutualFriends(api_pb2.ListMutualFriendsReq(user_id=user2.id)).mutual_friends 

1213 assert len(mutual_friends) == 1 

1214 assert mutual_friends[0].user_id == user3.id 

1215 

1216 

1217def test_mutual_friends_self(db): 

1218 user1, token1 = generate_user() 

1219 user2, token2 = generate_user() 

1220 user3, token3 = generate_user() 

1221 user4, token4 = generate_user() 

1222 

1223 make_friends(user1, user2) 

1224 make_friends(user2, user3) 

1225 make_friends(user1, user4) 

1226 

1227 with api_session(token1) as api: 

1228 res = api.ListMutualFriends(api_pb2.ListMutualFriendsReq(user_id=user1.id)) 

1229 assert len(res.mutual_friends) == 0 

1230 

1231 with api_session(token2) as api: 

1232 res = api.ListMutualFriends(api_pb2.ListMutualFriendsReq(user_id=user2.id)) 

1233 assert len(res.mutual_friends) == 0 

1234 

1235 with api_session(token3) as api: 

1236 res = api.ListMutualFriends(api_pb2.ListMutualFriendsReq(user_id=user3.id)) 

1237 assert len(res.mutual_friends) == 0 

1238 

1239 with api_session(token4) as api: 

1240 res = api.ListMutualFriends(api_pb2.ListMutualFriendsReq(user_id=user4.id)) 

1241 assert len(res.mutual_friends) == 0 

1242 

1243 

1244def test_CancelFriendRequest(db): 

1245 user1, token1 = generate_user() 

1246 user2, token2 = generate_user() 

1247 

1248 with api_session(token1) as api: 

1249 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id)) 

1250 

1251 res = api.ListFriendRequests(empty_pb2.Empty()) 

1252 assert res.sent[0].user_id == user2.id 

1253 fr_id = res.sent[0].friend_request_id 

1254 

1255 api.CancelFriendRequest(api_pb2.CancelFriendRequestReq(friend_request_id=fr_id)) 

1256 

1257 # check it's gone 

1258 res = api.ListFriendRequests(empty_pb2.Empty()) 

1259 assert len(res.sent) == 0 

1260 assert len(res.received) == 0 

1261 

1262 # check not friends 

1263 res = api.ListFriends(empty_pb2.Empty()) 

1264 assert len(res.user_ids) == 0 

1265 

1266 with api_session(token2) as api: 

1267 # check it's gone 

1268 res = api.ListFriendRequests(empty_pb2.Empty()) 

1269 assert len(res.sent) == 0 

1270 assert len(res.received) == 0 

1271 

1272 # check we're not friends 

1273 res = api.ListFriends(empty_pb2.Empty()) 

1274 assert len(res.user_ids) == 0 

1275 

1276 with api_session(token1) as api: 

1277 # check we can send another friend req 

1278 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id)) 

1279 

1280 res = api.ListFriendRequests(empty_pb2.Empty()) 

1281 assert res.sent[0].state == api_pb2.FriendRequest.FriendRequestStatus.PENDING 

1282 assert res.sent[0].user_id == user2.id 

1283 

1284 

1285def test_accept_friend_request(db, moderator): 

1286 user1, token1 = generate_user() 

1287 user2, token2 = generate_user() 

1288 

1289 with session_scope() as session: 

1290 moderation_state = ModerationState( 

1291 object_type=ModerationObjectType.friend_request, 

1292 object_id=0, 

1293 visibility=ModerationVisibility.visible, 

1294 ) 

1295 session.add(moderation_state) 

1296 session.flush() 

1297 friend_request = FriendRelationship( 

1298 from_user_id=user1.id, 

1299 to_user_id=user2.id, 

1300 status=FriendStatus.pending, 

1301 moderation_state_id=moderation_state.id, 

1302 ) 

1303 session.add(friend_request) 

1304 session.flush() 

1305 moderation_state.object_id = friend_request.id 

1306 session.commit() 

1307 friend_request_id = friend_request.id 

1308 

1309 with api_session(token2) as api: 

1310 # check request pending 

1311 res = api.ListFriendRequests(empty_pb2.Empty()) 

1312 assert len(res.received) == 1 

1313 assert res.received[0].user_id == user1.id 

1314 

1315 api.RespondFriendRequest(api_pb2.RespondFriendRequestReq(friend_request_id=friend_request_id, accept=True)) 

1316 

1317 # check request is gone 

1318 res = api.ListFriendRequests(empty_pb2.Empty()) 

1319 assert len(res.sent) == 0 

1320 assert len(res.received) == 0 

1321 

1322 # check now friends 

1323 res = api.ListFriends(empty_pb2.Empty()) 

1324 assert len(res.user_ids) == 1 

1325 assert res.user_ids[0] == user1.id 

1326 

1327 with api_session(token1) as api: 

1328 # check request gone 

1329 res = api.ListFriendRequests(empty_pb2.Empty()) 

1330 assert len(res.sent) == 0 

1331 assert len(res.received) == 0 

1332 

1333 # check now friends 

1334 res = api.ListFriends(empty_pb2.Empty()) 

1335 assert len(res.user_ids) == 1 

1336 assert res.user_ids[0] == user2.id 

1337 

1338 

1339def test_reject_friend_request(db, moderator): 

1340 user1, token1 = generate_user() 

1341 user2, token2 = generate_user() 

1342 

1343 with api_session(token1) as api: 

1344 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id)) 

1345 

1346 res = api.ListFriendRequests(empty_pb2.Empty()) 

1347 assert res.sent[0].state == api_pb2.FriendRequest.FriendRequestStatus.PENDING 

1348 assert res.sent[0].user_id == user2.id 

1349 fr_id = res.sent[0].friend_request_id 

1350 

1351 # Moderator approves the friend request so recipient can see it 

1352 moderator.approve_friend_request(fr_id) 

1353 

1354 with api_session(token2) as api: 

1355 res = api.ListFriendRequests(empty_pb2.Empty()) 

1356 assert res.received[0].state == api_pb2.FriendRequest.FriendRequestStatus.PENDING 

1357 assert res.received[0].user_id == user1.id 

1358 

1359 fr_id = res.received[0].friend_request_id 

1360 

1361 # reject it 

1362 api.RespondFriendRequest(api_pb2.RespondFriendRequestReq(friend_request_id=fr_id, accept=False)) 

1363 

1364 # check it's gone 

1365 res = api.ListFriendRequests(empty_pb2.Empty()) 

1366 assert len(res.sent) == 0 

1367 assert len(res.received) == 0 

1368 

1369 # check not friends 

1370 res = api.ListFriends(empty_pb2.Empty()) 

1371 assert len(res.user_ids) == 0 

1372 

1373 with api_session(token1) as api: 

1374 # check it's gone 

1375 res = api.ListFriendRequests(empty_pb2.Empty()) 

1376 assert len(res.sent) == 0 

1377 assert len(res.received) == 0 

1378 

1379 # check we're not friends 

1380 res = api.ListFriends(empty_pb2.Empty()) 

1381 assert len(res.user_ids) == 0 

1382 

1383 # check we can send another friend req 

1384 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id)) 

1385 

1386 res = api.ListFriendRequests(empty_pb2.Empty()) 

1387 assert res.sent[0].state == api_pb2.FriendRequest.FriendRequestStatus.PENDING 

1388 assert res.sent[0].user_id == user2.id 

1389 

1390 

1391def test_hosting_preferences(db): 

1392 user1, token1 = generate_user() 

1393 user2, token2 = generate_user() 

1394 

1395 with api_session(token1) as api: 

1396 res = api.GetUser(api_pb2.GetUserReq(user=user1.username)) 

1397 assert not res.HasField("max_guests") 

1398 assert not res.HasField("last_minute") 

1399 assert not res.HasField("has_pets") 

1400 assert not res.HasField("accepts_pets") 

1401 assert not res.HasField("pet_details") 

1402 assert not res.HasField("has_kids") 

1403 assert not res.HasField("accepts_kids") 

1404 assert not res.HasField("kid_details") 

1405 assert not res.HasField("has_housemates") 

1406 assert not res.HasField("housemate_details") 

1407 assert not res.HasField("wheelchair_accessible") 

1408 assert res.smoking_allowed == api_pb2.SMOKING_LOCATION_UNKNOWN 

1409 assert not res.HasField("smokes_at_home") 

1410 assert not res.HasField("drinking_allowed") 

1411 assert not res.HasField("drinks_at_home") 

1412 assert not res.HasField("other_host_info") 

1413 assert res.sleeping_arrangement == api_pb2.SLEEPING_ARRANGEMENT_UNKNOWN 

1414 assert not res.HasField("sleeping_details") 

1415 assert not res.HasField("area") 

1416 assert not res.HasField("house_rules") 

1417 assert not res.HasField("parking") 

1418 assert res.parking_details == api_pb2.PARKING_DETAILS_UNKNOWN 

1419 assert not res.HasField("camping_ok") 

1420 

1421 api.UpdateProfile( 

1422 api_pb2.UpdateProfileReq( 

1423 max_guests=api_pb2.NullableUInt32Value(value=3), 

1424 last_minute=api_pb2.NullableBoolValue(value=True), 

1425 has_pets=api_pb2.NullableBoolValue(value=False), 

1426 accepts_pets=api_pb2.NullableBoolValue(value=True), 

1427 pet_details=api_pb2.NullableStringValue(value="I love dogs"), 

1428 has_kids=api_pb2.NullableBoolValue(value=False), 

1429 accepts_kids=api_pb2.NullableBoolValue(value=True), 

1430 kid_details=api_pb2.NullableStringValue(value="I hate kids"), 

1431 has_housemates=api_pb2.NullableBoolValue(value=False), 

1432 housemate_details=api_pb2.NullableStringValue(value="I have 7 housemates"), 

1433 wheelchair_accessible=api_pb2.NullableBoolValue(value=True), 

1434 smoking_allowed=api_pb2.SMOKING_LOCATION_WINDOW, 

1435 area=api_pb2.NullableStringValue(value="area!"), 

1436 smokes_at_home=api_pb2.NullableBoolValue(value=False), 

1437 drinking_allowed=api_pb2.NullableBoolValue(value=True), 

1438 drinks_at_home=api_pb2.NullableBoolValue(value=False), 

1439 other_host_info=api_pb2.NullableStringValue(value="I'm pretty swell"), 

1440 sleeping_arrangement=api_pb2.SLEEPING_ARRANGEMENT_COMMON, 

1441 sleeping_details=api_pb2.NullableStringValue(value="Couch in living room"), 

1442 house_rules=api_pb2.NullableStringValue(value="RULES!"), 

1443 parking=api_pb2.NullableBoolValue(value=True), 

1444 parking_details=api_pb2.PARKING_DETAILS_PAID_ONSITE, 

1445 camping_ok=api_pb2.NullableBoolValue(value=False), 

1446 ) 

1447 ) 

1448 

1449 # Use a second user to view the hosting preferences just to check 

1450 # that it is public information. 

1451 with api_session(token2) as api: 

1452 res = api.GetUser(api_pb2.GetUserReq(user=user1.username)) 

1453 assert res.max_guests.value == 3 

1454 assert res.last_minute.value 

1455 assert not res.has_pets.value 

1456 assert res.accepts_pets.value 

1457 assert res.pet_details.value == "I love dogs" 

1458 assert not res.has_kids.value 

1459 assert res.accepts_kids.value 

1460 assert res.kid_details.value == "I hate kids" 

1461 assert not res.has_housemates.value 

1462 assert res.housemate_details.value == "I have 7 housemates" 

1463 assert res.wheelchair_accessible.value 

1464 assert res.smoking_allowed == api_pb2.SMOKING_LOCATION_WINDOW 

1465 assert not res.smokes_at_home.value 

1466 assert res.drinking_allowed.value 

1467 assert not res.drinks_at_home.value 

1468 assert res.other_host_info.value == "I'm pretty swell" 

1469 assert res.sleeping_arrangement == api_pb2.SLEEPING_ARRANGEMENT_COMMON 

1470 assert res.sleeping_details.value == "Couch in living room" 

1471 assert res.area.value == "area!" 

1472 assert res.house_rules.value == "RULES!" 

1473 assert res.parking.value 

1474 assert res.parking_details == api_pb2.PARKING_DETAILS_PAID_ONSITE 

1475 assert not res.camping_ok.value 

1476 

1477 # test unsetting 

1478 with api_session(token1) as api: 

1479 api.UpdateProfile( 

1480 api_pb2.UpdateProfileReq( 

1481 max_guests=api_pb2.NullableUInt32Value(is_null=True), 

1482 last_minute=api_pb2.NullableBoolValue(is_null=True), 

1483 has_pets=api_pb2.NullableBoolValue(is_null=True), 

1484 accepts_pets=api_pb2.NullableBoolValue(is_null=True), 

1485 pet_details=api_pb2.NullableStringValue(is_null=True), 

1486 has_kids=api_pb2.NullableBoolValue(is_null=True), 

1487 accepts_kids=api_pb2.NullableBoolValue(is_null=True), 

1488 kid_details=api_pb2.NullableStringValue(is_null=True), 

1489 has_housemates=api_pb2.NullableBoolValue(is_null=True), 

1490 housemate_details=api_pb2.NullableStringValue(is_null=True), 

1491 wheelchair_accessible=api_pb2.NullableBoolValue(is_null=True), 

1492 smoking_allowed=api_pb2.SMOKING_LOCATION_UNKNOWN, 

1493 area=api_pb2.NullableStringValue(is_null=True), 

1494 smokes_at_home=api_pb2.NullableBoolValue(is_null=True), 

1495 drinking_allowed=api_pb2.NullableBoolValue(is_null=True), 

1496 drinks_at_home=api_pb2.NullableBoolValue(is_null=True), 

1497 other_host_info=api_pb2.NullableStringValue(is_null=True), 

1498 sleeping_arrangement=api_pb2.SLEEPING_ARRANGEMENT_UNKNOWN, 

1499 sleeping_details=api_pb2.NullableStringValue(is_null=True), 

1500 house_rules=api_pb2.NullableStringValue(is_null=True), 

1501 parking=api_pb2.NullableBoolValue(is_null=True), 

1502 parking_details=api_pb2.PARKING_DETAILS_UNKNOWN, 

1503 camping_ok=api_pb2.NullableBoolValue(is_null=True), 

1504 ) 

1505 ) 

1506 

1507 res = api.GetUser(api_pb2.GetUserReq(user=user1.username)) 

1508 assert not res.HasField("max_guests") 

1509 assert not res.HasField("last_minute") 

1510 assert not res.HasField("has_pets") 

1511 assert not res.HasField("accepts_pets") 

1512 assert not res.HasField("pet_details") 

1513 assert not res.HasField("has_kids") 

1514 assert not res.HasField("accepts_kids") 

1515 assert not res.HasField("kid_details") 

1516 assert not res.HasField("has_housemates") 

1517 assert not res.HasField("housemate_details") 

1518 assert not res.HasField("wheelchair_accessible") 

1519 assert res.smoking_allowed == api_pb2.SMOKING_LOCATION_UNKNOWN 

1520 assert not res.HasField("smokes_at_home") 

1521 assert not res.HasField("drinking_allowed") 

1522 assert not res.HasField("drinks_at_home") 

1523 assert not res.HasField("other_host_info") 

1524 assert res.sleeping_arrangement == api_pb2.SLEEPING_ARRANGEMENT_UNKNOWN 

1525 assert not res.HasField("sleeping_details") 

1526 assert not res.HasField("area") 

1527 assert not res.HasField("house_rules") 

1528 assert not res.HasField("parking") 

1529 assert res.parking_details == api_pb2.PARKING_DETAILS_UNKNOWN 

1530 assert not res.HasField("camping_ok") 

1531 

1532 

1533def test_badges(db): 

1534 user1, _ = generate_user(last_donated=None) 

1535 user2, _ = generate_user(last_donated=None) 

1536 user3, _ = generate_user(last_donated=None) 

1537 user4, token = generate_user(last_donated=None) 

1538 

1539 update_badges(empty_pb2.Empty()) 

1540 

1541 founder_badge = get_badge_dict()["founder"] 

1542 board_member_badge = get_badge_dict()["board_member"] 

1543 

1544 with api_session(token) as api: 

1545 assert api.GetUser(api_pb2.GetUserReq(user=user1.username)).badges == ["founder", "board_member"] 

1546 assert api.GetUser(api_pb2.GetUserReq(user=user2.username)).badges == ["founder", "board_member"] 

1547 assert api.GetUser(api_pb2.GetUserReq(user=user3.username)).badges == [] 

1548 

1549 assert api.ListBadgeUsers(api_pb2.ListBadgeUsersReq(badge_id=founder_badge.id)).user_ids == [1, 2] 

1550 res = api.ListBadgeUsers(api_pb2.ListBadgeUsersReq(badge_id=board_member_badge.id, page_size=1)) 

1551 assert res.user_ids == [1] 

1552 res2 = api.ListBadgeUsers( 

1553 api_pb2.ListBadgeUsersReq(badge_id=board_member_badge.id, page_token=res.next_page_token) 

1554 ) 

1555 assert res2.user_ids == [2] 

1556 

1557 

1558@pytest.mark.parametrize("flag", ["deleted_at", "banned_at"]) 

1559def test_ListBadgeUsers_excludes_ghost_users(db, flag): 

1560 """Test that ListBadgeUsers does not return deleted/banned users.""" 

1561 from couchers.helpers.badges import user_add_badge 

1562 

1563 user1, token1 = generate_user() 

1564 user2, _ = generate_user() 

1565 user3, _ = generate_user() 

1566 

1567 volunteer_badge = get_badge_dict()["volunteer"] 

1568 

1569 # Give all three users the volunteer badge 

1570 with session_scope() as session: 

1571 user_add_badge(session, user1.id, "volunteer", do_notify=False) 

1572 user_add_badge(session, user2.id, "volunteer", do_notify=False) 

1573 user_add_badge(session, user3.id, "volunteer", do_notify=False) 

1574 

1575 # Verify all three users appear in the badge list 

1576 with api_session(token1) as api: 

1577 res = api.ListBadgeUsers(api_pb2.ListBadgeUsersReq(badge_id=volunteer_badge.id)) 

1578 assert set(res.user_ids) == {user1.id, user2.id, user3.id} 

1579 

1580 # Make user2 invisible (deleted or banned) 

1581 with session_scope() as session: 

1582 db_user2 = session.execute(select(User).where(User.id == user2.id)).scalar_one() 

1583 setattr(db_user2, flag, now()) 

1584 

1585 # Now user2 should not appear in the badge list 

1586 with api_session(token1) as api: 

1587 res = api.ListBadgeUsers(api_pb2.ListBadgeUsersReq(badge_id=volunteer_badge.id)) 

1588 assert set(res.user_ids) == {user1.id, user3.id} 

1589 

1590 

1591@pytest.mark.parametrize("flag", ["deleted_at", "banned_at"]) 

1592def test_GetLiteUser_ghost_user_by_username(db, flag): 

1593 """Test that GetLiteUser returns a ghost profile for deleted/banned users when querying by username.""" 

1594 user1, token1 = generate_user() 

1595 user2, _ = generate_user() 

1596 

1597 # Make user2 invisible 

1598 with session_scope() as session: 

1599 db_user2 = session.merge(user2) 

1600 setattr(db_user2, flag, now()) 

1601 session.commit() 

1602 

1603 # Refresh the materialized view 

1604 refresh_materialized_views_rapid(empty_pb2.Empty()) 

1605 

1606 with api_session(token1) as api: 

1607 # Query by username 

1608 lite_user = api.GetLiteUser(api_pb2.GetLiteUserReq(user=user2.username)) 

1609 

1610 assert lite_user.user_id == user2.id 

1611 assert lite_user.username == "ghost" 

1612 assert lite_user.name == "Deactivated Account" 

1613 assert lite_user.lat == 0 

1614 assert lite_user.lng == 0 

1615 assert lite_user.radius == 0 

1616 assert lite_user.city == "" 

1617 assert lite_user.age == 0 

1618 assert lite_user.avatar_url == "" 

1619 assert lite_user.avatar_thumbnail_url == "" 

1620 assert not lite_user.has_strong_verification 

1621 

1622 

1623@pytest.mark.parametrize("flag", ["deleted_at", "banned_at"]) 

1624def test_GetLiteUser_ghost_user_by_id(db, flag): 

1625 """Test that GetLiteUser returns a ghost profile for deleted/banned users when querying by ID.""" 

1626 user1, token1 = generate_user() 

1627 user2, _ = generate_user() 

1628 

1629 # Make user2 invisible 

1630 with session_scope() as session: 

1631 db_user2 = session.merge(user2) 

1632 setattr(db_user2, flag, now()) 

1633 session.commit() 

1634 

1635 # Refresh the materialized view 

1636 refresh_materialized_views_rapid(empty_pb2.Empty()) 

1637 

1638 with api_session(token1) as api: 

1639 # Query by ID 

1640 lite_user = api.GetLiteUser(api_pb2.GetLiteUserReq(user=str(user2.id))) 

1641 

1642 assert lite_user.user_id == user2.id 

1643 assert lite_user.username == "ghost" 

1644 assert lite_user.name == "Deactivated Account" 

1645 assert lite_user.lat == 0 

1646 assert lite_user.lng == 0 

1647 assert lite_user.radius == 0 

1648 assert lite_user.city == "" 

1649 assert lite_user.age == 0 

1650 assert lite_user.avatar_url == "" 

1651 assert lite_user.avatar_thumbnail_url == "" 

1652 assert not lite_user.has_strong_verification 

1653 

1654 

1655def test_GetLiteUser_blocked_user(db): 

1656 """Test that GetLiteUser returns a ghost profile for blocked users.""" 

1657 user1, token1 = generate_user() 

1658 user2, _ = generate_user() 

1659 

1660 # User1 blocks user2 

1661 make_user_block(user1, user2) 

1662 

1663 # Refresh the materialized view 

1664 refresh_materialized_views_rapid(empty_pb2.Empty()) 

1665 

1666 with api_session(token1) as api: 

1667 # Query by username 

1668 lite_user = api.GetLiteUser(api_pb2.GetLiteUserReq(user=user2.username)) 

1669 

1670 assert lite_user.user_id == user2.id 

1671 assert lite_user.is_ghost 

1672 assert lite_user.username == "ghost" 

1673 assert lite_user.name == "Deactivated Account" 

1674 

1675 # Query by ID 

1676 lite_user = api.GetLiteUser(api_pb2.GetLiteUserReq(user=str(user2.id))) 

1677 

1678 assert lite_user.user_id == user2.id 

1679 assert lite_user.is_ghost 

1680 assert lite_user.username == "ghost" 

1681 assert lite_user.name == "Deactivated Account" 

1682 

1683 

1684def test_GetLiteUser_blocking_user(db): 

1685 """Test that GetLiteUser returns a ghost profile when the target user has blocked the requester.""" 

1686 user1, token1 = generate_user() 

1687 user2, _ = generate_user() 

1688 

1689 # User2 blocks user1 

1690 make_user_block(user2, user1) 

1691 

1692 # Refresh the materialized view 

1693 refresh_materialized_views_rapid(empty_pb2.Empty()) 

1694 

1695 with api_session(token1) as api: 

1696 # Query by username 

1697 lite_user = api.GetLiteUser(api_pb2.GetLiteUserReq(user=user2.username)) 

1698 

1699 assert lite_user.user_id == user2.id 

1700 assert lite_user.is_ghost 

1701 assert lite_user.username == "ghost" 

1702 assert lite_user.name == "Deactivated Account" 

1703 

1704 # Query by ID 

1705 lite_user = api.GetLiteUser(api_pb2.GetLiteUserReq(user=str(user2.id))) 

1706 

1707 assert lite_user.user_id == user2.id 

1708 assert lite_user.is_ghost 

1709 assert lite_user.username == "ghost" 

1710 assert lite_user.name == "Deactivated Account" 

1711 

1712 

1713@pytest.mark.parametrize("flag", ["deleted_at", "banned_at"]) 

1714def test_GetLiteUsers_ghost_users(db, flag): 

1715 """Test that GetLiteUsers returns ghost profiles for deleted/banned users.""" 

1716 user1, token1 = generate_user() 

1717 user2, _ = generate_user() 

1718 user3, _ = generate_user() 

1719 user4, _ = generate_user() 

1720 

1721 # Make user2 and user4 invisible 

1722 with session_scope() as session: 

1723 db_user2 = session.merge(user2) 

1724 setattr(db_user2, flag, now()) 

1725 db_user4 = session.merge(user4) 

1726 setattr(db_user4, flag, now()) 

1727 session.commit() 

1728 

1729 # Refresh the materialized view 

1730 refresh_materialized_views_rapid(empty_pb2.Empty()) 

1731 

1732 with api_session(token1) as api: 

1733 res = api.GetLiteUsers( 

1734 api_pb2.GetLiteUsersReq( 

1735 users=[ 

1736 user1.username, # visible 

1737 user2.username, # ghost 

1738 str(user3.id), # visible 

1739 str(user4.id), # ghost 

1740 ] 

1741 ) 

1742 ) 

1743 

1744 assert len(res.responses) == 4 

1745 

1746 # user1 - visible, normal profile 

1747 assert res.responses[0].query == user1.username 

1748 assert not res.responses[0].not_found 

1749 assert res.responses[0].user.user_id == user1.id 

1750 assert res.responses[0].user.username == user1.username 

1751 assert res.responses[0].user.name == user1.name 

1752 

1753 # user2 - ghost by username 

1754 assert res.responses[1].query == user2.username 

1755 assert not res.responses[1].not_found 

1756 assert res.responses[1].user.user_id == user2.id 

1757 assert res.responses[1].user.is_ghost 

1758 assert res.responses[1].user.username == "ghost" 

1759 assert res.responses[1].user.name == "Deactivated Account" 

1760 

1761 # user3 - visible, normal profile 

1762 assert res.responses[2].query == str(user3.id) 

1763 assert not res.responses[2].not_found 

1764 assert res.responses[2].user.user_id == user3.id 

1765 assert res.responses[2].user.username == user3.username 

1766 assert res.responses[2].user.name == user3.name 

1767 

1768 # user4 - ghost by ID 

1769 assert res.responses[3].query == str(user4.id) 

1770 assert not res.responses[3].not_found 

1771 assert res.responses[3].user.user_id == user4.id 

1772 assert res.responses[3].user.is_ghost 

1773 assert res.responses[3].user.username == "ghost" 

1774 assert res.responses[3].user.name == "Deactivated Account" 

1775 

1776 

1777def test_GetLiteUsers_blocked_users(db): 

1778 """Test that GetLiteUsers returns ghost profiles for blocked users.""" 

1779 user1, token1 = generate_user() 

1780 user2, _ = generate_user() 

1781 user3, _ = generate_user() 

1782 user4, _ = generate_user() 

1783 user5, _ = generate_user() 

1784 

1785 # User1 blocks user2 

1786 make_user_block(user1, user2) 

1787 # User4 blocks user1 

1788 make_user_block(user4, user1) 

1789 

1790 # Refresh the materialized view 

1791 refresh_materialized_views_rapid(empty_pb2.Empty()) 

1792 

1793 with api_session(token1) as api: 

1794 res = api.GetLiteUsers( 

1795 api_pb2.GetLiteUsersReq( 

1796 users=[ 

1797 user2.username, # user1 blocked user2 

1798 str(user3.id), # visible 

1799 user4.username, # user4 blocked user1 

1800 str(user5.id), # visible 

1801 ] 

1802 ) 

1803 ) 

1804 

1805 assert len(res.responses) == 4 

1806 

1807 # user2 - blocked by user1, should be ghost 

1808 assert res.responses[0].query == user2.username 

1809 assert not res.responses[0].not_found 

1810 assert res.responses[0].user.user_id == user2.id 

1811 assert res.responses[0].user.is_ghost 

1812 assert res.responses[0].user.username == "ghost" 

1813 assert res.responses[0].user.name == "Deactivated Account" 

1814 

1815 # user3 - visible 

1816 assert res.responses[1].query == str(user3.id) 

1817 assert not res.responses[1].not_found 

1818 assert res.responses[1].user.user_id == user3.id 

1819 assert res.responses[1].user.username == user3.username 

1820 

1821 # user4 - user4 blocked user1, should be ghost 

1822 assert res.responses[2].query == user4.username 

1823 assert not res.responses[2].not_found 

1824 assert res.responses[2].user.user_id == user4.id 

1825 assert res.responses[2].user.is_ghost 

1826 assert res.responses[2].user.username == "ghost" 

1827 assert res.responses[2].user.name == "Deactivated Account" 

1828 

1829 # user5 - visible 

1830 assert res.responses[3].query == str(user5.id) 

1831 assert not res.responses[3].not_found 

1832 assert res.responses[3].user.user_id == user5.id 

1833 assert res.responses[3].user.username == user5.username 

1834 

1835 

1836@pytest.mark.parametrize("flag", ["deleted_at", "banned_at"]) 

1837def test_GetUser_ghost_user_by_id(db, flag): 

1838 """Test that GetUser returns a ghost profile for deleted/banned users when querying by ID.""" 

1839 user1, token1 = generate_user() 

1840 user2, _ = generate_user() 

1841 

1842 # Make user2 invisible 

1843 with session_scope() as session: 

1844 db_user2 = session.merge(user2) 

1845 setattr(db_user2, flag, now()) 

1846 session.commit() 

1847 

1848 with api_session(token1) as api: 

1849 # Query by ID 

1850 user_pb = api.GetUser(api_pb2.GetUserReq(user=str(user2.id))) 

1851 

1852 assert user_pb.user_id == user2.id 

1853 assert user_pb.username == "ghost" 

1854 assert user_pb.name == "Deactivated Account" 

1855 assert user_pb.city == "" 

1856 assert user_pb.hosting_status == 0 

1857 assert user_pb.meetup_status == 0 

1858 

1859 

1860def test_GetUser_blocked_user(db): 

1861 """Test that GetUser returns a ghost profile for blocked users.""" 

1862 user1, token1 = generate_user() 

1863 user2, _ = generate_user() 

1864 

1865 # User1 blocks user2 

1866 make_user_block(user1, user2) 

1867 

1868 with api_session(token1) as api: 

1869 # Query by username 

1870 user_pb = api.GetUser(api_pb2.GetUserReq(user=user2.username)) 

1871 

1872 assert user_pb.user_id == user2.id 

1873 assert user_pb.username == "ghost" 

1874 assert user_pb.name == "Deactivated Account" 

1875 

1876 # Query by ID 

1877 user_pb = api.GetUser(api_pb2.GetUserReq(user=str(user2.id))) 

1878 

1879 assert user_pb.user_id == user2.id 

1880 assert user_pb.username == "ghost" 

1881 assert user_pb.name == "Deactivated Account" 

1882 

1883 

1884def test_GetUser_blocking_user(db): 

1885 """Test that GetUser returns a ghost profile when the target user has blocked the requester.""" 

1886 user1, token1 = generate_user() 

1887 user2, _ = generate_user() 

1888 

1889 # User2 blocks user1 

1890 make_user_block(user2, user1) 

1891 

1892 with api_session(token1) as api: 

1893 # Query by username 

1894 user_pb = api.GetUser(api_pb2.GetUserReq(user=user2.username)) 

1895 

1896 assert user_pb.user_id == user2.id 

1897 assert user_pb.username == "ghost" 

1898 assert user_pb.name == "Deactivated Account" 

1899 

1900 # Query by ID 

1901 user_pb = api.GetUser(api_pb2.GetUserReq(user=str(user2.id))) 

1902 

1903 assert user_pb.user_id == user2.id 

1904 assert user_pb.username == "ghost" 

1905 assert user_pb.name == "Deactivated Account"