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

1112 statements  

« prev     ^ index     » next       coverage.py v7.13.2, created at 2026-02-03 06:18 +0000

1from datetime import timedelta 

2 

3import grpc 

4import pytest 

5from google.protobuf import empty_pb2, wrappers_pb2 

6from sqlalchemy import 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 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.utils import create_coordinate, to_aware_datetime 

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

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

18from tests.fixtures.sessions import ( 

19 api_session, 

20 blocking_session, 

21 notifications_session, 

22 real_api_session, 

23 real_jail_session, 

24) 

25from tests.fixtures.sessions import ( 

26 real_admin_session as admin_session, 

27) 

28 

29 

30@pytest.fixture(autouse=True) 

31def _(testconfig): 

32 pass 

33 

34 

35def test_ping(db): 

36 user, token = generate_user( 

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

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

39 language_abilities=[ 

40 ("fin", LanguageFluency.fluent), 

41 ("fra", LanguageFluency.beginner), 

42 ], 

43 ) 

44 

45 with real_api_session(token) as api: 

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

47 

48 assert res.user.user_id == user.id 

49 assert res.user.username == user.username 

50 assert res.user.name == user.name 

51 assert res.user.city == user.city 

52 assert res.user.hometown == user.hometown 

53 assert res.user.verification == 0.0 

54 assert res.user.community_standing == user.community_standing 

55 assert res.user.num_references == 0 

56 assert res.user.gender == user.gender 

57 assert res.user.pronouns == user.pronouns 

58 assert res.user.age == user.age 

59 

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

61 

62 # the joined time is fuzzed 

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

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

65 # same for last_active 

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

67 

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

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

70 

71 assert res.user.occupation == user.occupation 

72 assert res.user.education == user.education 

73 assert res.user.about_me == user.about_me 

74 assert res.user.things_i_like == user.things_i_like 

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

76 assert res.user.about_place == user.about_place 

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

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

79 assert res.user.additional_information == user.additional_information 

80 

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

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

83 

84 

85def test_coords(db): 

86 # make them need to update location 

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

88 user2, token2 = generate_user() 

89 

90 with api_session(token2) as api: 

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

92 assert res.user.city == user2.city 

93 lat, lng = user2.coordinates 

94 assert res.user.lat == lat 

95 assert res.user.lng == lng 

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

97 

98 with api_session(token2) as api: 

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

100 assert res.city == user1.city 

101 assert res.lat == 1.0 

102 assert res.lng == 0.0 

103 assert res.radius == 2000.0 

104 

105 # Check coordinate wrapping 

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

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

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

109 

110 with api_session(token3) as api: 

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

112 assert res.lat == 40.0 

113 assert res.lng == 179.5 

114 

115 with api_session(token4) as api: 

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

117 assert res.lat == 40.0 

118 assert res.lng == 20.0 

119 

120 # PostGIS does not wrap longitude for latitude overflow 

121 with api_session(token5) as api: 

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

123 assert res.lat == 89.5 

124 assert res.lng == 20.0 

125 

126 with real_jail_session(token1) as jail: 

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

128 assert res.jailed 

129 assert res.needs_to_update_location 

130 

131 res = jail.SetLocation( 

132 jail_pb2.SetLocationReq( 

133 city="New York City", 

134 lat=40.7812, 

135 lng=-73.9647, 

136 radius=250, 

137 ) 

138 ) 

139 

140 assert not res.jailed 

141 assert not res.needs_to_update_location 

142 

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

144 assert not res.jailed 

145 assert not res.needs_to_update_location 

146 

147 with api_session(token2) as api: 

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

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

150 assert res.lat == 40.7812 

151 assert res.lng == -73.9647 

152 assert res.radius == 250 

153 

154 

155def test_get_user(db): 

156 user1, token1 = generate_user() 

157 user2, token2 = generate_user() 

158 

159 with api_session(token1) as api: 

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

161 assert res.user_id == user2.id 

162 assert res.username == user2.username 

163 assert res.name == user2.name 

164 

165 with api_session(token1) as api: 

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

167 assert res.user_id == user2.id 

168 assert res.username == user2.username 

169 assert res.name == user2.name 

170 

171 

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

173def test_user_model_to_pb_ghost_user(db, flag): 

174 user1, token1 = generate_user() 

175 user2, _ = generate_user() 

176 

177 with session_scope() as session: 

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

179 

180 refresh_materialized_views_rapid(empty_pb2.Empty()) 

181 

182 with api_session(token1) as api: 

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

184 

185 assert user_pb.user_id == user2.id 

186 assert user_pb.is_ghost 

187 assert user_pb.username == "ghost" 

188 assert user_pb.name == "Deactivated Account" 

189 assert ( 

190 user_pb.about_me 

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

192 ) 

193 

194 assert user_pb.lat == 0 

195 assert user_pb.lng == 0 

196 assert user_pb.radius == 0 

197 assert user_pb.verification == 0.0 

198 assert user_pb.community_standing == 0.0 

199 assert user_pb.num_references == 0 

200 assert user_pb.age == 0 

201 assert user_pb.hosting_status == 0 

202 assert user_pb.meetup_status == 0 

203 assert user_pb.city == "" 

204 assert user_pb.hometown == "" 

205 assert user_pb.timezone == "" 

206 assert user_pb.gender == "" 

207 assert user_pb.pronouns == "" 

208 assert user_pb.occupation == "" 

209 assert user_pb.education == "" 

210 assert user_pb.things_i_like == "" 

211 assert user_pb.about_place == "" 

212 assert user_pb.additional_information == "" 

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

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

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

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

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

218 assert user_pb.avatar_url == "" 

219 assert user_pb.avatar_thumbnail_url == "" 

220 assert not user_pb.has_strong_verification 

221 

222 with api_session(token1) as api: 

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

224 

225 assert lite_user_pb.user_id == user2.id 

226 assert lite_user_pb.is_ghost 

227 assert lite_user_pb.username == "ghost" 

228 assert lite_user_pb.name == "Deactivated Account" 

229 assert lite_user_pb.city == "" 

230 assert lite_user_pb.age == 0 

231 assert lite_user_pb.avatar_url == "" 

232 assert lite_user_pb.avatar_thumbnail_url == "" 

233 assert lite_user_pb.lat == 0 

234 assert lite_user_pb.lng == 0 

235 assert lite_user_pb.radius == 0 

236 assert not lite_user_pb.has_strong_verification 

237 

238 

239def test_user_model_to_pb_ghost_user_blocked(db): 

240 user1, token1 = generate_user() 

241 user2, _ = generate_user() 

242 

243 with blocking_session(token1) as user_blocks: 

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

245 

246 refresh_materialized_views_rapid(empty_pb2.Empty()) 

247 

248 with api_session(token1) as api: 

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

250 

251 assert user_pb.user_id == user2.id 

252 assert user_pb.is_ghost 

253 assert user_pb.username == "ghost" 

254 assert user_pb.name == "Deactivated Account" 

255 assert ( 

256 user_pb.about_me 

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

258 ) 

259 

260 assert user_pb.lat == 0 

261 assert user_pb.lng == 0 

262 assert user_pb.radius == 0 

263 assert user_pb.verification == 0.0 

264 assert user_pb.community_standing == 0.0 

265 assert user_pb.num_references == 0 

266 assert user_pb.age == 0 

267 assert user_pb.hosting_status == 0 

268 assert user_pb.meetup_status == 0 

269 assert user_pb.city == "" 

270 assert user_pb.hometown == "" 

271 assert user_pb.timezone == "" 

272 assert user_pb.gender == "" 

273 assert user_pb.pronouns == "" 

274 assert user_pb.occupation == "" 

275 assert user_pb.education == "" 

276 assert user_pb.things_i_like == "" 

277 assert user_pb.about_place == "" 

278 assert user_pb.additional_information == "" 

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

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

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

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

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

284 assert user_pb.avatar_url == "" 

285 assert user_pb.avatar_thumbnail_url == "" 

286 assert not user_pb.has_strong_verification 

287 

288 with api_session(token1) as api: 

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

290 

291 assert lite_user_pb.user_id == user2.id 

292 assert lite_user_pb.is_ghost 

293 assert lite_user_pb.username == "ghost" 

294 assert lite_user_pb.name == "Deactivated Account" 

295 assert lite_user_pb.city == "" 

296 assert lite_user_pb.age == 0 

297 assert lite_user_pb.avatar_url == "" 

298 assert lite_user_pb.avatar_thumbnail_url == "" 

299 assert lite_user_pb.lat == 0 

300 assert lite_user_pb.lng == 0 

301 assert lite_user_pb.radius == 0 

302 assert not lite_user_pb.has_strong_verification 

303 

304 

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

306def test_admin_viewing_ghost_users_sees_full_profile(db, flag): 

307 admin, token_admin = generate_user(is_superuser=True) 

308 user, _ = generate_user() 

309 

310 with session_scope() as session: 

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

312 

313 with admin_session(token_admin) as api: 

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

315 

316 assert user_pb.user_id == user.id 

317 assert user_pb.username == user.username 

318 assert user_pb.name == user.name 

319 assert user_pb.city == user.city 

320 assert user_pb.name != "Deactivated Account" 

321 assert user_pb.username != "ghost" 

322 assert user_pb.hosting_status in ( 

323 api_pb2.HOSTING_STATUS_UNKNOWN, 

324 api_pb2.HOSTING_STATUS_CAN_HOST, 

325 api_pb2.HOSTING_STATUS_MAYBE, 

326 api_pb2.HOSTING_STATUS_CANT_HOST, 

327 ) 

328 

329 

330def test_lite_coords(db): 

331 # make them need to update location 

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

333 user2, token2 = generate_user() 

334 

335 refresh_materialized_views_rapid(empty_pb2.Empty()) 

336 

337 with api_session(token2) as api: 

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

339 assert res.user.city == user2.city 

340 lat, lng = user2.coordinates 

341 assert res.user.lat == lat 

342 assert res.user.lng == lng 

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

344 

345 with api_session(token2) as api: 

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

347 assert res.city == user1.city 

348 assert res.lat == 0.0 

349 assert res.lng == 0.0 

350 assert res.radius == 0.0 

351 

352 # Check coordinate wrapping 

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

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

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

356 

357 refresh_materialized_views_rapid(empty_pb2.Empty()) 

358 

359 with api_session(token3) as api: 

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

361 assert res.lat == 40.0 

362 assert res.lng == 179.5 

363 

364 with api_session(token4) as api: 

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

366 assert res.lat == 40.0 

367 assert res.lng == 20.0 

368 

369 # PostGIS does not wrap longitude for latitude overflow 

370 with api_session(token5) as api: 

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

372 assert res.lat == 89.5 

373 assert res.lng == 20.0 

374 

375 with real_jail_session(token1) as jail: 

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

377 assert res.jailed 

378 assert res.needs_to_update_location 

379 

380 res = jail.SetLocation( 

381 jail_pb2.SetLocationReq( 

382 city="New York City", 

383 lat=40.7812, 

384 lng=-73.9647, 

385 radius=250, 

386 ) 

387 ) 

388 

389 assert not res.jailed 

390 assert not res.needs_to_update_location 

391 

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

393 assert not res.jailed 

394 assert not res.needs_to_update_location 

395 

396 refresh_materialized_views_rapid(empty_pb2.Empty()) 

397 

398 with api_session(token2) as api: 

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

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

401 assert res.lat == 40.7812 

402 assert res.lng == -73.9647 

403 assert res.radius == 250 

404 

405 

406def test_lite_get_user(db): 

407 user1, token1 = generate_user() 

408 user2, token2 = generate_user() 

409 

410 refresh_materialized_views_rapid(empty_pb2.Empty()) 

411 

412 with api_session(token1) as api: 

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

414 assert res.user_id == user2.id 

415 assert res.username == user2.username 

416 assert res.name == user2.name 

417 

418 with api_session(token1) as api: 

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

420 assert res.user_id == user2.id 

421 assert res.username == user2.username 

422 assert res.name == user2.name 

423 

424 

425def test_GetLiteUsers(db): 

426 user1, token1 = generate_user() 

427 user2, _ = generate_user() 

428 user3, _ = generate_user() 

429 user4, _ = generate_user() 

430 user5, _ = generate_user() 

431 user6, _ = generate_user() 

432 

433 make_user_block(user4, user1) 

434 

435 refresh_materialized_views_rapid(empty_pb2.Empty()) 

436 

437 with api_session(token1) as api: 

438 res = api.GetLiteUsers( 

439 api_pb2.GetLiteUsersReq( 

440 users=[ 

441 user1.username, 

442 str(user1.id), 

443 "nonexistent", 

444 str(user2.id), 

445 "9994", 

446 user6.username, 

447 str(user5.id), 

448 "notreal", 

449 user4.username, 

450 ] 

451 ) 

452 ) 

453 

454 assert len(res.responses) == 9 

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

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

457 

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

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

460 

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

462 assert res.responses[2].not_found 

463 

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

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

466 

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

468 assert res.responses[4].not_found 

469 

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

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

472 

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

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

475 

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

477 assert res.responses[7].not_found 

478 

479 # blocked - should return ghost profile 

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

481 assert not res.responses[8].not_found 

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

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

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

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

486 

487 with api_session(token1) as api: 

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

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

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

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

492 

493 

494def test_update_profile(db): 

495 user, token = generate_user() 

496 

497 with api_session(token) as api: 

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

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

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

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

502 

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

504 api.UpdateProfile( 

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

506 ) 

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

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

509 

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

511 api.UpdateProfile( 

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

513 ) 

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

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

516 

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

518 api.UpdateProfile( 

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

520 ) 

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

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

523 

524 api.UpdateProfile( 

525 api_pb2.UpdateProfileReq( 

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

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

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

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

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

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

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

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

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

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

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

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

538 hosting_status=api_pb2.HOSTING_STATUS_CAN_HOST, 

539 meetup_status=api_pb2.MEETUP_STATUS_WANTS_TO_MEETUP, 

540 language_abilities=api_pb2.RepeatedLanguageAbilityValue( 

541 value=[ 

542 api_pb2.LanguageAbility( 

543 code="eng", 

544 fluency=api_pb2.LanguageAbility.Fluency.FLUENCY_FLUENT, 

545 ) 

546 ], 

547 ), 

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

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

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

551 ) 

552 ) 

553 

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

555 assert user_details.name == "New name" 

556 assert user_details.city == "Timbuktu" 

557 assert user_details.hometown == "Walla Walla" 

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

559 assert user_details.education == "Couchers U" 

560 assert user_details.things_i_like == "Couchers" 

561 assert user_details.lat == 0.01 

562 assert user_details.lng == -2 

563 assert user_details.radius == 321 

564 assert user_details.occupation == "Testing" 

565 assert user_details.about_me == "I rule" 

566 assert user_details.about_place == "My place" 

567 assert user_details.hosting_status == api_pb2.HOSTING_STATUS_CAN_HOST 

568 assert user_details.meetup_status == api_pb2.MEETUP_STATUS_WANTS_TO_MEETUP 

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

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

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

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

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

574 

575 # Test unset values 

576 api.UpdateProfile( 

577 api_pb2.UpdateProfileReq( 

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

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

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

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

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

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

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

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

586 hosting_status=api_pb2.HOSTING_STATUS_CAN_HOST, 

587 meetup_status=api_pb2.MEETUP_STATUS_WANTS_TO_MEETUP, 

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

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

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

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

592 ) 

593 ) 

594 

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

596 assert not user_details.hometown 

597 assert not user_details.radius 

598 assert not user_details.pronouns 

599 assert not user_details.occupation 

600 assert not user_details.education 

601 assert not user_details.about_me 

602 assert not user_details.things_i_like 

603 assert not user_details.about_place 

604 assert user_details.hosting_status == api_pb2.HOSTING_STATUS_CAN_HOST 

605 assert user_details.meetup_status == api_pb2.MEETUP_STATUS_WANTS_TO_MEETUP 

606 assert not user_details.language_abilities 

607 assert not user_details.regions_visited 

608 assert not user_details.regions_lived 

609 assert not user_details.additional_information 

610 

611 

612def test_update_profile_do_not_email(db): 

613 user, token = generate_user() 

614 

615 with notifications_session(token) as notifications: 

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

617 

618 with api_session(token) as api: 

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

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

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

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

623 

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

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

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

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

628 

629 

630def test_language_abilities(db): 

631 user, token = generate_user( 

632 language_abilities=[ 

633 ("fin", LanguageFluency.fluent), 

634 ("fra", LanguageFluency.beginner), 

635 ], 

636 ) 

637 

638 with api_session(token) as api: 

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

640 assert len(res.language_abilities) == 2 

641 

642 # can't add non-existent languages 

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

644 api.UpdateProfile( 

645 api_pb2.UpdateProfileReq( 

646 language_abilities=api_pb2.RepeatedLanguageAbilityValue( 

647 value=[ 

648 api_pb2.LanguageAbility( 

649 code="QQQ", 

650 fluency=api_pb2.LanguageAbility.Fluency.FLUENCY_FLUENT, 

651 ) 

652 ], 

653 ), 

654 ) 

655 ) 

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

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

658 

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

660 with pytest.raises(Exception) as err2: 

661 api.UpdateProfile( 

662 api_pb2.UpdateProfileReq( 

663 language_abilities=api_pb2.RepeatedLanguageAbilityValue( 

664 value=[ 

665 api_pb2.LanguageAbility( 

666 code="eng", 

667 fluency=api_pb2.LanguageAbility.Fluency.FLUENCY_FLUENT, 

668 ), 

669 api_pb2.LanguageAbility( 

670 code="eng", 

671 fluency=api_pb2.LanguageAbility.Fluency.FLUENCY_FLUENT, 

672 ), 

673 ], 

674 ), 

675 ) 

676 ) 

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

678 

679 # nothing changed 

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

681 assert len(res.language_abilities) == 2 

682 

683 # now actually add a value 

684 api.UpdateProfile( 

685 api_pb2.UpdateProfileReq( 

686 language_abilities=api_pb2.RepeatedLanguageAbilityValue( 

687 value=[ 

688 api_pb2.LanguageAbility( 

689 code="eng", 

690 fluency=api_pb2.LanguageAbility.Fluency.FLUENCY_FLUENT, 

691 ) 

692 ], 

693 ), 

694 ) 

695 ) 

696 

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

698 assert len(res.language_abilities) == 1 

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

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

701 

702 # change the value to a new one 

703 api.UpdateProfile( 

704 api_pb2.UpdateProfileReq( 

705 language_abilities=api_pb2.RepeatedLanguageAbilityValue( 

706 value=[ 

707 api_pb2.LanguageAbility( 

708 code="fin", 

709 fluency=api_pb2.LanguageAbility.Fluency.FLUENCY_BEGINNER, 

710 ) 

711 ], 

712 ), 

713 ) 

714 ) 

715 

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

717 assert len(res.language_abilities) == 1 

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

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

720 

721 # should be able to set to same value still 

722 api.UpdateProfile( 

723 api_pb2.UpdateProfileReq( 

724 language_abilities=api_pb2.RepeatedLanguageAbilityValue( 

725 value=[ 

726 api_pb2.LanguageAbility( 

727 code="fin", 

728 fluency=api_pb2.LanguageAbility.Fluency.FLUENCY_BEGINNER, 

729 ) 

730 ], 

731 ), 

732 ) 

733 ) 

734 

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

736 assert len(res.language_abilities) == 1 

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

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

739 

740 # don't change it 

741 api.UpdateProfile(api_pb2.UpdateProfileReq()) 

742 

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

744 assert len(res.language_abilities) == 1 

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

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

747 

748 # remove value 

749 api.UpdateProfile( 

750 api_pb2.UpdateProfileReq( 

751 language_abilities=api_pb2.RepeatedLanguageAbilityValue( 

752 value=[], 

753 ), 

754 ) 

755 ) 

756 

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

758 assert len(res.language_abilities) == 0 

759 

760 

761def test_pending_friend_request_count(db): 

762 user1, token1 = generate_user() 

763 user2, token2 = generate_user() 

764 user3, token3 = generate_user() 

765 

766 with api_session(token2) as api: 

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

768 assert res.pending_friend_request_count == 0 

769 

770 with api_session(token1) as api: 

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

772 assert res.pending_friend_request_count == 0 

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

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

775 assert res.pending_friend_request_count == 0 

776 

777 with api_session(token2) as api: 

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

779 assert res.pending_friend_request_count == 1 

780 

781 with api_session(token2) as api: 

782 # check it's there 

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

784 assert len(res.sent) == 0 

785 assert len(res.received) == 1 

786 

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

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

789 

790 fr_id = res.received[0].friend_request_id 

791 

792 # accept it 

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

794 

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

796 assert res.pending_friend_request_count == 0 

797 

798 

799def test_friend_request_flow(db, push_collector: PushCollector): 

800 user1, token1 = generate_user(complete_profile=True) 

801 user2, token2 = generate_user(complete_profile=True) 

802 user3, token3 = generate_user() 

803 

804 # send a friend request from user1 to user2 

805 with mock_notification_email() as mock: 

806 with api_session(token1) as api: 

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

808 

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

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

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

812 

813 mock.assert_called_once() 

814 e = email_fields(mock) 

815 assert e.recipient == user2.email 

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

817 assert user2.name in e.plain 

818 assert user2.name in e.html 

819 assert user1.name in e.plain 

820 assert user1.name in e.html 

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

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

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

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

825 

826 with session_scope() as session: 

827 friend_request_id = session.execute( 

828 select(FriendRelationship.id).where( 

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

830 ) 

831 ).scalar_one_or_none() 

832 

833 with api_session(token1) as api: 

834 # check it went through 

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

836 assert len(res.sent) == 1 

837 assert len(res.received) == 0 

838 

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

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

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

842 

843 with api_session(token2) as api: 

844 # check it's there 

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

846 assert len(res.sent) == 0 

847 assert len(res.received) == 1 

848 

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

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

851 

852 fr_id = res.received[0].friend_request_id 

853 

854 # accept it 

855 with mock_notification_email() as mock: 

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

857 

858 # check it's gone 

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

860 assert len(res.sent) == 0 

861 assert len(res.received) == 0 

862 

863 # check we're friends now 

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

865 assert len(res.user_ids) == 1 

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

867 

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

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

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

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

872 

873 mock.assert_called_once() 

874 e = email_fields(mock) 

875 assert e.recipient == user1.email 

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

877 assert user1.name in e.plain 

878 assert user1.name in e.html 

879 assert user2.name in e.plain 

880 assert user2.name in e.html 

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

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

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

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

885 

886 with api_session(token1) as api: 

887 # check it's gone 

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

889 assert len(res.sent) == 0 

890 assert len(res.received) == 0 

891 

892 # check we're friends now 

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

894 assert len(res.user_ids) == 1 

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

896 

897 with api_session(token1) as api: 

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

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

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

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

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

903 

904 # we can unfriend 

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

906 

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

908 assert len(res.user_ids) == 0 

909 

910 

911def test_RemoveFriend_regression(db, push_collector: PushCollector): 

912 user1, token1 = generate_user(complete_profile=True) 

913 user2, token2 = generate_user(complete_profile=True) 

914 user3, token3 = generate_user() 

915 user4, token4 = generate_user() 

916 user5, token5 = generate_user() 

917 user6, token6 = generate_user() 

918 

919 with api_session(token4) as api: 

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

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

922 

923 with api_session(token5) as api: 

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

925 

926 with api_session(token1) as api: 

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

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

929 

930 api.RespondFriendRequest( 

931 api_pb2.RespondFriendRequestReq( 

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

933 ) 

934 ) 

935 

936 with api_session(token2) as api: 

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

938 api.RespondFriendRequest( 

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

940 ) 

941 

942 with api_session(token1) as api: 

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

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

945 

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

947 

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

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

950 

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

952 

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

954 assert not res.user_ids 

955 

956 

957def test_cant_friend_request_twice(db): 

958 user1, token1 = generate_user() 

959 user2, token2 = generate_user() 

960 

961 # send friend request from user1 to user2 

962 with api_session(token1) as api: 

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

964 

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

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

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

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

969 

970 

971def test_cant_friend_request_pending(db): 

972 user1, token1 = generate_user() 

973 user2, token2 = generate_user() 

974 

975 # send friend request from user1 to user2 

976 with api_session(token1) as api: 

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

978 

979 with api_session(token2) as api: 

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

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

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

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

984 

985 

986def test_cant_friend_request_already_friends(db): 

987 user1, token1 = generate_user() 

988 user2, token2 = generate_user() 

989 make_friends(user1, user2) 

990 

991 with api_session(token1) as api: 

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

993 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.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 with api_session(token2) as api: 

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

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

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

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

1002 

1003 

1004def test_excessive_friend_requests_are_reported(db): 

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

1006 user, token = generate_user() 

1007 rate_limit_definition = RATE_LIMIT_DEFINITIONS[RateLimitAction.friend_request] 

1008 with api_session(token) as api: 

1009 # Test warning email 

1010 with mock_notification_email() as mock_email: 

1011 for _ in range(rate_limit_definition.warning_limit): 

1012 friend_user, _ = generate_user() 

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

1014 

1015 assert mock_email.call_count == 0 

1016 friend_user, _ = generate_user() 

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

1018 

1019 assert mock_email.call_count == 1 

1020 email = email_fields(mock_email).plain 

1021 assert email.startswith( 

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

1023 ) 

1024 

1025 # Test ban after exceeding FRIEND_REQUEST_HARD_LIMIT 

1026 with mock_notification_email() as mock_email: 

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

1028 friend_user, _ = generate_user() 

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

1030 

1031 assert mock_email.call_count == 0 

1032 friend_user, _ = generate_user() 

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

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

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

1036 assert ( 

1037 exc_info.value.details() 

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

1039 ) 

1040 

1041 assert mock_email.call_count == 1 

1042 email = email_fields(mock_email).plain 

1043 assert email.startswith( 

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

1045 ) 

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

1047 

1048 

1049def test_ListFriends(db): 

1050 user1, token1 = generate_user() 

1051 user2, token2 = generate_user() 

1052 user3, token3 = generate_user() 

1053 

1054 # send friend request from user1 to user2 and user3 

1055 with api_session(token1) as api: 

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

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

1058 

1059 with api_session(token3) as api: 

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

1061 

1062 with api_session(token2) as api: 

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

1064 assert len(res.received) == 2 

1065 

1066 # order is an implementation detail 

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

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

1069 

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

1071 assert user1_req.user_id == user1.id 

1072 api.RespondFriendRequest( 

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

1074 ) 

1075 

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

1077 assert user3_req.user_id == user3.id 

1078 api.RespondFriendRequest( 

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

1080 ) 

1081 

1082 # check we now have two friends 

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

1084 assert len(res.user_ids) == 2 

1085 assert user1.id in res.user_ids 

1086 assert user3.id in res.user_ids 

1087 

1088 with api_session(token3) as api: 

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

1090 assert len(res.user_ids) == 1 

1091 assert user2.id in res.user_ids 

1092 

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

1094 assert len(res.received) == 1 

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

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

1097 fr_id = res.received[0].friend_request_id 

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

1099 

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

1101 assert len(res.user_ids) == 2 

1102 assert user1.id in res.user_ids 

1103 assert user2.id in res.user_ids 

1104 

1105 with api_session(token1) as api: 

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

1107 assert len(res.user_ids) == 2 

1108 assert user2.id in res.user_ids 

1109 assert user3.id in res.user_ids 

1110 

1111 

1112def test_ListMutualFriends(db): 

1113 user1, token1 = generate_user() 

1114 user2, token2 = generate_user() 

1115 user3, token3 = generate_user() 

1116 user4, token4 = generate_user() 

1117 user5, token5 = generate_user() 

1118 

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

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

1121 make_friends(user1, user2) 

1122 make_friends(user1, user3) 

1123 make_friends(user1, user4) 

1124 make_friends(user1, user5) 

1125 make_friends(user3, user2) 

1126 make_friends(user3, user4) 

1127 

1128 with api_session(token1) as api: 

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

1130 assert len(mutual_friends) == 1 

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

1132 

1133 # and other way around same 

1134 with api_session(token2) as api: 

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

1136 assert len(mutual_friends) == 1 

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

1138 

1139 # Check pending request doesn't have effect 

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

1141 

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

1143 assert len(mutual_friends) == 1 

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

1145 

1146 # both ways 

1147 with api_session(token1) as api: 

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

1149 assert len(mutual_friends) == 1 

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

1151 

1152 

1153def test_mutual_friends_self(db): 

1154 user1, token1 = generate_user() 

1155 user2, token2 = generate_user() 

1156 user3, token3 = generate_user() 

1157 user4, token4 = generate_user() 

1158 

1159 make_friends(user1, user2) 

1160 make_friends(user2, user3) 

1161 make_friends(user1, user4) 

1162 

1163 with api_session(token1) as api: 

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

1165 assert len(res.mutual_friends) == 0 

1166 

1167 with api_session(token2) as api: 

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

1169 assert len(res.mutual_friends) == 0 

1170 

1171 with api_session(token3) as api: 

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

1173 assert len(res.mutual_friends) == 0 

1174 

1175 with api_session(token4) as api: 

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

1177 assert len(res.mutual_friends) == 0 

1178 

1179 

1180def test_CancelFriendRequest(db): 

1181 user1, token1 = generate_user() 

1182 user2, token2 = generate_user() 

1183 

1184 with api_session(token1) as api: 

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

1186 

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

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

1189 fr_id = res.sent[0].friend_request_id 

1190 

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

1192 

1193 # check it's gone 

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

1195 assert len(res.sent) == 0 

1196 assert len(res.received) == 0 

1197 

1198 # check not friends 

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

1200 assert len(res.user_ids) == 0 

1201 

1202 with api_session(token2) as api: 

1203 # check it's gone 

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

1205 assert len(res.sent) == 0 

1206 assert len(res.received) == 0 

1207 

1208 # check we're not friends 

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

1210 assert len(res.user_ids) == 0 

1211 

1212 with api_session(token1) as api: 

1213 # check we can send another friend req 

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

1215 

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

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

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

1219 

1220 

1221def test_accept_friend_request(db): 

1222 user1, token1 = generate_user() 

1223 user2, token2 = generate_user() 

1224 

1225 with session_scope() as session: 

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

1227 session.add(friend_request) 

1228 session.commit() 

1229 friend_request_id = friend_request.id 

1230 

1231 with api_session(token2) as api: 

1232 # check request pending 

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

1234 assert len(res.received) == 1 

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

1236 

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

1238 

1239 # check request is gone 

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

1241 assert len(res.sent) == 0 

1242 assert len(res.received) == 0 

1243 

1244 # check now friends 

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

1246 assert len(res.user_ids) == 1 

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

1248 

1249 with api_session(token1) as api: 

1250 # check request gone 

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

1252 assert len(res.sent) == 0 

1253 assert len(res.received) == 0 

1254 

1255 # check now friends 

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

1257 assert len(res.user_ids) == 1 

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

1259 

1260 

1261def test_reject_friend_request(db): 

1262 user1, token1 = generate_user() 

1263 user2, token2 = generate_user() 

1264 

1265 with api_session(token1) as api: 

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

1267 

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

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

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

1271 

1272 with api_session(token2) as api: 

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

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

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

1276 

1277 fr_id = res.received[0].friend_request_id 

1278 

1279 # reject it 

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

1281 

1282 # check it's gone 

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

1284 assert len(res.sent) == 0 

1285 assert len(res.received) == 0 

1286 

1287 # check not friends 

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

1289 assert len(res.user_ids) == 0 

1290 

1291 with api_session(token1) as api: 

1292 # check it's gone 

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

1294 assert len(res.sent) == 0 

1295 assert len(res.received) == 0 

1296 

1297 # check we're not friends 

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

1299 assert len(res.user_ids) == 0 

1300 

1301 # check we can send another friend req 

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

1303 

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

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

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

1307 

1308 

1309def test_hosting_preferences(db): 

1310 user1, token1 = generate_user() 

1311 user2, token2 = generate_user() 

1312 

1313 with api_session(token1) as api: 

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

1315 assert not res.HasField("max_guests") 

1316 assert not res.HasField("last_minute") 

1317 assert not res.HasField("has_pets") 

1318 assert not res.HasField("accepts_pets") 

1319 assert not res.HasField("pet_details") 

1320 assert not res.HasField("has_kids") 

1321 assert not res.HasField("accepts_kids") 

1322 assert not res.HasField("kid_details") 

1323 assert not res.HasField("has_housemates") 

1324 assert not res.HasField("housemate_details") 

1325 assert not res.HasField("wheelchair_accessible") 

1326 assert res.smoking_allowed == api_pb2.SMOKING_LOCATION_UNKNOWN 

1327 assert not res.HasField("smokes_at_home") 

1328 assert not res.HasField("drinking_allowed") 

1329 assert not res.HasField("drinks_at_home") 

1330 assert not res.HasField("other_host_info") 

1331 assert res.sleeping_arrangement == api_pb2.SLEEPING_ARRANGEMENT_UNKNOWN 

1332 assert not res.HasField("sleeping_details") 

1333 assert not res.HasField("area") 

1334 assert not res.HasField("house_rules") 

1335 assert not res.HasField("parking") 

1336 assert res.parking_details == api_pb2.PARKING_DETAILS_UNKNOWN 

1337 assert not res.HasField("camping_ok") 

1338 

1339 api.UpdateProfile( 

1340 api_pb2.UpdateProfileReq( 

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

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

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

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

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

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

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

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

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

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

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

1352 smoking_allowed=api_pb2.SMOKING_LOCATION_WINDOW, 

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

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

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

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

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

1358 sleeping_arrangement=api_pb2.SLEEPING_ARRANGEMENT_COMMON, 

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

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

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

1362 parking_details=api_pb2.PARKING_DETAILS_PAID_ONSITE, 

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

1364 ) 

1365 ) 

1366 

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

1368 # that it is public information. 

1369 with api_session(token2) as api: 

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

1371 assert res.max_guests.value == 3 

1372 assert res.last_minute.value 

1373 assert not res.has_pets.value 

1374 assert res.accepts_pets.value 

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

1376 assert not res.has_kids.value 

1377 assert res.accepts_kids.value 

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

1379 assert not res.has_housemates.value 

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

1381 assert res.wheelchair_accessible.value 

1382 assert res.smoking_allowed == api_pb2.SMOKING_LOCATION_WINDOW 

1383 assert not res.smokes_at_home.value 

1384 assert res.drinking_allowed.value 

1385 assert not res.drinks_at_home.value 

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

1387 assert res.sleeping_arrangement == api_pb2.SLEEPING_ARRANGEMENT_COMMON 

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

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

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

1391 assert res.parking.value 

1392 assert res.parking_details == api_pb2.PARKING_DETAILS_PAID_ONSITE 

1393 assert not res.camping_ok.value 

1394 

1395 # test unsetting 

1396 with api_session(token1) as api: 

1397 api.UpdateProfile( 

1398 api_pb2.UpdateProfileReq( 

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

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

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

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

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

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

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

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

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

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

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

1410 smoking_allowed=api_pb2.SMOKING_LOCATION_UNKNOWN, 

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

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

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

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

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

1416 sleeping_arrangement=api_pb2.SLEEPING_ARRANGEMENT_UNKNOWN, 

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

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

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

1420 parking_details=api_pb2.PARKING_DETAILS_UNKNOWN, 

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

1422 ) 

1423 ) 

1424 

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

1426 assert not res.HasField("max_guests") 

1427 assert not res.HasField("last_minute") 

1428 assert not res.HasField("has_pets") 

1429 assert not res.HasField("accepts_pets") 

1430 assert not res.HasField("pet_details") 

1431 assert not res.HasField("has_kids") 

1432 assert not res.HasField("accepts_kids") 

1433 assert not res.HasField("kid_details") 

1434 assert not res.HasField("has_housemates") 

1435 assert not res.HasField("housemate_details") 

1436 assert not res.HasField("wheelchair_accessible") 

1437 assert res.smoking_allowed == api_pb2.SMOKING_LOCATION_UNKNOWN 

1438 assert not res.HasField("smokes_at_home") 

1439 assert not res.HasField("drinking_allowed") 

1440 assert not res.HasField("drinks_at_home") 

1441 assert not res.HasField("other_host_info") 

1442 assert res.sleeping_arrangement == api_pb2.SLEEPING_ARRANGEMENT_UNKNOWN 

1443 assert not res.HasField("sleeping_details") 

1444 assert not res.HasField("area") 

1445 assert not res.HasField("house_rules") 

1446 assert not res.HasField("parking") 

1447 assert res.parking_details == api_pb2.PARKING_DETAILS_UNKNOWN 

1448 assert not res.HasField("camping_ok") 

1449 

1450 

1451def test_badges(db): 

1452 user1, _ = generate_user(last_donated=None) 

1453 user2, _ = generate_user(last_donated=None) 

1454 user3, _ = generate_user(last_donated=None) 

1455 user4, token = generate_user(last_donated=None) 

1456 

1457 update_badges(empty_pb2.Empty()) 

1458 

1459 founder_badge = get_badge_dict()["founder"] 

1460 board_member_badge = get_badge_dict()["board_member"] 

1461 

1462 with api_session(token) as api: 

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

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

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

1466 

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

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

1469 assert res.user_ids == [1] 

1470 res2 = api.ListBadgeUsers( 

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

1472 ) 

1473 assert res2.user_ids == [2] 

1474 

1475 

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

1477def test_ListBadgeUsers_excludes_ghost_users(db, flag): 

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

1479 from couchers.helpers.badges import user_add_badge 

1480 

1481 user1, token1 = generate_user() 

1482 user2, _ = generate_user() 

1483 user3, _ = generate_user() 

1484 

1485 volunteer_badge = get_badge_dict()["volunteer"] 

1486 

1487 # Give all three users the volunteer badge 

1488 with session_scope() as session: 

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

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

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

1492 

1493 # Verify all three users appear in the badge list 

1494 with api_session(token1) as api: 

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

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

1497 

1498 # Make user2 invisible (deleted or banned) 

1499 with session_scope() as session: 

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

1501 setattr(db_user2, flag, True) 

1502 

1503 # Now user2 should not appear in the badge list 

1504 with api_session(token1) as api: 

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

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

1507 

1508 

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

1510def test_GetLiteUser_ghost_user_by_username(db, flag): 

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

1512 user1, token1 = generate_user() 

1513 user2, _ = generate_user() 

1514 

1515 # Make user2 invisible 

1516 with session_scope() as session: 

1517 db_user2 = session.merge(user2) 

1518 setattr(db_user2, flag, True) 

1519 session.commit() 

1520 

1521 # Refresh the materialized view 

1522 refresh_materialized_views_rapid(empty_pb2.Empty()) 

1523 

1524 with api_session(token1) as api: 

1525 # Query by username 

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

1527 

1528 assert lite_user.user_id == user2.id 

1529 assert lite_user.username == "ghost" 

1530 assert lite_user.name == "Deactivated Account" 

1531 assert lite_user.lat == 0 

1532 assert lite_user.lng == 0 

1533 assert lite_user.radius == 0 

1534 assert lite_user.city == "" 

1535 assert lite_user.age == 0 

1536 assert lite_user.avatar_url == "" 

1537 assert lite_user.avatar_thumbnail_url == "" 

1538 assert not lite_user.has_strong_verification 

1539 

1540 

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

1542def test_GetLiteUser_ghost_user_by_id(db, flag): 

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

1544 user1, token1 = generate_user() 

1545 user2, _ = generate_user() 

1546 

1547 # Make user2 invisible 

1548 with session_scope() as session: 

1549 db_user2 = session.merge(user2) 

1550 setattr(db_user2, flag, True) 

1551 session.commit() 

1552 

1553 # Refresh the materialized view 

1554 refresh_materialized_views_rapid(empty_pb2.Empty()) 

1555 

1556 with api_session(token1) as api: 

1557 # Query by ID 

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

1559 

1560 assert lite_user.user_id == user2.id 

1561 assert lite_user.username == "ghost" 

1562 assert lite_user.name == "Deactivated Account" 

1563 assert lite_user.lat == 0 

1564 assert lite_user.lng == 0 

1565 assert lite_user.radius == 0 

1566 assert lite_user.city == "" 

1567 assert lite_user.age == 0 

1568 assert lite_user.avatar_url == "" 

1569 assert lite_user.avatar_thumbnail_url == "" 

1570 assert not lite_user.has_strong_verification 

1571 

1572 

1573def test_GetLiteUser_blocked_user(db): 

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

1575 user1, token1 = generate_user() 

1576 user2, _ = generate_user() 

1577 

1578 # User1 blocks user2 

1579 make_user_block(user1, user2) 

1580 

1581 # Refresh the materialized view 

1582 refresh_materialized_views_rapid(empty_pb2.Empty()) 

1583 

1584 with api_session(token1) as api: 

1585 # Query by username 

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

1587 

1588 assert lite_user.user_id == user2.id 

1589 assert lite_user.is_ghost 

1590 assert lite_user.username == "ghost" 

1591 assert lite_user.name == "Deactivated Account" 

1592 

1593 # Query by ID 

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

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 == "Deactivated Account" 

1600 

1601 

1602def test_GetLiteUser_blocking_user(db): 

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

1604 user1, token1 = generate_user() 

1605 user2, _ = generate_user() 

1606 

1607 # User2 blocks user1 

1608 make_user_block(user2, user1) 

1609 

1610 # Refresh the materialized view 

1611 refresh_materialized_views_rapid(empty_pb2.Empty()) 

1612 

1613 with api_session(token1) as api: 

1614 # Query by username 

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

1616 

1617 assert lite_user.user_id == user2.id 

1618 assert lite_user.is_ghost 

1619 assert lite_user.username == "ghost" 

1620 assert lite_user.name == "Deactivated Account" 

1621 

1622 # Query by ID 

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

1624 

1625 assert lite_user.user_id == user2.id 

1626 assert lite_user.is_ghost 

1627 assert lite_user.username == "ghost" 

1628 assert lite_user.name == "Deactivated Account" 

1629 

1630 

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

1632def test_GetLiteUsers_ghost_users(db, flag): 

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

1634 user1, token1 = generate_user() 

1635 user2, _ = generate_user() 

1636 user3, _ = generate_user() 

1637 user4, _ = generate_user() 

1638 

1639 # Make user2 and user4 invisible 

1640 with session_scope() as session: 

1641 db_user2 = session.merge(user2) 

1642 setattr(db_user2, flag, True) 

1643 db_user4 = session.merge(user4) 

1644 setattr(db_user4, flag, True) 

1645 session.commit() 

1646 

1647 # Refresh the materialized view 

1648 refresh_materialized_views_rapid(empty_pb2.Empty()) 

1649 

1650 with api_session(token1) as api: 

1651 res = api.GetLiteUsers( 

1652 api_pb2.GetLiteUsersReq( 

1653 users=[ 

1654 user1.username, # visible 

1655 user2.username, # ghost 

1656 str(user3.id), # visible 

1657 str(user4.id), # ghost 

1658 ] 

1659 ) 

1660 ) 

1661 

1662 assert len(res.responses) == 4 

1663 

1664 # user1 - visible, normal profile 

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

1666 assert not res.responses[0].not_found 

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

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

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

1670 

1671 # user2 - ghost by username 

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

1673 assert not res.responses[1].not_found 

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

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

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

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

1678 

1679 # user3 - visible, normal profile 

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

1681 assert not res.responses[2].not_found 

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

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

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

1685 

1686 # user4 - ghost by ID 

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

1688 assert not res.responses[3].not_found 

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

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

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

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

1693 

1694 

1695def test_GetLiteUsers_blocked_users(db): 

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

1697 user1, token1 = generate_user() 

1698 user2, _ = generate_user() 

1699 user3, _ = generate_user() 

1700 user4, _ = generate_user() 

1701 user5, _ = generate_user() 

1702 

1703 # User1 blocks user2 

1704 make_user_block(user1, user2) 

1705 # User4 blocks user1 

1706 make_user_block(user4, user1) 

1707 

1708 # Refresh the materialized view 

1709 refresh_materialized_views_rapid(empty_pb2.Empty()) 

1710 

1711 with api_session(token1) as api: 

1712 res = api.GetLiteUsers( 

1713 api_pb2.GetLiteUsersReq( 

1714 users=[ 

1715 user2.username, # user1 blocked user2 

1716 str(user3.id), # visible 

1717 user4.username, # user4 blocked user1 

1718 str(user5.id), # visible 

1719 ] 

1720 ) 

1721 ) 

1722 

1723 assert len(res.responses) == 4 

1724 

1725 # user2 - blocked by user1, should be ghost 

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

1727 assert not res.responses[0].not_found 

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

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

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

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

1732 

1733 # user3 - visible 

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

1735 assert not res.responses[1].not_found 

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

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

1738 

1739 # user4 - user4 blocked user1, should be ghost 

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

1741 assert not res.responses[2].not_found 

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

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

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

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

1746 

1747 # user5 - visible 

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

1749 assert not res.responses[3].not_found 

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

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

1752 

1753 

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

1755def test_GetUser_ghost_user_by_id(db, flag): 

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

1757 user1, token1 = generate_user() 

1758 user2, _ = generate_user() 

1759 

1760 # Make user2 invisible 

1761 with session_scope() as session: 

1762 db_user2 = session.merge(user2) 

1763 setattr(db_user2, flag, True) 

1764 session.commit() 

1765 

1766 with api_session(token1) as api: 

1767 # Query by ID 

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

1769 

1770 assert user_pb.user_id == user2.id 

1771 assert user_pb.username == "ghost" 

1772 assert user_pb.name == "Deactivated Account" 

1773 assert user_pb.city == "" 

1774 assert user_pb.hosting_status == 0 

1775 assert user_pb.meetup_status == 0 

1776 

1777 

1778def test_GetUser_blocked_user(db): 

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

1780 user1, token1 = generate_user() 

1781 user2, _ = generate_user() 

1782 

1783 # User1 blocks user2 

1784 make_user_block(user1, user2) 

1785 

1786 with api_session(token1) as api: 

1787 # Query by username 

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

1789 

1790 assert user_pb.user_id == user2.id 

1791 assert user_pb.username == "ghost" 

1792 assert user_pb.name == "Deactivated Account" 

1793 

1794 # Query by ID 

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

1796 

1797 assert user_pb.user_id == user2.id 

1798 assert user_pb.username == "ghost" 

1799 assert user_pb.name == "Deactivated Account" 

1800 

1801 

1802def test_GetUser_blocking_user(db): 

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

1804 user1, token1 = generate_user() 

1805 user2, _ = generate_user() 

1806 

1807 # User2 blocks user1 

1808 make_user_block(user2, user1) 

1809 

1810 with api_session(token1) as api: 

1811 # Query by username 

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

1813 

1814 assert user_pb.user_id == user2.id 

1815 assert user_pb.username == "ghost" 

1816 assert user_pb.name == "Deactivated Account" 

1817 

1818 # Query by ID 

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

1820 

1821 assert user_pb.user_id == user2.id 

1822 assert user_pb.username == "ghost" 

1823 assert user_pb.name == "Deactivated Account"