Coverage for app / backend / src / tests / test_requests.py: 99%

728 statements  

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

1import html 

2import re 

3from datetime import timedelta 

4from urllib.parse import parse_qs, urlparse 

5 

6import grpc 

7import pytest 

8from sqlalchemy import select 

9from sqlalchemy_utils import refresh_materialized_view 

10 

11from couchers.constants import HOST_REQUEST_MIN_LENGTH_UTF16 

12from couchers.crypto import b64decode 

13from couchers.db import session_scope 

14from couchers.i18n import LocalizationContext 

15from couchers.models import ( 

16 Message, 

17 MessageType, 

18 RateLimitAction, 

19) 

20from couchers.proto import ( 

21 api_pb2, 

22 auth_pb2, 

23 conversations_pb2, 

24 requests_pb2, 

25) 

26from couchers.proto.internal import unsubscribe_pb2 

27from couchers.rate_limits.definitions import RATE_LIMIT_DEFINITIONS, RATE_LIMIT_HOURS 

28from couchers.utils import create_coordinate, now, today 

29from tests.fixtures.db import generate_user 

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

31from tests.fixtures.sessions import api_session, auth_api_session, requests_session 

32 

33 

34@pytest.fixture(autouse=True) 

35def _(testconfig): 

36 pass 

37 

38 

39def valid_request_text(text: str = "Test request") -> str: 

40 """Pads a request text to a valid length.""" 

41 # Request lengths are measured in utf-16 code units to match the frontend. 

42 utf16_length = len(text.encode("utf-16-le")) // 2 

43 if utf16_length >= HOST_REQUEST_MIN_LENGTH_UTF16: 43 ↛ 44line 43 didn't jump to line 44 because the condition on line 43 was never true

44 return text 

45 padding_length = HOST_REQUEST_MIN_LENGTH_UTF16 - utf16_length 

46 return text + ("_" * padding_length) # Each "_" adds one utf16 code unit. 

47 

48 

49def test_create_request(db, moderator): 

50 user1, token1 = generate_user() 

51 hosting_city = "Morningside Heights, New York City" 

52 hosting_lat = 40.8086 

53 hosting_lng = -73.9616 

54 hosting_radius = 500 

55 user2, token2 = generate_user( 

56 city=hosting_city, 

57 geom=create_coordinate(hosting_lat, hosting_lng), 

58 geom_radius=hosting_radius, 

59 ) 

60 

61 today_plus_2 = today() + timedelta(days=2) 

62 today_plus_3 = today() + timedelta(days=3) 

63 today_minus_2 = today() - timedelta(days=2) 

64 today_minus_3 = today() - timedelta(days=3) 

65 

66 with requests_session(token1) as api: 

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

68 api.CreateHostRequest( 

69 requests_pb2.CreateHostRequestReq( 

70 host_user_id=user1.id, 

71 from_date=today_plus_2.isoformat(), 

72 to_date=today_plus_3.isoformat(), 

73 text=valid_request_text(), 

74 ) 

75 ) 

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

77 assert e.value.details() == "Can't request hosting from yourself." 

78 

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

80 api.CreateHostRequest( 

81 requests_pb2.CreateHostRequestReq( 

82 host_user_id=999, 

83 from_date=today_plus_2.isoformat(), 

84 to_date=today_plus_3.isoformat(), 

85 text=valid_request_text(), 

86 ) 

87 ) 

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

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

90 

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

92 api.CreateHostRequest( 

93 requests_pb2.CreateHostRequestReq( 

94 host_user_id=user2.id, 

95 from_date=today_plus_3.isoformat(), 

96 to_date=today_plus_2.isoformat(), 

97 text=valid_request_text(), 

98 ) 

99 ) 

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

101 assert e.value.details() == "From date can't be after to date." 

102 

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

104 api.CreateHostRequest( 

105 requests_pb2.CreateHostRequestReq( 

106 host_user_id=user2.id, 

107 from_date=today_minus_3.isoformat(), 

108 to_date=today_plus_2.isoformat(), 

109 text=valid_request_text(), 

110 ) 

111 ) 

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

113 assert e.value.details() == "From date must be today or later." 

114 

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

116 api.CreateHostRequest( 

117 requests_pb2.CreateHostRequestReq( 

118 host_user_id=user2.id, 

119 from_date=today_plus_2.isoformat(), 

120 to_date=today_minus_2.isoformat(), 

121 text=valid_request_text(), 

122 ) 

123 ) 

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

125 assert e.value.details() == "From date can't be after to date." 

126 

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

128 api.CreateHostRequest( 

129 requests_pb2.CreateHostRequestReq( 

130 host_user_id=user2.id, 

131 from_date="2020-00-06", 

132 to_date=today_minus_2.isoformat(), 

133 text=valid_request_text(), 

134 ) 

135 ) 

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

137 assert e.value.details() == "Invalid date." 

138 

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

140 api.CreateHostRequest( 

141 requests_pb2.CreateHostRequestReq( 

142 host_user_id=user2.id, 

143 from_date=today_plus_2.isoformat(), 

144 to_date=today_plus_3.isoformat(), 

145 text="Too short.", 

146 ) 

147 ) 

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

149 assert e.value.details() == "Host request cannot be shorter than 250 characters." 

150 

151 res = api.CreateHostRequest( 

152 requests_pb2.CreateHostRequestReq( 

153 host_user_id=user2.id, 

154 from_date=today_plus_2.isoformat(), 

155 to_date=today_plus_3.isoformat(), 

156 text=valid_request_text(), 

157 ) 

158 ) 

159 host_request_id = res.host_request_id 

160 

161 moderator.approve_host_request(host_request_id) 

162 

163 with requests_session(token1) as api: 

164 host_requests = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_sent=True)).host_requests 

165 

166 assert len(host_requests) == 1 

167 hr = host_requests[0] 

168 

169 assert hr.latest_message.text.text == valid_request_text() 

170 

171 assert hr.hosting_city == hosting_city 

172 assert round(hr.hosting_lat, 4) == hosting_lat 

173 assert round(hr.hosting_lng, 4) == hosting_lng 

174 assert hr.hosting_radius == hosting_radius 

175 

176 today_ = today() 

177 today_plus_one_year = today_ + timedelta(days=365) 

178 today_plus_one_year_plus_2 = today_plus_one_year + timedelta(days=2) 

179 today_plus_one_year_plus_3 = today_plus_one_year + timedelta(days=3) 

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

181 api.CreateHostRequest( 

182 requests_pb2.CreateHostRequestReq( 

183 host_user_id=user2.id, 

184 from_date=today_plus_one_year_plus_2.isoformat(), 

185 to_date=today_plus_one_year_plus_3.isoformat(), 

186 text=valid_request_text("Test from date after one year"), 

187 ) 

188 ) 

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

190 assert e.value.details() == "The start date must be within one year from today." 

191 

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

193 api.CreateHostRequest( 

194 requests_pb2.CreateHostRequestReq( 

195 host_user_id=user2.id, 

196 from_date=today_plus_2.isoformat(), 

197 to_date=today_plus_one_year_plus_3.isoformat(), 

198 text=valid_request_text("Test to date one year after from date"), 

199 ) 

200 ) 

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

202 assert e.value.details() == "You cannot request to stay with someone for longer than one year." 

203 

204 

205def test_create_request_incomplete_profile(db): 

206 user1, token1 = generate_user(complete_profile=False) 

207 user2, _ = generate_user() 

208 today_plus_2 = today() + timedelta(days=2) 

209 today_plus_3 = today() + timedelta(days=3) 

210 with requests_session(token1) as api: 

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

212 api.CreateHostRequest( 

213 requests_pb2.CreateHostRequestReq( 

214 host_user_id=user2.id, 

215 from_date=today_plus_2.isoformat(), 

216 to_date=today_plus_3.isoformat(), 

217 text=valid_request_text(), 

218 ) 

219 ) 

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

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

222 

223 

224def test_excessive_requests_are_reported(db): 

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

226 user, token = generate_user() 

227 today_plus_2 = today() + timedelta(days=2) 

228 today_plus_3 = today() + timedelta(days=3) 

229 rate_limit_definition = RATE_LIMIT_DEFINITIONS[RateLimitAction.host_request] 

230 with requests_session(token) as api: 

231 # Test warning email 

232 with mock_notification_email() as mock_email: 

233 for _ in range(rate_limit_definition.warning_limit): 

234 host_user, _ = generate_user() 

235 _ = api.CreateHostRequest( 

236 requests_pb2.CreateHostRequestReq( 

237 host_user_id=host_user.id, 

238 from_date=today_plus_2.isoformat(), 

239 to_date=today_plus_3.isoformat(), 

240 text=valid_request_text(), 

241 ) 

242 ) 

243 

244 assert mock_email.call_count == 0 

245 host_user, _ = generate_user() 

246 _ = api.CreateHostRequest( 

247 requests_pb2.CreateHostRequestReq( 

248 host_user_id=host_user.id, 

249 from_date=today_plus_2.isoformat(), 

250 to_date=today_plus_3.isoformat(), 

251 text=valid_request_text("Excessive test request"), 

252 ) 

253 ) 

254 assert mock_email.call_count == 1 

255 email = email_fields(mock_email).plain 

256 assert email.startswith( 

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

258 ) 

259 

260 # Test ban after exceeding HOST_REQUEST_HARD_LIMIT 

261 with mock_notification_email() as mock_email: 

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

263 host_user, _ = generate_user() 

264 _ = api.CreateHostRequest( 

265 requests_pb2.CreateHostRequestReq( 

266 host_user_id=host_user.id, 

267 from_date=today_plus_2.isoformat(), 

268 to_date=today_plus_3.isoformat(), 

269 text=valid_request_text(), 

270 ) 

271 ) 

272 

273 assert mock_email.call_count == 0 

274 host_user, _ = generate_user() 

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

276 _ = api.CreateHostRequest( 

277 requests_pb2.CreateHostRequestReq( 

278 host_user_id=host_user.id, 

279 from_date=today_plus_2.isoformat(), 

280 to_date=today_plus_3.isoformat(), 

281 text=valid_request_text("Excessive test request"), 

282 ) 

283 ) 

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

285 assert ( 

286 exc_info.value.details() 

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

288 ) 

289 

290 assert mock_email.call_count == 1 

291 email = email_fields(mock_email).plain 

292 assert email.startswith( 

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

294 ) 

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

296 

297 

298def add_message(db, text, author_id, conversation_id): 

299 with session_scope() as session: 

300 message = Message( 

301 conversation_id=conversation_id, author_id=author_id, text=text, message_type=MessageType.text 

302 ) 

303 

304 session.add(message) 

305 

306 

307def test_GetHostRequest(db): 

308 user1, token1 = generate_user() 

309 user2, token2 = generate_user() 

310 user3, token3 = generate_user() 

311 today_plus_2 = today() + timedelta(days=2) 

312 today_plus_3 = today() + timedelta(days=3) 

313 with requests_session(token1) as api: 

314 host_request_id = api.CreateHostRequest( 

315 requests_pb2.CreateHostRequestReq( 

316 host_user_id=user2.id, 

317 from_date=today_plus_2.isoformat(), 

318 to_date=today_plus_3.isoformat(), 

319 text=valid_request_text("Test request 1"), 

320 ) 

321 ).host_request_id 

322 

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

324 api.GetHostRequest(requests_pb2.GetHostRequestReq(host_request_id=999)) 

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

326 assert e.value.details() == "Couldn't find that host request." 

327 

328 api.SendHostRequestMessage( 

329 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 1") 

330 ) 

331 

332 res = api.GetHostRequest(requests_pb2.GetHostRequestReq(host_request_id=host_request_id)) 

333 assert res.latest_message.text.text == "Test message 1" 

334 

335 

336def test_ListHostRequests(db, moderator): 

337 user1, token1 = generate_user() 

338 user2, token2 = generate_user() 

339 user3, token3 = generate_user() 

340 today_plus_2 = today() + timedelta(days=2) 

341 today_plus_3 = today() + timedelta(days=3) 

342 with requests_session(token1) as api: 

343 host_request_1 = api.CreateHostRequest( 

344 requests_pb2.CreateHostRequestReq( 

345 host_user_id=user2.id, 

346 from_date=today_plus_2.isoformat(), 

347 to_date=today_plus_3.isoformat(), 

348 text=valid_request_text("Test request 1"), 

349 ) 

350 ).host_request_id 

351 

352 host_request_2 = api.CreateHostRequest( 

353 requests_pb2.CreateHostRequestReq( 

354 host_user_id=user3.id, 

355 from_date=today_plus_2.isoformat(), 

356 to_date=today_plus_3.isoformat(), 

357 text=valid_request_text("Test request 2"), 

358 ) 

359 ).host_request_id 

360 

361 moderator.approve_host_request(host_request_1) 

362 moderator.approve_host_request(host_request_2) 

363 

364 with requests_session(token1) as api: 

365 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_sent=True)) 

366 assert res.no_more 

367 assert len(res.host_requests) == 2 

368 

369 with requests_session(token2) as api: 

370 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_received=True)) 

371 assert res.no_more 

372 assert len(res.host_requests) == 1 

373 assert res.host_requests[0].latest_message.text.text == valid_request_text("Test request 1") 

374 assert res.host_requests[0].surfer_user_id == user1.id 

375 assert res.host_requests[0].host_user_id == user2.id 

376 assert res.host_requests[0].status == conversations_pb2.HOST_REQUEST_STATUS_PENDING 

377 

378 add_message(db, "Test request 1 message 1", user2.id, host_request_1) 

379 add_message(db, "Test request 1 message 2", user2.id, host_request_1) 

380 add_message(db, "Test request 1 message 3", user2.id, host_request_1) 

381 

382 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_received=True)) 

383 assert res.host_requests[0].latest_message.text.text == "Test request 1 message 3" 

384 

385 host_request_3 = api.CreateHostRequest( 

386 requests_pb2.CreateHostRequestReq( 

387 host_user_id=user1.id, 

388 from_date=today_plus_2.isoformat(), 

389 to_date=today_plus_3.isoformat(), 

390 text=valid_request_text("Test request 3"), 

391 ) 

392 ).host_request_id 

393 

394 moderator.approve_host_request(host_request_3) 

395 

396 add_message(db, "Test request 2 message 1", user1.id, host_request_2) 

397 add_message(db, "Test request 2 message 2", user3.id, host_request_2) 

398 

399 with requests_session(token3) as api: 

400 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_received=True)) 

401 assert res.no_more 

402 assert len(res.host_requests) == 1 

403 assert res.host_requests[0].latest_message.text.text == "Test request 2 message 2" 

404 

405 with requests_session(token1) as api: 

406 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_received=True)) 

407 assert len(res.host_requests) == 1 

408 

409 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq()) 

410 assert len(res.host_requests) == 3 

411 

412 

413def test_ListHostRequests_pagination_regression(db, moderator): 

414 """ 

415 ListHostRequests was skipping a request when getting multiple pages 

416 """ 

417 user1, token1 = generate_user() 

418 user2, token2 = generate_user() 

419 today_plus_2 = today() + timedelta(days=2) 

420 today_plus_3 = today() + timedelta(days=3) 

421 with requests_session(token1) as api: 

422 host_request_1 = api.CreateHostRequest( 

423 requests_pb2.CreateHostRequestReq( 

424 host_user_id=user2.id, 

425 from_date=today_plus_2.isoformat(), 

426 to_date=today_plus_3.isoformat(), 

427 text=valid_request_text("Test request 1"), 

428 ) 

429 ).host_request_id 

430 

431 host_request_2 = api.CreateHostRequest( 

432 requests_pb2.CreateHostRequestReq( 

433 host_user_id=user2.id, 

434 from_date=today_plus_2.isoformat(), 

435 to_date=today_plus_3.isoformat(), 

436 text=valid_request_text("Test request 2"), 

437 ) 

438 ).host_request_id 

439 

440 host_request_3 = api.CreateHostRequest( 

441 requests_pb2.CreateHostRequestReq( 

442 host_user_id=user2.id, 

443 from_date=today_plus_2.isoformat(), 

444 to_date=today_plus_3.isoformat(), 

445 text=valid_request_text("Test request 3"), 

446 ) 

447 ).host_request_id 

448 

449 moderator.approve_host_request(host_request_1) 

450 moderator.approve_host_request(host_request_2) 

451 moderator.approve_host_request(host_request_3) 

452 

453 with requests_session(token2) as api: 

454 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_received=True)) 

455 assert res.no_more 

456 assert len(res.host_requests) == 3 

457 assert res.host_requests[0].latest_message.text.text == valid_request_text("Test request 3") 

458 assert res.host_requests[1].latest_message.text.text == valid_request_text("Test request 2") 

459 assert res.host_requests[2].latest_message.text.text == valid_request_text("Test request 1") 

460 

461 with requests_session(token2) as api: 

462 api.RespondHostRequest( 

463 requests_pb2.RespondHostRequestReq( 

464 host_request_id=host_request_2, 

465 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED, 

466 text="Accepting host request 2", 

467 ) 

468 ) 

469 api.RespondHostRequest( 

470 requests_pb2.RespondHostRequestReq( 

471 host_request_id=host_request_1, 

472 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED, 

473 text="Accepting host request 1", 

474 ) 

475 ) 

476 api.RespondHostRequest( 

477 requests_pb2.RespondHostRequestReq( 

478 host_request_id=host_request_3, 

479 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED, 

480 text="Accepting host request 3", 

481 ) 

482 ) 

483 

484 with requests_session(token2) as api: 

485 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_received=True)) 

486 assert res.no_more 

487 assert len(res.host_requests) == 3 

488 assert res.host_requests[0].latest_message.text.text == "Accepting host request 3" 

489 assert res.host_requests[1].latest_message.text.text == "Accepting host request 1" 

490 assert res.host_requests[2].latest_message.text.text == "Accepting host request 2" 

491 

492 with requests_session(token2) as api: 

493 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_received=True, number=1)) 

494 assert not res.no_more 

495 assert len(res.host_requests) == 1 

496 assert res.host_requests[0].latest_message.text.text == "Accepting host request 3" 

497 res = api.ListHostRequests( 

498 requests_pb2.ListHostRequestsReq(only_received=True, number=1, last_request_id=res.last_request_id) 

499 ) 

500 assert not res.no_more 

501 assert len(res.host_requests) == 1 

502 assert res.host_requests[0].latest_message.text.text == "Accepting host request 1" 

503 res = api.ListHostRequests( 

504 requests_pb2.ListHostRequestsReq(only_received=True, number=1, last_request_id=res.last_request_id) 

505 ) 

506 assert res.no_more 

507 assert len(res.host_requests) == 1 

508 assert res.host_requests[0].latest_message.text.text == "Accepting host request 2" 

509 

510 

511def test_ListHostRequests_active_filter(db, moderator): 

512 user1, token1 = generate_user() 

513 user2, token2 = generate_user() 

514 today_plus_2 = today() + timedelta(days=2) 

515 today_plus_3 = today() + timedelta(days=3) 

516 

517 with requests_session(token1) as api: 

518 request_id = api.CreateHostRequest( 

519 requests_pb2.CreateHostRequestReq( 

520 host_user_id=user2.id, 

521 from_date=today_plus_2.isoformat(), 

522 to_date=today_plus_3.isoformat(), 

523 text=valid_request_text("Test request 1"), 

524 ) 

525 ).host_request_id 

526 

527 moderator.approve_host_request(request_id) 

528 

529 with requests_session(token1) as api: 

530 api.RespondHostRequest( 

531 requests_pb2.RespondHostRequestReq( 

532 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED 

533 ) 

534 ) 

535 

536 with requests_session(token2) as api: 

537 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_received=True)) 

538 assert len(res.host_requests) == 1 

539 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_active=True)) 

540 assert len(res.host_requests) == 0 

541 

542 

543def test_RespondHostRequests(db, moderator): 

544 user1, token1 = generate_user() 

545 user2, token2 = generate_user() 

546 user3, token3 = generate_user() 

547 today_plus_2 = today() + timedelta(days=2) 

548 today_plus_3 = today() + timedelta(days=3) 

549 

550 with requests_session(token1) as api: 

551 request_id = api.CreateHostRequest( 

552 requests_pb2.CreateHostRequestReq( 

553 host_user_id=user2.id, 

554 from_date=today_plus_2.isoformat(), 

555 to_date=today_plus_3.isoformat(), 

556 text=valid_request_text("Test request 1"), 

557 ) 

558 ).host_request_id 

559 

560 moderator.approve_host_request(request_id) 

561 

562 # another user can't access 

563 with requests_session(token3) as api: 

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

565 api.RespondHostRequest( 

566 requests_pb2.RespondHostRequestReq( 

567 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED 

568 ) 

569 ) 

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

571 assert e.value.details() == "Couldn't find that host request." 

572 

573 with requests_session(token1) as api: 

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

575 api.RespondHostRequest( 

576 requests_pb2.RespondHostRequestReq( 

577 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED 

578 ) 

579 ) 

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

581 assert e.value.details() == "You are not the host of this request." 

582 

583 with requests_session(token2) as api: 

584 # non existing id 

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

586 api.RespondHostRequest( 

587 requests_pb2.RespondHostRequestReq( 

588 host_request_id=9999, status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED 

589 ) 

590 ) 

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

592 

593 # host can't confirm or cancel (host should accept/reject) 

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

595 api.RespondHostRequest( 

596 requests_pb2.RespondHostRequestReq( 

597 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CONFIRMED 

598 ) 

599 ) 

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

601 assert e.value.details() == "You can't set the host request status to that." 

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

603 api.RespondHostRequest( 

604 requests_pb2.RespondHostRequestReq( 

605 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED 

606 ) 

607 ) 

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

609 assert e.value.details() == "You can't set the host request status to that." 

610 

611 api.RespondHostRequest( 

612 requests_pb2.RespondHostRequestReq( 

613 host_request_id=request_id, 

614 status=conversations_pb2.HOST_REQUEST_STATUS_REJECTED, 

615 text="Test rejection message", 

616 ) 

617 ) 

618 res = api.GetHostRequestMessages(requests_pb2.GetHostRequestMessagesReq(host_request_id=request_id)) 

619 assert res.messages[0].text.text == "Test rejection message" 

620 assert res.messages[1].WhichOneof("content") == "host_request_status_changed" 

621 assert res.messages[1].host_request_status_changed.status == conversations_pb2.HOST_REQUEST_STATUS_REJECTED 

622 # should be able to move from rejected -> accepted 

623 api.RespondHostRequest( 

624 requests_pb2.RespondHostRequestReq( 

625 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED 

626 ) 

627 ) 

628 

629 with requests_session(token1) as api: 

630 # can't make pending 

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

632 api.RespondHostRequest( 

633 requests_pb2.RespondHostRequestReq( 

634 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_PENDING 

635 ) 

636 ) 

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

638 assert e.value.details() == "You can't set the host request status to that." 

639 

640 # can confirm then cancel 

641 api.RespondHostRequest( 

642 requests_pb2.RespondHostRequestReq( 

643 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CONFIRMED 

644 ) 

645 ) 

646 

647 api.RespondHostRequest( 

648 requests_pb2.RespondHostRequestReq( 

649 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED 

650 ) 

651 ) 

652 

653 # can't confirm after having cancelled 

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

655 api.RespondHostRequest( 

656 requests_pb2.RespondHostRequestReq( 

657 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CONFIRMED 

658 ) 

659 ) 

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

661 assert e.value.details() == "You can't set the host request status to that." 

662 

663 # at this point there should be 7 messages 

664 # 2 for creation, 2 for the status change with message, 3 for the other status changed 

665 with requests_session(token1) as api: 

666 res = api.GetHostRequestMessages(requests_pb2.GetHostRequestMessagesReq(host_request_id=request_id)) 

667 assert len(res.messages) == 7 

668 assert res.messages[0].host_request_status_changed.status == conversations_pb2.HOST_REQUEST_STATUS_CANCELLED 

669 assert res.messages[1].host_request_status_changed.status == conversations_pb2.HOST_REQUEST_STATUS_CONFIRMED 

670 assert res.messages[2].host_request_status_changed.status == conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED 

671 assert res.messages[4].host_request_status_changed.status == conversations_pb2.HOST_REQUEST_STATUS_REJECTED 

672 assert res.messages[6].WhichOneof("content") == "chat_created" 

673 

674 

675def test_get_host_request_messages(db, moderator): 

676 user1, token1 = generate_user() 

677 user2, token2 = generate_user() 

678 today_plus_2 = today() + timedelta(days=2) 

679 today_plus_3 = today() + timedelta(days=3) 

680 with requests_session(token1) as api: 

681 res = api.CreateHostRequest( 

682 requests_pb2.CreateHostRequestReq( 

683 host_user_id=user2.id, 

684 from_date=today_plus_2.isoformat(), 

685 to_date=today_plus_3.isoformat(), 

686 text=valid_request_text("Test request 1"), 

687 ) 

688 ) 

689 conversation_id = res.host_request_id 

690 

691 moderator.approve_host_request(conversation_id) 

692 

693 add_message(db, "Test request 1 message 1", user1.id, conversation_id) 

694 add_message(db, "Test request 1 message 2", user1.id, conversation_id) 

695 add_message(db, "Test request 1 message 3", user1.id, conversation_id) 

696 

697 with requests_session(token2) as api: 

698 api.RespondHostRequest( 

699 requests_pb2.RespondHostRequestReq( 

700 host_request_id=conversation_id, status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED 

701 ) 

702 ) 

703 

704 add_message(db, "Test request 1 message 4", user2.id, conversation_id) 

705 add_message(db, "Test request 1 message 5", user2.id, conversation_id) 

706 

707 api.RespondHostRequest( 

708 requests_pb2.RespondHostRequestReq( 

709 host_request_id=conversation_id, status=conversations_pb2.HOST_REQUEST_STATUS_REJECTED 

710 ) 

711 ) 

712 

713 with requests_session(token1) as api: 

714 # 9 including initial message 

715 res = api.GetHostRequestMessages(requests_pb2.GetHostRequestMessagesReq(host_request_id=conversation_id)) 

716 assert len(res.messages) == 9 

717 assert res.no_more 

718 

719 res = api.GetHostRequestMessages( 

720 requests_pb2.GetHostRequestMessagesReq(host_request_id=conversation_id, number=3) 

721 ) 

722 assert not res.no_more 

723 assert len(res.messages) == 3 

724 assert res.messages[0].host_request_status_changed.status == conversations_pb2.HOST_REQUEST_STATUS_REJECTED 

725 assert res.messages[0].WhichOneof("content") == "host_request_status_changed" 

726 assert res.messages[1].text.text == "Test request 1 message 5" 

727 assert res.messages[2].text.text == "Test request 1 message 4" 

728 

729 res = api.GetHostRequestMessages( 

730 requests_pb2.GetHostRequestMessagesReq( 

731 host_request_id=conversation_id, 

732 last_message_id=res.messages[2].message_id, 

733 number=6, 

734 ) 

735 ) 

736 assert res.no_more 

737 assert len(res.messages) == 6 

738 assert res.messages[0].host_request_status_changed.status == conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED 

739 assert res.messages[0].WhichOneof("content") == "host_request_status_changed" 

740 assert res.messages[1].text.text == "Test request 1 message 3" 

741 assert res.messages[2].text.text == "Test request 1 message 2" 

742 assert res.messages[3].text.text == "Test request 1 message 1" 

743 assert res.messages[4].text.text == valid_request_text("Test request 1") 

744 assert res.messages[5].WhichOneof("content") == "chat_created" 

745 

746 

747def test_SendHostRequestMessage(db, moderator): 

748 user1, token1 = generate_user() 

749 user2, token2 = generate_user() 

750 user3, token3 = generate_user() 

751 today_plus_2 = today() + timedelta(days=2) 

752 today_plus_3 = today() + timedelta(days=3) 

753 with requests_session(token1) as api: 

754 host_request_id = api.CreateHostRequest( 

755 requests_pb2.CreateHostRequestReq( 

756 host_user_id=user2.id, 

757 from_date=today_plus_2.isoformat(), 

758 to_date=today_plus_3.isoformat(), 

759 text=valid_request_text("Test request 1"), 

760 ) 

761 ).host_request_id 

762 

763 moderator.approve_host_request(host_request_id) 

764 

765 with requests_session(token1) as api: 

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

767 api.SendHostRequestMessage( 

768 requests_pb2.SendHostRequestMessageReq(host_request_id=999, text="Test message 1") 

769 ) 

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

771 

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

773 api.SendHostRequestMessage(requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="")) 

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

775 assert e.value.details() == "Invalid message." 

776 

777 api.SendHostRequestMessage( 

778 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 1") 

779 ) 

780 res = api.GetHostRequestMessages(requests_pb2.GetHostRequestMessagesReq(host_request_id=host_request_id)) 

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

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

783 

784 with requests_session(token3) as api: 

785 # other user can't send 

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

787 api.SendHostRequestMessage( 

788 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 2") 

789 ) 

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

791 assert e.value.details() == "Couldn't find that host request." 

792 

793 with requests_session(token2) as api: 

794 api.SendHostRequestMessage( 

795 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 2") 

796 ) 

797 res = api.GetHostRequestMessages(requests_pb2.GetHostRequestMessagesReq(host_request_id=host_request_id)) 

798 # including 2 for creation control message and message 

799 assert len(res.messages) == 4 

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

801 assert res.messages[0].author_user_id == user2.id 

802 

803 # CAN send messages to a rejected, confirmed or cancelled request, and for accepted 

804 api.RespondHostRequest( 

805 requests_pb2.RespondHostRequestReq( 

806 host_request_id=host_request_id, status=conversations_pb2.HOST_REQUEST_STATUS_REJECTED 

807 ) 

808 ) 

809 api.SendHostRequestMessage( 

810 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 3") 

811 ) 

812 

813 api.RespondHostRequest( 

814 requests_pb2.RespondHostRequestReq( 

815 host_request_id=host_request_id, status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED 

816 ) 

817 ) 

818 

819 with requests_session(token1) as api: 

820 api.RespondHostRequest( 

821 requests_pb2.RespondHostRequestReq( 

822 host_request_id=host_request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CONFIRMED 

823 ) 

824 ) 

825 api.SendHostRequestMessage( 

826 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 3") 

827 ) 

828 

829 api.RespondHostRequest( 

830 requests_pb2.RespondHostRequestReq( 

831 host_request_id=host_request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED 

832 ) 

833 ) 

834 api.SendHostRequestMessage( 

835 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 3") 

836 ) 

837 

838 

839def test_get_updates(db, moderator): 

840 user1, token1 = generate_user() 

841 user2, token2 = generate_user() 

842 user3, token3 = generate_user() 

843 today_plus_2 = today() + timedelta(days=2) 

844 today_plus_3 = today() + timedelta(days=3) 

845 with requests_session(token1) as api: 

846 host_request_id = api.CreateHostRequest( 

847 requests_pb2.CreateHostRequestReq( 

848 host_user_id=user2.id, 

849 from_date=today_plus_2.isoformat(), 

850 to_date=today_plus_3.isoformat(), 

851 text=valid_request_text("Test message 0"), 

852 ) 

853 ).host_request_id 

854 

855 moderator.approve_host_request(host_request_id) 

856 

857 with requests_session(token1) as api: 

858 api.SendHostRequestMessage( 

859 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 1") 

860 ) 

861 api.SendHostRequestMessage( 

862 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 2") 

863 ) 

864 api.RespondHostRequest( 

865 requests_pb2.RespondHostRequestReq( 

866 host_request_id=host_request_id, 

867 status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED, 

868 text="Test message 3", 

869 ) 

870 ) 

871 

872 api.CreateHostRequest( 

873 requests_pb2.CreateHostRequestReq( 

874 host_user_id=user2.id, 

875 from_date=today_plus_2.isoformat(), 

876 to_date=today_plus_3.isoformat(), 

877 text=valid_request_text("Test message 4"), 

878 ) 

879 ) 

880 

881 res = api.GetHostRequestMessages(requests_pb2.GetHostRequestMessagesReq(host_request_id=host_request_id)) 

882 assert len(res.messages) == 6 

883 assert res.messages[0].text.text == "Test message 3" 

884 assert res.messages[1].host_request_status_changed.status == conversations_pb2.HOST_REQUEST_STATUS_CANCELLED 

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

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

887 assert res.messages[4].text.text == valid_request_text("Test message 0") 

888 message_id_3 = res.messages[0].message_id 

889 message_id_cancel = res.messages[1].message_id 

890 message_id_2 = res.messages[2].message_id 

891 message_id_1 = res.messages[3].message_id 

892 message_id_0 = res.messages[4].message_id 

893 

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

895 api.GetHostRequestUpdates(requests_pb2.GetHostRequestUpdatesReq(newest_message_id=0)) 

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

897 

898 res = api.GetHostRequestUpdates(requests_pb2.GetHostRequestUpdatesReq(newest_message_id=message_id_1)) 

899 assert res.no_more 

900 assert len(res.updates) == 5 

901 assert res.updates[0].message.text.text == "Test message 2" 

902 assert ( 

903 res.updates[1].message.host_request_status_changed.status == conversations_pb2.HOST_REQUEST_STATUS_CANCELLED 

904 ) 

905 assert res.updates[1].status == conversations_pb2.HOST_REQUEST_STATUS_CANCELLED 

906 assert res.updates[2].message.text.text == "Test message 3" 

907 assert res.updates[3].message.WhichOneof("content") == "chat_created" 

908 assert res.updates[3].status == conversations_pb2.HOST_REQUEST_STATUS_PENDING 

909 assert res.updates[4].message.text.text == valid_request_text("Test message 4") 

910 

911 res = api.GetHostRequestUpdates(requests_pb2.GetHostRequestUpdatesReq(newest_message_id=message_id_1, number=1)) 

912 assert not res.no_more 

913 assert len(res.updates) == 1 

914 assert res.updates[0].message.text.text == "Test message 2" 

915 assert res.updates[0].status == conversations_pb2.HOST_REQUEST_STATUS_CANCELLED 

916 

917 with requests_session(token3) as api: 

918 # other user can't access 

919 res = api.GetHostRequestUpdates(requests_pb2.GetHostRequestUpdatesReq(newest_message_id=message_id_1)) 

920 assert len(res.updates) == 0 

921 

922 

923def test_archive_host_request(db, moderator): 

924 user1, token1 = generate_user() 

925 user2, token2 = generate_user() 

926 

927 today_plus_2 = today() + timedelta(days=2) 

928 today_plus_3 = today() + timedelta(days=3) 

929 

930 with requests_session(token1) as api: 

931 host_request_id = api.CreateHostRequest( 

932 requests_pb2.CreateHostRequestReq( 

933 host_user_id=user2.id, 

934 from_date=today_plus_2.isoformat(), 

935 to_date=today_plus_3.isoformat(), 

936 text=valid_request_text("Test message 0"), 

937 ) 

938 ).host_request_id 

939 

940 api.SendHostRequestMessage( 

941 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 1") 

942 ) 

943 api.SendHostRequestMessage( 

944 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 2") 

945 ) 

946 

947 moderator.approve_host_request(host_request_id) 

948 

949 # happy path archiving host request 

950 with requests_session(token1) as api: 

951 api.RespondHostRequest( 

952 requests_pb2.RespondHostRequestReq( 

953 host_request_id=host_request_id, 

954 status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED, 

955 text="Test message 3", 

956 ) 

957 ) 

958 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_sent=True)) 

959 assert len(res.host_requests) == 1 

960 assert res.host_requests[0].status == conversations_pb2.HOST_REQUEST_STATUS_CANCELLED 

961 api.SetHostRequestArchiveStatus( 

962 requests_pb2.SetHostRequestArchiveStatusReq(host_request_id=host_request_id, is_archived=True) 

963 ) 

964 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_archived=True)) 

965 assert len(res.host_requests) == 1 

966 

967 

968def test_mark_last_seen(db, moderator): 

969 user1, token1 = generate_user() 

970 user2, token2 = generate_user() 

971 user3, token3 = generate_user() 

972 today_plus_2 = today() + timedelta(days=2) 

973 today_plus_3 = today() + timedelta(days=3) 

974 with requests_session(token1) as api: 

975 host_request_id = api.CreateHostRequest( 

976 requests_pb2.CreateHostRequestReq( 

977 host_user_id=user2.id, 

978 from_date=today_plus_2.isoformat(), 

979 to_date=today_plus_3.isoformat(), 

980 text=valid_request_text("Test message 0"), 

981 ) 

982 ).host_request_id 

983 

984 host_request_id_2 = api.CreateHostRequest( 

985 requests_pb2.CreateHostRequestReq( 

986 host_user_id=user2.id, 

987 from_date=today_plus_2.isoformat(), 

988 to_date=today_plus_3.isoformat(), 

989 text=valid_request_text("Test message 0a"), 

990 ) 

991 ).host_request_id 

992 

993 moderator.approve_host_request(host_request_id) 

994 moderator.approve_host_request(host_request_id_2) 

995 

996 with requests_session(token1) as api: 

997 api.SendHostRequestMessage( 

998 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 1") 

999 ) 

1000 api.SendHostRequestMessage( 

1001 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 2") 

1002 ) 

1003 api.RespondHostRequest( 

1004 requests_pb2.RespondHostRequestReq( 

1005 host_request_id=host_request_id, 

1006 status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED, 

1007 text="Test message 3", 

1008 ) 

1009 ) 

1010 

1011 moderator.approve_host_request(host_request_id) 

1012 moderator.approve_host_request(host_request_id_2) 

1013 

1014 # test Ping unseen host request count, should be automarked after sending 

1015 with api_session(token1) as api: 

1016 assert api.Ping(api_pb2.PingReq()).unseen_received_host_request_count == 0 

1017 assert api.Ping(api_pb2.PingReq()).unseen_sent_host_request_count == 0 

1018 

1019 with api_session(token2) as api: 

1020 assert api.Ping(api_pb2.PingReq()).unseen_received_host_request_count == 2 

1021 assert api.Ping(api_pb2.PingReq()).unseen_sent_host_request_count == 0 

1022 

1023 with requests_session(token2) as api: 

1024 assert api.ListHostRequests(requests_pb2.ListHostRequestsReq()).host_requests[0].last_seen_message_id == 0 

1025 

1026 api.MarkLastSeenHostRequest( 

1027 requests_pb2.MarkLastSeenHostRequestReq(host_request_id=host_request_id, last_seen_message_id=3) 

1028 ) 

1029 

1030 assert api.ListHostRequests(requests_pb2.ListHostRequestsReq()).host_requests[0].last_seen_message_id == 3 

1031 

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

1033 api.MarkLastSeenHostRequest( 

1034 requests_pb2.MarkLastSeenHostRequestReq(host_request_id=host_request_id, last_seen_message_id=1) 

1035 ) 

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

1037 assert e.value.details() == "You can't unsee messages." 

1038 

1039 # this will be used to test sent request notifications 

1040 host_request_id_3 = api.CreateHostRequest( 

1041 requests_pb2.CreateHostRequestReq( 

1042 host_user_id=user1.id, 

1043 from_date=today_plus_2.isoformat(), 

1044 to_date=today_plus_3.isoformat(), 

1045 text=valid_request_text("Another test request"), 

1046 ) 

1047 ).host_request_id 

1048 

1049 moderator.approve_host_request(host_request_id_3) 

1050 

1051 with requests_session(token2) as api: 

1052 # this should make id_2 all read 

1053 api.SendHostRequestMessage( 

1054 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id_2, text="Test") 

1055 ) 

1056 

1057 with api_session(token2) as api: 

1058 assert api.Ping(api_pb2.PingReq()).unseen_received_host_request_count == 1 

1059 assert api.Ping(api_pb2.PingReq()).unseen_sent_host_request_count == 0 

1060 

1061 # make sure sent and received count for unseen notifications 

1062 with requests_session(token1) as api: 

1063 api.SendHostRequestMessage( 

1064 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id_3, text="Test message") 

1065 ) 

1066 

1067 with api_session(token2) as api: 

1068 assert api.Ping(api_pb2.PingReq()).unseen_received_host_request_count == 1 

1069 assert api.Ping(api_pb2.PingReq()).unseen_sent_host_request_count == 1 

1070 

1071 

1072def test_response_rate(db, moderator): 

1073 user1, token1 = generate_user() 

1074 user2, token2 = generate_user() 

1075 user3, token3 = generate_user(delete_user=True) 

1076 

1077 today_plus_2 = today() + timedelta(days=2) 

1078 today_plus_3 = today() + timedelta(days=3) 

1079 

1080 with session_scope() as session: 

1081 refresh_materialized_view(session, "user_response_rates") 

1082 

1083 with requests_session(token1) as api: 

1084 # deleted: not found 

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

1086 api.GetResponseRate(requests_pb2.GetResponseRateReq(user_id=user3.id)) 

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

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

1089 

1090 # no requests: insufficient 

1091 res = api.GetResponseRate(requests_pb2.GetResponseRateReq(user_id=user2.id)) 

1092 assert res.HasField("insufficient_data") 

1093 

1094 # send a request and back date it by 36 hours 

1095 host_request_1 = api.CreateHostRequest( 

1096 requests_pb2.CreateHostRequestReq( 

1097 host_user_id=user2.id, 

1098 from_date=today_plus_2.isoformat(), 

1099 to_date=today_plus_3.isoformat(), 

1100 text=valid_request_text("Test request"), 

1101 ) 

1102 ).host_request_id 

1103 moderator.approve_host_request(host_request_1) 

1104 with session_scope() as session: 

1105 session.execute( 

1106 select(Message) 

1107 .where(Message.conversation_id == host_request_1) 

1108 .where(Message.message_type == MessageType.chat_created) 

1109 ).scalar_one().time = now() - timedelta(hours=36) 

1110 refresh_materialized_view(session, "user_response_rates") 

1111 

1112 # still insufficient 

1113 res = api.GetResponseRate(requests_pb2.GetResponseRateReq(user_id=user2.id)) 

1114 assert res.HasField("insufficient_data") 

1115 

1116 # send a request and back date it by 35 hours 

1117 host_request_2 = api.CreateHostRequest( 

1118 requests_pb2.CreateHostRequestReq( 

1119 host_user_id=user2.id, 

1120 from_date=today_plus_2.isoformat(), 

1121 to_date=today_plus_3.isoformat(), 

1122 text=valid_request_text("Test request"), 

1123 ) 

1124 ).host_request_id 

1125 moderator.approve_host_request(host_request_2) 

1126 with session_scope() as session: 

1127 session.execute( 

1128 select(Message) 

1129 .where(Message.conversation_id == host_request_2) 

1130 .where(Message.message_type == MessageType.chat_created) 

1131 ).scalar_one().time = now() - timedelta(hours=35) 

1132 refresh_materialized_view(session, "user_response_rates") 

1133 

1134 # still insufficient 

1135 res = api.GetResponseRate(requests_pb2.GetResponseRateReq(user_id=user2.id)) 

1136 assert res.HasField("insufficient_data") 

1137 

1138 # send a request and back date it by 34 hours 

1139 host_request_3 = api.CreateHostRequest( 

1140 requests_pb2.CreateHostRequestReq( 

1141 host_user_id=user2.id, 

1142 from_date=today_plus_2.isoformat(), 

1143 to_date=today_plus_3.isoformat(), 

1144 text=valid_request_text("Test request"), 

1145 ) 

1146 ).host_request_id 

1147 moderator.approve_host_request(host_request_3) 

1148 with session_scope() as session: 

1149 session.execute( 

1150 select(Message) 

1151 .where(Message.conversation_id == host_request_3) 

1152 .where(Message.message_type == MessageType.chat_created) 

1153 ).scalar_one().time = now() - timedelta(hours=34) 

1154 refresh_materialized_view(session, "user_response_rates") 

1155 

1156 # now low 

1157 res = api.GetResponseRate(requests_pb2.GetResponseRateReq(user_id=user2.id)) 

1158 assert res.HasField("low") 

1159 

1160 with requests_session(token2) as api: 

1161 # accept a host req 

1162 api.RespondHostRequest( 

1163 requests_pb2.RespondHostRequestReq( 

1164 host_request_id=host_request_2, 

1165 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED, 

1166 text="Accepting host request", 

1167 ) 

1168 ) 

1169 

1170 with session_scope() as session: 

1171 refresh_materialized_view(session, "user_response_rates") 

1172 

1173 with requests_session(token1) as api: 

1174 # now some w p33 = 35h 

1175 res = api.GetResponseRate(requests_pb2.GetResponseRateReq(user_id=user2.id)) 

1176 assert res.HasField("some") 

1177 assert res.some.response_time_p33.ToTimedelta() == timedelta(hours=35) 

1178 

1179 with requests_session(token2) as api: 

1180 # accept another host req 

1181 api.RespondHostRequest( 

1182 requests_pb2.RespondHostRequestReq( 

1183 host_request_id=host_request_3, 

1184 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED, 

1185 text="Accepting host request", 

1186 ) 

1187 ) 

1188 

1189 with session_scope() as session: 

1190 refresh_materialized_view(session, "user_response_rates") 

1191 

1192 with requests_session(token1) as api: 

1193 # now most w p33 = 34h, p66 = 35h 

1194 res = api.GetResponseRate(requests_pb2.GetResponseRateReq(user_id=user2.id)) 

1195 assert res.HasField("most") 

1196 assert res.most.response_time_p33.ToTimedelta() == timedelta(hours=34) 

1197 assert res.most.response_time_p66.ToTimedelta() == timedelta(hours=35) 

1198 

1199 with requests_session(token2) as api: 

1200 # accept last host req 

1201 api.RespondHostRequest( 

1202 requests_pb2.RespondHostRequestReq( 

1203 host_request_id=host_request_1, 

1204 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED, 

1205 text="Accepting host request", 

1206 ) 

1207 ) 

1208 

1209 with session_scope() as session: 

1210 refresh_materialized_view(session, "user_response_rates") 

1211 

1212 with requests_session(token1) as api: 

1213 # now all w p33 = 34h, p66 = 35h 

1214 res = api.GetResponseRate(requests_pb2.GetResponseRateReq(user_id=user2.id)) 

1215 assert res.HasField("almost_all") 

1216 assert res.almost_all.response_time_p33.ToTimedelta() == timedelta(hours=34) 

1217 assert res.almost_all.response_time_p66.ToTimedelta() == timedelta(hours=35) 

1218 

1219 # send a request and back date it by 2 hours 

1220 host_request_4 = api.CreateHostRequest( 

1221 requests_pb2.CreateHostRequestReq( 

1222 host_user_id=user2.id, 

1223 from_date=today_plus_2.isoformat(), 

1224 to_date=today_plus_3.isoformat(), 

1225 text=valid_request_text("Test request"), 

1226 ) 

1227 ).host_request_id 

1228 moderator.approve_host_request(host_request_4) 

1229 with session_scope() as session: 

1230 session.execute( 

1231 select(Message) 

1232 .where(Message.conversation_id == host_request_4) 

1233 .where(Message.message_type == MessageType.chat_created) 

1234 ).scalar_one().time = now() - timedelta(hours=2) 

1235 refresh_materialized_view(session, "user_response_rates") 

1236 

1237 # send a request and back date it by 4 hours 

1238 host_request_5 = api.CreateHostRequest( 

1239 requests_pb2.CreateHostRequestReq( 

1240 host_user_id=user2.id, 

1241 from_date=today_plus_2.isoformat(), 

1242 to_date=today_plus_3.isoformat(), 

1243 text=valid_request_text("Test request"), 

1244 ) 

1245 ).host_request_id 

1246 moderator.approve_host_request(host_request_5) 

1247 with session_scope() as session: 

1248 session.execute( 

1249 select(Message) 

1250 .where(Message.conversation_id == host_request_5) 

1251 .where(Message.message_type == MessageType.chat_created) 

1252 ).scalar_one().time = now() - timedelta(hours=4) 

1253 refresh_materialized_view(session, "user_response_rates") 

1254 

1255 # now some w p33 = 35h 

1256 res = api.GetResponseRate(requests_pb2.GetResponseRateReq(user_id=user2.id)) 

1257 assert res.HasField("some") 

1258 assert res.some.response_time_p33.ToTimedelta() == timedelta(hours=35) 

1259 

1260 with requests_session(token2) as api: 

1261 # accept host req 

1262 api.RespondHostRequest( 

1263 requests_pb2.RespondHostRequestReq( 

1264 host_request_id=host_request_5, 

1265 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED, 

1266 text="Accepting host request", 

1267 ) 

1268 ) 

1269 

1270 with session_scope() as session: 

1271 refresh_materialized_view(session, "user_response_rates") 

1272 

1273 with requests_session(token1) as api: 

1274 # now most w p33 = 34h, p66 = 36h 

1275 res = api.GetResponseRate(requests_pb2.GetResponseRateReq(user_id=user2.id)) 

1276 assert res.HasField("most") 

1277 assert res.most.response_time_p33.ToTimedelta() == timedelta(hours=34) 

1278 assert res.most.response_time_p66.ToTimedelta() == timedelta(hours=36) 

1279 

1280 with requests_session(token2) as api: 

1281 # accept host req 

1282 api.RespondHostRequest( 

1283 requests_pb2.RespondHostRequestReq( 

1284 host_request_id=host_request_4, 

1285 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED, 

1286 text="Accepting host request", 

1287 ) 

1288 ) 

1289 

1290 with session_scope() as session: 

1291 refresh_materialized_view(session, "user_response_rates") 

1292 

1293 with requests_session(token1) as api: 

1294 # now most w p33 = 4h, p66 = 35h 

1295 res = api.GetResponseRate(requests_pb2.GetResponseRateReq(user_id=user2.id)) 

1296 assert res.HasField("almost_all") 

1297 assert res.almost_all.response_time_p33.ToTimedelta() == timedelta(hours=4) 

1298 assert res.almost_all.response_time_p66.ToTimedelta() == timedelta(hours=35) 

1299 

1300 

1301def test_request_notifications(db, push_collector: PushCollector, moderator): 

1302 host, host_token = generate_user(complete_profile=True) 

1303 surfer, surfer_token = generate_user(complete_profile=True) 

1304 

1305 host_loc_context = LocalizationContext.from_user(host) 

1306 surfer_loc_context = LocalizationContext.from_user(surfer) 

1307 

1308 today_plus_2 = today() + timedelta(days=2) 

1309 today_plus_3 = today() + timedelta(days=3) 

1310 

1311 with requests_session(surfer_token) as api: 

1312 hr_id = api.CreateHostRequest( 

1313 requests_pb2.CreateHostRequestReq( 

1314 host_user_id=host.id, 

1315 from_date=today_plus_2.isoformat(), 

1316 to_date=today_plus_3.isoformat(), 

1317 text=valid_request_text("can i stay plz"), 

1318 ) 

1319 ).host_request_id 

1320 

1321 with mock_notification_email() as mock: 

1322 moderator.approve_host_request(hr_id) 

1323 

1324 mock.assert_called_once() 

1325 e = email_fields(mock) 

1326 assert e.recipient == host.email 

1327 assert "host request" in e.subject.lower() 

1328 assert host.name in e.plain 

1329 assert host.name in e.html 

1330 assert "quick decline" in e.plain.lower(), e.plain 

1331 assert "quick decline" in e.html.lower() 

1332 assert surfer.name in e.plain 

1333 assert surfer.name in e.html 

1334 assert host_loc_context.localize_date(today_plus_2) in e.plain 

1335 assert host_loc_context.localize_date(today_plus_2) in e.html 

1336 assert host_loc_context.localize_date(today_plus_3) in e.plain 

1337 assert host_loc_context.localize_date(today_plus_3) in e.html 

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

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

1340 assert f"http://localhost:3000/messages/request/{hr_id}" in e.plain 

1341 assert f"http://localhost:3000/messages/request/{hr_id}" in e.html 

1342 

1343 assert push_collector.pop_for_user(host.id, last=True).content.title == f"New host request from {surfer.name}" 

1344 

1345 with requests_session(host_token) as api: 

1346 with mock_notification_email() as mock: 

1347 api.RespondHostRequest( 

1348 requests_pb2.RespondHostRequestReq( 

1349 host_request_id=hr_id, 

1350 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED, 

1351 text="Accepting host request", 

1352 ) 

1353 ) 

1354 

1355 e = email_fields(mock) 

1356 assert e.recipient == surfer.email 

1357 assert "host request" in e.subject.lower() 

1358 assert host.name in e.plain 

1359 assert host.name in e.html 

1360 assert surfer.name in e.plain 

1361 assert surfer.name in e.html 

1362 assert surfer_loc_context.localize_date(today_plus_2) in e.plain 

1363 assert surfer_loc_context.localize_date(today_plus_2) in e.html 

1364 assert surfer_loc_context.localize_date(today_plus_3) in e.plain 

1365 assert surfer_loc_context.localize_date(today_plus_3) in e.html 

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

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

1368 assert f"http://localhost:3000/messages/request/{hr_id}" in e.plain 

1369 assert f"http://localhost:3000/messages/request/{hr_id}" in e.html 

1370 

1371 assert push_collector.pop_for_user(surfer.id, last=True).content.title == f"{host.name} accepted your host request" 

1372 

1373 

1374def test_quick_decline(db, push_collector: PushCollector, moderator): 

1375 host, host_token = generate_user(complete_profile=True) 

1376 surfer, surfer_token = generate_user(complete_profile=True) 

1377 

1378 host_loc_context = LocalizationContext.from_user(host) 

1379 

1380 today_plus_2 = today() + timedelta(days=2) 

1381 today_plus_3 = today() + timedelta(days=3) 

1382 

1383 with requests_session(surfer_token) as api: 

1384 hr_id = api.CreateHostRequest( 

1385 requests_pb2.CreateHostRequestReq( 

1386 host_user_id=host.id, 

1387 from_date=today_plus_2.isoformat(), 

1388 to_date=today_plus_3.isoformat(), 

1389 text=valid_request_text("can i stay plz"), 

1390 ) 

1391 ).host_request_id 

1392 

1393 with mock_notification_email() as mock: 

1394 moderator.approve_host_request(hr_id) 

1395 

1396 mock.assert_called_once() 

1397 e = email_fields(mock) 

1398 assert e.recipient == host.email 

1399 assert "host request" in e.subject.lower() 

1400 assert host.name in e.plain 

1401 assert host.name in e.html 

1402 assert "quick decline" in e.plain.lower(), e.plain 

1403 assert "quick decline" in e.html.lower() 

1404 assert surfer.name in e.plain 

1405 assert surfer.name in e.html 

1406 assert host_loc_context.localize_date(today_plus_2) in e.plain 

1407 assert host_loc_context.localize_date(today_plus_2) in e.html 

1408 assert host_loc_context.localize_date(today_plus_3) in e.plain 

1409 assert host_loc_context.localize_date(today_plus_3) in e.html 

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

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

1412 assert f"http://localhost:3000/messages/request/{hr_id}" in e.plain 

1413 assert f"http://localhost:3000/messages/request/{hr_id}" in e.html 

1414 

1415 assert push_collector.pop_for_user(host.id, last=True).content.title == f"New host request from {surfer.name}" 

1416 

1417 # very ugly 

1418 # http://localhost:3000/quick-link?payload=CAEiGAoOZnJpZW5kX3JlcXVlc3QSBmFjY2VwdA==&sig=BQdk024NTATm8zlR0krSXTBhP5U9TlFv7VhJeIHZtUg= 

1419 for link in re.findall(r'<a href="(.*?)"', email_fields(mock).html): 1419 ↛ 1438line 1419 didn't jump to line 1438 because the loop on line 1419 didn't complete

1420 if "payload" not in link: 

1421 continue 

1422 print(link) 

1423 url_parts = urlparse(html.unescape(link)) 

1424 params = parse_qs(url_parts.query) 

1425 print(params["payload"][0]) 

1426 payload = unsubscribe_pb2.UnsubscribePayload.FromString(b64decode(params["payload"][0])) 

1427 if payload.HasField("host_request_quick_decline"): 1427 ↛ 1419line 1427 didn't jump to line 1419 because the condition on line 1427 was always true

1428 with auth_api_session() as (auth_api, metadata_interceptor): 

1429 res = auth_api.Unsubscribe( 

1430 auth_pb2.UnsubscribeReq( 

1431 payload=b64decode(params["payload"][0]), 

1432 sig=b64decode(params["sig"][0]), 

1433 ) 

1434 ) 

1435 assert res.response == "Thank you for responding to the host request!" 

1436 break 

1437 else: 

1438 raise Exception("Didn't find link") 

1439 

1440 with requests_session(surfer_token) as api: 

1441 res = api.GetHostRequest(requests_pb2.GetHostRequestReq(host_request_id=hr_id)) 

1442 assert res.status == conversations_pb2.HOST_REQUEST_STATUS_REJECTED 

1443 

1444 

1445def test_host_req_feedback(db, moderator): 

1446 host, host_token = generate_user(complete_profile=True) 

1447 host2, host2_token = generate_user(complete_profile=True) 

1448 host3, host3_token = generate_user(complete_profile=True) 

1449 surfer, surfer_token = generate_user(complete_profile=True) 

1450 

1451 today_plus_2 = today() + timedelta(days=2) 

1452 today_plus_3 = today() + timedelta(days=3) 

1453 

1454 with requests_session(surfer_token) as api: 

1455 hr_id = api.CreateHostRequest( 

1456 requests_pb2.CreateHostRequestReq( 

1457 host_user_id=host.id, 

1458 from_date=today_plus_2.isoformat(), 

1459 to_date=today_plus_3.isoformat(), 

1460 text=valid_request_text("can i stay plz"), 

1461 ) 

1462 ).host_request_id 

1463 hr2_id = api.CreateHostRequest( 

1464 requests_pb2.CreateHostRequestReq( 

1465 host_user_id=host2.id, 

1466 from_date=today_plus_2.isoformat(), 

1467 to_date=today_plus_3.isoformat(), 

1468 text=valid_request_text("can i stay plz"), 

1469 ) 

1470 ).host_request_id 

1471 hr3_id = api.CreateHostRequest( 

1472 requests_pb2.CreateHostRequestReq( 

1473 host_user_id=host3.id, 

1474 from_date=today_plus_2.isoformat(), 

1475 to_date=today_plus_3.isoformat(), 

1476 text=valid_request_text("can i stay plz"), 

1477 ) 

1478 ).host_request_id 

1479 

1480 moderator.approve_host_request(hr_id) 

1481 moderator.approve_host_request(hr2_id) 

1482 moderator.approve_host_request(hr3_id) 

1483 

1484 with requests_session(host_token) as api: 

1485 res = api.GetHostRequest(requests_pb2.GetHostRequestReq(host_request_id=hr_id)) 

1486 assert not res.need_host_request_feedback 

1487 

1488 api.RespondHostRequest( 

1489 requests_pb2.RespondHostRequestReq( 

1490 host_request_id=hr_id, 

1491 status=conversations_pb2.HOST_REQUEST_STATUS_REJECTED, 

1492 ) 

1493 ) 

1494 

1495 res = api.GetHostRequest(requests_pb2.GetHostRequestReq(host_request_id=hr_id)) 

1496 assert res.need_host_request_feedback 

1497 

1498 # surfer can't leave feedback 

1499 with requests_session(surfer_token) as api: 

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

1501 api.SendHostRequestFeedback( 

1502 requests_pb2.SendHostRequestFeedbackReq( 

1503 host_request_id=hr_id, 

1504 ) 

1505 ) 

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

1507 assert e.value.details() == "Couldn't find that host request." 

1508 

1509 with requests_session(host_token) as api: 

1510 api.SendHostRequestFeedback( 

1511 requests_pb2.SendHostRequestFeedbackReq( 

1512 host_request_id=hr_id, 

1513 host_request_quality=requests_pb2.HOST_REQUEST_QUALITY_LOW, 

1514 ) 

1515 ) 

1516 res = api.GetHostRequest(requests_pb2.GetHostRequestReq(host_request_id=hr_id)) 

1517 assert not res.need_host_request_feedback 

1518 

1519 # can't leave it twice 

1520 with requests_session(host_token) as api: 

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

1522 api.SendHostRequestFeedback( 

1523 requests_pb2.SendHostRequestFeedbackReq( 

1524 host_request_id=hr_id, 

1525 ) 

1526 ) 

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

1528 assert e.value.details() == "You have already left feedback for this host request!" 

1529 

1530 res = api.GetHostRequest(requests_pb2.GetHostRequestReq(host_request_id=hr_id)) 

1531 assert not res.need_host_request_feedback 

1532 

1533 with requests_session(host2_token) as api: 

1534 api.RespondHostRequest( 

1535 requests_pb2.RespondHostRequestReq( 

1536 host_request_id=hr2_id, status=conversations_pb2.HOST_REQUEST_STATUS_REJECTED 

1537 ) 

1538 ) 

1539 # can't leave feedback on the wrong one 

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

1541 api.SendHostRequestFeedback( 

1542 requests_pb2.SendHostRequestFeedbackReq( 

1543 host_request_id=hr_id, 

1544 ) 

1545 ) 

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

1547 assert e.value.details() == "Couldn't find that host request." 

1548 

1549 # null feedback is still feedback 

1550 api.SendHostRequestFeedback(requests_pb2.SendHostRequestFeedbackReq(host_request_id=hr2_id)) 

1551 

1552 with requests_session(host3_token) as api: 

1553 api.RespondHostRequest( 

1554 requests_pb2.RespondHostRequestReq( 

1555 host_request_id=hr3_id, status=conversations_pb2.HOST_REQUEST_STATUS_REJECTED 

1556 ) 

1557 ) 

1558 

1559 api.SendHostRequestFeedback( 

1560 requests_pb2.SendHostRequestFeedbackReq(host_request_id=hr3_id, decline_reason="bad req") 

1561 )