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

676 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2025-11-10 02:35 +0000

1import re 

2from datetime import timedelta 

3from urllib.parse import parse_qs, urlparse 

4 

5import grpc 

6import pytest 

7from sqlalchemy.sql import select 

8 

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.rate_limits.definitions import RATE_LIMIT_DEFINITIONS, RATE_LIMIT_INTERVAL_STRING 

18from couchers.sql import couchers_select as select 

19from couchers.templates.v2 import v2date 

20from couchers.utils import create_coordinate, now, today 

21from proto import ( 

22 api_pb2, 

23 auth_pb2, 

24 conversations_pb2, 

25 requests_pb2, 

26) 

27from proto.internal import unsubscribe_pb2 

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 push_collector, 

36 requests_session, 

37 testconfig, 

38) 

39 

40 

41@pytest.fixture(autouse=True) 

42def _(testconfig): 

43 pass 

44 

45 

46def test_create_request(db): 

47 user1, token1 = generate_user() 

48 hosting_city = "Morningside Heights, New York City" 

49 hosting_lat = 40.8086 

50 hosting_lng = -73.9616 

51 hosting_radius = 500 

52 user2, token2 = generate_user( 

53 city=hosting_city, 

54 geom=create_coordinate(hosting_lat, hosting_lng), 

55 geom_radius=hosting_radius, 

56 ) 

57 

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

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

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

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

62 with requests_session(token1) as api: 

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

64 api.CreateHostRequest( 

65 requests_pb2.CreateHostRequestReq( 

66 host_user_id=user1.id, from_date=today_plus_2, to_date=today_plus_3, text="Test request" 

67 ) 

68 ) 

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

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

71 

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

73 api.CreateHostRequest( 

74 requests_pb2.CreateHostRequestReq( 

75 host_user_id=999, from_date=today_plus_2, to_date=today_plus_3, text="Test request" 

76 ) 

77 ) 

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

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

80 

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

82 api.CreateHostRequest( 

83 requests_pb2.CreateHostRequestReq( 

84 host_user_id=user2.id, from_date=today_plus_3, to_date=today_plus_2, text="Test request" 

85 ) 

86 ) 

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

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

89 

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

91 api.CreateHostRequest( 

92 requests_pb2.CreateHostRequestReq( 

93 host_user_id=user2.id, from_date=today_minus_3, to_date=today_plus_2, text="Test request" 

94 ) 

95 ) 

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

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

98 

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

100 api.CreateHostRequest( 

101 requests_pb2.CreateHostRequestReq( 

102 host_user_id=user2.id, from_date=today_plus_2, to_date=today_minus_2, text="Test request" 

103 ) 

104 ) 

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

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

107 

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

109 api.CreateHostRequest( 

110 requests_pb2.CreateHostRequestReq( 

111 host_user_id=user2.id, from_date="2020-00-06", to_date=today_minus_2, text="Test request" 

112 ) 

113 ) 

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

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

116 

117 res = api.CreateHostRequest( 

118 requests_pb2.CreateHostRequestReq( 

119 host_user_id=user2.id, 

120 from_date=today_plus_2, 

121 to_date=today_plus_3, 

122 text="Test request", 

123 ) 

124 ) 

125 

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

127 

128 assert len(host_requests) == 1 

129 hr = host_requests[0] 

130 

131 assert hr.latest_message.text.text == "Test request" 

132 

133 assert hr.hosting_city == hosting_city 

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

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

136 assert hr.hosting_radius == hosting_radius 

137 

138 today_ = today() 

139 today_plus_one_year = today_ + timedelta(days=365) 

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

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

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

143 api.CreateHostRequest( 

144 requests_pb2.CreateHostRequestReq( 

145 host_user_id=user2.id, 

146 from_date=today_plus_one_year_plus_2, 

147 to_date=today_plus_one_year_plus_3, 

148 text="Test from date after one year", 

149 ) 

150 ) 

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

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

153 

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

155 api.CreateHostRequest( 

156 requests_pb2.CreateHostRequestReq( 

157 host_user_id=user2.id, 

158 from_date=today_plus_2, 

159 to_date=today_plus_one_year_plus_3, 

160 text="Test to date one year after from date", 

161 ) 

162 ) 

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

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

165 

166 

167def test_create_request_incomplete_profile(db): 

168 user1, token1 = generate_user(complete_profile=False) 

169 user2, _ = generate_user() 

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

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

172 with requests_session(token1) as api: 

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

174 api.CreateHostRequest( 

175 requests_pb2.CreateHostRequestReq( 

176 host_user_id=user2.id, from_date=today_plus_2, to_date=today_plus_3, text="Test request" 

177 ) 

178 ) 

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

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

181 

182 

183def test_excessive_requests_are_reported(db): 

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

185 user, token = generate_user() 

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

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

188 rate_limit_definition = RATE_LIMIT_DEFINITIONS[RateLimitAction.host_request] 

189 with requests_session(token) as api: 

190 # Test warning email 

191 with mock_notification_email() as mock_email: 

192 for _ in range(rate_limit_definition.warning_limit): 

193 host_user, _ = generate_user() 

194 _ = api.CreateHostRequest( 

195 requests_pb2.CreateHostRequestReq( 

196 host_user_id=host_user.id, from_date=today_plus_2, to_date=today_plus_3, text="Test request" 

197 ) 

198 ) 

199 

200 assert mock_email.call_count == 0 

201 host_user, _ = generate_user() 

202 _ = api.CreateHostRequest( 

203 requests_pb2.CreateHostRequestReq( 

204 host_user_id=host_user.id, 

205 from_date=today_plus_2, 

206 to_date=today_plus_3, 

207 text="Excessive test request", 

208 ) 

209 ) 

210 assert mock_email.call_count == 1 

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

212 assert email.startswith( 

213 f"User {user.username} has sent {rate_limit_definition.warning_limit} host requests in the past {RATE_LIMIT_INTERVAL_STRING}." 

214 ) 

215 

216 # Test ban after exceeding HOST_REQUEST_HARD_LIMIT 

217 with mock_notification_email() as mock_email: 

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

219 host_user, _ = generate_user() 

220 _ = api.CreateHostRequest( 

221 requests_pb2.CreateHostRequestReq( 

222 host_user_id=host_user.id, from_date=today_plus_2, to_date=today_plus_3, text="Test request" 

223 ) 

224 ) 

225 

226 assert mock_email.call_count == 0 

227 host_user, _ = generate_user() 

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

229 _ = api.CreateHostRequest( 

230 requests_pb2.CreateHostRequestReq( 

231 host_user_id=host_user.id, 

232 from_date=today_plus_2, 

233 to_date=today_plus_3, 

234 text="Excessive test request", 

235 ) 

236 ) 

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

238 assert ( 

239 exc_info.value.details() 

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

241 ) 

242 

243 assert mock_email.call_count == 1 

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

245 assert email.startswith( 

246 f"User {user.username} has sent {rate_limit_definition.hard_limit} host requests in the past {RATE_LIMIT_INTERVAL_STRING}." 

247 ) 

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

249 

250 

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

252 with session_scope() as session: 

253 message = Message( 

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

255 ) 

256 

257 session.add(message) 

258 

259 

260def test_GetHostRequest(db): 

261 user1, token1 = generate_user() 

262 user2, token2 = generate_user() 

263 user3, token3 = generate_user() 

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

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

266 with requests_session(token1) as api: 

267 host_request_id = api.CreateHostRequest( 

268 requests_pb2.CreateHostRequestReq( 

269 host_user_id=user2.id, from_date=today_plus_2, to_date=today_plus_3, text="Test request 1" 

270 ) 

271 ).host_request_id 

272 

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

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

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

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

277 

278 api.SendHostRequestMessage( 

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

280 ) 

281 

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

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

284 

285 

286def test_ListHostRequests(db): 

287 user1, token1 = generate_user() 

288 user2, token2 = generate_user() 

289 user3, token3 = generate_user() 

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

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

292 with requests_session(token1) as api: 

293 host_request_1 = api.CreateHostRequest( 

294 requests_pb2.CreateHostRequestReq( 

295 host_user_id=user2.id, from_date=today_plus_2, to_date=today_plus_3, text="Test request 1" 

296 ) 

297 ).host_request_id 

298 

299 host_request_2 = api.CreateHostRequest( 

300 requests_pb2.CreateHostRequestReq( 

301 host_user_id=user3.id, from_date=today_plus_2, to_date=today_plus_3, text="Test request 2" 

302 ) 

303 ).host_request_id 

304 

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

306 assert res.no_more 

307 assert len(res.host_requests) == 2 

308 

309 with requests_session(token2) as api: 

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

311 assert res.no_more 

312 assert len(res.host_requests) == 1 

313 assert res.host_requests[0].latest_message.text.text == "Test request 1" 

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

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

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

317 

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

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

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

321 

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

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

324 

325 api.CreateHostRequest( 

326 requests_pb2.CreateHostRequestReq( 

327 host_user_id=user1.id, from_date=today_plus_2, to_date=today_plus_3, text="Test request 3" 

328 ) 

329 ) 

330 

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

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

333 

334 with requests_session(token3) as api: 

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

336 assert res.no_more 

337 assert len(res.host_requests) == 1 

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

339 

340 with requests_session(token1) as api: 

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

342 assert len(res.host_requests) == 1 

343 

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

345 assert len(res.host_requests) == 3 

346 

347 

348def test_ListHostRequests_pagination_regression(db): 

349 """ 

350 ListHostRequests was skipping a request when getting multiple pages 

351 """ 

352 user1, token1 = generate_user() 

353 user2, token2 = generate_user() 

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

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

356 with requests_session(token1) as api: 

357 host_request_1 = api.CreateHostRequest( 

358 requests_pb2.CreateHostRequestReq( 

359 host_user_id=user2.id, from_date=today_plus_2, to_date=today_plus_3, text="Test request 1" 

360 ) 

361 ).host_request_id 

362 

363 host_request_2 = api.CreateHostRequest( 

364 requests_pb2.CreateHostRequestReq( 

365 host_user_id=user2.id, from_date=today_plus_2, to_date=today_plus_3, text="Test request 2" 

366 ) 

367 ).host_request_id 

368 

369 host_request_3 = api.CreateHostRequest( 

370 requests_pb2.CreateHostRequestReq( 

371 host_user_id=user2.id, from_date=today_plus_2, to_date=today_plus_3, text="Test request 3" 

372 ) 

373 ).host_request_id 

374 

375 with requests_session(token2) as api: 

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

377 assert res.no_more 

378 assert len(res.host_requests) == 3 

379 assert res.host_requests[0].latest_message.text.text == "Test request 3" 

380 assert res.host_requests[1].latest_message.text.text == "Test request 2" 

381 assert res.host_requests[2].latest_message.text.text == "Test request 1" 

382 

383 with requests_session(token2) as api: 

384 api.RespondHostRequest( 

385 requests_pb2.RespondHostRequestReq( 

386 host_request_id=host_request_2, 

387 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED, 

388 text="Accepting host request 2", 

389 ) 

390 ) 

391 api.RespondHostRequest( 

392 requests_pb2.RespondHostRequestReq( 

393 host_request_id=host_request_1, 

394 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED, 

395 text="Accepting host request 1", 

396 ) 

397 ) 

398 api.RespondHostRequest( 

399 requests_pb2.RespondHostRequestReq( 

400 host_request_id=host_request_3, 

401 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED, 

402 text="Accepting host request 3", 

403 ) 

404 ) 

405 

406 with requests_session(token2) as api: 

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

408 assert res.no_more 

409 assert len(res.host_requests) == 3 

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

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

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

413 

414 with requests_session(token2) as api: 

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

416 assert not res.no_more 

417 assert len(res.host_requests) == 1 

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

419 res = api.ListHostRequests( 

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

421 ) 

422 assert not res.no_more 

423 assert len(res.host_requests) == 1 

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

425 res = api.ListHostRequests( 

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

427 ) 

428 assert res.no_more 

429 assert len(res.host_requests) == 1 

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

431 

432 

433def test_ListHostRequests_active_filter(db): 

434 user1, token1 = generate_user() 

435 user2, token2 = generate_user() 

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

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

438 

439 with requests_session(token1) as api: 

440 request_id = api.CreateHostRequest( 

441 requests_pb2.CreateHostRequestReq( 

442 host_user_id=user2.id, from_date=today_plus_2, to_date=today_plus_3, text="Test request 1" 

443 ) 

444 ).host_request_id 

445 api.RespondHostRequest( 

446 requests_pb2.RespondHostRequestReq( 

447 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED 

448 ) 

449 ) 

450 

451 with requests_session(token2) as api: 

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

453 assert len(res.host_requests) == 1 

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

455 assert len(res.host_requests) == 0 

456 

457 

458def test_RespondHostRequests(db): 

459 user1, token1 = generate_user() 

460 user2, token2 = generate_user() 

461 user3, token3 = generate_user() 

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

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

464 

465 with requests_session(token1) as api: 

466 request_id = api.CreateHostRequest( 

467 requests_pb2.CreateHostRequestReq( 

468 host_user_id=user2.id, from_date=today_plus_2, to_date=today_plus_3, text="Test request 1" 

469 ) 

470 ).host_request_id 

471 

472 # another user can't access 

473 with requests_session(token3) as api: 

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

475 api.RespondHostRequest( 

476 requests_pb2.RespondHostRequestReq( 

477 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED 

478 ) 

479 ) 

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

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

482 

483 with requests_session(token1) as api: 

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

485 api.RespondHostRequest( 

486 requests_pb2.RespondHostRequestReq( 

487 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED 

488 ) 

489 ) 

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

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

492 

493 with requests_session(token2) as api: 

494 # non existing id 

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

496 api.RespondHostRequest( 

497 requests_pb2.RespondHostRequestReq( 

498 host_request_id=9999, status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED 

499 ) 

500 ) 

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

502 

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

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

505 api.RespondHostRequest( 

506 requests_pb2.RespondHostRequestReq( 

507 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CONFIRMED 

508 ) 

509 ) 

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

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

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

513 api.RespondHostRequest( 

514 requests_pb2.RespondHostRequestReq( 

515 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED 

516 ) 

517 ) 

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

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

520 

521 api.RespondHostRequest( 

522 requests_pb2.RespondHostRequestReq( 

523 host_request_id=request_id, 

524 status=conversations_pb2.HOST_REQUEST_STATUS_REJECTED, 

525 text="Test rejection message", 

526 ) 

527 ) 

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

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

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

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

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

533 api.RespondHostRequest( 

534 requests_pb2.RespondHostRequestReq( 

535 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED 

536 ) 

537 ) 

538 

539 with requests_session(token1) as api: 

540 # can't make pending 

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

542 api.RespondHostRequest( 

543 requests_pb2.RespondHostRequestReq( 

544 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_PENDING 

545 ) 

546 ) 

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

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

549 

550 # can confirm then cancel 

551 api.RespondHostRequest( 

552 requests_pb2.RespondHostRequestReq( 

553 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CONFIRMED 

554 ) 

555 ) 

556 

557 api.RespondHostRequest( 

558 requests_pb2.RespondHostRequestReq( 

559 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED 

560 ) 

561 ) 

562 

563 # can't confirm after having cancelled 

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_CONFIRMED 

568 ) 

569 ) 

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

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

572 

573 # at this point there should be 7 messages 

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

575 with requests_session(token1) as api: 

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

577 assert len(res.messages) == 7 

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

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

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

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

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

583 

584 

585def test_get_host_request_messages(db): 

586 user1, token1 = generate_user() 

587 user2, token2 = generate_user() 

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

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

590 with requests_session(token1) as api: 

591 res = api.CreateHostRequest( 

592 requests_pb2.CreateHostRequestReq( 

593 host_user_id=user2.id, from_date=today_plus_2, to_date=today_plus_3, text="Test request 1" 

594 ) 

595 ) 

596 conversation_id = res.host_request_id 

597 

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

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

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

601 

602 with requests_session(token2) as api: 

603 api.RespondHostRequest( 

604 requests_pb2.RespondHostRequestReq( 

605 host_request_id=conversation_id, status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED 

606 ) 

607 ) 

608 

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

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

611 

612 api.RespondHostRequest( 

613 requests_pb2.RespondHostRequestReq( 

614 host_request_id=conversation_id, status=conversations_pb2.HOST_REQUEST_STATUS_REJECTED 

615 ) 

616 ) 

617 

618 with requests_session(token1) as api: 

619 # 9 including initial message 

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

621 assert len(res.messages) == 9 

622 assert res.no_more 

623 

624 res = api.GetHostRequestMessages( 

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

626 ) 

627 assert not res.no_more 

628 assert len(res.messages) == 3 

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

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

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

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

633 

634 res = api.GetHostRequestMessages( 

635 requests_pb2.GetHostRequestMessagesReq( 

636 host_request_id=conversation_id, 

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

638 number=6, 

639 ) 

640 ) 

641 assert res.no_more 

642 assert len(res.messages) == 6 

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

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

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

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

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

648 assert res.messages[4].text.text == "Test request 1" 

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

650 

651 

652def test_SendHostRequestMessage(db): 

653 user1, token1 = generate_user() 

654 user2, token2 = generate_user() 

655 user3, token3 = generate_user() 

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

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

658 with requests_session(token1) as api: 

659 host_request_id = api.CreateHostRequest( 

660 requests_pb2.CreateHostRequestReq( 

661 host_user_id=user2.id, from_date=today_plus_2, to_date=today_plus_3, text="Test request 1" 

662 ) 

663 ).host_request_id 

664 

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

666 api.SendHostRequestMessage( 

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

668 ) 

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

670 

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

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

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

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

675 

676 api.SendHostRequestMessage( 

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

678 ) 

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

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

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

682 

683 with requests_session(token3) as api: 

684 # other user can't send 

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

686 api.SendHostRequestMessage( 

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

688 ) 

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

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

691 

692 with requests_session(token2) as api: 

693 api.SendHostRequestMessage( 

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

695 ) 

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

697 # including 2 for creation control message and message 

698 assert len(res.messages) == 4 

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

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

701 

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

703 api.RespondHostRequest( 

704 requests_pb2.RespondHostRequestReq( 

705 host_request_id=host_request_id, status=conversations_pb2.HOST_REQUEST_STATUS_REJECTED 

706 ) 

707 ) 

708 api.SendHostRequestMessage( 

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

710 ) 

711 

712 api.RespondHostRequest( 

713 requests_pb2.RespondHostRequestReq( 

714 host_request_id=host_request_id, status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED 

715 ) 

716 ) 

717 

718 with requests_session(token1) as api: 

719 api.RespondHostRequest( 

720 requests_pb2.RespondHostRequestReq( 

721 host_request_id=host_request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CONFIRMED 

722 ) 

723 ) 

724 api.SendHostRequestMessage( 

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

726 ) 

727 

728 api.RespondHostRequest( 

729 requests_pb2.RespondHostRequestReq( 

730 host_request_id=host_request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED 

731 ) 

732 ) 

733 api.SendHostRequestMessage( 

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

735 ) 

736 

737 

738def test_get_updates(db): 

739 user1, token1 = generate_user() 

740 user2, token2 = generate_user() 

741 user3, token3 = generate_user() 

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

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

744 with requests_session(token1) as api: 

745 host_request_id = api.CreateHostRequest( 

746 requests_pb2.CreateHostRequestReq( 

747 host_user_id=user2.id, from_date=today_plus_2, to_date=today_plus_3, text="Test message 0" 

748 ) 

749 ).host_request_id 

750 

751 api.SendHostRequestMessage( 

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

753 ) 

754 api.SendHostRequestMessage( 

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

756 ) 

757 api.RespondHostRequest( 

758 requests_pb2.RespondHostRequestReq( 

759 host_request_id=host_request_id, 

760 status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED, 

761 text="Test message 3", 

762 ) 

763 ) 

764 

765 api.CreateHostRequest( 

766 requests_pb2.CreateHostRequestReq( 

767 host_user_id=user2.id, from_date=today_plus_2, to_date=today_plus_3, text="Test message 4" 

768 ) 

769 ) 

770 

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

772 assert len(res.messages) == 6 

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

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

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

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

777 assert res.messages[4].text.text == "Test message 0" 

778 message_id_3 = res.messages[0].message_id 

779 message_id_cancel = res.messages[1].message_id 

780 message_id_2 = res.messages[2].message_id 

781 message_id_1 = res.messages[3].message_id 

782 message_id_0 = res.messages[4].message_id 

783 

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

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

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

787 

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

789 assert res.no_more 

790 assert len(res.updates) == 5 

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

792 assert ( 

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

794 ) 

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

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

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

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

799 assert res.updates[4].message.text.text == "Test message 4" 

800 

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

802 assert not res.no_more 

803 assert len(res.updates) == 1 

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

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

806 

807 with requests_session(token3) as api: 

808 # other user can't access 

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

810 assert len(res.updates) == 0 

811 

812 

813def test_archive_host_request(db): 

814 user1, token1 = generate_user() 

815 user2, token2 = generate_user() 

816 

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

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

819 

820 with requests_session(token1) as api: 

821 host_request_id = api.CreateHostRequest( 

822 requests_pb2.CreateHostRequestReq( 

823 host_user_id=user2.id, from_date=today_plus_2, to_date=today_plus_3, text="Test message 0" 

824 ) 

825 ).host_request_id 

826 

827 api.SendHostRequestMessage( 

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

829 ) 

830 api.SendHostRequestMessage( 

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

832 ) 

833 # happy path archiving host request 

834 with requests_session(token1) as api: 

835 api.RespondHostRequest( 

836 requests_pb2.RespondHostRequestReq( 

837 host_request_id=host_request_id, 

838 status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED, 

839 text="Test message 3", 

840 ) 

841 ) 

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

843 assert len(res.host_requests) == 1 

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

845 api.SetHostRequestArchiveStatus( 

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

847 ) 

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

849 assert len(res.host_requests) == 1 

850 

851 

852def test_mark_last_seen(db): 

853 user1, token1 = generate_user() 

854 user2, token2 = generate_user() 

855 user3, token3 = generate_user() 

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

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

858 with requests_session(token1) as api: 

859 host_request_id = api.CreateHostRequest( 

860 requests_pb2.CreateHostRequestReq( 

861 host_user_id=user2.id, from_date=today_plus_2, to_date=today_plus_3, text="Test message 0" 

862 ) 

863 ).host_request_id 

864 

865 host_request_id_2 = api.CreateHostRequest( 

866 requests_pb2.CreateHostRequestReq( 

867 host_user_id=user2.id, from_date=today_plus_2, to_date=today_plus_3, text="Test message 0a" 

868 ) 

869 ).host_request_id 

870 

871 api.SendHostRequestMessage( 

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

873 ) 

874 api.SendHostRequestMessage( 

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

876 ) 

877 api.RespondHostRequest( 

878 requests_pb2.RespondHostRequestReq( 

879 host_request_id=host_request_id, 

880 status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED, 

881 text="Test message 3", 

882 ) 

883 ) 

884 

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

886 with api_session(token1) as api: 

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

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

889 

890 with api_session(token2) as api: 

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

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

893 

894 with requests_session(token2) as api: 

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

896 

897 api.MarkLastSeenHostRequest( 

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

899 ) 

900 

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

902 

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

904 api.MarkLastSeenHostRequest( 

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

906 ) 

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

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

909 

910 # this will be used to test sent request notifications 

911 host_request_id_3 = api.CreateHostRequest( 

912 requests_pb2.CreateHostRequestReq( 

913 host_user_id=user1.id, from_date=today_plus_2, to_date=today_plus_3, text="Another test request" 

914 ) 

915 ).host_request_id 

916 

917 # this should make id_2 all read 

918 api.SendHostRequestMessage( 

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

920 ) 

921 

922 with api_session(token2) as api: 

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

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

925 

926 # make sure sent and received count for unseen notifications 

927 with requests_session(token1) as api: 

928 api.SendHostRequestMessage( 

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

930 ) 

931 

932 with api_session(token2) as api: 

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

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

935 

936 

937def test_response_rate(db): 

938 user1, token1 = generate_user() 

939 user2, token2 = generate_user() 

940 user3, token3 = generate_user(delete_user=True) 

941 

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

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

944 

945 with session_scope() as session: 

946 refresh_materialized_view(session, "user_response_rates") 

947 

948 with requests_session(token1) as api: 

949 # deleted: not found 

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

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

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

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

954 

955 # no requests: insufficient 

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

957 assert res.HasField("insufficient_data") 

958 

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

960 host_request_1 = api.CreateHostRequest( 

961 requests_pb2.CreateHostRequestReq( 

962 host_user_id=user2.id, from_date=today_plus_2, to_date=today_plus_3, text="Test request" 

963 ) 

964 ).host_request_id 

965 with session_scope() as session: 

966 session.execute( 

967 select(Message) 

968 .where(Message.conversation_id == host_request_1) 

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

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

971 refresh_materialized_view(session, "user_response_rates") 

972 

973 # still insufficient 

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

975 assert res.HasField("insufficient_data") 

976 

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

978 host_request_2 = api.CreateHostRequest( 

979 requests_pb2.CreateHostRequestReq( 

980 host_user_id=user2.id, from_date=today_plus_2, to_date=today_plus_3, text="Test request" 

981 ) 

982 ).host_request_id 

983 with session_scope() as session: 

984 session.execute( 

985 select(Message) 

986 .where(Message.conversation_id == host_request_2) 

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

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

989 refresh_materialized_view(session, "user_response_rates") 

990 

991 # still insufficient 

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

993 assert res.HasField("insufficient_data") 

994 

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

996 host_request_3 = api.CreateHostRequest( 

997 requests_pb2.CreateHostRequestReq( 

998 host_user_id=user2.id, from_date=today_plus_2, to_date=today_plus_3, text="Test request" 

999 ) 

1000 ).host_request_id 

1001 with session_scope() as session: 

1002 session.execute( 

1003 select(Message) 

1004 .where(Message.conversation_id == host_request_3) 

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

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

1007 refresh_materialized_view(session, "user_response_rates") 

1008 

1009 # now low 

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

1011 assert res.HasField("low") 

1012 

1013 with requests_session(token2) as api: 

1014 # accept a host req 

1015 api.RespondHostRequest( 

1016 requests_pb2.RespondHostRequestReq( 

1017 host_request_id=host_request_2, 

1018 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED, 

1019 text="Accepting host request", 

1020 ) 

1021 ) 

1022 

1023 with session_scope() as session: 

1024 refresh_materialized_view(session, "user_response_rates") 

1025 

1026 with requests_session(token1) as api: 

1027 # now some w p33 = 35h 

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

1029 assert res.HasField("some") 

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

1031 

1032 with requests_session(token2) as api: 

1033 # accept another host req 

1034 api.RespondHostRequest( 

1035 requests_pb2.RespondHostRequestReq( 

1036 host_request_id=host_request_3, 

1037 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED, 

1038 text="Accepting host request", 

1039 ) 

1040 ) 

1041 

1042 with session_scope() as session: 

1043 refresh_materialized_view(session, "user_response_rates") 

1044 

1045 with requests_session(token1) as api: 

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

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

1048 assert res.HasField("most") 

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

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

1051 

1052 with requests_session(token2) as api: 

1053 # accept last host req 

1054 api.RespondHostRequest( 

1055 requests_pb2.RespondHostRequestReq( 

1056 host_request_id=host_request_1, 

1057 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED, 

1058 text="Accepting host request", 

1059 ) 

1060 ) 

1061 

1062 with session_scope() as session: 

1063 refresh_materialized_view(session, "user_response_rates") 

1064 

1065 with requests_session(token1) as api: 

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

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

1068 assert res.HasField("almost_all") 

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

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

1071 

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

1073 host_request_4 = api.CreateHostRequest( 

1074 requests_pb2.CreateHostRequestReq( 

1075 host_user_id=user2.id, from_date=today_plus_2, to_date=today_plus_3, text="Test request" 

1076 ) 

1077 ).host_request_id 

1078 with session_scope() as session: 

1079 session.execute( 

1080 select(Message) 

1081 .where(Message.conversation_id == host_request_4) 

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

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

1084 refresh_materialized_view(session, "user_response_rates") 

1085 

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

1087 host_request_5 = api.CreateHostRequest( 

1088 requests_pb2.CreateHostRequestReq( 

1089 host_user_id=user2.id, from_date=today_plus_2, to_date=today_plus_3, text="Test request" 

1090 ) 

1091 ).host_request_id 

1092 with session_scope() as session: 

1093 session.execute( 

1094 select(Message) 

1095 .where(Message.conversation_id == host_request_5) 

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

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

1098 refresh_materialized_view(session, "user_response_rates") 

1099 

1100 # now some w p33 = 35h 

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

1102 assert res.HasField("some") 

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

1104 

1105 with requests_session(token2) as api: 

1106 # accept host req 

1107 api.RespondHostRequest( 

1108 requests_pb2.RespondHostRequestReq( 

1109 host_request_id=host_request_5, 

1110 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED, 

1111 text="Accepting host request", 

1112 ) 

1113 ) 

1114 

1115 with session_scope() as session: 

1116 refresh_materialized_view(session, "user_response_rates") 

1117 

1118 with requests_session(token1) as api: 

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

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

1121 assert res.HasField("most") 

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

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

1124 

1125 with requests_session(token2) as api: 

1126 # accept host req 

1127 api.RespondHostRequest( 

1128 requests_pb2.RespondHostRequestReq( 

1129 host_request_id=host_request_4, 

1130 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED, 

1131 text="Accepting host request", 

1132 ) 

1133 ) 

1134 

1135 with session_scope() as session: 

1136 refresh_materialized_view(session, "user_response_rates") 

1137 

1138 with requests_session(token1) as api: 

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

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

1141 assert res.HasField("almost_all") 

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

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

1144 

1145 

1146def test_request_notifications(db, push_collector): 

1147 host, host_token = generate_user(complete_profile=True) 

1148 surfer, surfer_token = generate_user(complete_profile=True) 

1149 

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

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

1152 

1153 with requests_session(surfer_token) as api: 

1154 with mock_notification_email() as mock: 

1155 hr_id = api.CreateHostRequest( 

1156 requests_pb2.CreateHostRequestReq( 

1157 host_user_id=host.id, 

1158 from_date=today_plus_2, 

1159 to_date=today_plus_3, 

1160 text="can i stay plz", 

1161 ) 

1162 ).host_request_id 

1163 

1164 mock.assert_called_once() 

1165 e = email_fields(mock) 

1166 assert e.recipient == host.email 

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

1168 assert host.name in e.plain 

1169 assert host.name in e.html 

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

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

1172 assert surfer.name in e.plain 

1173 assert surfer.name in e.html 

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

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

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

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

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

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

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

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

1182 

1183 push_collector.assert_user_has_single_matching( 

1184 host.id, 

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

1186 ) 

1187 

1188 with requests_session(host_token) as api: 

1189 with mock_notification_email() as mock: 

1190 api.RespondHostRequest( 

1191 requests_pb2.RespondHostRequestReq( 

1192 host_request_id=hr_id, 

1193 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED, 

1194 text="Accepting host request", 

1195 ) 

1196 ) 

1197 

1198 e = email_fields(mock) 

1199 assert e.recipient == surfer.email 

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

1201 assert host.name in e.plain 

1202 assert host.name in e.html 

1203 assert surfer.name in e.plain 

1204 assert surfer.name in e.html 

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

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

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

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

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

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

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

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

1213 

1214 push_collector.assert_user_has_single_matching( 

1215 surfer.id, 

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

1217 ) 

1218 

1219 

1220def test_quick_decline(db, push_collector): 

1221 host, host_token = generate_user(complete_profile=True) 

1222 surfer, surfer_token = generate_user(complete_profile=True) 

1223 

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

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

1226 

1227 with requests_session(surfer_token) as api: 

1228 with mock_notification_email() as mock: 

1229 hr_id = api.CreateHostRequest( 

1230 requests_pb2.CreateHostRequestReq( 

1231 host_user_id=host.id, 

1232 from_date=today_plus_2, 

1233 to_date=today_plus_3, 

1234 text="can i stay plz", 

1235 ) 

1236 ).host_request_id 

1237 

1238 mock.assert_called_once() 

1239 e = email_fields(mock) 

1240 assert e.recipient == host.email 

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

1242 assert host.name in e.plain 

1243 assert host.name in e.html 

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

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

1246 assert surfer.name in e.plain 

1247 assert surfer.name in e.html 

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

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

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

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

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

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

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

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

1256 

1257 push_collector.assert_user_has_single_matching( 

1258 host.id, 

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

1260 ) 

1261 

1262 # very ugly 

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

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

1265 if "payload" not in link: 

1266 continue 

1267 print(link) 

1268 url_parts = urlparse(link) 

1269 params = parse_qs(url_parts.query) 

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

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

1272 if payload.HasField("host_request_quick_decline"): 

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

1274 res = auth_api.Unsubscribe( 

1275 auth_pb2.UnsubscribeReq( 

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

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

1278 ) 

1279 ) 

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

1281 break 

1282 else: 

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

1284 

1285 with requests_session(surfer_token) as api: 

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

1287 assert res.status == conversations_pb2.HOST_REQUEST_STATUS_REJECTED 

1288 

1289 

1290def test_host_req_feedback(db): 

1291 host, host_token = generate_user(complete_profile=True) 

1292 host2, host2_token = generate_user(complete_profile=True) 

1293 host3, host3_token = generate_user(complete_profile=True) 

1294 surfer, surfer_token = generate_user(complete_profile=True) 

1295 

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

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

1298 

1299 with requests_session(surfer_token) as api: 

1300 hr_id = api.CreateHostRequest( 

1301 requests_pb2.CreateHostRequestReq( 

1302 host_user_id=host.id, 

1303 from_date=today_plus_2, 

1304 to_date=today_plus_3, 

1305 text="can i stay plz", 

1306 ) 

1307 ).host_request_id 

1308 hr2_id = api.CreateHostRequest( 

1309 requests_pb2.CreateHostRequestReq( 

1310 host_user_id=host2.id, 

1311 from_date=today_plus_2, 

1312 to_date=today_plus_3, 

1313 text="can i stay plz", 

1314 ) 

1315 ).host_request_id 

1316 hr3_id = api.CreateHostRequest( 

1317 requests_pb2.CreateHostRequestReq( 

1318 host_user_id=host3.id, 

1319 from_date=today_plus_2, 

1320 to_date=today_plus_3, 

1321 text="can i stay plz", 

1322 ) 

1323 ).host_request_id 

1324 

1325 with requests_session(host_token) as api: 

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

1327 assert not res.need_host_request_feedback 

1328 

1329 api.RespondHostRequest( 

1330 requests_pb2.RespondHostRequestReq( 

1331 host_request_id=hr_id, 

1332 status=conversations_pb2.HOST_REQUEST_STATUS_REJECTED, 

1333 ) 

1334 ) 

1335 

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

1337 assert res.need_host_request_feedback 

1338 

1339 # surfer can't leave feedback 

1340 with requests_session(surfer_token) as api: 

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

1342 api.SendHostRequestFeedback( 

1343 requests_pb2.SendHostRequestFeedbackReq( 

1344 host_request_id=hr_id, 

1345 ) 

1346 ) 

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

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

1349 

1350 with requests_session(host_token) as api: 

1351 api.SendHostRequestFeedback( 

1352 requests_pb2.SendHostRequestFeedbackReq( 

1353 host_request_id=hr_id, 

1354 host_request_quality=requests_pb2.HOST_REQUEST_QUALITY_LOW, 

1355 ) 

1356 ) 

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

1358 assert not res.need_host_request_feedback 

1359 

1360 # can't leave it twice 

1361 with requests_session(host_token) as api: 

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

1363 api.SendHostRequestFeedback( 

1364 requests_pb2.SendHostRequestFeedbackReq( 

1365 host_request_id=hr_id, 

1366 ) 

1367 ) 

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

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

1370 

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

1372 assert not res.need_host_request_feedback 

1373 

1374 with requests_session(host2_token) as api: 

1375 api.RespondHostRequest( 

1376 requests_pb2.RespondHostRequestReq( 

1377 host_request_id=hr2_id, status=conversations_pb2.HOST_REQUEST_STATUS_REJECTED 

1378 ) 

1379 ) 

1380 # can't leave feedback on the wrong one 

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

1382 api.SendHostRequestFeedback( 

1383 requests_pb2.SendHostRequestFeedbackReq( 

1384 host_request_id=hr_id, 

1385 ) 

1386 ) 

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

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

1389 

1390 # null feedback is still feedback 

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

1392 

1393 with requests_session(host3_token) as api: 

1394 api.RespondHostRequest( 

1395 requests_pb2.RespondHostRequestReq( 

1396 host_request_id=hr3_id, status=conversations_pb2.HOST_REQUEST_STATUS_REJECTED 

1397 ) 

1398 ) 

1399 

1400 api.SendHostRequestFeedback( 

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

1402 )