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

676 statements  

« prev     ^ index     » next       coverage.py v7.6.10, created at 2025-08-28 14:55 +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 import errors 

10from couchers.crypto import b64decode 

11from couchers.db import session_scope 

12from couchers.materialized_views import refresh_materialized_view 

13from couchers.models import ( 

14 Message, 

15 MessageType, 

16 RateLimitAction, 

17) 

18from couchers.rate_limits.definitions import RATE_LIMIT_DEFINITIONS, RATE_LIMIT_INTERVAL_STRING 

19from couchers.sql import couchers_select as select 

20from couchers.templates.v2 import v2date 

21from couchers.utils import create_coordinate, now, today 

22from proto import ( 

23 api_pb2, 

24 auth_pb2, 

25 conversations_pb2, 

26 requests_pb2, 

27) 

28from proto.internal import unsubscribe_pb2 

29from tests.test_fixtures import ( # noqa 

30 api_session, 

31 auth_api_session, 

32 db, 

33 email_fields, 

34 generate_user, 

35 mock_notification_email, 

36 push_collector, 

37 requests_session, 

38 testconfig, 

39) 

40 

41 

42@pytest.fixture(autouse=True) 

43def _(testconfig): 

44 pass 

45 

46 

47def test_create_request(db): 

48 user1, token1 = generate_user() 

49 hosting_city = "Morningside Heights, New York City" 

50 hosting_lat = 40.8086 

51 hosting_lng = -73.9616 

52 hosting_radius = 500 

53 user2, token2 = generate_user( 

54 city=hosting_city, 

55 geom=create_coordinate(hosting_lat, hosting_lng), 

56 geom_radius=hosting_radius, 

57 ) 

58 

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

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

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

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

63 with requests_session(token1) as api: 

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

65 api.CreateHostRequest( 

66 requests_pb2.CreateHostRequestReq( 

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

68 ) 

69 ) 

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

71 assert e.value.details() == errors.CANT_REQUEST_SELF 

72 

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

74 api.CreateHostRequest( 

75 requests_pb2.CreateHostRequestReq( 

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

77 ) 

78 ) 

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

80 assert e.value.details() == errors.USER_NOT_FOUND 

81 

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

83 api.CreateHostRequest( 

84 requests_pb2.CreateHostRequestReq( 

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

86 ) 

87 ) 

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

89 assert e.value.details() == errors.DATE_FROM_AFTER_TO 

90 

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

92 api.CreateHostRequest( 

93 requests_pb2.CreateHostRequestReq( 

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

95 ) 

96 ) 

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

98 assert e.value.details() == errors.DATE_FROM_BEFORE_TODAY 

99 

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

101 api.CreateHostRequest( 

102 requests_pb2.CreateHostRequestReq( 

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

104 ) 

105 ) 

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

107 assert e.value.details() == errors.DATE_FROM_AFTER_TO 

108 

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

110 api.CreateHostRequest( 

111 requests_pb2.CreateHostRequestReq( 

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

113 ) 

114 ) 

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

116 assert e.value.details() == errors.INVALID_DATE 

117 

118 res = api.CreateHostRequest( 

119 requests_pb2.CreateHostRequestReq( 

120 host_user_id=user2.id, 

121 from_date=today_plus_2, 

122 to_date=today_plus_3, 

123 text="Test request", 

124 ) 

125 ) 

126 

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

128 

129 assert len(host_requests) == 1 

130 hr = host_requests[0] 

131 

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

133 

134 assert hr.hosting_city == hosting_city 

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

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

137 assert hr.hosting_radius == hosting_radius 

138 

139 today_ = today() 

140 today_plus_one_year = today_ + timedelta(days=365) 

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

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

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

144 api.CreateHostRequest( 

145 requests_pb2.CreateHostRequestReq( 

146 host_user_id=user2.id, 

147 from_date=today_plus_one_year_plus_2, 

148 to_date=today_plus_one_year_plus_3, 

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

150 ) 

151 ) 

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

153 assert e.value.details() == errors.DATE_FROM_AFTER_ONE_YEAR 

154 

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

156 api.CreateHostRequest( 

157 requests_pb2.CreateHostRequestReq( 

158 host_user_id=user2.id, 

159 from_date=today_plus_2, 

160 to_date=today_plus_one_year_plus_3, 

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

162 ) 

163 ) 

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

165 assert e.value.details() == errors.DATE_TO_AFTER_ONE_YEAR 

166 

167 

168def test_create_request_incomplete_profile(db): 

169 user1, token1 = generate_user(complete_profile=False) 

170 user2, _ = generate_user() 

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

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

173 with requests_session(token1) as api: 

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

175 api.CreateHostRequest( 

176 requests_pb2.CreateHostRequestReq( 

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

178 ) 

179 ) 

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

181 assert e.value.details() == errors.INCOMPLETE_PROFILE_SEND_REQUEST 

182 

183 

184def test_excessive_requests_are_reported(db): 

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

186 user, token = generate_user() 

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

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

189 rate_limit_definition = RATE_LIMIT_DEFINITIONS[RateLimitAction.host_request] 

190 with requests_session(token) as api: 

191 # Test warning email 

192 with mock_notification_email() as mock_email: 

193 for _ in range(rate_limit_definition.warning_limit): 

194 host_user, _ = generate_user() 

195 _ = api.CreateHostRequest( 

196 requests_pb2.CreateHostRequestReq( 

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

198 ) 

199 ) 

200 

201 assert mock_email.call_count == 0 

202 host_user, _ = generate_user() 

203 _ = api.CreateHostRequest( 

204 requests_pb2.CreateHostRequestReq( 

205 host_user_id=host_user.id, 

206 from_date=today_plus_2, 

207 to_date=today_plus_3, 

208 text="Excessive test request", 

209 ) 

210 ) 

211 assert mock_email.call_count == 1 

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

213 assert email.startswith( 

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

215 ) 

216 

217 # Test ban after exceeding HOST_REQUEST_HARD_LIMIT 

218 with mock_notification_email() as mock_email: 

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

220 host_user, _ = generate_user() 

221 _ = api.CreateHostRequest( 

222 requests_pb2.CreateHostRequestReq( 

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

224 ) 

225 ) 

226 

227 assert mock_email.call_count == 0 

228 host_user, _ = generate_user() 

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

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="Excessive test request", 

236 ) 

237 ) 

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

239 assert exc_info.value.details() == errors.HOST_REQUEST_RATE_LIMIT 

240 

241 assert mock_email.call_count == 1 

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

243 assert email.startswith( 

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

245 ) 

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

247 

248 

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

250 with session_scope() as session: 

251 message = Message( 

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

253 ) 

254 

255 session.add(message) 

256 

257 

258def test_GetHostRequest(db): 

259 user1, token1 = generate_user() 

260 user2, token2 = generate_user() 

261 user3, token3 = generate_user() 

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

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

264 with requests_session(token1) as api: 

265 host_request_id = api.CreateHostRequest( 

266 requests_pb2.CreateHostRequestReq( 

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

268 ) 

269 ).host_request_id 

270 

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

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

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

274 assert e.value.details() == errors.HOST_REQUEST_NOT_FOUND 

275 

276 api.SendHostRequestMessage( 

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

278 ) 

279 

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

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

282 

283 

284def test_ListHostRequests(db): 

285 user1, token1 = generate_user() 

286 user2, token2 = generate_user() 

287 user3, token3 = generate_user() 

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

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

290 with requests_session(token1) as api: 

291 host_request_1 = api.CreateHostRequest( 

292 requests_pb2.CreateHostRequestReq( 

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

294 ) 

295 ).host_request_id 

296 

297 host_request_2 = api.CreateHostRequest( 

298 requests_pb2.CreateHostRequestReq( 

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

300 ) 

301 ).host_request_id 

302 

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

304 assert res.no_more 

305 assert len(res.host_requests) == 2 

306 

307 with requests_session(token2) as api: 

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

309 assert res.no_more 

310 assert len(res.host_requests) == 1 

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

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

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

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

315 

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

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

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

319 

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

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

322 

323 api.CreateHostRequest( 

324 requests_pb2.CreateHostRequestReq( 

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

326 ) 

327 ) 

328 

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

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

331 

332 with requests_session(token3) as api: 

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

334 assert res.no_more 

335 assert len(res.host_requests) == 1 

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

337 

338 with requests_session(token1) as api: 

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

340 assert len(res.host_requests) == 1 

341 

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

343 assert len(res.host_requests) == 3 

344 

345 

346def test_ListHostRequests_pagination_regression(db): 

347 """ 

348 ListHostRequests was skipping a request when getting multiple pages 

349 """ 

350 user1, token1 = generate_user() 

351 user2, token2 = generate_user() 

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

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

354 with requests_session(token1) as api: 

355 host_request_1 = api.CreateHostRequest( 

356 requests_pb2.CreateHostRequestReq( 

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

358 ) 

359 ).host_request_id 

360 

361 host_request_2 = api.CreateHostRequest( 

362 requests_pb2.CreateHostRequestReq( 

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

364 ) 

365 ).host_request_id 

366 

367 host_request_3 = api.CreateHostRequest( 

368 requests_pb2.CreateHostRequestReq( 

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

370 ) 

371 ).host_request_id 

372 

373 with requests_session(token2) as api: 

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

375 assert res.no_more 

376 assert len(res.host_requests) == 3 

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

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

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

380 

381 with requests_session(token2) as api: 

382 api.RespondHostRequest( 

383 requests_pb2.RespondHostRequestReq( 

384 host_request_id=host_request_2, 

385 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED, 

386 text="Accepting host request 2", 

387 ) 

388 ) 

389 api.RespondHostRequest( 

390 requests_pb2.RespondHostRequestReq( 

391 host_request_id=host_request_1, 

392 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED, 

393 text="Accepting host request 1", 

394 ) 

395 ) 

396 api.RespondHostRequest( 

397 requests_pb2.RespondHostRequestReq( 

398 host_request_id=host_request_3, 

399 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED, 

400 text="Accepting host request 3", 

401 ) 

402 ) 

403 

404 with requests_session(token2) as api: 

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

406 assert res.no_more 

407 assert len(res.host_requests) == 3 

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

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

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

411 

412 with requests_session(token2) as api: 

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

414 assert not res.no_more 

415 assert len(res.host_requests) == 1 

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

417 res = api.ListHostRequests( 

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

419 ) 

420 assert not res.no_more 

421 assert len(res.host_requests) == 1 

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

423 res = api.ListHostRequests( 

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

425 ) 

426 assert res.no_more 

427 assert len(res.host_requests) == 1 

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

429 

430 

431def test_ListHostRequests_active_filter(db): 

432 user1, token1 = generate_user() 

433 user2, token2 = generate_user() 

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

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

436 

437 with requests_session(token1) as api: 

438 request_id = api.CreateHostRequest( 

439 requests_pb2.CreateHostRequestReq( 

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

441 ) 

442 ).host_request_id 

443 api.RespondHostRequest( 

444 requests_pb2.RespondHostRequestReq( 

445 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED 

446 ) 

447 ) 

448 

449 with requests_session(token2) as api: 

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

451 assert len(res.host_requests) == 1 

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

453 assert len(res.host_requests) == 0 

454 

455 

456def test_RespondHostRequests(db): 

457 user1, token1 = generate_user() 

458 user2, token2 = generate_user() 

459 user3, token3 = generate_user() 

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

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

462 

463 with requests_session(token1) as api: 

464 request_id = api.CreateHostRequest( 

465 requests_pb2.CreateHostRequestReq( 

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

467 ) 

468 ).host_request_id 

469 

470 # another user can't access 

471 with requests_session(token3) as api: 

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

473 api.RespondHostRequest( 

474 requests_pb2.RespondHostRequestReq( 

475 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED 

476 ) 

477 ) 

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

479 assert e.value.details() == errors.HOST_REQUEST_NOT_FOUND 

480 

481 with requests_session(token1) as api: 

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

483 api.RespondHostRequest( 

484 requests_pb2.RespondHostRequestReq( 

485 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED 

486 ) 

487 ) 

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

489 assert e.value.details() == errors.NOT_THE_HOST 

490 

491 with requests_session(token2) as api: 

492 # non existing id 

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

494 api.RespondHostRequest( 

495 requests_pb2.RespondHostRequestReq( 

496 host_request_id=9999, status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED 

497 ) 

498 ) 

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

500 

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

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

503 api.RespondHostRequest( 

504 requests_pb2.RespondHostRequestReq( 

505 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CONFIRMED 

506 ) 

507 ) 

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

509 assert e.value.details() == errors.INVALID_HOST_REQUEST_STATUS 

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

511 api.RespondHostRequest( 

512 requests_pb2.RespondHostRequestReq( 

513 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED 

514 ) 

515 ) 

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

517 assert e.value.details() == errors.INVALID_HOST_REQUEST_STATUS 

518 

519 api.RespondHostRequest( 

520 requests_pb2.RespondHostRequestReq( 

521 host_request_id=request_id, 

522 status=conversations_pb2.HOST_REQUEST_STATUS_REJECTED, 

523 text="Test rejection message", 

524 ) 

525 ) 

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

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

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

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

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

531 api.RespondHostRequest( 

532 requests_pb2.RespondHostRequestReq( 

533 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED 

534 ) 

535 ) 

536 

537 with requests_session(token1) as api: 

538 # can't make pending 

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

540 api.RespondHostRequest( 

541 requests_pb2.RespondHostRequestReq( 

542 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_PENDING 

543 ) 

544 ) 

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

546 assert e.value.details() == errors.INVALID_HOST_REQUEST_STATUS 

547 

548 # can confirm then cancel 

549 api.RespondHostRequest( 

550 requests_pb2.RespondHostRequestReq( 

551 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CONFIRMED 

552 ) 

553 ) 

554 

555 api.RespondHostRequest( 

556 requests_pb2.RespondHostRequestReq( 

557 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED 

558 ) 

559 ) 

560 

561 # can't confirm after having cancelled 

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

563 api.RespondHostRequest( 

564 requests_pb2.RespondHostRequestReq( 

565 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CONFIRMED 

566 ) 

567 ) 

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

569 assert e.value.details() == errors.INVALID_HOST_REQUEST_STATUS 

570 

571 # at this point there should be 7 messages 

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

573 with requests_session(token1) as api: 

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

575 assert len(res.messages) == 7 

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

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

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

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

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

581 

582 

583def test_get_host_request_messages(db): 

584 user1, token1 = generate_user() 

585 user2, token2 = generate_user() 

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

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

588 with requests_session(token1) as api: 

589 res = api.CreateHostRequest( 

590 requests_pb2.CreateHostRequestReq( 

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

592 ) 

593 ) 

594 conversation_id = res.host_request_id 

595 

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

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

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

599 

600 with requests_session(token2) as api: 

601 api.RespondHostRequest( 

602 requests_pb2.RespondHostRequestReq( 

603 host_request_id=conversation_id, status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED 

604 ) 

605 ) 

606 

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

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

609 

610 api.RespondHostRequest( 

611 requests_pb2.RespondHostRequestReq( 

612 host_request_id=conversation_id, status=conversations_pb2.HOST_REQUEST_STATUS_REJECTED 

613 ) 

614 ) 

615 

616 with requests_session(token1) as api: 

617 # 9 including initial message 

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

619 assert len(res.messages) == 9 

620 assert res.no_more 

621 

622 res = api.GetHostRequestMessages( 

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

624 ) 

625 assert not res.no_more 

626 assert len(res.messages) == 3 

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

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

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

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

631 

632 res = api.GetHostRequestMessages( 

633 requests_pb2.GetHostRequestMessagesReq( 

634 host_request_id=conversation_id, 

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

636 number=6, 

637 ) 

638 ) 

639 assert res.no_more 

640 assert len(res.messages) == 6 

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

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

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

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

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

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

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

648 

649 

650def test_SendHostRequestMessage(db): 

651 user1, token1 = generate_user() 

652 user2, token2 = generate_user() 

653 user3, token3 = generate_user() 

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

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

656 with requests_session(token1) as api: 

657 host_request_id = api.CreateHostRequest( 

658 requests_pb2.CreateHostRequestReq( 

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

660 ) 

661 ).host_request_id 

662 

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

664 api.SendHostRequestMessage( 

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

666 ) 

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

668 

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

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

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

672 assert e.value.details() == errors.INVALID_MESSAGE 

673 

674 api.SendHostRequestMessage( 

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

676 ) 

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

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

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

680 

681 with requests_session(token3) as api: 

682 # other user can't send 

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

684 api.SendHostRequestMessage( 

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

686 ) 

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

688 assert e.value.details() == errors.HOST_REQUEST_NOT_FOUND 

689 

690 with requests_session(token2) as api: 

691 api.SendHostRequestMessage( 

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

693 ) 

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

695 # including 2 for creation control message and message 

696 assert len(res.messages) == 4 

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

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

699 

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

701 api.RespondHostRequest( 

702 requests_pb2.RespondHostRequestReq( 

703 host_request_id=host_request_id, status=conversations_pb2.HOST_REQUEST_STATUS_REJECTED 

704 ) 

705 ) 

706 api.SendHostRequestMessage( 

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

708 ) 

709 

710 api.RespondHostRequest( 

711 requests_pb2.RespondHostRequestReq( 

712 host_request_id=host_request_id, status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED 

713 ) 

714 ) 

715 

716 with requests_session(token1) as api: 

717 api.RespondHostRequest( 

718 requests_pb2.RespondHostRequestReq( 

719 host_request_id=host_request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CONFIRMED 

720 ) 

721 ) 

722 api.SendHostRequestMessage( 

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

724 ) 

725 

726 api.RespondHostRequest( 

727 requests_pb2.RespondHostRequestReq( 

728 host_request_id=host_request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED 

729 ) 

730 ) 

731 api.SendHostRequestMessage( 

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

733 ) 

734 

735 

736def test_get_updates(db): 

737 user1, token1 = generate_user() 

738 user2, token2 = generate_user() 

739 user3, token3 = generate_user() 

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

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

742 with requests_session(token1) as api: 

743 host_request_id = api.CreateHostRequest( 

744 requests_pb2.CreateHostRequestReq( 

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

746 ) 

747 ).host_request_id 

748 

749 api.SendHostRequestMessage( 

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

751 ) 

752 api.SendHostRequestMessage( 

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

754 ) 

755 api.RespondHostRequest( 

756 requests_pb2.RespondHostRequestReq( 

757 host_request_id=host_request_id, 

758 status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED, 

759 text="Test message 3", 

760 ) 

761 ) 

762 

763 api.CreateHostRequest( 

764 requests_pb2.CreateHostRequestReq( 

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

766 ) 

767 ) 

768 

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

770 assert len(res.messages) == 6 

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

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

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

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

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

776 message_id_3 = res.messages[0].message_id 

777 message_id_cancel = res.messages[1].message_id 

778 message_id_2 = res.messages[2].message_id 

779 message_id_1 = res.messages[3].message_id 

780 message_id_0 = res.messages[4].message_id 

781 

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

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

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

785 

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

787 assert res.no_more 

788 assert len(res.updates) == 5 

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

790 assert ( 

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

792 ) 

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

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

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

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

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

798 

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

800 assert not res.no_more 

801 assert len(res.updates) == 1 

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

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

804 

805 with requests_session(token3) as api: 

806 # other user can't access 

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

808 assert len(res.updates) == 0 

809 

810 

811def test_archive_host_request(db): 

812 user1, token1 = generate_user() 

813 user2, token2 = generate_user() 

814 

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

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

817 

818 with requests_session(token1) as api: 

819 host_request_id = api.CreateHostRequest( 

820 requests_pb2.CreateHostRequestReq( 

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

822 ) 

823 ).host_request_id 

824 

825 api.SendHostRequestMessage( 

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

827 ) 

828 api.SendHostRequestMessage( 

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

830 ) 

831 # happy path archiving host request 

832 with requests_session(token1) as api: 

833 api.RespondHostRequest( 

834 requests_pb2.RespondHostRequestReq( 

835 host_request_id=host_request_id, 

836 status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED, 

837 text="Test message 3", 

838 ) 

839 ) 

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

841 assert len(res.host_requests) == 1 

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

843 api.SetHostRequestArchiveStatus( 

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

845 ) 

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

847 assert len(res.host_requests) == 1 

848 

849 

850def test_mark_last_seen(db): 

851 user1, token1 = generate_user() 

852 user2, token2 = generate_user() 

853 user3, token3 = generate_user() 

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

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

856 with requests_session(token1) as api: 

857 host_request_id = api.CreateHostRequest( 

858 requests_pb2.CreateHostRequestReq( 

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

860 ) 

861 ).host_request_id 

862 

863 host_request_id_2 = api.CreateHostRequest( 

864 requests_pb2.CreateHostRequestReq( 

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

866 ) 

867 ).host_request_id 

868 

869 api.SendHostRequestMessage( 

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

871 ) 

872 api.SendHostRequestMessage( 

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

874 ) 

875 api.RespondHostRequest( 

876 requests_pb2.RespondHostRequestReq( 

877 host_request_id=host_request_id, 

878 status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED, 

879 text="Test message 3", 

880 ) 

881 ) 

882 

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

884 with api_session(token1) as api: 

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

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

887 

888 with api_session(token2) as api: 

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

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

891 

892 with requests_session(token2) as api: 

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

894 

895 api.MarkLastSeenHostRequest( 

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

897 ) 

898 

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

900 

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

902 api.MarkLastSeenHostRequest( 

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

904 ) 

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

906 assert e.value.details() == errors.CANT_UNSEE_MESSAGES 

907 

908 # this will be used to test sent request notifications 

909 host_request_id_3 = api.CreateHostRequest( 

910 requests_pb2.CreateHostRequestReq( 

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

912 ) 

913 ).host_request_id 

914 

915 # this should make id_2 all read 

916 api.SendHostRequestMessage( 

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

918 ) 

919 

920 with api_session(token2) as api: 

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

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

923 

924 # make sure sent and received count for unseen notifications 

925 with requests_session(token1) as api: 

926 api.SendHostRequestMessage( 

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

928 ) 

929 

930 with api_session(token2) as api: 

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

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

933 

934 

935def test_response_rate(db): 

936 user1, token1 = generate_user() 

937 user2, token2 = generate_user() 

938 user3, token3 = generate_user(delete_user=True) 

939 

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

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

942 

943 with session_scope() as session: 

944 refresh_materialized_view(session, "user_response_rates") 

945 

946 with requests_session(token1) as api: 

947 # deleted: not found 

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

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

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

951 assert e.value.details() == errors.USER_NOT_FOUND 

952 

953 # no requests: insufficient 

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

955 assert res.HasField("insufficient_data") 

956 

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

958 host_request_1 = api.CreateHostRequest( 

959 requests_pb2.CreateHostRequestReq( 

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

961 ) 

962 ).host_request_id 

963 with session_scope() as session: 

964 session.execute( 

965 select(Message) 

966 .where(Message.conversation_id == host_request_1) 

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

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

969 refresh_materialized_view(session, "user_response_rates") 

970 

971 # still insufficient 

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

973 assert res.HasField("insufficient_data") 

974 

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

976 host_request_2 = api.CreateHostRequest( 

977 requests_pb2.CreateHostRequestReq( 

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

979 ) 

980 ).host_request_id 

981 with session_scope() as session: 

982 session.execute( 

983 select(Message) 

984 .where(Message.conversation_id == host_request_2) 

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

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

987 refresh_materialized_view(session, "user_response_rates") 

988 

989 # still insufficient 

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

991 assert res.HasField("insufficient_data") 

992 

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

994 host_request_3 = api.CreateHostRequest( 

995 requests_pb2.CreateHostRequestReq( 

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

997 ) 

998 ).host_request_id 

999 with session_scope() as session: 

1000 session.execute( 

1001 select(Message) 

1002 .where(Message.conversation_id == host_request_3) 

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

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

1005 refresh_materialized_view(session, "user_response_rates") 

1006 

1007 # now low 

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

1009 assert res.HasField("low") 

1010 

1011 with requests_session(token2) as api: 

1012 # accept a host req 

1013 api.RespondHostRequest( 

1014 requests_pb2.RespondHostRequestReq( 

1015 host_request_id=host_request_2, 

1016 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED, 

1017 text="Accepting host request", 

1018 ) 

1019 ) 

1020 

1021 with session_scope() as session: 

1022 refresh_materialized_view(session, "user_response_rates") 

1023 

1024 with requests_session(token1) as api: 

1025 # now some w p33 = 35h 

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

1027 assert res.HasField("some") 

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

1029 

1030 with requests_session(token2) as api: 

1031 # accept another host req 

1032 api.RespondHostRequest( 

1033 requests_pb2.RespondHostRequestReq( 

1034 host_request_id=host_request_3, 

1035 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED, 

1036 text="Accepting host request", 

1037 ) 

1038 ) 

1039 

1040 with session_scope() as session: 

1041 refresh_materialized_view(session, "user_response_rates") 

1042 

1043 with requests_session(token1) as api: 

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

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

1046 assert res.HasField("most") 

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

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

1049 

1050 with requests_session(token2) as api: 

1051 # accept last host req 

1052 api.RespondHostRequest( 

1053 requests_pb2.RespondHostRequestReq( 

1054 host_request_id=host_request_1, 

1055 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED, 

1056 text="Accepting host request", 

1057 ) 

1058 ) 

1059 

1060 with session_scope() as session: 

1061 refresh_materialized_view(session, "user_response_rates") 

1062 

1063 with requests_session(token1) as api: 

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

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

1066 assert res.HasField("almost_all") 

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

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

1069 

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

1071 host_request_4 = api.CreateHostRequest( 

1072 requests_pb2.CreateHostRequestReq( 

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

1074 ) 

1075 ).host_request_id 

1076 with session_scope() as session: 

1077 session.execute( 

1078 select(Message) 

1079 .where(Message.conversation_id == host_request_4) 

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

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

1082 refresh_materialized_view(session, "user_response_rates") 

1083 

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

1085 host_request_5 = api.CreateHostRequest( 

1086 requests_pb2.CreateHostRequestReq( 

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

1088 ) 

1089 ).host_request_id 

1090 with session_scope() as session: 

1091 session.execute( 

1092 select(Message) 

1093 .where(Message.conversation_id == host_request_5) 

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

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

1096 refresh_materialized_view(session, "user_response_rates") 

1097 

1098 # now some w p33 = 35h 

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

1100 assert res.HasField("some") 

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

1102 

1103 with requests_session(token2) as api: 

1104 # accept host req 

1105 api.RespondHostRequest( 

1106 requests_pb2.RespondHostRequestReq( 

1107 host_request_id=host_request_5, 

1108 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED, 

1109 text="Accepting host request", 

1110 ) 

1111 ) 

1112 

1113 with session_scope() as session: 

1114 refresh_materialized_view(session, "user_response_rates") 

1115 

1116 with requests_session(token1) as api: 

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

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

1119 assert res.HasField("most") 

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

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

1122 

1123 with requests_session(token2) as api: 

1124 # accept host req 

1125 api.RespondHostRequest( 

1126 requests_pb2.RespondHostRequestReq( 

1127 host_request_id=host_request_4, 

1128 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED, 

1129 text="Accepting host request", 

1130 ) 

1131 ) 

1132 

1133 with session_scope() as session: 

1134 refresh_materialized_view(session, "user_response_rates") 

1135 

1136 with requests_session(token1) as api: 

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

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

1139 assert res.HasField("almost_all") 

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

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

1142 

1143 

1144def test_request_notifications(db, push_collector): 

1145 host, host_token = generate_user(complete_profile=True) 

1146 surfer, surfer_token = generate_user(complete_profile=True) 

1147 

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

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

1150 

1151 with requests_session(surfer_token) as api: 

1152 with mock_notification_email() as mock: 

1153 hr_id = api.CreateHostRequest( 

1154 requests_pb2.CreateHostRequestReq( 

1155 host_user_id=host.id, 

1156 from_date=today_plus_2, 

1157 to_date=today_plus_3, 

1158 text="can i stay plz", 

1159 ) 

1160 ).host_request_id 

1161 

1162 mock.assert_called_once() 

1163 e = email_fields(mock) 

1164 assert e.recipient == host.email 

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

1166 assert host.name in e.plain 

1167 assert host.name in e.html 

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

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

1170 assert surfer.name in e.plain 

1171 assert surfer.name in e.html 

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

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

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

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

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

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

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

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

1180 

1181 push_collector.assert_user_has_single_matching( 

1182 host.id, 

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

1184 ) 

1185 

1186 with requests_session(host_token) as api: 

1187 with mock_notification_email() as mock: 

1188 api.RespondHostRequest( 

1189 requests_pb2.RespondHostRequestReq( 

1190 host_request_id=hr_id, 

1191 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED, 

1192 text="Accepting host request", 

1193 ) 

1194 ) 

1195 

1196 e = email_fields(mock) 

1197 assert e.recipient == surfer.email 

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

1199 assert host.name in e.plain 

1200 assert host.name in e.html 

1201 assert surfer.name in e.plain 

1202 assert surfer.name in e.html 

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

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

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

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

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

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

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

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

1211 

1212 push_collector.assert_user_has_single_matching( 

1213 surfer.id, 

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

1215 ) 

1216 

1217 

1218def test_quick_decline(db, push_collector): 

1219 host, host_token = generate_user(complete_profile=True) 

1220 surfer, surfer_token = generate_user(complete_profile=True) 

1221 

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

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

1224 

1225 with requests_session(surfer_token) as api: 

1226 with mock_notification_email() as mock: 

1227 hr_id = api.CreateHostRequest( 

1228 requests_pb2.CreateHostRequestReq( 

1229 host_user_id=host.id, 

1230 from_date=today_plus_2, 

1231 to_date=today_plus_3, 

1232 text="can i stay plz", 

1233 ) 

1234 ).host_request_id 

1235 

1236 mock.assert_called_once() 

1237 e = email_fields(mock) 

1238 assert e.recipient == host.email 

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

1240 assert host.name in e.plain 

1241 assert host.name in e.html 

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

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

1244 assert surfer.name in e.plain 

1245 assert surfer.name in e.html 

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

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

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

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

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

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

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

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

1254 

1255 push_collector.assert_user_has_single_matching( 

1256 host.id, 

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

1258 ) 

1259 

1260 # very ugly 

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

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

1263 if "payload" not in link: 

1264 continue 

1265 print(link) 

1266 url_parts = urlparse(link) 

1267 params = parse_qs(url_parts.query) 

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

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

1270 if payload.HasField("host_request_quick_decline"): 

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

1272 res = auth_api.Unsubscribe( 

1273 auth_pb2.UnsubscribeReq( 

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

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

1276 ) 

1277 ) 

1278 break 

1279 else: 

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

1281 

1282 with requests_session(surfer_token) as api: 

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

1284 assert res.status == conversations_pb2.HOST_REQUEST_STATUS_REJECTED 

1285 

1286 

1287def test_host_req_feedback(db): 

1288 host, host_token = generate_user(complete_profile=True) 

1289 host2, host2_token = generate_user(complete_profile=True) 

1290 host3, host3_token = generate_user(complete_profile=True) 

1291 surfer, surfer_token = generate_user(complete_profile=True) 

1292 

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

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

1295 

1296 with requests_session(surfer_token) as api: 

1297 hr_id = api.CreateHostRequest( 

1298 requests_pb2.CreateHostRequestReq( 

1299 host_user_id=host.id, 

1300 from_date=today_plus_2, 

1301 to_date=today_plus_3, 

1302 text="can i stay plz", 

1303 ) 

1304 ).host_request_id 

1305 hr2_id = api.CreateHostRequest( 

1306 requests_pb2.CreateHostRequestReq( 

1307 host_user_id=host2.id, 

1308 from_date=today_plus_2, 

1309 to_date=today_plus_3, 

1310 text="can i stay plz", 

1311 ) 

1312 ).host_request_id 

1313 hr3_id = api.CreateHostRequest( 

1314 requests_pb2.CreateHostRequestReq( 

1315 host_user_id=host3.id, 

1316 from_date=today_plus_2, 

1317 to_date=today_plus_3, 

1318 text="can i stay plz", 

1319 ) 

1320 ).host_request_id 

1321 

1322 with requests_session(host_token) as api: 

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

1324 assert not res.need_host_request_feedback 

1325 

1326 api.RespondHostRequest( 

1327 requests_pb2.RespondHostRequestReq( 

1328 host_request_id=hr_id, 

1329 status=conversations_pb2.HOST_REQUEST_STATUS_REJECTED, 

1330 ) 

1331 ) 

1332 

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

1334 assert res.need_host_request_feedback 

1335 

1336 # surfer can't leave feedback 

1337 with requests_session(surfer_token) as api: 

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

1339 api.SendHostRequestFeedback( 

1340 requests_pb2.SendHostRequestFeedbackReq( 

1341 host_request_id=hr_id, 

1342 ) 

1343 ) 

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

1345 assert e.value.details() == errors.HOST_REQUEST_NOT_FOUND 

1346 

1347 with requests_session(host_token) as api: 

1348 api.SendHostRequestFeedback( 

1349 requests_pb2.SendHostRequestFeedbackReq( 

1350 host_request_id=hr_id, 

1351 host_request_quality=requests_pb2.HOST_REQUEST_QUALITY_LOW, 

1352 ) 

1353 ) 

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

1355 assert not res.need_host_request_feedback 

1356 

1357 # can't leave it twice 

1358 with requests_session(host_token) as api: 

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

1360 api.SendHostRequestFeedback( 

1361 requests_pb2.SendHostRequestFeedbackReq( 

1362 host_request_id=hr_id, 

1363 ) 

1364 ) 

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

1366 assert e.value.details() == errors.ALREADY_LEFT_HOST_REQUEST_FEEDBACK 

1367 

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

1369 assert not res.need_host_request_feedback 

1370 

1371 with requests_session(host2_token) as api: 

1372 api.RespondHostRequest( 

1373 requests_pb2.RespondHostRequestReq( 

1374 host_request_id=hr2_id, status=conversations_pb2.HOST_REQUEST_STATUS_REJECTED 

1375 ) 

1376 ) 

1377 # can't leave feedback on the wrong one 

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

1379 api.SendHostRequestFeedback( 

1380 requests_pb2.SendHostRequestFeedbackReq( 

1381 host_request_id=hr_id, 

1382 ) 

1383 ) 

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

1385 assert e.value.details() == errors.HOST_REQUEST_NOT_FOUND 

1386 

1387 # null feedback is still feedback 

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

1389 

1390 with requests_session(host3_token) as api: 

1391 api.RespondHostRequest( 

1392 requests_pb2.RespondHostRequestReq( 

1393 host_request_id=hr3_id, status=conversations_pb2.HOST_REQUEST_STATUS_REJECTED 

1394 ) 

1395 ) 

1396 

1397 api.SendHostRequestFeedback( 

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

1399 )