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

1087 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2025-12-20 11:53 +0000

1from datetime import timedelta 

2 

3import grpc 

4import pytest 

5from google.protobuf import empty_pb2, wrappers_pb2 

6from sqlalchemy import 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 FriendRelationship, FriendStatus, LanguageFluency, RateLimitAction, User 

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

13from couchers.rate_limits.definitions import RATE_LIMIT_DEFINITIONS, RATE_LIMIT_HOURS 

14from couchers.resources import get_badge_dict 

15from couchers.sql import couchers_select as select 

16from couchers.utils import create_coordinate, to_aware_datetime 

17from tests.test_fixtures import ( # noqa 

18 api_session, 

19 blocking_session, 

20 db, 

21 email_fields, 

22 generate_user, 

23 make_friends, 

24 make_user_block, 

25 make_user_invisible, 

26 mock_notification_email, 

27 notifications_session, 

28 push_collector, 

29 real_api_session, 

30 real_jail_session, 

31 testconfig, 

32) 

33from tests.test_fixtures import real_admin_session as admin_session 

34 

35 

36@pytest.fixture(autouse=True) 

37def _(testconfig): 

38 pass 

39 

40 

41def test_ping(db): 

42 user, token = generate_user( 

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

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

45 language_abilities=[ 

46 ("fin", LanguageFluency.fluent), 

47 ("fra", LanguageFluency.beginner), 

48 ], 

49 ) 

50 

51 with real_api_session(token) as api: 

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

53 

54 assert res.user.user_id == user.id 

55 assert res.user.username == user.username 

56 assert res.user.name == user.name 

57 assert res.user.city == user.city 

58 assert res.user.hometown == user.hometown 

59 assert res.user.verification == 0.0 

60 assert res.user.community_standing == user.community_standing 

61 assert res.user.num_references == 0 

62 assert res.user.gender == user.gender 

63 assert res.user.pronouns == user.pronouns 

64 assert res.user.age == user.age 

65 

66 assert (res.user.lat, res.user.lng) == (user.coordinates or (0, 0)) 

67 

68 # the joined time is fuzzed 

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

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

71 # same for last_active 

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

73 

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

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

76 

77 assert res.user.occupation == user.occupation 

78 assert res.user.education == user.education 

79 assert res.user.about_me == user.about_me 

80 assert res.user.things_i_like == user.things_i_like 

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

82 assert res.user.about_place == user.about_place 

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

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

85 assert res.user.additional_information == user.additional_information 

86 

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

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

89 

90 

91def test_coords(db): 

92 # make them need to update location 

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

94 user2, token2 = generate_user() 

95 

96 with api_session(token2) as api: 

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

98 assert res.user.city == user2.city 

99 lat, lng = user2.coordinates or (0, 0) 

100 assert res.user.lat == lat 

101 assert res.user.lng == lng 

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

103 

104 with api_session(token2) as api: 

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

106 assert res.city == user1.city 

107 assert res.lat == 1.0 

108 assert res.lng == 0.0 

109 assert res.radius == 2000.0 

110 

111 # Check coordinate wrapping 

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

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

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

115 

116 with api_session(token3) as api: 

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

118 assert res.lat == 40.0 

119 assert res.lng == 179.5 

120 

121 with api_session(token4) as api: 

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

123 assert res.lat == 40.0 

124 assert res.lng == 20.0 

125 

126 # PostGIS does not wrap longitude for latitude overflow 

127 with api_session(token5) as api: 

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

129 assert res.lat == 89.5 

130 assert res.lng == 20.0 

131 

132 with real_jail_session(token1) as jail: 

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

134 assert res.jailed 

135 assert res.needs_to_update_location 

136 

137 res = jail.SetLocation( 

138 jail_pb2.SetLocationReq( 

139 city="New York City", 

140 lat=40.7812, 

141 lng=-73.9647, 

142 radius=250, 

143 ) 

144 ) 

145 

146 assert not res.jailed 

147 assert not res.needs_to_update_location 

148 

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

150 assert not res.jailed 

151 assert not res.needs_to_update_location 

152 

153 with api_session(token2) as api: 

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

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

156 assert res.lat == 40.7812 

157 assert res.lng == -73.9647 

158 assert res.radius == 250 

159 

160 

161def test_get_user(db): 

162 user1, token1 = generate_user() 

163 user2, token2 = generate_user() 

164 

165 with api_session(token1) as api: 

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

167 assert res.user_id == user2.id 

168 assert res.username == user2.username 

169 assert res.name == user2.name 

170 

171 with api_session(token1) as api: 

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

173 assert res.user_id == user2.id 

174 assert res.username == user2.username 

175 assert res.name == user2.name 

176 

177 

178@pytest.mark.parametrize("flag", ["is_deleted", "is_banned"]) 

179def test_user_model_to_pb_ghost_user(db, flag): 

180 user1, token1 = generate_user() 

181 user2, _ = generate_user() 

182 

183 with session_scope() as session: 

184 session.execute(update(User).where(User.id == user2.id).values(**{flag: True})) 

185 

186 refresh_materialized_views_rapid(None) 

187 

188 with api_session(token1) as api: 

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

190 

191 assert user_pb.user_id == user2.id 

192 assert user_pb.is_ghost 

193 assert user_pb.username == "ghost" 

194 assert user_pb.name == "Ghost" 

195 assert ( 

196 user_pb.about_me 

197 == "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." 

198 ) 

199 

200 assert user_pb.lat == 0 

201 assert user_pb.lng == 0 

202 assert user_pb.radius == 0 

203 assert user_pb.verification == 0.0 

204 assert user_pb.community_standing == 0.0 

205 assert user_pb.num_references == 0 

206 assert user_pb.age == 0 

207 assert user_pb.hosting_status == 0 

208 assert user_pb.meetup_status == 0 

209 assert user_pb.city == "" 

210 assert user_pb.hometown == "" 

211 assert user_pb.timezone == "" 

212 assert user_pb.gender == "" 

213 assert user_pb.pronouns == "" 

214 assert user_pb.occupation == "" 

215 assert user_pb.education == "" 

216 assert user_pb.things_i_like == "" 

217 assert user_pb.about_place == "" 

218 assert user_pb.additional_information == "" 

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

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

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

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

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

224 assert user_pb.avatar_url == "" 

225 assert user_pb.avatar_thumbnail_url == "" 

226 assert not user_pb.has_strong_verification 

227 

228 with api_session(token1) as api: 

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

230 

231 assert lite_user_pb.user_id == user2.id 

232 assert lite_user_pb.is_ghost 

233 assert lite_user_pb.username == "ghost" 

234 assert lite_user_pb.name == "Ghost" 

235 assert lite_user_pb.city == "" 

236 assert lite_user_pb.age == 0 

237 assert lite_user_pb.avatar_url == "" 

238 assert lite_user_pb.avatar_thumbnail_url == "" 

239 assert lite_user_pb.lat == 0 

240 assert lite_user_pb.lng == 0 

241 assert lite_user_pb.radius == 0 

242 assert not lite_user_pb.has_strong_verification 

243 

244 

245def test_user_model_to_pb_ghost_user_blocked(db): 

246 user1, token1 = generate_user() 

247 user2, _ = generate_user() 

248 

249 with blocking_session(token1) as user_blocks: 

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

251 

252 refresh_materialized_views_rapid(None) 

253 

254 with api_session(token1) as api: 

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

256 

257 assert user_pb.user_id == user2.id 

258 assert user_pb.is_ghost 

259 assert user_pb.username == "ghost" 

260 assert user_pb.name == "Ghost" 

261 assert ( 

262 user_pb.about_me 

263 == "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." 

264 ) 

265 

266 assert user_pb.lat == 0 

267 assert user_pb.lng == 0 

268 assert user_pb.radius == 0 

269 assert user_pb.verification == 0.0 

270 assert user_pb.community_standing == 0.0 

271 assert user_pb.num_references == 0 

272 assert user_pb.age == 0 

273 assert user_pb.hosting_status == 0 

274 assert user_pb.meetup_status == 0 

275 assert user_pb.city == "" 

276 assert user_pb.hometown == "" 

277 assert user_pb.timezone == "" 

278 assert user_pb.gender == "" 

279 assert user_pb.pronouns == "" 

280 assert user_pb.occupation == "" 

281 assert user_pb.education == "" 

282 assert user_pb.things_i_like == "" 

283 assert user_pb.about_place == "" 

284 assert user_pb.additional_information == "" 

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

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

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

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

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

290 assert user_pb.avatar_url == "" 

291 assert user_pb.avatar_thumbnail_url == "" 

292 assert not user_pb.has_strong_verification 

293 

294 with api_session(token1) as api: 

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

296 

297 assert lite_user_pb.user_id == user2.id 

298 assert lite_user_pb.is_ghost 

299 assert lite_user_pb.username == "ghost" 

300 assert lite_user_pb.name == "Ghost" 

301 assert lite_user_pb.city == "" 

302 assert lite_user_pb.age == 0 

303 assert lite_user_pb.avatar_url == "" 

304 assert lite_user_pb.avatar_thumbnail_url == "" 

305 assert lite_user_pb.lat == 0 

306 assert lite_user_pb.lng == 0 

307 assert lite_user_pb.radius == 0 

308 assert not lite_user_pb.has_strong_verification 

309 

310 

311@pytest.mark.parametrize("flag", ["is_deleted", "is_banned"]) 

312def test_admin_viewing_ghost_users_sees_full_profile(db, flag): 

313 admin, token_admin = generate_user(is_superuser=True) 

314 user, _ = generate_user() 

315 

316 with session_scope() as session: 

317 session.execute(update(User).where(User.id == user.id).values(**{flag: True})) 

318 

319 with admin_session(token_admin) as api: 

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

321 

322 assert user_pb.user_id == user.id 

323 assert user_pb.username == user.username 

324 assert user_pb.name == user.name 

325 assert user_pb.city == user.city 

326 assert user_pb.name != "Ghost" 

327 assert user_pb.username != "ghost" 

328 assert user_pb.hosting_status in ( 

329 api_pb2.HOSTING_STATUS_UNKNOWN, 

330 api_pb2.HOSTING_STATUS_CAN_HOST, 

331 api_pb2.HOSTING_STATUS_MAYBE, 

332 api_pb2.HOSTING_STATUS_CANT_HOST, 

333 ) 

334 

335 

336def test_lite_coords(db): 

337 # make them need to update location 

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

339 user2, token2 = generate_user() 

340 

341 refresh_materialized_views_rapid(None) 

342 

343 with api_session(token2) as api: 

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

345 assert res.user.city == user2.city 

346 lat, lng = user2.coordinates or (0, 0) 

347 assert res.user.lat == lat 

348 assert res.user.lng == lng 

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

350 

351 with api_session(token2) as api: 

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

353 assert res.city == user1.city 

354 assert res.lat == 0.0 

355 assert res.lng == 0.0 

356 assert res.radius == 0.0 

357 

358 # Check coordinate wrapping 

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

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

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

362 

363 refresh_materialized_views_rapid(None) 

364 

365 with api_session(token3) as api: 

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

367 assert res.lat == 40.0 

368 assert res.lng == 179.5 

369 

370 with api_session(token4) as api: 

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

372 assert res.lat == 40.0 

373 assert res.lng == 20.0 

374 

375 # PostGIS does not wrap longitude for latitude overflow 

376 with api_session(token5) as api: 

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

378 assert res.lat == 89.5 

379 assert res.lng == 20.0 

380 

381 with real_jail_session(token1) as jail: 

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

383 assert res.jailed 

384 assert res.needs_to_update_location 

385 

386 res = jail.SetLocation( 

387 jail_pb2.SetLocationReq( 

388 city="New York City", 

389 lat=40.7812, 

390 lng=-73.9647, 

391 radius=250, 

392 ) 

393 ) 

394 

395 assert not res.jailed 

396 assert not res.needs_to_update_location 

397 

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

399 assert not res.jailed 

400 assert not res.needs_to_update_location 

401 

402 refresh_materialized_views_rapid(None) 

403 

404 with api_session(token2) as api: 

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

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

407 assert res.lat == 40.7812 

408 assert res.lng == -73.9647 

409 assert res.radius == 250 

410 

411 

412def test_lite_get_user(db): 

413 user1, token1 = generate_user() 

414 user2, token2 = generate_user() 

415 

416 refresh_materialized_views_rapid(None) 

417 

418 with api_session(token1) as api: 

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

420 assert res.user_id == user2.id 

421 assert res.username == user2.username 

422 assert res.name == user2.name 

423 

424 with api_session(token1) as api: 

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

426 assert res.user_id == user2.id 

427 assert res.username == user2.username 

428 assert res.name == user2.name 

429 

430 

431def test_GetLiteUsers(db): 

432 user1, token1 = generate_user() 

433 user2, _ = generate_user() 

434 user3, _ = generate_user() 

435 user4, _ = generate_user() 

436 user5, _ = generate_user() 

437 user6, _ = generate_user() 

438 

439 make_user_block(user4, user1) 

440 

441 refresh_materialized_views_rapid(None) 

442 

443 with api_session(token1) as api: 

444 res = api.GetLiteUsers( 

445 api_pb2.GetLiteUsersReq( 

446 users=[ 

447 user1.username, 

448 str(user1.id), 

449 "nonexistent", 

450 str(user2.id), 

451 "9994", 

452 user6.username, 

453 str(user5.id), 

454 "notreal", 

455 user4.username, 

456 ] 

457 ) 

458 ) 

459 

460 assert len(res.responses) == 9 

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

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

463 

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

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

466 

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

468 assert res.responses[2].not_found 

469 

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

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

472 

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

474 assert res.responses[4].not_found 

475 

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

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

478 

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

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

481 

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

483 assert res.responses[7].not_found 

484 

485 # blocked - should return ghost profile 

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

487 assert not res.responses[8].not_found 

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

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

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

491 assert res.responses[8].user.name == "Ghost" 

492 

493 with api_session(token1) as api: 

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

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

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

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

498 

499 

500def test_update_profile(db): 

501 user, token = generate_user() 

502 

503 with api_session(token) as api: 

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

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

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

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

508 

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

510 api.UpdateProfile( 

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

512 ) 

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

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

515 

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

517 api.UpdateProfile( 

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

519 ) 

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

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

522 

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

524 api.UpdateProfile( 

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

526 ) 

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

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

529 

530 api.UpdateProfile( 

531 api_pb2.UpdateProfileReq( 

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

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

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

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

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

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

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

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

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

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

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

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

544 hosting_status=api_pb2.HOSTING_STATUS_CAN_HOST, 

545 meetup_status=api_pb2.MEETUP_STATUS_WANTS_TO_MEETUP, 

546 language_abilities=api_pb2.RepeatedLanguageAbilityValue( 

547 value=[ 

548 api_pb2.LanguageAbility( 

549 code="eng", 

550 fluency=api_pb2.LanguageAbility.Fluency.FLUENCY_FLUENT, 

551 ) 

552 ], 

553 ), 

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

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

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

557 ) 

558 ) 

559 

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

561 assert user_details.name == "New name" 

562 assert user_details.city == "Timbuktu" 

563 assert user_details.hometown == "Walla Walla" 

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

565 assert user_details.education == "Couchers U" 

566 assert user_details.things_i_like == "Couchers" 

567 assert user_details.lat == 0.01 

568 assert user_details.lng == -2 

569 assert user_details.radius == 321 

570 assert user_details.occupation == "Testing" 

571 assert user_details.about_me == "I rule" 

572 assert user_details.about_place == "My place" 

573 assert user_details.hosting_status == api_pb2.HOSTING_STATUS_CAN_HOST 

574 assert user_details.meetup_status == api_pb2.MEETUP_STATUS_WANTS_TO_MEETUP 

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

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

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

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

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

580 

581 # Test unset values 

582 api.UpdateProfile( 

583 api_pb2.UpdateProfileReq( 

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

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

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

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

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

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

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

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

592 hosting_status=api_pb2.HOSTING_STATUS_CAN_HOST, 

593 meetup_status=api_pb2.MEETUP_STATUS_WANTS_TO_MEETUP, 

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

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

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

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

598 ) 

599 ) 

600 

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

602 assert not user_details.hometown 

603 assert not user_details.radius 

604 assert not user_details.pronouns 

605 assert not user_details.occupation 

606 assert not user_details.education 

607 assert not user_details.about_me 

608 assert not user_details.things_i_like 

609 assert not user_details.about_place 

610 assert user_details.hosting_status == api_pb2.HOSTING_STATUS_CAN_HOST 

611 assert user_details.meetup_status == api_pb2.MEETUP_STATUS_WANTS_TO_MEETUP 

612 assert not user_details.language_abilities 

613 assert not user_details.regions_visited 

614 assert not user_details.regions_lived 

615 assert not user_details.additional_information 

616 

617 

618def test_update_profile_do_not_email(db): 

619 user, token = generate_user() 

620 

621 with notifications_session(token) as notifications: 

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

623 

624 with api_session(token) as api: 

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

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

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

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

629 

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

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

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

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

634 

635 

636def test_language_abilities(db): 

637 user, token = generate_user( 

638 language_abilities=[ 

639 ("fin", LanguageFluency.fluent), 

640 ("fra", LanguageFluency.beginner), 

641 ], 

642 ) 

643 

644 with api_session(token) as api: 

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

646 assert len(res.language_abilities) == 2 

647 

648 # can't add non-existent languages 

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

650 res = api.UpdateProfile( 

651 api_pb2.UpdateProfileReq( 

652 language_abilities=api_pb2.RepeatedLanguageAbilityValue( 

653 value=[ 

654 api_pb2.LanguageAbility( 

655 code="QQQ", 

656 fluency=api_pb2.LanguageAbility.Fluency.FLUENCY_FLUENT, 

657 ) 

658 ], 

659 ), 

660 ) 

661 ) 

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

663 assert e.value.details() == "Invalid language." 

664 

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

666 with pytest.raises(Exception) as e: 

667 res = api.UpdateProfile( 

668 api_pb2.UpdateProfileReq( 

669 language_abilities=api_pb2.RepeatedLanguageAbilityValue( 

670 value=[ 

671 api_pb2.LanguageAbility( 

672 code="eng", 

673 fluency=api_pb2.LanguageAbility.Fluency.FLUENCY_FLUENT, 

674 ), 

675 api_pb2.LanguageAbility( 

676 code="eng", 

677 fluency=api_pb2.LanguageAbility.Fluency.FLUENCY_FLUENT, 

678 ), 

679 ], 

680 ), 

681 ) 

682 ) 

683 assert "violates unique constraint" in str(e.value) 

684 

685 # nothing changed 

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

687 assert len(res.language_abilities) == 2 

688 

689 # now actually add a value 

690 api.UpdateProfile( 

691 api_pb2.UpdateProfileReq( 

692 language_abilities=api_pb2.RepeatedLanguageAbilityValue( 

693 value=[ 

694 api_pb2.LanguageAbility( 

695 code="eng", 

696 fluency=api_pb2.LanguageAbility.Fluency.FLUENCY_FLUENT, 

697 ) 

698 ], 

699 ), 

700 ) 

701 ) 

702 

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

704 assert len(res.language_abilities) == 1 

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

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

707 

708 # change the value to a new one 

709 api.UpdateProfile( 

710 api_pb2.UpdateProfileReq( 

711 language_abilities=api_pb2.RepeatedLanguageAbilityValue( 

712 value=[ 

713 api_pb2.LanguageAbility( 

714 code="fin", 

715 fluency=api_pb2.LanguageAbility.Fluency.FLUENCY_BEGINNER, 

716 ) 

717 ], 

718 ), 

719 ) 

720 ) 

721 

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

723 assert len(res.language_abilities) == 1 

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

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

726 

727 # should be able to set to same value still 

728 api.UpdateProfile( 

729 api_pb2.UpdateProfileReq( 

730 language_abilities=api_pb2.RepeatedLanguageAbilityValue( 

731 value=[ 

732 api_pb2.LanguageAbility( 

733 code="fin", 

734 fluency=api_pb2.LanguageAbility.Fluency.FLUENCY_BEGINNER, 

735 ) 

736 ], 

737 ), 

738 ) 

739 ) 

740 

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

742 assert len(res.language_abilities) == 1 

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

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

745 

746 # don't change it 

747 api.UpdateProfile(api_pb2.UpdateProfileReq()) 

748 

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

750 assert len(res.language_abilities) == 1 

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

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

753 

754 # remove value 

755 api.UpdateProfile( 

756 api_pb2.UpdateProfileReq( 

757 language_abilities=api_pb2.RepeatedLanguageAbilityValue( 

758 value=[], 

759 ), 

760 ) 

761 ) 

762 

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

764 assert len(res.language_abilities) == 0 

765 

766 

767def test_pending_friend_request_count(db): 

768 user1, token1 = generate_user() 

769 user2, token2 = generate_user() 

770 user3, token3 = generate_user() 

771 

772 with api_session(token2) as api: 

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

774 assert res.pending_friend_request_count == 0 

775 

776 with api_session(token1) as api: 

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

778 assert res.pending_friend_request_count == 0 

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

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

781 assert res.pending_friend_request_count == 0 

782 

783 with api_session(token2) as api: 

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

785 assert res.pending_friend_request_count == 1 

786 

787 with api_session(token2) as api: 

788 # check it's there 

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

790 assert len(res.sent) == 0 

791 assert len(res.received) == 1 

792 

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

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

795 

796 fr_id = res.received[0].friend_request_id 

797 

798 # accept it 

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

800 

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

802 assert res.pending_friend_request_count == 0 

803 

804 

805def test_friend_request_flow(db, push_collector): 

806 user1, token1 = generate_user(complete_profile=True) 

807 user2, token2 = generate_user(complete_profile=True) 

808 user3, token3 = generate_user() 

809 

810 # send a friend request from user1 to user2 

811 with mock_notification_email() as mock: 

812 with api_session(token1) as api: 

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

814 

815 push_collector.assert_user_has_single_matching( 

816 user2.id, 

817 title=f"{user1.name} wants to be your friend", 

818 body=f"You've received a friend request from {user1.name}", 

819 ) 

820 

821 mock.assert_called_once() 

822 e = email_fields(mock) 

823 assert e.recipient == user2.email 

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

825 assert user2.name in e.plain 

826 assert user2.name in e.html 

827 assert user1.name in e.plain 

828 assert user1.name in e.html 

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

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

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

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

833 

834 with session_scope() as session: 

835 friend_request_id = ( 

836 session.execute( 

837 select(FriendRelationship).where( 

838 FriendRelationship.from_user_id == user1.id and FriendRelationship.to_user_id == user2.id 

839 ) 

840 ).scalar_one_or_none() 

841 ).id 

842 

843 with api_session(token1) as api: 

844 # check it went through 

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

846 assert len(res.sent) == 1 

847 assert len(res.received) == 0 

848 

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

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

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

852 

853 with api_session(token2) as api: 

854 # check it's there 

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

856 assert len(res.sent) == 0 

857 assert len(res.received) == 1 

858 

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

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

861 

862 fr_id = res.received[0].friend_request_id 

863 

864 # accept it 

865 with mock_notification_email() as mock: 

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

867 

868 # check it's gone 

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

870 assert len(res.sent) == 0 

871 assert len(res.received) == 0 

872 

873 # check we're friends now 

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

875 assert len(res.user_ids) == 1 

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

877 

878 push_collector.assert_user_has_count(user2.id, 1) 

879 push_collector.assert_user_push_matches_fields( 

880 user1.id, 

881 title=f"{user2.name} accepted your friend request!", 

882 body=f"{user2.name} has accepted your friend request", 

883 ) 

884 

885 mock.assert_called_once() 

886 e = email_fields(mock) 

887 assert e.recipient == user1.email 

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

889 assert user1.name in e.plain 

890 assert user1.name in e.html 

891 assert user2.name in e.plain 

892 assert user2.name in e.html 

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

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

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

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

897 

898 with api_session(token1) as api: 

899 # check it's gone 

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

901 assert len(res.sent) == 0 

902 assert len(res.received) == 0 

903 

904 # check we're friends now 

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

906 assert len(res.user_ids) == 1 

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

908 

909 with api_session(token1) as api: 

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

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

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

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

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

915 

916 # we can unfriend 

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

918 

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

920 assert len(res.user_ids) == 0 

921 

922 

923def test_RemoveFriend_regression(db, push_collector): 

924 user1, token1 = generate_user(complete_profile=True) 

925 user2, token2 = generate_user(complete_profile=True) 

926 user3, token3 = generate_user() 

927 user4, token4 = generate_user() 

928 user5, token5 = generate_user() 

929 user6, token6 = generate_user() 

930 

931 with api_session(token4) as api: 

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

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

934 

935 with api_session(token5) as api: 

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

937 

938 with api_session(token1) as api: 

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

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

941 

942 api.RespondFriendRequest( 

943 api_pb2.RespondFriendRequestReq( 

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

945 ) 

946 ) 

947 

948 with api_session(token2) as api: 

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

950 api.RespondFriendRequest( 

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

952 ) 

953 

954 with api_session(token1) as api: 

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

956 assert sorted(res.user_ids) == [2, 4] 

957 

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

959 

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

961 assert sorted(res.user_ids) == [4] 

962 

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

964 

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

966 assert not res.user_ids 

967 

968 

969def test_cant_friend_request_twice(db): 

970 user1, token1 = generate_user() 

971 user2, token2 = generate_user() 

972 

973 # send friend request from user1 to user2 

974 with api_session(token1) as api: 

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

976 

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

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

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

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

981 

982 

983def test_cant_friend_request_pending(db): 

984 user1, token1 = generate_user() 

985 user2, token2 = generate_user() 

986 

987 # send friend request from user1 to user2 

988 with api_session(token1) as api: 

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

990 

991 with api_session(token2) as api: 

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

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

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

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

996 

997 

998def test_cant_friend_request_already_friends(db): 

999 user1, token1 = generate_user() 

1000 user2, token2 = generate_user() 

1001 make_friends(user1, user2) 

1002 

1003 with api_session(token1) as api: 

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

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

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

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

1008 

1009 with api_session(token2) as api: 

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

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

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

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

1014 

1015 

1016def test_excessive_friend_requests_are_reported(db): 

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

1018 user, token = generate_user() 

1019 rate_limit_definition = RATE_LIMIT_DEFINITIONS[RateLimitAction.friend_request] 

1020 with api_session(token) as api: 

1021 # Test warning email 

1022 with mock_notification_email() as mock_email: 

1023 for _ in range(rate_limit_definition.warning_limit): 

1024 friend_user, _ = generate_user() 

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

1026 

1027 assert mock_email.call_count == 0 

1028 friend_user, _ = generate_user() 

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

1030 

1031 assert mock_email.call_count == 1 

1032 email = mock_email.mock_calls[0].kwargs["plain"] 

1033 assert email.startswith( 

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

1035 ) 

1036 

1037 # Test ban after exceeding FRIEND_REQUEST_HARD_LIMIT 

1038 with mock_notification_email() as mock_email: 

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

1040 friend_user, _ = generate_user() 

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

1042 

1043 assert mock_email.call_count == 0 

1044 friend_user, _ = generate_user() 

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

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

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

1048 assert ( 

1049 exc_info.value.details() 

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

1051 ) 

1052 

1053 assert mock_email.call_count == 1 

1054 email = mock_email.mock_calls[0].kwargs["plain"] 

1055 assert email.startswith( 

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

1057 ) 

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

1059 

1060 

1061def test_ListFriends(db): 

1062 user1, token1 = generate_user() 

1063 user2, token2 = generate_user() 

1064 user3, token3 = generate_user() 

1065 

1066 # send friend request from user1 to user2 and user3 

1067 with api_session(token1) as api: 

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

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

1070 

1071 with api_session(token3) as api: 

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

1073 

1074 with api_session(token2) as api: 

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

1076 assert len(res.received) == 2 

1077 

1078 # order is an implementation detail 

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

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

1081 

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

1083 assert user1_req.user_id == user1.id 

1084 api.RespondFriendRequest( 

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

1086 ) 

1087 

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

1089 assert user3_req.user_id == user3.id 

1090 api.RespondFriendRequest( 

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

1092 ) 

1093 

1094 # check we now have two friends 

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

1096 assert len(res.user_ids) == 2 

1097 assert user1.id in res.user_ids 

1098 assert user3.id in res.user_ids 

1099 

1100 with api_session(token3) as api: 

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

1102 assert len(res.user_ids) == 1 

1103 assert user2.id in res.user_ids 

1104 

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

1106 assert len(res.received) == 1 

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

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

1109 fr_id = res.received[0].friend_request_id 

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

1111 

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

1113 assert len(res.user_ids) == 2 

1114 assert user1.id in res.user_ids 

1115 assert user2.id in res.user_ids 

1116 

1117 with api_session(token1) as api: 

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

1119 assert len(res.user_ids) == 2 

1120 assert user2.id in res.user_ids 

1121 assert user3.id in res.user_ids 

1122 

1123 

1124def test_ListMutualFriends(db): 

1125 user1, token1 = generate_user() 

1126 user2, token2 = generate_user() 

1127 user3, token3 = generate_user() 

1128 user4, token4 = generate_user() 

1129 user5, token5 = generate_user() 

1130 

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

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

1133 make_friends(user1, user2) 

1134 make_friends(user1, user3) 

1135 make_friends(user1, user4) 

1136 make_friends(user1, user5) 

1137 make_friends(user3, user2) 

1138 make_friends(user3, user4) 

1139 

1140 with api_session(token1) as api: 

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

1142 assert len(mutual_friends) == 1 

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

1144 

1145 # and other way around same 

1146 with api_session(token2) as api: 

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

1148 assert len(mutual_friends) == 1 

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

1150 

1151 # Check pending request doesn't have effect 

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

1153 

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

1155 assert len(mutual_friends) == 1 

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

1157 

1158 # both ways 

1159 with api_session(token1) as api: 

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

1161 assert len(mutual_friends) == 1 

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

1163 

1164 

1165def test_mutual_friends_self(db): 

1166 user1, token1 = generate_user() 

1167 user2, token2 = generate_user() 

1168 user3, token3 = generate_user() 

1169 user4, token4 = generate_user() 

1170 

1171 make_friends(user1, user2) 

1172 make_friends(user2, user3) 

1173 make_friends(user1, user4) 

1174 

1175 with api_session(token1) as api: 

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

1177 assert len(res.mutual_friends) == 0 

1178 

1179 with api_session(token2) as api: 

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

1181 assert len(res.mutual_friends) == 0 

1182 

1183 with api_session(token3) as api: 

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

1185 assert len(res.mutual_friends) == 0 

1186 

1187 with api_session(token4) as api: 

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

1189 assert len(res.mutual_friends) == 0 

1190 

1191 

1192def test_CancelFriendRequest(db): 

1193 user1, token1 = generate_user() 

1194 user2, token2 = generate_user() 

1195 

1196 with api_session(token1) as api: 

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

1198 

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

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

1201 fr_id = res.sent[0].friend_request_id 

1202 

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

1204 

1205 # check it's gone 

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

1207 assert len(res.sent) == 0 

1208 assert len(res.received) == 0 

1209 

1210 # check not friends 

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

1212 assert len(res.user_ids) == 0 

1213 

1214 with api_session(token2) as api: 

1215 # check it's gone 

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

1217 assert len(res.sent) == 0 

1218 assert len(res.received) == 0 

1219 

1220 # check we're not friends 

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

1222 assert len(res.user_ids) == 0 

1223 

1224 with api_session(token1) as api: 

1225 # check we can send another friend req 

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

1227 

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

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

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

1231 

1232 

1233def test_accept_friend_request(db): 

1234 user1, token1 = generate_user() 

1235 user2, token2 = generate_user() 

1236 

1237 with session_scope() as session: 

1238 friend_request = FriendRelationship(from_user_id=user1.id, to_user_id=user2.id, status=FriendStatus.pending) 

1239 session.add(friend_request) 

1240 session.commit() 

1241 friend_request_id = friend_request.id 

1242 

1243 with api_session(token2) as api: 

1244 # check request pending 

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

1246 assert len(res.received) == 1 

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

1248 

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

1250 

1251 # check request is gone 

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

1253 assert len(res.sent) == 0 

1254 assert len(res.received) == 0 

1255 

1256 # check now friends 

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

1258 assert len(res.user_ids) == 1 

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

1260 

1261 with api_session(token1) as api: 

1262 # check request gone 

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

1264 assert len(res.sent) == 0 

1265 assert len(res.received) == 0 

1266 

1267 # check now friends 

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

1269 assert len(res.user_ids) == 1 

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

1271 

1272 

1273def test_reject_friend_request(db): 

1274 user1, token1 = generate_user() 

1275 user2, token2 = generate_user() 

1276 

1277 with api_session(token1) as api: 

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 with api_session(token2) as api: 

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

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

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

1288 

1289 fr_id = res.received[0].friend_request_id 

1290 

1291 # reject it 

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

1293 

1294 # check it's gone 

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

1296 assert len(res.sent) == 0 

1297 assert len(res.received) == 0 

1298 

1299 # check not friends 

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

1301 assert len(res.user_ids) == 0 

1302 

1303 with api_session(token1) as api: 

1304 # check it's gone 

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

1306 assert len(res.sent) == 0 

1307 assert len(res.received) == 0 

1308 

1309 # check we're not friends 

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

1311 assert len(res.user_ids) == 0 

1312 

1313 # check we can send another friend req 

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

1315 

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

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

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

1319 

1320 

1321def test_hosting_preferences(db): 

1322 user1, token1 = generate_user() 

1323 user2, token2 = generate_user() 

1324 

1325 with api_session(token1) as api: 

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

1327 assert not res.HasField("max_guests") 

1328 assert not res.HasField("last_minute") 

1329 assert not res.HasField("has_pets") 

1330 assert not res.HasField("accepts_pets") 

1331 assert not res.HasField("pet_details") 

1332 assert not res.HasField("has_kids") 

1333 assert not res.HasField("accepts_kids") 

1334 assert not res.HasField("kid_details") 

1335 assert not res.HasField("has_housemates") 

1336 assert not res.HasField("housemate_details") 

1337 assert not res.HasField("wheelchair_accessible") 

1338 assert res.smoking_allowed == api_pb2.SMOKING_LOCATION_UNKNOWN 

1339 assert not res.HasField("smokes_at_home") 

1340 assert not res.HasField("drinking_allowed") 

1341 assert not res.HasField("drinks_at_home") 

1342 assert not res.HasField("other_host_info") 

1343 assert res.sleeping_arrangement == api_pb2.SLEEPING_ARRANGEMENT_UNKNOWN 

1344 assert not res.HasField("sleeping_details") 

1345 assert not res.HasField("area") 

1346 assert not res.HasField("house_rules") 

1347 assert not res.HasField("parking") 

1348 assert res.parking_details == api_pb2.PARKING_DETAILS_UNKNOWN 

1349 assert not res.HasField("camping_ok") 

1350 

1351 api.UpdateProfile( 

1352 api_pb2.UpdateProfileReq( 

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

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

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

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

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

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

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

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

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

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

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

1364 smoking_allowed=api_pb2.SMOKING_LOCATION_WINDOW, 

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

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

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

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

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

1370 sleeping_arrangement=api_pb2.SLEEPING_ARRANGEMENT_COMMON, 

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

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

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

1374 parking_details=api_pb2.PARKING_DETAILS_PAID_ONSITE, 

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

1376 ) 

1377 ) 

1378 

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

1380 # that it is public information. 

1381 with api_session(token2) as api: 

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

1383 assert res.max_guests.value == 3 

1384 assert res.last_minute.value 

1385 assert not res.has_pets.value 

1386 assert res.accepts_pets.value 

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

1388 assert not res.has_kids.value 

1389 assert res.accepts_kids.value 

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

1391 assert not res.has_housemates.value 

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

1393 assert res.wheelchair_accessible.value 

1394 assert res.smoking_allowed == api_pb2.SMOKING_LOCATION_WINDOW 

1395 assert not res.smokes_at_home.value 

1396 assert res.drinking_allowed.value 

1397 assert not res.drinks_at_home.value 

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

1399 assert res.sleeping_arrangement == api_pb2.SLEEPING_ARRANGEMENT_COMMON 

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

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

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

1403 assert res.parking.value 

1404 assert res.parking_details == api_pb2.PARKING_DETAILS_PAID_ONSITE 

1405 assert not res.camping_ok.value 

1406 

1407 # test unsetting 

1408 with api_session(token1) as api: 

1409 api.UpdateProfile( 

1410 api_pb2.UpdateProfileReq( 

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

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

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

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

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

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

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

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

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

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

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

1422 smoking_allowed=api_pb2.SMOKING_LOCATION_UNKNOWN, 

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

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

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

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

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

1428 sleeping_arrangement=api_pb2.SLEEPING_ARRANGEMENT_UNKNOWN, 

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

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

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

1432 parking_details=api_pb2.PARKING_DETAILS_UNKNOWN, 

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

1434 ) 

1435 ) 

1436 

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

1438 assert not res.HasField("max_guests") 

1439 assert not res.HasField("last_minute") 

1440 assert not res.HasField("has_pets") 

1441 assert not res.HasField("accepts_pets") 

1442 assert not res.HasField("pet_details") 

1443 assert not res.HasField("has_kids") 

1444 assert not res.HasField("accepts_kids") 

1445 assert not res.HasField("kid_details") 

1446 assert not res.HasField("has_housemates") 

1447 assert not res.HasField("housemate_details") 

1448 assert not res.HasField("wheelchair_accessible") 

1449 assert res.smoking_allowed == api_pb2.SMOKING_LOCATION_UNKNOWN 

1450 assert not res.HasField("smokes_at_home") 

1451 assert not res.HasField("drinking_allowed") 

1452 assert not res.HasField("drinks_at_home") 

1453 assert not res.HasField("other_host_info") 

1454 assert res.sleeping_arrangement == api_pb2.SLEEPING_ARRANGEMENT_UNKNOWN 

1455 assert not res.HasField("sleeping_details") 

1456 assert not res.HasField("area") 

1457 assert not res.HasField("house_rules") 

1458 assert not res.HasField("parking") 

1459 assert res.parking_details == api_pb2.PARKING_DETAILS_UNKNOWN 

1460 assert not res.HasField("camping_ok") 

1461 

1462 

1463def test_badges(db): 

1464 user1, _ = generate_user(last_donated=None) 

1465 user2, _ = generate_user(last_donated=None) 

1466 user3, _ = generate_user(last_donated=None) 

1467 user4, token = generate_user(last_donated=None) 

1468 

1469 update_badges(empty_pb2.Empty()) 

1470 

1471 founder_badge = get_badge_dict()["founder"] 

1472 board_member_badge = get_badge_dict()["board_member"] 

1473 

1474 with api_session(token) as api: 

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

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

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

1478 

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

1480 res = api.ListBadgeUsers(api_pb2.ListBadgeUsersReq(badge_id=board_member_badge["id"], page_size=1)) 

1481 assert res.user_ids == [1] 

1482 res2 = api.ListBadgeUsers( 

1483 api_pb2.ListBadgeUsersReq(badge_id=board_member_badge["id"], page_token=res.next_page_token) 

1484 ) 

1485 assert res2.user_ids == [2] 

1486 

1487 

1488@pytest.mark.parametrize("flag", ["is_deleted", "is_banned"]) 

1489def test_GetLiteUser_ghost_user_by_username(db, flag): 

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

1491 user1, token1 = generate_user() 

1492 user2, _ = generate_user() 

1493 

1494 # Make user2 invisible 

1495 with session_scope() as session: 

1496 db_user2 = session.merge(user2) 

1497 setattr(db_user2, flag, True) 

1498 session.commit() 

1499 

1500 # Refresh the materialized view 

1501 refresh_materialized_views_rapid(None) 

1502 

1503 with api_session(token1) as api: 

1504 # Query by username 

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

1506 

1507 assert lite_user.user_id == user2.id 

1508 assert lite_user.username == "ghost" 

1509 assert lite_user.name == "Ghost" 

1510 assert lite_user.lat == 0 

1511 assert lite_user.lng == 0 

1512 assert lite_user.radius == 0 

1513 assert lite_user.city == "" 

1514 assert lite_user.age == 0 

1515 assert lite_user.avatar_url == "" 

1516 assert lite_user.avatar_thumbnail_url == "" 

1517 assert not lite_user.has_strong_verification 

1518 

1519 

1520@pytest.mark.parametrize("flag", ["is_deleted", "is_banned"]) 

1521def test_GetLiteUser_ghost_user_by_id(db, flag): 

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

1523 user1, token1 = generate_user() 

1524 user2, _ = generate_user() 

1525 

1526 # Make user2 invisible 

1527 with session_scope() as session: 

1528 db_user2 = session.merge(user2) 

1529 setattr(db_user2, flag, True) 

1530 session.commit() 

1531 

1532 # Refresh the materialized view 

1533 refresh_materialized_views_rapid(None) 

1534 

1535 with api_session(token1) as api: 

1536 # Query by ID 

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

1538 

1539 assert lite_user.user_id == user2.id 

1540 assert lite_user.username == "ghost" 

1541 assert lite_user.name == "Ghost" 

1542 assert lite_user.lat == 0 

1543 assert lite_user.lng == 0 

1544 assert lite_user.radius == 0 

1545 assert lite_user.city == "" 

1546 assert lite_user.age == 0 

1547 assert lite_user.avatar_url == "" 

1548 assert lite_user.avatar_thumbnail_url == "" 

1549 assert not lite_user.has_strong_verification 

1550 

1551 

1552def test_GetLiteUser_blocked_user(db): 

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

1554 user1, token1 = generate_user() 

1555 user2, _ = generate_user() 

1556 

1557 # User1 blocks user2 

1558 make_user_block(user1, user2) 

1559 

1560 # Refresh the materialized view 

1561 refresh_materialized_views_rapid(None) 

1562 

1563 with api_session(token1) as api: 

1564 # Query by username 

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

1566 

1567 assert lite_user.user_id == user2.id 

1568 assert lite_user.is_ghost 

1569 assert lite_user.username == "ghost" 

1570 assert lite_user.name == "Ghost" 

1571 

1572 # Query by ID 

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

1574 

1575 assert lite_user.user_id == user2.id 

1576 assert lite_user.is_ghost 

1577 assert lite_user.username == "ghost" 

1578 assert lite_user.name == "Ghost" 

1579 

1580 

1581def test_GetLiteUser_blocking_user(db): 

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

1583 user1, token1 = generate_user() 

1584 user2, _ = generate_user() 

1585 

1586 # User2 blocks user1 

1587 make_user_block(user2, user1) 

1588 

1589 # Refresh the materialized view 

1590 refresh_materialized_views_rapid(None) 

1591 

1592 with api_session(token1) as api: 

1593 # Query by username 

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

1595 

1596 assert lite_user.user_id == user2.id 

1597 assert lite_user.is_ghost 

1598 assert lite_user.username == "ghost" 

1599 assert lite_user.name == "Ghost" 

1600 

1601 # Query by ID 

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

1603 

1604 assert lite_user.user_id == user2.id 

1605 assert lite_user.is_ghost 

1606 assert lite_user.username == "ghost" 

1607 assert lite_user.name == "Ghost" 

1608 

1609 

1610@pytest.mark.parametrize("flag", ["is_deleted", "is_banned"]) 

1611def test_GetLiteUsers_ghost_users(db, flag): 

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

1613 user1, token1 = generate_user() 

1614 user2, _ = generate_user() 

1615 user3, _ = generate_user() 

1616 user4, _ = generate_user() 

1617 

1618 # Make user2 and user4 invisible 

1619 with session_scope() as session: 

1620 db_user2 = session.merge(user2) 

1621 setattr(db_user2, flag, True) 

1622 db_user4 = session.merge(user4) 

1623 setattr(db_user4, flag, True) 

1624 session.commit() 

1625 

1626 # Refresh the materialized view 

1627 refresh_materialized_views_rapid(None) 

1628 

1629 with api_session(token1) as api: 

1630 res = api.GetLiteUsers( 

1631 api_pb2.GetLiteUsersReq( 

1632 users=[ 

1633 user1.username, # visible 

1634 user2.username, # ghost 

1635 str(user3.id), # visible 

1636 str(user4.id), # ghost 

1637 ] 

1638 ) 

1639 ) 

1640 

1641 assert len(res.responses) == 4 

1642 

1643 # user1 - visible, normal profile 

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

1645 assert not res.responses[0].not_found 

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

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

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

1649 

1650 # user2 - ghost by username 

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

1652 assert not res.responses[1].not_found 

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

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

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

1656 assert res.responses[1].user.name == "Ghost" 

1657 

1658 # user3 - visible, normal profile 

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

1660 assert not res.responses[2].not_found 

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

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

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

1664 

1665 # user4 - ghost by ID 

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

1667 assert not res.responses[3].not_found 

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

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

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

1671 assert res.responses[3].user.name == "Ghost" 

1672 

1673 

1674def test_GetLiteUsers_blocked_users(db): 

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

1676 user1, token1 = generate_user() 

1677 user2, _ = generate_user() 

1678 user3, _ = generate_user() 

1679 user4, _ = generate_user() 

1680 user5, _ = generate_user() 

1681 

1682 # User1 blocks user2 

1683 make_user_block(user1, user2) 

1684 # User4 blocks user1 

1685 make_user_block(user4, user1) 

1686 

1687 # Refresh the materialized view 

1688 refresh_materialized_views_rapid(None) 

1689 

1690 with api_session(token1) as api: 

1691 res = api.GetLiteUsers( 

1692 api_pb2.GetLiteUsersReq( 

1693 users=[ 

1694 user2.username, # user1 blocked user2 

1695 str(user3.id), # visible 

1696 user4.username, # user4 blocked user1 

1697 str(user5.id), # visible 

1698 ] 

1699 ) 

1700 ) 

1701 

1702 assert len(res.responses) == 4 

1703 

1704 # user2 - blocked by user1, should be ghost 

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

1706 assert not res.responses[0].not_found 

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

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

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

1710 assert res.responses[0].user.name == "Ghost" 

1711 

1712 # user3 - visible 

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

1714 assert not res.responses[1].not_found 

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

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

1717 

1718 # user4 - user4 blocked user1, should be ghost 

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

1720 assert not res.responses[2].not_found 

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

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

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

1724 assert res.responses[2].user.name == "Ghost" 

1725 

1726 # user5 - visible 

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

1728 assert not res.responses[3].not_found 

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

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

1731 

1732 

1733@pytest.mark.parametrize("flag", ["is_deleted", "is_banned"]) 

1734def test_GetUser_ghost_user_by_id(db, flag): 

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

1736 user1, token1 = generate_user() 

1737 user2, _ = generate_user() 

1738 

1739 # Make user2 invisible 

1740 with session_scope() as session: 

1741 db_user2 = session.merge(user2) 

1742 setattr(db_user2, flag, True) 

1743 session.commit() 

1744 

1745 with api_session(token1) as api: 

1746 # Query by ID 

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

1748 

1749 assert user_pb.user_id == user2.id 

1750 assert user_pb.username == "ghost" 

1751 assert user_pb.name == "Ghost" 

1752 assert user_pb.city == "" 

1753 assert user_pb.hosting_status == 0 

1754 assert user_pb.meetup_status == 0 

1755 

1756 

1757def test_GetUser_blocked_user(db): 

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

1759 user1, token1 = generate_user() 

1760 user2, _ = generate_user() 

1761 

1762 # User1 blocks user2 

1763 make_user_block(user1, user2) 

1764 

1765 with api_session(token1) as api: 

1766 # Query by username 

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

1768 

1769 assert user_pb.user_id == user2.id 

1770 assert user_pb.username == "ghost" 

1771 assert user_pb.name == "Ghost" 

1772 

1773 # Query by ID 

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

1775 

1776 assert user_pb.user_id == user2.id 

1777 assert user_pb.username == "ghost" 

1778 assert user_pb.name == "Ghost" 

1779 

1780 

1781def test_GetUser_blocking_user(db): 

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

1783 user1, token1 = generate_user() 

1784 user2, _ = generate_user() 

1785 

1786 # User2 blocks user1 

1787 make_user_block(user2, user1) 

1788 

1789 with api_session(token1) as api: 

1790 # Query by username 

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

1792 

1793 assert user_pb.user_id == user2.id 

1794 assert user_pb.username == "ghost" 

1795 assert user_pb.name == "Ghost" 

1796 

1797 # Query by ID 

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

1799 

1800 assert user_pb.user_id == user2.id 

1801 assert user_pb.username == "ghost" 

1802 assert user_pb.name == "Ghost"