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

722 statements  

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

1import re 

2from datetime import timedelta 

3from urllib.parse import parse_qs, urlparse 

4 

5import grpc 

6import pytest 

7 

8from couchers.constants import HOST_REQUEST_MIN_LENGTH_UTF16 

9from couchers.crypto import b64decode 

10from couchers.db import session_scope 

11from couchers.materialized_views import refresh_materialized_view 

12from couchers.models import ( 

13 Message, 

14 MessageType, 

15 RateLimitAction, 

16) 

17from couchers.proto import ( 

18 api_pb2, 

19 auth_pb2, 

20 conversations_pb2, 

21 requests_pb2, 

22) 

23from couchers.proto.internal import unsubscribe_pb2 

24from couchers.rate_limits.definitions import RATE_LIMIT_DEFINITIONS, RATE_LIMIT_HOURS 

25from couchers.sql import couchers_select as select 

26from couchers.templates.v2 import v2date 

27from couchers.utils import create_coordinate, now, today 

28from tests.test_fixtures import ( # noqa 

29 api_session, 

30 auth_api_session, 

31 db, 

32 email_fields, 

33 generate_user, 

34 mock_notification_email, 

35 moderator, 

36 push_collector, 

37 requests_session, 

38 testconfig, 

39) 

40 

41 

42@pytest.fixture(autouse=True) 

43def _(testconfig): 

44 pass 

45 

46 

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

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

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

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

51 if utf16_length >= HOST_REQUEST_MIN_LENGTH_UTF16: 

52 return text 

53 padding_length = HOST_REQUEST_MIN_LENGTH_UTF16 - utf16_length 

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

55 

56 

57def test_create_request(db, moderator): 

58 user1, token1 = generate_user() 

59 hosting_city = "Morningside Heights, New York City" 

60 hosting_lat = 40.8086 

61 hosting_lng = -73.9616 

62 hosting_radius = 500 

63 user2, token2 = generate_user( 

64 city=hosting_city, 

65 geom=create_coordinate(hosting_lat, hosting_lng), 

66 geom_radius=hosting_radius, 

67 ) 

68 

69 today_plus_2 = (today() + timedelta(days=2)).isoformat() 

70 today_plus_3 = (today() + timedelta(days=3)).isoformat() 

71 today_minus_2 = (today() - timedelta(days=2)).isoformat() 

72 today_minus_3 = (today() - timedelta(days=3)).isoformat() 

73 

74 with requests_session(token1) as api: 

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

76 api.CreateHostRequest( 

77 requests_pb2.CreateHostRequestReq( 

78 host_user_id=user1.id, from_date=today_plus_2, to_date=today_plus_3, text=valid_request_text() 

79 ) 

80 ) 

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

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

83 

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

85 api.CreateHostRequest( 

86 requests_pb2.CreateHostRequestReq( 

87 host_user_id=999, from_date=today_plus_2, to_date=today_plus_3, text=valid_request_text() 

88 ) 

89 ) 

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

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

92 

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

94 api.CreateHostRequest( 

95 requests_pb2.CreateHostRequestReq( 

96 host_user_id=user2.id, from_date=today_plus_3, to_date=today_plus_2, text=valid_request_text() 

97 ) 

98 ) 

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

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

101 

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

103 api.CreateHostRequest( 

104 requests_pb2.CreateHostRequestReq( 

105 host_user_id=user2.id, from_date=today_minus_3, to_date=today_plus_2, text=valid_request_text() 

106 ) 

107 ) 

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

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

110 

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

112 api.CreateHostRequest( 

113 requests_pb2.CreateHostRequestReq( 

114 host_user_id=user2.id, from_date=today_plus_2, to_date=today_minus_2, text=valid_request_text() 

115 ) 

116 ) 

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

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

119 

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

121 api.CreateHostRequest( 

122 requests_pb2.CreateHostRequestReq( 

123 host_user_id=user2.id, from_date="2020-00-06", to_date=today_minus_2, text=valid_request_text() 

124 ) 

125 ) 

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

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

128 

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

130 api.CreateHostRequest( 

131 requests_pb2.CreateHostRequestReq( 

132 host_user_id=user2.id, from_date=today_plus_2, to_date=today_plus_3, text="Too short." 

133 ) 

134 ) 

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

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

137 

138 res = api.CreateHostRequest( 

139 requests_pb2.CreateHostRequestReq( 

140 host_user_id=user2.id, 

141 from_date=today_plus_2, 

142 to_date=today_plus_3, 

143 text=valid_request_text(), 

144 ) 

145 ) 

146 host_request_id = res.host_request_id 

147 

148 moderator.approve_host_request(host_request_id) 

149 

150 with requests_session(token1) as api: 

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

152 

153 assert len(host_requests) == 1 

154 hr = host_requests[0] 

155 

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

157 

158 assert hr.hosting_city == hosting_city 

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

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

161 assert hr.hosting_radius == hosting_radius 

162 

163 today_ = today() 

164 today_plus_one_year = today_ + timedelta(days=365) 

165 today_plus_one_year_plus_2 = (today_plus_one_year + timedelta(days=2)).isoformat() 

166 today_plus_one_year_plus_3 = (today_plus_one_year + timedelta(days=3)).isoformat() 

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

168 api.CreateHostRequest( 

169 requests_pb2.CreateHostRequestReq( 

170 host_user_id=user2.id, 

171 from_date=today_plus_one_year_plus_2, 

172 to_date=today_plus_one_year_plus_3, 

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

174 ) 

175 ) 

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

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

178 

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

180 api.CreateHostRequest( 

181 requests_pb2.CreateHostRequestReq( 

182 host_user_id=user2.id, 

183 from_date=today_plus_2, 

184 to_date=today_plus_one_year_plus_3, 

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

186 ) 

187 ) 

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

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

190 

191 

192def test_create_request_incomplete_profile(db): 

193 user1, token1 = generate_user(complete_profile=False) 

194 user2, _ = generate_user() 

195 today_plus_2 = (today() + timedelta(days=2)).isoformat() 

196 today_plus_3 = (today() + timedelta(days=3)).isoformat() 

197 with requests_session(token1) as api: 

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

199 api.CreateHostRequest( 

200 requests_pb2.CreateHostRequestReq( 

201 host_user_id=user2.id, from_date=today_plus_2, to_date=today_plus_3, text=valid_request_text() 

202 ) 

203 ) 

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

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

206 

207 

208def test_excessive_requests_are_reported(db): 

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

210 user, token = generate_user() 

211 today_plus_2 = (today() + timedelta(days=2)).isoformat() 

212 today_plus_3 = (today() + timedelta(days=3)).isoformat() 

213 rate_limit_definition = RATE_LIMIT_DEFINITIONS[RateLimitAction.host_request] 

214 with requests_session(token) as api: 

215 # Test warning email 

216 with mock_notification_email() as mock_email: 

217 for _ in range(rate_limit_definition.warning_limit): 

218 host_user, _ = generate_user() 

219 _ = api.CreateHostRequest( 

220 requests_pb2.CreateHostRequestReq( 

221 host_user_id=host_user.id, 

222 from_date=today_plus_2, 

223 to_date=today_plus_3, 

224 text=valid_request_text(), 

225 ) 

226 ) 

227 

228 assert mock_email.call_count == 0 

229 host_user, _ = generate_user() 

230 _ = api.CreateHostRequest( 

231 requests_pb2.CreateHostRequestReq( 

232 host_user_id=host_user.id, 

233 from_date=today_plus_2, 

234 to_date=today_plus_3, 

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

236 ) 

237 ) 

238 assert mock_email.call_count == 1 

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

240 assert email.startswith( 

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

242 ) 

243 

244 # Test ban after exceeding HOST_REQUEST_HARD_LIMIT 

245 with mock_notification_email() as mock_email: 

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

247 host_user, _ = generate_user() 

248 _ = api.CreateHostRequest( 

249 requests_pb2.CreateHostRequestReq( 

250 host_user_id=host_user.id, 

251 from_date=today_plus_2, 

252 to_date=today_plus_3, 

253 text=valid_request_text(), 

254 ) 

255 ) 

256 

257 assert mock_email.call_count == 0 

258 host_user, _ = generate_user() 

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

260 _ = api.CreateHostRequest( 

261 requests_pb2.CreateHostRequestReq( 

262 host_user_id=host_user.id, 

263 from_date=today_plus_2, 

264 to_date=today_plus_3, 

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

266 ) 

267 ) 

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

269 assert ( 

270 exc_info.value.details() 

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

272 ) 

273 

274 assert mock_email.call_count == 1 

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

276 assert email.startswith( 

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

278 ) 

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

280 

281 

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

283 with session_scope() as session: 

284 message = Message( 

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

286 ) 

287 

288 session.add(message) 

289 

290 

291def test_GetHostRequest(db): 

292 user1, token1 = generate_user() 

293 user2, token2 = generate_user() 

294 user3, token3 = generate_user() 

295 today_plus_2 = (today() + timedelta(days=2)).isoformat() 

296 today_plus_3 = (today() + timedelta(days=3)).isoformat() 

297 with requests_session(token1) as api: 

298 host_request_id = api.CreateHostRequest( 

299 requests_pb2.CreateHostRequestReq( 

300 host_user_id=user2.id, 

301 from_date=today_plus_2, 

302 to_date=today_plus_3, 

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

304 ) 

305 ).host_request_id 

306 

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

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

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

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

311 

312 api.SendHostRequestMessage( 

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

314 ) 

315 

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

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

318 

319 

320def test_ListHostRequests(db, moderator): 

321 user1, token1 = generate_user() 

322 user2, token2 = generate_user() 

323 user3, token3 = generate_user() 

324 today_plus_2 = (today() + timedelta(days=2)).isoformat() 

325 today_plus_3 = (today() + timedelta(days=3)).isoformat() 

326 with requests_session(token1) as api: 

327 host_request_1 = api.CreateHostRequest( 

328 requests_pb2.CreateHostRequestReq( 

329 host_user_id=user2.id, 

330 from_date=today_plus_2, 

331 to_date=today_plus_3, 

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

333 ) 

334 ).host_request_id 

335 

336 host_request_2 = api.CreateHostRequest( 

337 requests_pb2.CreateHostRequestReq( 

338 host_user_id=user3.id, 

339 from_date=today_plus_2, 

340 to_date=today_plus_3, 

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

342 ) 

343 ).host_request_id 

344 

345 moderator.approve_host_request(host_request_1) 

346 moderator.approve_host_request(host_request_2) 

347 

348 with requests_session(token1) as api: 

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

350 assert res.no_more 

351 assert len(res.host_requests) == 2 

352 

353 with requests_session(token2) as api: 

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

355 assert res.no_more 

356 assert len(res.host_requests) == 1 

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

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

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

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

361 

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

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

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

365 

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

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

368 

369 host_request_3 = api.CreateHostRequest( 

370 requests_pb2.CreateHostRequestReq( 

371 host_user_id=user1.id, 

372 from_date=today_plus_2, 

373 to_date=today_plus_3, 

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

375 ) 

376 ).host_request_id 

377 

378 moderator.approve_host_request(host_request_3) 

379 

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

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

382 

383 with requests_session(token3) as api: 

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

385 assert res.no_more 

386 assert len(res.host_requests) == 1 

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

388 

389 with requests_session(token1) as api: 

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

391 assert len(res.host_requests) == 1 

392 

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

394 assert len(res.host_requests) == 3 

395 

396 

397def test_ListHostRequests_pagination_regression(db, moderator): 

398 """ 

399 ListHostRequests was skipping a request when getting multiple pages 

400 """ 

401 user1, token1 = generate_user() 

402 user2, token2 = generate_user() 

403 today_plus_2 = (today() + timedelta(days=2)).isoformat() 

404 today_plus_3 = (today() + timedelta(days=3)).isoformat() 

405 with requests_session(token1) as api: 

406 host_request_1 = api.CreateHostRequest( 

407 requests_pb2.CreateHostRequestReq( 

408 host_user_id=user2.id, 

409 from_date=today_plus_2, 

410 to_date=today_plus_3, 

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

412 ) 

413 ).host_request_id 

414 

415 host_request_2 = api.CreateHostRequest( 

416 requests_pb2.CreateHostRequestReq( 

417 host_user_id=user2.id, 

418 from_date=today_plus_2, 

419 to_date=today_plus_3, 

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

421 ) 

422 ).host_request_id 

423 

424 host_request_3 = api.CreateHostRequest( 

425 requests_pb2.CreateHostRequestReq( 

426 host_user_id=user2.id, 

427 from_date=today_plus_2, 

428 to_date=today_plus_3, 

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

430 ) 

431 ).host_request_id 

432 

433 moderator.approve_host_request(host_request_1) 

434 moderator.approve_host_request(host_request_2) 

435 moderator.approve_host_request(host_request_3) 

436 

437 with requests_session(token2) as api: 

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

439 assert res.no_more 

440 assert len(res.host_requests) == 3 

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

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

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

444 

445 with requests_session(token2) as api: 

446 api.RespondHostRequest( 

447 requests_pb2.RespondHostRequestReq( 

448 host_request_id=host_request_2, 

449 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED, 

450 text="Accepting host request 2", 

451 ) 

452 ) 

453 api.RespondHostRequest( 

454 requests_pb2.RespondHostRequestReq( 

455 host_request_id=host_request_1, 

456 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED, 

457 text="Accepting host request 1", 

458 ) 

459 ) 

460 api.RespondHostRequest( 

461 requests_pb2.RespondHostRequestReq( 

462 host_request_id=host_request_3, 

463 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED, 

464 text="Accepting host request 3", 

465 ) 

466 ) 

467 

468 with requests_session(token2) as api: 

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

470 assert res.no_more 

471 assert len(res.host_requests) == 3 

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

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

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

475 

476 with requests_session(token2) as api: 

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

478 assert not res.no_more 

479 assert len(res.host_requests) == 1 

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

481 res = api.ListHostRequests( 

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

483 ) 

484 assert not res.no_more 

485 assert len(res.host_requests) == 1 

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

487 res = api.ListHostRequests( 

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

489 ) 

490 assert res.no_more 

491 assert len(res.host_requests) == 1 

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

493 

494 

495def test_ListHostRequests_active_filter(db, moderator): 

496 user1, token1 = generate_user() 

497 user2, token2 = generate_user() 

498 today_plus_2 = (today() + timedelta(days=2)).isoformat() 

499 today_plus_3 = (today() + timedelta(days=3)).isoformat() 

500 

501 with requests_session(token1) as api: 

502 request_id = api.CreateHostRequest( 

503 requests_pb2.CreateHostRequestReq( 

504 host_user_id=user2.id, 

505 from_date=today_plus_2, 

506 to_date=today_plus_3, 

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

508 ) 

509 ).host_request_id 

510 

511 moderator.approve_host_request(request_id) 

512 

513 with requests_session(token1) as api: 

514 api.RespondHostRequest( 

515 requests_pb2.RespondHostRequestReq( 

516 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED 

517 ) 

518 ) 

519 

520 with requests_session(token2) as api: 

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

522 assert len(res.host_requests) == 1 

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

524 assert len(res.host_requests) == 0 

525 

526 

527def test_RespondHostRequests(db, moderator): 

528 user1, token1 = generate_user() 

529 user2, token2 = generate_user() 

530 user3, token3 = generate_user() 

531 today_plus_2 = (today() + timedelta(days=2)).isoformat() 

532 today_plus_3 = (today() + timedelta(days=3)).isoformat() 

533 

534 with requests_session(token1) as api: 

535 request_id = api.CreateHostRequest( 

536 requests_pb2.CreateHostRequestReq( 

537 host_user_id=user2.id, 

538 from_date=today_plus_2, 

539 to_date=today_plus_3, 

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

541 ) 

542 ).host_request_id 

543 

544 moderator.approve_host_request(request_id) 

545 

546 # another user can't access 

547 with requests_session(token3) as api: 

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

549 api.RespondHostRequest( 

550 requests_pb2.RespondHostRequestReq( 

551 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED 

552 ) 

553 ) 

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

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

556 

557 with requests_session(token1) as api: 

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

559 api.RespondHostRequest( 

560 requests_pb2.RespondHostRequestReq( 

561 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED 

562 ) 

563 ) 

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

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

566 

567 with requests_session(token2) as api: 

568 # non existing id 

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

570 api.RespondHostRequest( 

571 requests_pb2.RespondHostRequestReq( 

572 host_request_id=9999, status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED 

573 ) 

574 ) 

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

576 

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

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

579 api.RespondHostRequest( 

580 requests_pb2.RespondHostRequestReq( 

581 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CONFIRMED 

582 ) 

583 ) 

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

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

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

587 api.RespondHostRequest( 

588 requests_pb2.RespondHostRequestReq( 

589 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED 

590 ) 

591 ) 

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

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

594 

595 api.RespondHostRequest( 

596 requests_pb2.RespondHostRequestReq( 

597 host_request_id=request_id, 

598 status=conversations_pb2.HOST_REQUEST_STATUS_REJECTED, 

599 text="Test rejection message", 

600 ) 

601 ) 

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

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

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

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

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

607 api.RespondHostRequest( 

608 requests_pb2.RespondHostRequestReq( 

609 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED 

610 ) 

611 ) 

612 

613 with requests_session(token1) as api: 

614 # can't make pending 

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

616 api.RespondHostRequest( 

617 requests_pb2.RespondHostRequestReq( 

618 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_PENDING 

619 ) 

620 ) 

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

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

623 

624 # can confirm then cancel 

625 api.RespondHostRequest( 

626 requests_pb2.RespondHostRequestReq( 

627 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CONFIRMED 

628 ) 

629 ) 

630 

631 api.RespondHostRequest( 

632 requests_pb2.RespondHostRequestReq( 

633 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED 

634 ) 

635 ) 

636 

637 # can't confirm after having cancelled 

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

639 api.RespondHostRequest( 

640 requests_pb2.RespondHostRequestReq( 

641 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CONFIRMED 

642 ) 

643 ) 

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

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

646 

647 # at this point there should be 7 messages 

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

649 with requests_session(token1) as api: 

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

651 assert len(res.messages) == 7 

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

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

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

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

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

657 

658 

659def test_get_host_request_messages(db, moderator): 

660 user1, token1 = generate_user() 

661 user2, token2 = generate_user() 

662 today_plus_2 = (today() + timedelta(days=2)).isoformat() 

663 today_plus_3 = (today() + timedelta(days=3)).isoformat() 

664 with requests_session(token1) as api: 

665 res = api.CreateHostRequest( 

666 requests_pb2.CreateHostRequestReq( 

667 host_user_id=user2.id, 

668 from_date=today_plus_2, 

669 to_date=today_plus_3, 

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

671 ) 

672 ) 

673 conversation_id = res.host_request_id 

674 

675 moderator.approve_host_request(conversation_id) 

676 

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

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

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

680 

681 with requests_session(token2) as api: 

682 api.RespondHostRequest( 

683 requests_pb2.RespondHostRequestReq( 

684 host_request_id=conversation_id, status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED 

685 ) 

686 ) 

687 

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

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

690 

691 api.RespondHostRequest( 

692 requests_pb2.RespondHostRequestReq( 

693 host_request_id=conversation_id, status=conversations_pb2.HOST_REQUEST_STATUS_REJECTED 

694 ) 

695 ) 

696 

697 with requests_session(token1) as api: 

698 # 9 including initial message 

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

700 assert len(res.messages) == 9 

701 assert res.no_more 

702 

703 res = api.GetHostRequestMessages( 

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

705 ) 

706 assert not res.no_more 

707 assert len(res.messages) == 3 

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

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

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

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

712 

713 res = api.GetHostRequestMessages( 

714 requests_pb2.GetHostRequestMessagesReq( 

715 host_request_id=conversation_id, 

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

717 number=6, 

718 ) 

719 ) 

720 assert res.no_more 

721 assert len(res.messages) == 6 

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

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

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

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

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

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

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

729 

730 

731def test_SendHostRequestMessage(db, moderator): 

732 user1, token1 = generate_user() 

733 user2, token2 = generate_user() 

734 user3, token3 = generate_user() 

735 today_plus_2 = (today() + timedelta(days=2)).isoformat() 

736 today_plus_3 = (today() + timedelta(days=3)).isoformat() 

737 with requests_session(token1) as api: 

738 host_request_id = api.CreateHostRequest( 

739 requests_pb2.CreateHostRequestReq( 

740 host_user_id=user2.id, 

741 from_date=today_plus_2, 

742 to_date=today_plus_3, 

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

744 ) 

745 ).host_request_id 

746 

747 moderator.approve_host_request(host_request_id) 

748 

749 with requests_session(token1) as api: 

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

751 api.SendHostRequestMessage( 

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

753 ) 

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

755 

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

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

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

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

760 

761 api.SendHostRequestMessage( 

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

763 ) 

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

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

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

767 

768 with requests_session(token3) as api: 

769 # other user can't send 

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

771 api.SendHostRequestMessage( 

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

773 ) 

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

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

776 

777 with requests_session(token2) as api: 

778 api.SendHostRequestMessage( 

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

780 ) 

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

782 # including 2 for creation control message and message 

783 assert len(res.messages) == 4 

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

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

786 

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

788 api.RespondHostRequest( 

789 requests_pb2.RespondHostRequestReq( 

790 host_request_id=host_request_id, status=conversations_pb2.HOST_REQUEST_STATUS_REJECTED 

791 ) 

792 ) 

793 api.SendHostRequestMessage( 

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

795 ) 

796 

797 api.RespondHostRequest( 

798 requests_pb2.RespondHostRequestReq( 

799 host_request_id=host_request_id, status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED 

800 ) 

801 ) 

802 

803 with requests_session(token1) as api: 

804 api.RespondHostRequest( 

805 requests_pb2.RespondHostRequestReq( 

806 host_request_id=host_request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CONFIRMED 

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_CANCELLED 

816 ) 

817 ) 

818 api.SendHostRequestMessage( 

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

820 ) 

821 

822 

823def test_get_updates(db, moderator): 

824 user1, token1 = generate_user() 

825 user2, token2 = generate_user() 

826 user3, token3 = generate_user() 

827 today_plus_2 = (today() + timedelta(days=2)).isoformat() 

828 today_plus_3 = (today() + timedelta(days=3)).isoformat() 

829 with requests_session(token1) as api: 

830 host_request_id = api.CreateHostRequest( 

831 requests_pb2.CreateHostRequestReq( 

832 host_user_id=user2.id, 

833 from_date=today_plus_2, 

834 to_date=today_plus_3, 

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

836 ) 

837 ).host_request_id 

838 

839 moderator.approve_host_request(host_request_id) 

840 

841 with requests_session(token1) as api: 

842 api.SendHostRequestMessage( 

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

844 ) 

845 api.SendHostRequestMessage( 

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

847 ) 

848 api.RespondHostRequest( 

849 requests_pb2.RespondHostRequestReq( 

850 host_request_id=host_request_id, 

851 status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED, 

852 text="Test message 3", 

853 ) 

854 ) 

855 

856 api.CreateHostRequest( 

857 requests_pb2.CreateHostRequestReq( 

858 host_user_id=user2.id, 

859 from_date=today_plus_2, 

860 to_date=today_plus_3, 

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

862 ) 

863 ) 

864 

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

866 assert len(res.messages) == 6 

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

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

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

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

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

872 message_id_3 = res.messages[0].message_id 

873 message_id_cancel = res.messages[1].message_id 

874 message_id_2 = res.messages[2].message_id 

875 message_id_1 = res.messages[3].message_id 

876 message_id_0 = res.messages[4].message_id 

877 

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

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

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

881 

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

883 assert res.no_more 

884 assert len(res.updates) == 5 

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

886 assert ( 

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

888 ) 

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

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

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

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

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

894 

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

896 assert not res.no_more 

897 assert len(res.updates) == 1 

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

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

900 

901 with requests_session(token3) as api: 

902 # other user can't access 

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

904 assert len(res.updates) == 0 

905 

906 

907def test_archive_host_request(db, moderator): 

908 user1, token1 = generate_user() 

909 user2, token2 = generate_user() 

910 

911 today_plus_2 = (today() + timedelta(days=2)).isoformat() 

912 today_plus_3 = (today() + timedelta(days=3)).isoformat() 

913 

914 with requests_session(token1) as api: 

915 host_request_id = api.CreateHostRequest( 

916 requests_pb2.CreateHostRequestReq( 

917 host_user_id=user2.id, 

918 from_date=today_plus_2, 

919 to_date=today_plus_3, 

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

921 ) 

922 ).host_request_id 

923 

924 api.SendHostRequestMessage( 

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

926 ) 

927 api.SendHostRequestMessage( 

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

929 ) 

930 

931 moderator.approve_host_request(host_request_id) 

932 

933 # happy path archiving host request 

934 with requests_session(token1) as api: 

935 api.RespondHostRequest( 

936 requests_pb2.RespondHostRequestReq( 

937 host_request_id=host_request_id, 

938 status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED, 

939 text="Test message 3", 

940 ) 

941 ) 

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

943 assert len(res.host_requests) == 1 

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

945 api.SetHostRequestArchiveStatus( 

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

947 ) 

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

949 assert len(res.host_requests) == 1 

950 

951 

952def test_mark_last_seen(db, moderator): 

953 user1, token1 = generate_user() 

954 user2, token2 = generate_user() 

955 user3, token3 = generate_user() 

956 today_plus_2 = (today() + timedelta(days=2)).isoformat() 

957 today_plus_3 = (today() + timedelta(days=3)).isoformat() 

958 with requests_session(token1) as api: 

959 host_request_id = api.CreateHostRequest( 

960 requests_pb2.CreateHostRequestReq( 

961 host_user_id=user2.id, 

962 from_date=today_plus_2, 

963 to_date=today_plus_3, 

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

965 ) 

966 ).host_request_id 

967 

968 host_request_id_2 = api.CreateHostRequest( 

969 requests_pb2.CreateHostRequestReq( 

970 host_user_id=user2.id, 

971 from_date=today_plus_2, 

972 to_date=today_plus_3, 

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

974 ) 

975 ).host_request_id 

976 

977 moderator.approve_host_request(host_request_id) 

978 moderator.approve_host_request(host_request_id_2) 

979 

980 with requests_session(token1) as api: 

981 api.SendHostRequestMessage( 

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

983 ) 

984 api.SendHostRequestMessage( 

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

986 ) 

987 api.RespondHostRequest( 

988 requests_pb2.RespondHostRequestReq( 

989 host_request_id=host_request_id, 

990 status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED, 

991 text="Test message 3", 

992 ) 

993 ) 

994 

995 moderator.approve_host_request(host_request_id) 

996 moderator.approve_host_request(host_request_id_2) 

997 

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

999 with api_session(token1) as api: 

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

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

1002 

1003 with api_session(token2) as api: 

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

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

1006 

1007 with requests_session(token2) as api: 

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

1009 

1010 api.MarkLastSeenHostRequest( 

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

1012 ) 

1013 

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

1015 

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

1017 api.MarkLastSeenHostRequest( 

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

1019 ) 

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

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

1022 

1023 # this will be used to test sent request notifications 

1024 host_request_id_3 = api.CreateHostRequest( 

1025 requests_pb2.CreateHostRequestReq( 

1026 host_user_id=user1.id, 

1027 from_date=today_plus_2, 

1028 to_date=today_plus_3, 

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

1030 ) 

1031 ).host_request_id 

1032 

1033 moderator.approve_host_request(host_request_id_3) 

1034 

1035 with requests_session(token2) as api: 

1036 # this should make id_2 all read 

1037 api.SendHostRequestMessage( 

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

1039 ) 

1040 

1041 with api_session(token2) as api: 

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

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

1044 

1045 # make sure sent and received count for unseen notifications 

1046 with requests_session(token1) as api: 

1047 api.SendHostRequestMessage( 

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

1049 ) 

1050 

1051 with api_session(token2) as api: 

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

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

1054 

1055 

1056def test_response_rate(db, moderator): 

1057 user1, token1 = generate_user() 

1058 user2, token2 = generate_user() 

1059 user3, token3 = generate_user(delete_user=True) 

1060 

1061 today_plus_2 = (today() + timedelta(days=2)).isoformat() 

1062 today_plus_3 = (today() + timedelta(days=3)).isoformat() 

1063 

1064 with session_scope() as session: 

1065 refresh_materialized_view(session, "user_response_rates") 

1066 

1067 with requests_session(token1) as api: 

1068 # deleted: not found 

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

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

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

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

1073 

1074 # no requests: insufficient 

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

1076 assert res.HasField("insufficient_data") 

1077 

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

1079 host_request_1 = api.CreateHostRequest( 

1080 requests_pb2.CreateHostRequestReq( 

1081 host_user_id=user2.id, 

1082 from_date=today_plus_2, 

1083 to_date=today_plus_3, 

1084 text=valid_request_text("Test request"), 

1085 ) 

1086 ).host_request_id 

1087 moderator.approve_host_request(host_request_1) 

1088 with session_scope() as session: 

1089 session.execute( 

1090 select(Message) 

1091 .where(Message.conversation_id == host_request_1) 

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

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

1094 refresh_materialized_view(session, "user_response_rates") 

1095 

1096 # still insufficient 

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

1098 assert res.HasField("insufficient_data") 

1099 

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

1101 host_request_2 = api.CreateHostRequest( 

1102 requests_pb2.CreateHostRequestReq( 

1103 host_user_id=user2.id, 

1104 from_date=today_plus_2, 

1105 to_date=today_plus_3, 

1106 text=valid_request_text("Test request"), 

1107 ) 

1108 ).host_request_id 

1109 moderator.approve_host_request(host_request_2) 

1110 with session_scope() as session: 

1111 session.execute( 

1112 select(Message) 

1113 .where(Message.conversation_id == host_request_2) 

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

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

1116 refresh_materialized_view(session, "user_response_rates") 

1117 

1118 # still insufficient 

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

1120 assert res.HasField("insufficient_data") 

1121 

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

1123 host_request_3 = api.CreateHostRequest( 

1124 requests_pb2.CreateHostRequestReq( 

1125 host_user_id=user2.id, 

1126 from_date=today_plus_2, 

1127 to_date=today_plus_3, 

1128 text=valid_request_text("Test request"), 

1129 ) 

1130 ).host_request_id 

1131 moderator.approve_host_request(host_request_3) 

1132 with session_scope() as session: 

1133 session.execute( 

1134 select(Message) 

1135 .where(Message.conversation_id == host_request_3) 

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

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

1138 refresh_materialized_view(session, "user_response_rates") 

1139 

1140 # now low 

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

1142 assert res.HasField("low") 

1143 

1144 with requests_session(token2) as api: 

1145 # accept a host req 

1146 api.RespondHostRequest( 

1147 requests_pb2.RespondHostRequestReq( 

1148 host_request_id=host_request_2, 

1149 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED, 

1150 text="Accepting host request", 

1151 ) 

1152 ) 

1153 

1154 with session_scope() as session: 

1155 refresh_materialized_view(session, "user_response_rates") 

1156 

1157 with requests_session(token1) as api: 

1158 # now some w p33 = 35h 

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

1160 assert res.HasField("some") 

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

1162 

1163 with requests_session(token2) as api: 

1164 # accept another host req 

1165 api.RespondHostRequest( 

1166 requests_pb2.RespondHostRequestReq( 

1167 host_request_id=host_request_3, 

1168 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED, 

1169 text="Accepting host request", 

1170 ) 

1171 ) 

1172 

1173 with session_scope() as session: 

1174 refresh_materialized_view(session, "user_response_rates") 

1175 

1176 with requests_session(token1) as api: 

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

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

1179 assert res.HasField("most") 

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

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

1182 

1183 with requests_session(token2) as api: 

1184 # accept last host req 

1185 api.RespondHostRequest( 

1186 requests_pb2.RespondHostRequestReq( 

1187 host_request_id=host_request_1, 

1188 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED, 

1189 text="Accepting host request", 

1190 ) 

1191 ) 

1192 

1193 with session_scope() as session: 

1194 refresh_materialized_view(session, "user_response_rates") 

1195 

1196 with requests_session(token1) as api: 

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

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

1199 assert res.HasField("almost_all") 

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

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

1202 

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

1204 host_request_4 = api.CreateHostRequest( 

1205 requests_pb2.CreateHostRequestReq( 

1206 host_user_id=user2.id, 

1207 from_date=today_plus_2, 

1208 to_date=today_plus_3, 

1209 text=valid_request_text("Test request"), 

1210 ) 

1211 ).host_request_id 

1212 moderator.approve_host_request(host_request_4) 

1213 with session_scope() as session: 

1214 session.execute( 

1215 select(Message) 

1216 .where(Message.conversation_id == host_request_4) 

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

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

1219 refresh_materialized_view(session, "user_response_rates") 

1220 

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

1222 host_request_5 = api.CreateHostRequest( 

1223 requests_pb2.CreateHostRequestReq( 

1224 host_user_id=user2.id, 

1225 from_date=today_plus_2, 

1226 to_date=today_plus_3, 

1227 text=valid_request_text("Test request"), 

1228 ) 

1229 ).host_request_id 

1230 moderator.approve_host_request(host_request_5) 

1231 with session_scope() as session: 

1232 session.execute( 

1233 select(Message) 

1234 .where(Message.conversation_id == host_request_5) 

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

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

1237 refresh_materialized_view(session, "user_response_rates") 

1238 

1239 # now some w p33 = 35h 

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

1241 assert res.HasField("some") 

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

1243 

1244 with requests_session(token2) as api: 

1245 # accept host req 

1246 api.RespondHostRequest( 

1247 requests_pb2.RespondHostRequestReq( 

1248 host_request_id=host_request_5, 

1249 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED, 

1250 text="Accepting host request", 

1251 ) 

1252 ) 

1253 

1254 with session_scope() as session: 

1255 refresh_materialized_view(session, "user_response_rates") 

1256 

1257 with requests_session(token1) as api: 

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

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

1260 assert res.HasField("most") 

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

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

1263 

1264 with requests_session(token2) as api: 

1265 # accept host req 

1266 api.RespondHostRequest( 

1267 requests_pb2.RespondHostRequestReq( 

1268 host_request_id=host_request_4, 

1269 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED, 

1270 text="Accepting host request", 

1271 ) 

1272 ) 

1273 

1274 with session_scope() as session: 

1275 refresh_materialized_view(session, "user_response_rates") 

1276 

1277 with requests_session(token1) as api: 

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

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

1280 assert res.HasField("almost_all") 

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

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

1283 

1284 

1285def test_request_notifications(db, push_collector, moderator): 

1286 host, host_token = generate_user(complete_profile=True) 

1287 surfer, surfer_token = generate_user(complete_profile=True) 

1288 

1289 today_plus_2 = (today() + timedelta(days=2)).isoformat() 

1290 today_plus_3 = (today() + timedelta(days=3)).isoformat() 

1291 

1292 with requests_session(surfer_token) as api: 

1293 hr_id = api.CreateHostRequest( 

1294 requests_pb2.CreateHostRequestReq( 

1295 host_user_id=host.id, 

1296 from_date=today_plus_2, 

1297 to_date=today_plus_3, 

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

1299 ) 

1300 ).host_request_id 

1301 

1302 with mock_notification_email() as mock: 

1303 moderator.approve_host_request(hr_id) 

1304 

1305 mock.assert_called_once() 

1306 e = email_fields(mock) 

1307 assert e.recipient == host.email 

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

1309 assert host.name in e.plain 

1310 assert host.name in e.html 

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

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

1313 assert surfer.name in e.plain 

1314 assert surfer.name in e.html 

1315 assert v2date(today_plus_2, host) in e.plain 

1316 assert v2date(today_plus_2, host) in e.html 

1317 assert v2date(today_plus_3, host) in e.plain 

1318 assert v2date(today_plus_3, host) in e.html 

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

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

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

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

1323 

1324 push_collector.assert_user_has_single_matching( 

1325 host.id, 

1326 title=f"{surfer.name} sent you a host request", 

1327 ) 

1328 

1329 with requests_session(host_token) as api: 

1330 with mock_notification_email() as mock: 

1331 api.RespondHostRequest( 

1332 requests_pb2.RespondHostRequestReq( 

1333 host_request_id=hr_id, 

1334 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED, 

1335 text="Accepting host request", 

1336 ) 

1337 ) 

1338 

1339 e = email_fields(mock) 

1340 assert e.recipient == surfer.email 

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

1342 assert host.name in e.plain 

1343 assert host.name in e.html 

1344 assert surfer.name in e.plain 

1345 assert surfer.name in e.html 

1346 assert v2date(today_plus_2, surfer) in e.plain 

1347 assert v2date(today_plus_2, surfer) in e.html 

1348 assert v2date(today_plus_3, surfer) in e.plain 

1349 assert v2date(today_plus_3, surfer) in e.html 

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

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

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

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

1354 

1355 push_collector.assert_user_has_single_matching( 

1356 surfer.id, 

1357 title=f"{host.name} accepted your host request", 

1358 ) 

1359 

1360 

1361def test_quick_decline(db, push_collector, moderator): 

1362 host, host_token = generate_user(complete_profile=True) 

1363 surfer, surfer_token = generate_user(complete_profile=True) 

1364 

1365 today_plus_2 = (today() + timedelta(days=2)).isoformat() 

1366 today_plus_3 = (today() + timedelta(days=3)).isoformat() 

1367 

1368 with requests_session(surfer_token) as api: 

1369 hr_id = api.CreateHostRequest( 

1370 requests_pb2.CreateHostRequestReq( 

1371 host_user_id=host.id, 

1372 from_date=today_plus_2, 

1373 to_date=today_plus_3, 

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

1375 ) 

1376 ).host_request_id 

1377 

1378 with mock_notification_email() as mock: 

1379 moderator.approve_host_request(hr_id) 

1380 

1381 mock.assert_called_once() 

1382 e = email_fields(mock) 

1383 assert e.recipient == host.email 

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

1385 assert host.name in e.plain 

1386 assert host.name in e.html 

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

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

1389 assert surfer.name in e.plain 

1390 assert surfer.name in e.html 

1391 assert v2date(today_plus_2, host) in e.plain 

1392 assert v2date(today_plus_2, host) in e.html 

1393 assert v2date(today_plus_3, host) in e.plain 

1394 assert v2date(today_plus_3, host) in e.html 

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

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

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

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

1399 

1400 push_collector.assert_user_has_single_matching( 

1401 host.id, 

1402 title=f"{surfer.name} sent you a host request", 

1403 ) 

1404 

1405 # very ugly 

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

1407 for link in re.findall(r'<a href="(.*?)"', email_fields(mock).html): 

1408 if "payload" not in link: 

1409 continue 

1410 print(link) 

1411 url_parts = urlparse(link) 

1412 params = parse_qs(url_parts.query) 

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

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

1415 if payload.HasField("host_request_quick_decline"): 

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

1417 res = auth_api.Unsubscribe( 

1418 auth_pb2.UnsubscribeReq( 

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

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

1421 ) 

1422 ) 

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

1424 break 

1425 else: 

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

1427 

1428 with requests_session(surfer_token) as api: 

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

1430 assert res.status == conversations_pb2.HOST_REQUEST_STATUS_REJECTED 

1431 

1432 

1433def test_host_req_feedback(db, moderator): 

1434 host, host_token = generate_user(complete_profile=True) 

1435 host2, host2_token = generate_user(complete_profile=True) 

1436 host3, host3_token = generate_user(complete_profile=True) 

1437 surfer, surfer_token = generate_user(complete_profile=True) 

1438 

1439 today_plus_2 = (today() + timedelta(days=2)).isoformat() 

1440 today_plus_3 = (today() + timedelta(days=3)).isoformat() 

1441 

1442 with requests_session(surfer_token) as api: 

1443 hr_id = api.CreateHostRequest( 

1444 requests_pb2.CreateHostRequestReq( 

1445 host_user_id=host.id, 

1446 from_date=today_plus_2, 

1447 to_date=today_plus_3, 

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

1449 ) 

1450 ).host_request_id 

1451 hr2_id = api.CreateHostRequest( 

1452 requests_pb2.CreateHostRequestReq( 

1453 host_user_id=host2.id, 

1454 from_date=today_plus_2, 

1455 to_date=today_plus_3, 

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

1457 ) 

1458 ).host_request_id 

1459 hr3_id = api.CreateHostRequest( 

1460 requests_pb2.CreateHostRequestReq( 

1461 host_user_id=host3.id, 

1462 from_date=today_plus_2, 

1463 to_date=today_plus_3, 

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

1465 ) 

1466 ).host_request_id 

1467 

1468 moderator.approve_host_request(hr_id) 

1469 moderator.approve_host_request(hr2_id) 

1470 moderator.approve_host_request(hr3_id) 

1471 

1472 with requests_session(host_token) as api: 

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

1474 assert not res.need_host_request_feedback 

1475 

1476 api.RespondHostRequest( 

1477 requests_pb2.RespondHostRequestReq( 

1478 host_request_id=hr_id, 

1479 status=conversations_pb2.HOST_REQUEST_STATUS_REJECTED, 

1480 ) 

1481 ) 

1482 

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

1484 assert res.need_host_request_feedback 

1485 

1486 # surfer can't leave feedback 

1487 with requests_session(surfer_token) as api: 

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

1489 api.SendHostRequestFeedback( 

1490 requests_pb2.SendHostRequestFeedbackReq( 

1491 host_request_id=hr_id, 

1492 ) 

1493 ) 

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

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

1496 

1497 with requests_session(host_token) as api: 

1498 api.SendHostRequestFeedback( 

1499 requests_pb2.SendHostRequestFeedbackReq( 

1500 host_request_id=hr_id, 

1501 host_request_quality=requests_pb2.HOST_REQUEST_QUALITY_LOW, 

1502 ) 

1503 ) 

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

1505 assert not res.need_host_request_feedback 

1506 

1507 # can't leave it twice 

1508 with requests_session(host_token) as api: 

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

1510 api.SendHostRequestFeedback( 

1511 requests_pb2.SendHostRequestFeedbackReq( 

1512 host_request_id=hr_id, 

1513 ) 

1514 ) 

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

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

1517 

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

1519 assert not res.need_host_request_feedback 

1520 

1521 with requests_session(host2_token) as api: 

1522 api.RespondHostRequest( 

1523 requests_pb2.RespondHostRequestReq( 

1524 host_request_id=hr2_id, status=conversations_pb2.HOST_REQUEST_STATUS_REJECTED 

1525 ) 

1526 ) 

1527 # can't leave feedback on the wrong one 

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

1529 api.SendHostRequestFeedback( 

1530 requests_pb2.SendHostRequestFeedbackReq( 

1531 host_request_id=hr_id, 

1532 ) 

1533 ) 

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

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

1536 

1537 # null feedback is still feedback 

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

1539 

1540 with requests_session(host3_token) as api: 

1541 api.RespondHostRequest( 

1542 requests_pb2.RespondHostRequestReq( 

1543 host_request_id=hr3_id, status=conversations_pb2.HOST_REQUEST_STATUS_REJECTED 

1544 ) 

1545 ) 

1546 

1547 api.SendHostRequestFeedback( 

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

1549 )