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

724 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-01-14 09:03 +0000

1import re 

2from datetime import timedelta 

3from urllib.parse import parse_qs, urlparse 

4 

5import grpc 

6import pytest 

7from sqlalchemy import select 

8from sqlalchemy_utils import refresh_materialized_view 

9 

10from couchers.constants import HOST_REQUEST_MIN_LENGTH_UTF16 

11from couchers.crypto import b64decode 

12from couchers.db import session_scope 

13from couchers.i18n.localize import localize_date 

14from couchers.models import ( 

15 Message, 

16 MessageType, 

17 RateLimitAction, 

18) 

19from couchers.proto import ( 

20 api_pb2, 

21 auth_pb2, 

22 conversations_pb2, 

23 requests_pb2, 

24) 

25from couchers.proto.internal import unsubscribe_pb2 

26from couchers.rate_limits.definitions import RATE_LIMIT_DEFINITIONS, RATE_LIMIT_HOURS 

27from couchers.utils import create_coordinate, now, today 

28from tests.fixtures.db import generate_user 

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

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

31 

32 

33@pytest.fixture(autouse=True) 

34def _(testconfig): 

35 pass 

36 

37 

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

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

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

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

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

43 return text 

44 padding_length = HOST_REQUEST_MIN_LENGTH_UTF16 - utf16_length 

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

46 

47 

48def test_create_request(db, moderator): 

49 user1, token1 = generate_user() 

50 hosting_city = "Morningside Heights, New York City" 

51 hosting_lat = 40.8086 

52 hosting_lng = -73.9616 

53 hosting_radius = 500 

54 user2, token2 = generate_user( 

55 city=hosting_city, 

56 geom=create_coordinate(hosting_lat, hosting_lng), 

57 geom_radius=hosting_radius, 

58 ) 

59 

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

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

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

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

64 

65 with requests_session(token1) as api: 

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

67 api.CreateHostRequest( 

68 requests_pb2.CreateHostRequestReq( 

69 host_user_id=user1.id, 

70 from_date=today_plus_2.isoformat(), 

71 to_date=today_plus_3.isoformat(), 

72 text=valid_request_text(), 

73 ) 

74 ) 

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

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

77 

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

79 api.CreateHostRequest( 

80 requests_pb2.CreateHostRequestReq( 

81 host_user_id=999, 

82 from_date=today_plus_2.isoformat(), 

83 to_date=today_plus_3.isoformat(), 

84 text=valid_request_text(), 

85 ) 

86 ) 

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

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

89 

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

91 api.CreateHostRequest( 

92 requests_pb2.CreateHostRequestReq( 

93 host_user_id=user2.id, 

94 from_date=today_plus_3.isoformat(), 

95 to_date=today_plus_2.isoformat(), 

96 text=valid_request_text(), 

97 ) 

98 ) 

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

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

101 

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

103 api.CreateHostRequest( 

104 requests_pb2.CreateHostRequestReq( 

105 host_user_id=user2.id, 

106 from_date=today_minus_3.isoformat(), 

107 to_date=today_plus_2.isoformat(), 

108 text=valid_request_text(), 

109 ) 

110 ) 

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

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

113 

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

115 api.CreateHostRequest( 

116 requests_pb2.CreateHostRequestReq( 

117 host_user_id=user2.id, 

118 from_date=today_plus_2.isoformat(), 

119 to_date=today_minus_2.isoformat(), 

120 text=valid_request_text(), 

121 ) 

122 ) 

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

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

125 

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

127 api.CreateHostRequest( 

128 requests_pb2.CreateHostRequestReq( 

129 host_user_id=user2.id, 

130 from_date="2020-00-06", 

131 to_date=today_minus_2.isoformat(), 

132 text=valid_request_text(), 

133 ) 

134 ) 

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

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

137 

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

139 api.CreateHostRequest( 

140 requests_pb2.CreateHostRequestReq( 

141 host_user_id=user2.id, 

142 from_date=today_plus_2.isoformat(), 

143 to_date=today_plus_3.isoformat(), 

144 text="Too short.", 

145 ) 

146 ) 

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

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

149 

150 res = api.CreateHostRequest( 

151 requests_pb2.CreateHostRequestReq( 

152 host_user_id=user2.id, 

153 from_date=today_plus_2.isoformat(), 

154 to_date=today_plus_3.isoformat(), 

155 text=valid_request_text(), 

156 ) 

157 ) 

158 host_request_id = res.host_request_id 

159 

160 moderator.approve_host_request(host_request_id) 

161 

162 with requests_session(token1) as api: 

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

164 

165 assert len(host_requests) == 1 

166 hr = host_requests[0] 

167 

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

169 

170 assert hr.hosting_city == hosting_city 

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

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

173 assert hr.hosting_radius == hosting_radius 

174 

175 today_ = today() 

176 today_plus_one_year = today_ + timedelta(days=365) 

177 today_plus_one_year_plus_2 = today_plus_one_year + timedelta(days=2) 

178 today_plus_one_year_plus_3 = today_plus_one_year + timedelta(days=3) 

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

180 api.CreateHostRequest( 

181 requests_pb2.CreateHostRequestReq( 

182 host_user_id=user2.id, 

183 from_date=today_plus_one_year_plus_2.isoformat(), 

184 to_date=today_plus_one_year_plus_3.isoformat(), 

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

186 ) 

187 ) 

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

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

190 

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

192 api.CreateHostRequest( 

193 requests_pb2.CreateHostRequestReq( 

194 host_user_id=user2.id, 

195 from_date=today_plus_2.isoformat(), 

196 to_date=today_plus_one_year_plus_3.isoformat(), 

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

198 ) 

199 ) 

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

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

202 

203 

204def test_create_request_incomplete_profile(db): 

205 user1, token1 = generate_user(complete_profile=False) 

206 user2, _ = generate_user() 

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

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

209 with requests_session(token1) as api: 

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

211 api.CreateHostRequest( 

212 requests_pb2.CreateHostRequestReq( 

213 host_user_id=user2.id, 

214 from_date=today_plus_2.isoformat(), 

215 to_date=today_plus_3.isoformat(), 

216 text=valid_request_text(), 

217 ) 

218 ) 

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

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

221 

222 

223def test_excessive_requests_are_reported(db): 

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

225 user, token = generate_user() 

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

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

228 rate_limit_definition = RATE_LIMIT_DEFINITIONS[RateLimitAction.host_request] 

229 with requests_session(token) as api: 

230 # Test warning email 

231 with mock_notification_email() as mock_email: 

232 for _ in range(rate_limit_definition.warning_limit): 

233 host_user, _ = generate_user() 

234 _ = api.CreateHostRequest( 

235 requests_pb2.CreateHostRequestReq( 

236 host_user_id=host_user.id, 

237 from_date=today_plus_2.isoformat(), 

238 to_date=today_plus_3.isoformat(), 

239 text=valid_request_text(), 

240 ) 

241 ) 

242 

243 assert mock_email.call_count == 0 

244 host_user, _ = generate_user() 

245 _ = api.CreateHostRequest( 

246 requests_pb2.CreateHostRequestReq( 

247 host_user_id=host_user.id, 

248 from_date=today_plus_2.isoformat(), 

249 to_date=today_plus_3.isoformat(), 

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

251 ) 

252 ) 

253 assert mock_email.call_count == 1 

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

255 assert email.startswith( 

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

257 ) 

258 

259 # Test ban after exceeding HOST_REQUEST_HARD_LIMIT 

260 with mock_notification_email() as mock_email: 

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

262 host_user, _ = generate_user() 

263 _ = api.CreateHostRequest( 

264 requests_pb2.CreateHostRequestReq( 

265 host_user_id=host_user.id, 

266 from_date=today_plus_2.isoformat(), 

267 to_date=today_plus_3.isoformat(), 

268 text=valid_request_text(), 

269 ) 

270 ) 

271 

272 assert mock_email.call_count == 0 

273 host_user, _ = generate_user() 

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

275 _ = api.CreateHostRequest( 

276 requests_pb2.CreateHostRequestReq( 

277 host_user_id=host_user.id, 

278 from_date=today_plus_2.isoformat(), 

279 to_date=today_plus_3.isoformat(), 

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

281 ) 

282 ) 

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

284 assert ( 

285 exc_info.value.details() 

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

287 ) 

288 

289 assert mock_email.call_count == 1 

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

291 assert email.startswith( 

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

293 ) 

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

295 

296 

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

298 with session_scope() as session: 

299 message = Message( 

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

301 ) 

302 

303 session.add(message) 

304 

305 

306def test_GetHostRequest(db): 

307 user1, token1 = generate_user() 

308 user2, token2 = generate_user() 

309 user3, token3 = generate_user() 

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

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

312 with requests_session(token1) as api: 

313 host_request_id = api.CreateHostRequest( 

314 requests_pb2.CreateHostRequestReq( 

315 host_user_id=user2.id, 

316 from_date=today_plus_2.isoformat(), 

317 to_date=today_plus_3.isoformat(), 

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

319 ) 

320 ).host_request_id 

321 

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

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

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

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

326 

327 api.SendHostRequestMessage( 

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

329 ) 

330 

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

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

333 

334 

335def test_ListHostRequests(db, moderator): 

336 user1, token1 = generate_user() 

337 user2, token2 = generate_user() 

338 user3, token3 = generate_user() 

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

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

341 with requests_session(token1) as api: 

342 host_request_1 = api.CreateHostRequest( 

343 requests_pb2.CreateHostRequestReq( 

344 host_user_id=user2.id, 

345 from_date=today_plus_2.isoformat(), 

346 to_date=today_plus_3.isoformat(), 

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

348 ) 

349 ).host_request_id 

350 

351 host_request_2 = api.CreateHostRequest( 

352 requests_pb2.CreateHostRequestReq( 

353 host_user_id=user3.id, 

354 from_date=today_plus_2.isoformat(), 

355 to_date=today_plus_3.isoformat(), 

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

357 ) 

358 ).host_request_id 

359 

360 moderator.approve_host_request(host_request_1) 

361 moderator.approve_host_request(host_request_2) 

362 

363 with requests_session(token1) as api: 

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

365 assert res.no_more 

366 assert len(res.host_requests) == 2 

367 

368 with requests_session(token2) as api: 

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

370 assert res.no_more 

371 assert len(res.host_requests) == 1 

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

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

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

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

376 

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

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

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

380 

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

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

383 

384 host_request_3 = api.CreateHostRequest( 

385 requests_pb2.CreateHostRequestReq( 

386 host_user_id=user1.id, 

387 from_date=today_plus_2.isoformat(), 

388 to_date=today_plus_3.isoformat(), 

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

390 ) 

391 ).host_request_id 

392 

393 moderator.approve_host_request(host_request_3) 

394 

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

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

397 

398 with requests_session(token3) as api: 

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

400 assert res.no_more 

401 assert len(res.host_requests) == 1 

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

403 

404 with requests_session(token1) as api: 

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

406 assert len(res.host_requests) == 1 

407 

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

409 assert len(res.host_requests) == 3 

410 

411 

412def test_ListHostRequests_pagination_regression(db, moderator): 

413 """ 

414 ListHostRequests was skipping a request when getting multiple pages 

415 """ 

416 user1, token1 = generate_user() 

417 user2, token2 = generate_user() 

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

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

420 with requests_session(token1) as api: 

421 host_request_1 = api.CreateHostRequest( 

422 requests_pb2.CreateHostRequestReq( 

423 host_user_id=user2.id, 

424 from_date=today_plus_2.isoformat(), 

425 to_date=today_plus_3.isoformat(), 

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

427 ) 

428 ).host_request_id 

429 

430 host_request_2 = api.CreateHostRequest( 

431 requests_pb2.CreateHostRequestReq( 

432 host_user_id=user2.id, 

433 from_date=today_plus_2.isoformat(), 

434 to_date=today_plus_3.isoformat(), 

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

436 ) 

437 ).host_request_id 

438 

439 host_request_3 = api.CreateHostRequest( 

440 requests_pb2.CreateHostRequestReq( 

441 host_user_id=user2.id, 

442 from_date=today_plus_2.isoformat(), 

443 to_date=today_plus_3.isoformat(), 

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

445 ) 

446 ).host_request_id 

447 

448 moderator.approve_host_request(host_request_1) 

449 moderator.approve_host_request(host_request_2) 

450 moderator.approve_host_request(host_request_3) 

451 

452 with requests_session(token2) as api: 

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

454 assert res.no_more 

455 assert len(res.host_requests) == 3 

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

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

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

459 

460 with requests_session(token2) as api: 

461 api.RespondHostRequest( 

462 requests_pb2.RespondHostRequestReq( 

463 host_request_id=host_request_2, 

464 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED, 

465 text="Accepting host request 2", 

466 ) 

467 ) 

468 api.RespondHostRequest( 

469 requests_pb2.RespondHostRequestReq( 

470 host_request_id=host_request_1, 

471 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED, 

472 text="Accepting host request 1", 

473 ) 

474 ) 

475 api.RespondHostRequest( 

476 requests_pb2.RespondHostRequestReq( 

477 host_request_id=host_request_3, 

478 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED, 

479 text="Accepting host request 3", 

480 ) 

481 ) 

482 

483 with requests_session(token2) as api: 

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

485 assert res.no_more 

486 assert len(res.host_requests) == 3 

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

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

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

490 

491 with requests_session(token2) as api: 

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

493 assert not res.no_more 

494 assert len(res.host_requests) == 1 

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

496 res = api.ListHostRequests( 

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

498 ) 

499 assert not res.no_more 

500 assert len(res.host_requests) == 1 

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

502 res = api.ListHostRequests( 

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

504 ) 

505 assert res.no_more 

506 assert len(res.host_requests) == 1 

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

508 

509 

510def test_ListHostRequests_active_filter(db, moderator): 

511 user1, token1 = generate_user() 

512 user2, token2 = generate_user() 

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

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

515 

516 with requests_session(token1) as api: 

517 request_id = api.CreateHostRequest( 

518 requests_pb2.CreateHostRequestReq( 

519 host_user_id=user2.id, 

520 from_date=today_plus_2.isoformat(), 

521 to_date=today_plus_3.isoformat(), 

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

523 ) 

524 ).host_request_id 

525 

526 moderator.approve_host_request(request_id) 

527 

528 with requests_session(token1) as api: 

529 api.RespondHostRequest( 

530 requests_pb2.RespondHostRequestReq( 

531 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED 

532 ) 

533 ) 

534 

535 with requests_session(token2) as api: 

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

537 assert len(res.host_requests) == 1 

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

539 assert len(res.host_requests) == 0 

540 

541 

542def test_RespondHostRequests(db, moderator): 

543 user1, token1 = generate_user() 

544 user2, token2 = generate_user() 

545 user3, token3 = generate_user() 

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

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

548 

549 with requests_session(token1) as api: 

550 request_id = api.CreateHostRequest( 

551 requests_pb2.CreateHostRequestReq( 

552 host_user_id=user2.id, 

553 from_date=today_plus_2.isoformat(), 

554 to_date=today_plus_3.isoformat(), 

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

556 ) 

557 ).host_request_id 

558 

559 moderator.approve_host_request(request_id) 

560 

561 # another user can't access 

562 with requests_session(token3) as api: 

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

564 api.RespondHostRequest( 

565 requests_pb2.RespondHostRequestReq( 

566 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED 

567 ) 

568 ) 

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

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

571 

572 with requests_session(token1) as api: 

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

574 api.RespondHostRequest( 

575 requests_pb2.RespondHostRequestReq( 

576 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED 

577 ) 

578 ) 

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

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

581 

582 with requests_session(token2) as api: 

583 # non existing id 

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

585 api.RespondHostRequest( 

586 requests_pb2.RespondHostRequestReq( 

587 host_request_id=9999, status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED 

588 ) 

589 ) 

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

591 

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

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

594 api.RespondHostRequest( 

595 requests_pb2.RespondHostRequestReq( 

596 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CONFIRMED 

597 ) 

598 ) 

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

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

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

602 api.RespondHostRequest( 

603 requests_pb2.RespondHostRequestReq( 

604 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED 

605 ) 

606 ) 

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

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

609 

610 api.RespondHostRequest( 

611 requests_pb2.RespondHostRequestReq( 

612 host_request_id=request_id, 

613 status=conversations_pb2.HOST_REQUEST_STATUS_REJECTED, 

614 text="Test rejection message", 

615 ) 

616 ) 

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

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

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

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

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

622 api.RespondHostRequest( 

623 requests_pb2.RespondHostRequestReq( 

624 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED 

625 ) 

626 ) 

627 

628 with requests_session(token1) as api: 

629 # can't make pending 

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

631 api.RespondHostRequest( 

632 requests_pb2.RespondHostRequestReq( 

633 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_PENDING 

634 ) 

635 ) 

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

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

638 

639 # can confirm then cancel 

640 api.RespondHostRequest( 

641 requests_pb2.RespondHostRequestReq( 

642 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CONFIRMED 

643 ) 

644 ) 

645 

646 api.RespondHostRequest( 

647 requests_pb2.RespondHostRequestReq( 

648 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED 

649 ) 

650 ) 

651 

652 # can't confirm after having cancelled 

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

654 api.RespondHostRequest( 

655 requests_pb2.RespondHostRequestReq( 

656 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CONFIRMED 

657 ) 

658 ) 

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

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

661 

662 # at this point there should be 7 messages 

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

664 with requests_session(token1) as api: 

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

666 assert len(res.messages) == 7 

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

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

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

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

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

672 

673 

674def test_get_host_request_messages(db, moderator): 

675 user1, token1 = generate_user() 

676 user2, token2 = generate_user() 

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

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

679 with requests_session(token1) as api: 

680 res = api.CreateHostRequest( 

681 requests_pb2.CreateHostRequestReq( 

682 host_user_id=user2.id, 

683 from_date=today_plus_2.isoformat(), 

684 to_date=today_plus_3.isoformat(), 

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

686 ) 

687 ) 

688 conversation_id = res.host_request_id 

689 

690 moderator.approve_host_request(conversation_id) 

691 

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

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

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

695 

696 with requests_session(token2) as api: 

697 api.RespondHostRequest( 

698 requests_pb2.RespondHostRequestReq( 

699 host_request_id=conversation_id, status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED 

700 ) 

701 ) 

702 

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

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

705 

706 api.RespondHostRequest( 

707 requests_pb2.RespondHostRequestReq( 

708 host_request_id=conversation_id, status=conversations_pb2.HOST_REQUEST_STATUS_REJECTED 

709 ) 

710 ) 

711 

712 with requests_session(token1) as api: 

713 # 9 including initial message 

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

715 assert len(res.messages) == 9 

716 assert res.no_more 

717 

718 res = api.GetHostRequestMessages( 

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

720 ) 

721 assert not res.no_more 

722 assert len(res.messages) == 3 

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

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

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

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

727 

728 res = api.GetHostRequestMessages( 

729 requests_pb2.GetHostRequestMessagesReq( 

730 host_request_id=conversation_id, 

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

732 number=6, 

733 ) 

734 ) 

735 assert res.no_more 

736 assert len(res.messages) == 6 

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

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

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

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

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

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

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

744 

745 

746def test_SendHostRequestMessage(db, moderator): 

747 user1, token1 = generate_user() 

748 user2, token2 = generate_user() 

749 user3, token3 = generate_user() 

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

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

752 with requests_session(token1) as api: 

753 host_request_id = api.CreateHostRequest( 

754 requests_pb2.CreateHostRequestReq( 

755 host_user_id=user2.id, 

756 from_date=today_plus_2.isoformat(), 

757 to_date=today_plus_3.isoformat(), 

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

759 ) 

760 ).host_request_id 

761 

762 moderator.approve_host_request(host_request_id) 

763 

764 with requests_session(token1) as api: 

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

766 api.SendHostRequestMessage( 

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

768 ) 

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

770 

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

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

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

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

775 

776 api.SendHostRequestMessage( 

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

778 ) 

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

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

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

782 

783 with requests_session(token3) as api: 

784 # other user can't send 

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

786 api.SendHostRequestMessage( 

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

788 ) 

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

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

791 

792 with requests_session(token2) as api: 

793 api.SendHostRequestMessage( 

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

795 ) 

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

797 # including 2 for creation control message and message 

798 assert len(res.messages) == 4 

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

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

801 

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

803 api.RespondHostRequest( 

804 requests_pb2.RespondHostRequestReq( 

805 host_request_id=host_request_id, status=conversations_pb2.HOST_REQUEST_STATUS_REJECTED 

806 ) 

807 ) 

808 api.SendHostRequestMessage( 

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

810 ) 

811 

812 api.RespondHostRequest( 

813 requests_pb2.RespondHostRequestReq( 

814 host_request_id=host_request_id, status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED 

815 ) 

816 ) 

817 

818 with requests_session(token1) as api: 

819 api.RespondHostRequest( 

820 requests_pb2.RespondHostRequestReq( 

821 host_request_id=host_request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CONFIRMED 

822 ) 

823 ) 

824 api.SendHostRequestMessage( 

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

826 ) 

827 

828 api.RespondHostRequest( 

829 requests_pb2.RespondHostRequestReq( 

830 host_request_id=host_request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED 

831 ) 

832 ) 

833 api.SendHostRequestMessage( 

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

835 ) 

836 

837 

838def test_get_updates(db, moderator): 

839 user1, token1 = generate_user() 

840 user2, token2 = generate_user() 

841 user3, token3 = generate_user() 

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

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

844 with requests_session(token1) as api: 

845 host_request_id = api.CreateHostRequest( 

846 requests_pb2.CreateHostRequestReq( 

847 host_user_id=user2.id, 

848 from_date=today_plus_2.isoformat(), 

849 to_date=today_plus_3.isoformat(), 

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

851 ) 

852 ).host_request_id 

853 

854 moderator.approve_host_request(host_request_id) 

855 

856 with requests_session(token1) as api: 

857 api.SendHostRequestMessage( 

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

859 ) 

860 api.SendHostRequestMessage( 

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

862 ) 

863 api.RespondHostRequest( 

864 requests_pb2.RespondHostRequestReq( 

865 host_request_id=host_request_id, 

866 status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED, 

867 text="Test message 3", 

868 ) 

869 ) 

870 

871 api.CreateHostRequest( 

872 requests_pb2.CreateHostRequestReq( 

873 host_user_id=user2.id, 

874 from_date=today_plus_2.isoformat(), 

875 to_date=today_plus_3.isoformat(), 

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

877 ) 

878 ) 

879 

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

881 assert len(res.messages) == 6 

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

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

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

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

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

887 message_id_3 = res.messages[0].message_id 

888 message_id_cancel = res.messages[1].message_id 

889 message_id_2 = res.messages[2].message_id 

890 message_id_1 = res.messages[3].message_id 

891 message_id_0 = res.messages[4].message_id 

892 

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

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

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

896 

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

898 assert res.no_more 

899 assert len(res.updates) == 5 

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

901 assert ( 

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

903 ) 

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

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

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

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

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

909 

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

911 assert not res.no_more 

912 assert len(res.updates) == 1 

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

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

915 

916 with requests_session(token3) as api: 

917 # other user can't access 

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

919 assert len(res.updates) == 0 

920 

921 

922def test_archive_host_request(db, moderator): 

923 user1, token1 = generate_user() 

924 user2, token2 = generate_user() 

925 

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

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

928 

929 with requests_session(token1) as api: 

930 host_request_id = api.CreateHostRequest( 

931 requests_pb2.CreateHostRequestReq( 

932 host_user_id=user2.id, 

933 from_date=today_plus_2.isoformat(), 

934 to_date=today_plus_3.isoformat(), 

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

936 ) 

937 ).host_request_id 

938 

939 api.SendHostRequestMessage( 

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

941 ) 

942 api.SendHostRequestMessage( 

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

944 ) 

945 

946 moderator.approve_host_request(host_request_id) 

947 

948 # happy path archiving host request 

949 with requests_session(token1) as api: 

950 api.RespondHostRequest( 

951 requests_pb2.RespondHostRequestReq( 

952 host_request_id=host_request_id, 

953 status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED, 

954 text="Test message 3", 

955 ) 

956 ) 

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

958 assert len(res.host_requests) == 1 

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

960 api.SetHostRequestArchiveStatus( 

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

962 ) 

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

964 assert len(res.host_requests) == 1 

965 

966 

967def test_mark_last_seen(db, moderator): 

968 user1, token1 = generate_user() 

969 user2, token2 = generate_user() 

970 user3, token3 = generate_user() 

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

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

973 with requests_session(token1) as api: 

974 host_request_id = api.CreateHostRequest( 

975 requests_pb2.CreateHostRequestReq( 

976 host_user_id=user2.id, 

977 from_date=today_plus_2.isoformat(), 

978 to_date=today_plus_3.isoformat(), 

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

980 ) 

981 ).host_request_id 

982 

983 host_request_id_2 = api.CreateHostRequest( 

984 requests_pb2.CreateHostRequestReq( 

985 host_user_id=user2.id, 

986 from_date=today_plus_2.isoformat(), 

987 to_date=today_plus_3.isoformat(), 

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

989 ) 

990 ).host_request_id 

991 

992 moderator.approve_host_request(host_request_id) 

993 moderator.approve_host_request(host_request_id_2) 

994 

995 with requests_session(token1) as api: 

996 api.SendHostRequestMessage( 

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

998 ) 

999 api.SendHostRequestMessage( 

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

1001 ) 

1002 api.RespondHostRequest( 

1003 requests_pb2.RespondHostRequestReq( 

1004 host_request_id=host_request_id, 

1005 status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED, 

1006 text="Test message 3", 

1007 ) 

1008 ) 

1009 

1010 moderator.approve_host_request(host_request_id) 

1011 moderator.approve_host_request(host_request_id_2) 

1012 

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

1014 with api_session(token1) as api: 

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

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

1017 

1018 with api_session(token2) as api: 

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

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

1021 

1022 with requests_session(token2) as api: 

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

1024 

1025 api.MarkLastSeenHostRequest( 

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

1027 ) 

1028 

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

1030 

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

1032 api.MarkLastSeenHostRequest( 

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

1034 ) 

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

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

1037 

1038 # this will be used to test sent request notifications 

1039 host_request_id_3 = api.CreateHostRequest( 

1040 requests_pb2.CreateHostRequestReq( 

1041 host_user_id=user1.id, 

1042 from_date=today_plus_2.isoformat(), 

1043 to_date=today_plus_3.isoformat(), 

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

1045 ) 

1046 ).host_request_id 

1047 

1048 moderator.approve_host_request(host_request_id_3) 

1049 

1050 with requests_session(token2) as api: 

1051 # this should make id_2 all read 

1052 api.SendHostRequestMessage( 

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

1054 ) 

1055 

1056 with api_session(token2) as api: 

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

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

1059 

1060 # make sure sent and received count for unseen notifications 

1061 with requests_session(token1) as api: 

1062 api.SendHostRequestMessage( 

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

1064 ) 

1065 

1066 with api_session(token2) as api: 

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

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

1069 

1070 

1071def test_response_rate(db, moderator): 

1072 user1, token1 = generate_user() 

1073 user2, token2 = generate_user() 

1074 user3, token3 = generate_user(delete_user=True) 

1075 

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

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

1078 

1079 with session_scope() as session: 

1080 refresh_materialized_view(session, "user_response_rates") 

1081 

1082 with requests_session(token1) as api: 

1083 # deleted: not found 

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

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

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

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

1088 

1089 # no requests: insufficient 

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

1091 assert res.HasField("insufficient_data") 

1092 

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

1094 host_request_1 = api.CreateHostRequest( 

1095 requests_pb2.CreateHostRequestReq( 

1096 host_user_id=user2.id, 

1097 from_date=today_plus_2.isoformat(), 

1098 to_date=today_plus_3.isoformat(), 

1099 text=valid_request_text("Test request"), 

1100 ) 

1101 ).host_request_id 

1102 moderator.approve_host_request(host_request_1) 

1103 with session_scope() as session: 

1104 session.execute( 

1105 select(Message) 

1106 .where(Message.conversation_id == host_request_1) 

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

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

1109 refresh_materialized_view(session, "user_response_rates") 

1110 

1111 # still insufficient 

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

1113 assert res.HasField("insufficient_data") 

1114 

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

1116 host_request_2 = api.CreateHostRequest( 

1117 requests_pb2.CreateHostRequestReq( 

1118 host_user_id=user2.id, 

1119 from_date=today_plus_2.isoformat(), 

1120 to_date=today_plus_3.isoformat(), 

1121 text=valid_request_text("Test request"), 

1122 ) 

1123 ).host_request_id 

1124 moderator.approve_host_request(host_request_2) 

1125 with session_scope() as session: 

1126 session.execute( 

1127 select(Message) 

1128 .where(Message.conversation_id == host_request_2) 

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

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

1131 refresh_materialized_view(session, "user_response_rates") 

1132 

1133 # still insufficient 

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

1135 assert res.HasField("insufficient_data") 

1136 

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

1138 host_request_3 = api.CreateHostRequest( 

1139 requests_pb2.CreateHostRequestReq( 

1140 host_user_id=user2.id, 

1141 from_date=today_plus_2.isoformat(), 

1142 to_date=today_plus_3.isoformat(), 

1143 text=valid_request_text("Test request"), 

1144 ) 

1145 ).host_request_id 

1146 moderator.approve_host_request(host_request_3) 

1147 with session_scope() as session: 

1148 session.execute( 

1149 select(Message) 

1150 .where(Message.conversation_id == host_request_3) 

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

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

1153 refresh_materialized_view(session, "user_response_rates") 

1154 

1155 # now low 

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

1157 assert res.HasField("low") 

1158 

1159 with requests_session(token2) as api: 

1160 # accept a host req 

1161 api.RespondHostRequest( 

1162 requests_pb2.RespondHostRequestReq( 

1163 host_request_id=host_request_2, 

1164 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED, 

1165 text="Accepting host request", 

1166 ) 

1167 ) 

1168 

1169 with session_scope() as session: 

1170 refresh_materialized_view(session, "user_response_rates") 

1171 

1172 with requests_session(token1) as api: 

1173 # now some w p33 = 35h 

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

1175 assert res.HasField("some") 

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

1177 

1178 with requests_session(token2) as api: 

1179 # accept another host req 

1180 api.RespondHostRequest( 

1181 requests_pb2.RespondHostRequestReq( 

1182 host_request_id=host_request_3, 

1183 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED, 

1184 text="Accepting host request", 

1185 ) 

1186 ) 

1187 

1188 with session_scope() as session: 

1189 refresh_materialized_view(session, "user_response_rates") 

1190 

1191 with requests_session(token1) as api: 

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

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

1194 assert res.HasField("most") 

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

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

1197 

1198 with requests_session(token2) as api: 

1199 # accept last host req 

1200 api.RespondHostRequest( 

1201 requests_pb2.RespondHostRequestReq( 

1202 host_request_id=host_request_1, 

1203 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED, 

1204 text="Accepting host request", 

1205 ) 

1206 ) 

1207 

1208 with session_scope() as session: 

1209 refresh_materialized_view(session, "user_response_rates") 

1210 

1211 with requests_session(token1) as api: 

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

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

1214 assert res.HasField("almost_all") 

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

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

1217 

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

1219 host_request_4 = api.CreateHostRequest( 

1220 requests_pb2.CreateHostRequestReq( 

1221 host_user_id=user2.id, 

1222 from_date=today_plus_2.isoformat(), 

1223 to_date=today_plus_3.isoformat(), 

1224 text=valid_request_text("Test request"), 

1225 ) 

1226 ).host_request_id 

1227 moderator.approve_host_request(host_request_4) 

1228 with session_scope() as session: 

1229 session.execute( 

1230 select(Message) 

1231 .where(Message.conversation_id == host_request_4) 

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

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

1234 refresh_materialized_view(session, "user_response_rates") 

1235 

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

1237 host_request_5 = api.CreateHostRequest( 

1238 requests_pb2.CreateHostRequestReq( 

1239 host_user_id=user2.id, 

1240 from_date=today_plus_2.isoformat(), 

1241 to_date=today_plus_3.isoformat(), 

1242 text=valid_request_text("Test request"), 

1243 ) 

1244 ).host_request_id 

1245 moderator.approve_host_request(host_request_5) 

1246 with session_scope() as session: 

1247 session.execute( 

1248 select(Message) 

1249 .where(Message.conversation_id == host_request_5) 

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

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

1252 refresh_materialized_view(session, "user_response_rates") 

1253 

1254 # now some w p33 = 35h 

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

1256 assert res.HasField("some") 

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

1258 

1259 with requests_session(token2) as api: 

1260 # accept host req 

1261 api.RespondHostRequest( 

1262 requests_pb2.RespondHostRequestReq( 

1263 host_request_id=host_request_5, 

1264 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED, 

1265 text="Accepting host request", 

1266 ) 

1267 ) 

1268 

1269 with session_scope() as session: 

1270 refresh_materialized_view(session, "user_response_rates") 

1271 

1272 with requests_session(token1) as api: 

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

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

1275 assert res.HasField("most") 

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

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

1278 

1279 with requests_session(token2) as api: 

1280 # accept host req 

1281 api.RespondHostRequest( 

1282 requests_pb2.RespondHostRequestReq( 

1283 host_request_id=host_request_4, 

1284 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED, 

1285 text="Accepting host request", 

1286 ) 

1287 ) 

1288 

1289 with session_scope() as session: 

1290 refresh_materialized_view(session, "user_response_rates") 

1291 

1292 with requests_session(token1) as api: 

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

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

1295 assert res.HasField("almost_all") 

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

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

1298 

1299 

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

1301 host, host_token = generate_user(complete_profile=True) 

1302 surfer, surfer_token = generate_user(complete_profile=True) 

1303 

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

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

1306 

1307 with requests_session(surfer_token) as api: 

1308 hr_id = api.CreateHostRequest( 

1309 requests_pb2.CreateHostRequestReq( 

1310 host_user_id=host.id, 

1311 from_date=today_plus_2.isoformat(), 

1312 to_date=today_plus_3.isoformat(), 

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

1314 ) 

1315 ).host_request_id 

1316 

1317 with mock_notification_email() as mock: 

1318 moderator.approve_host_request(hr_id) 

1319 

1320 mock.assert_called_once() 

1321 e = email_fields(mock) 

1322 assert e.recipient == host.email 

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

1324 assert host.name in e.plain 

1325 assert host.name in e.html 

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

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

1328 assert surfer.name in e.plain 

1329 assert surfer.name in e.html 

1330 assert localize_date(today_plus_2, host.ui_language_preference or "en") in e.plain 

1331 assert localize_date(today_plus_2, host.ui_language_preference or "en") in e.html 

1332 assert localize_date(today_plus_3, host.ui_language_preference or "en") in e.plain 

1333 assert localize_date(today_plus_3, host.ui_language_preference or "en") in e.html 

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

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

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

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

1338 

1339 assert push_collector.get_for_user(host.id).content.title == f"New host request from {surfer.name}" 

1340 

1341 with requests_session(host_token) as api: 

1342 with mock_notification_email() as mock: 

1343 api.RespondHostRequest( 

1344 requests_pb2.RespondHostRequestReq( 

1345 host_request_id=hr_id, 

1346 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED, 

1347 text="Accepting host request", 

1348 ) 

1349 ) 

1350 

1351 e = email_fields(mock) 

1352 assert e.recipient == surfer.email 

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

1354 assert host.name in e.plain 

1355 assert host.name in e.html 

1356 assert surfer.name in e.plain 

1357 assert surfer.name in e.html 

1358 assert localize_date(today_plus_2, surfer.ui_language_preference or "en") in e.plain 

1359 assert localize_date(today_plus_2, surfer.ui_language_preference or "en") in e.html 

1360 assert localize_date(today_plus_3, surfer.ui_language_preference or "en") in e.plain 

1361 assert localize_date(today_plus_3, surfer.ui_language_preference or "en") in e.html 

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

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

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

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

1366 

1367 assert push_collector.get_for_user(surfer.id).content.title == f"{host.name} accepted your request" 

1368 

1369 

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

1371 host, host_token = generate_user(complete_profile=True) 

1372 surfer, surfer_token = generate_user(complete_profile=True) 

1373 

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

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

1376 

1377 with requests_session(surfer_token) as api: 

1378 hr_id = api.CreateHostRequest( 

1379 requests_pb2.CreateHostRequestReq( 

1380 host_user_id=host.id, 

1381 from_date=today_plus_2.isoformat(), 

1382 to_date=today_plus_3.isoformat(), 

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

1384 ) 

1385 ).host_request_id 

1386 

1387 with mock_notification_email() as mock: 

1388 moderator.approve_host_request(hr_id) 

1389 

1390 mock.assert_called_once() 

1391 e = email_fields(mock) 

1392 assert e.recipient == host.email 

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

1394 assert host.name in e.plain 

1395 assert host.name in e.html 

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

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

1398 assert surfer.name in e.plain 

1399 assert surfer.name in e.html 

1400 assert localize_date(today_plus_2, host.ui_language_preference or "en") in e.plain 

1401 assert localize_date(today_plus_2, host.ui_language_preference or "en") in e.html 

1402 assert localize_date(today_plus_3, host.ui_language_preference or "en") in e.plain 

1403 assert localize_date(today_plus_3, host.ui_language_preference or "en") in e.html 

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

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

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

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

1408 

1409 assert push_collector.get_for_user(host.id).content.title == f"New host request from {surfer.name}" 

1410 

1411 # very ugly 

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

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

1414 if "payload" not in link: 

1415 continue 

1416 print(link) 

1417 url_parts = urlparse(link) 

1418 params = parse_qs(url_parts.query) 

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

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

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

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

1423 res = auth_api.Unsubscribe( 

1424 auth_pb2.UnsubscribeReq( 

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

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

1427 ) 

1428 ) 

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

1430 break 

1431 else: 

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

1433 

1434 with requests_session(surfer_token) as api: 

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

1436 assert res.status == conversations_pb2.HOST_REQUEST_STATUS_REJECTED 

1437 

1438 

1439def test_host_req_feedback(db, moderator): 

1440 host, host_token = generate_user(complete_profile=True) 

1441 host2, host2_token = generate_user(complete_profile=True) 

1442 host3, host3_token = generate_user(complete_profile=True) 

1443 surfer, surfer_token = generate_user(complete_profile=True) 

1444 

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

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

1447 

1448 with requests_session(surfer_token) as api: 

1449 hr_id = api.CreateHostRequest( 

1450 requests_pb2.CreateHostRequestReq( 

1451 host_user_id=host.id, 

1452 from_date=today_plus_2.isoformat(), 

1453 to_date=today_plus_3.isoformat(), 

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

1455 ) 

1456 ).host_request_id 

1457 hr2_id = api.CreateHostRequest( 

1458 requests_pb2.CreateHostRequestReq( 

1459 host_user_id=host2.id, 

1460 from_date=today_plus_2.isoformat(), 

1461 to_date=today_plus_3.isoformat(), 

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

1463 ) 

1464 ).host_request_id 

1465 hr3_id = api.CreateHostRequest( 

1466 requests_pb2.CreateHostRequestReq( 

1467 host_user_id=host3.id, 

1468 from_date=today_plus_2.isoformat(), 

1469 to_date=today_plus_3.isoformat(), 

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

1471 ) 

1472 ).host_request_id 

1473 

1474 moderator.approve_host_request(hr_id) 

1475 moderator.approve_host_request(hr2_id) 

1476 moderator.approve_host_request(hr3_id) 

1477 

1478 with requests_session(host_token) as api: 

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

1480 assert not res.need_host_request_feedback 

1481 

1482 api.RespondHostRequest( 

1483 requests_pb2.RespondHostRequestReq( 

1484 host_request_id=hr_id, 

1485 status=conversations_pb2.HOST_REQUEST_STATUS_REJECTED, 

1486 ) 

1487 ) 

1488 

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

1490 assert res.need_host_request_feedback 

1491 

1492 # surfer can't leave feedback 

1493 with requests_session(surfer_token) as api: 

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

1495 api.SendHostRequestFeedback( 

1496 requests_pb2.SendHostRequestFeedbackReq( 

1497 host_request_id=hr_id, 

1498 ) 

1499 ) 

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

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

1502 

1503 with requests_session(host_token) as api: 

1504 api.SendHostRequestFeedback( 

1505 requests_pb2.SendHostRequestFeedbackReq( 

1506 host_request_id=hr_id, 

1507 host_request_quality=requests_pb2.HOST_REQUEST_QUALITY_LOW, 

1508 ) 

1509 ) 

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

1511 assert not res.need_host_request_feedback 

1512 

1513 # can't leave it twice 

1514 with requests_session(host_token) as api: 

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

1516 api.SendHostRequestFeedback( 

1517 requests_pb2.SendHostRequestFeedbackReq( 

1518 host_request_id=hr_id, 

1519 ) 

1520 ) 

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

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

1523 

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

1525 assert not res.need_host_request_feedback 

1526 

1527 with requests_session(host2_token) as api: 

1528 api.RespondHostRequest( 

1529 requests_pb2.RespondHostRequestReq( 

1530 host_request_id=hr2_id, status=conversations_pb2.HOST_REQUEST_STATUS_REJECTED 

1531 ) 

1532 ) 

1533 # can't leave feedback on the wrong one 

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

1535 api.SendHostRequestFeedback( 

1536 requests_pb2.SendHostRequestFeedbackReq( 

1537 host_request_id=hr_id, 

1538 ) 

1539 ) 

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

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

1542 

1543 # null feedback is still feedback 

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

1545 

1546 with requests_session(host3_token) as api: 

1547 api.RespondHostRequest( 

1548 requests_pb2.RespondHostRequestReq( 

1549 host_request_id=hr3_id, status=conversations_pb2.HOST_REQUEST_STATUS_REJECTED 

1550 ) 

1551 ) 

1552 

1553 api.SendHostRequestFeedback( 

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

1555 )