Coverage for app / backend / src / tests / test_public_trips.py: 100%

299 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-17 17:16 +0000

1from datetime import timedelta 

2 

3import grpc 

4import pytest 

5from sqlalchemy import select 

6 

7from couchers.db import session_scope 

8from couchers.models import Node, NodeType 

9from couchers.models.public_trips import PublicTrip, PublicTripStatus 

10from couchers.proto import public_trips_pb2 

11from couchers.utils import create_polygon_lat_lng, to_multi, today 

12from tests.fixtures.db import generate_user 

13from tests.fixtures.sessions import public_trips_session 

14 

15 

16@pytest.fixture(autouse=True) 

17def _(testconfig): 

18 pass 

19 

20 

21# 150+ utf-16 code units to satisfy PUBLIC_TRIP_DESCRIPTION_MIN_LENGTH_UTF16. 

22VALID_DESCRIPTION = ( 

23 "Visiting the area for a week for a music festival. I love meeting new people " 

24 "and would really appreciate local tips. Happy to help with tasks in exchange." 

25) 

26 

27 

28def _make_node(node_type: NodeType = NodeType.locality) -> int: 

29 with session_scope() as session: 

30 node = Node( 

31 geom=to_multi(create_polygon_lat_lng([[0, 0], [0, 2], [2, 2], [2, 0], [0, 0]])), 

32 node_type=node_type, 

33 ) 

34 session.add(node) 

35 session.flush() 

36 return node.id 

37 

38 

39def _create_trip_directly( 

40 user_id: int, node_id: int, from_date, to_date, *, description: str = "Looking for a host!", status=None 

41) -> int: 

42 with session_scope() as session: 

43 trip = PublicTrip( 

44 user_id=user_id, 

45 node_id=node_id, 

46 from_date=from_date, 

47 to_date=to_date, 

48 description=description, 

49 status=status or PublicTripStatus.searching_for_host, 

50 ) 

51 session.add(trip) 

52 session.flush() 

53 return trip.id 

54 

55 

56def test_create_public_trip(db): 

57 user, token = generate_user() 

58 node_id = _make_node() 

59 

60 from_date = today() + timedelta(days=5) 

61 to_date = today() + timedelta(days=10) 

62 

63 with public_trips_session(token) as api: 

64 res = api.CreatePublicTrip( 

65 public_trips_pb2.CreatePublicTripReq( 

66 node_id=node_id, 

67 from_date=from_date.isoformat(), 

68 to_date=to_date.isoformat(), 

69 description=VALID_DESCRIPTION, 

70 ) 

71 ) 

72 

73 assert res.trip_id > 0 

74 assert res.user.user_id == user.id 

75 assert res.node_id == node_id 

76 assert res.from_date == from_date.isoformat() 

77 assert res.to_date == to_date.isoformat() 

78 assert res.description == VALID_DESCRIPTION 

79 assert res.status == public_trips_pb2.PUBLIC_TRIP_STATUS_SEARCHING_FOR_HOST 

80 

81 with session_scope() as session: 

82 trip = session.execute(select(PublicTrip).where(PublicTrip.id == res.trip_id)).scalar_one() 

83 assert trip.user_id == user.id 

84 assert trip.node_id == node_id 

85 assert trip.status == PublicTripStatus.searching_for_host 

86 

87 

88def test_create_public_trip_incomplete_profile(db): 

89 _, token = generate_user(complete_profile=False) 

90 node_id = _make_node() 

91 

92 with public_trips_session(token) as api: 

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

94 api.CreatePublicTrip( 

95 public_trips_pb2.CreatePublicTripReq( 

96 node_id=node_id, 

97 from_date=(today() + timedelta(days=5)).isoformat(), 

98 to_date=(today() + timedelta(days=10)).isoformat(), 

99 description="Visiting town!", 

100 ) 

101 ) 

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

103 assert e.value.details() == "You have to complete your profile before you can create a public trip." 

104 

105 

106def test_create_public_trip_community_not_found(db): 

107 _, token = generate_user() 

108 

109 with public_trips_session(token) as api: 

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

111 api.CreatePublicTrip( 

112 public_trips_pb2.CreatePublicTripReq( 

113 node_id=999999, 

114 from_date=(today() + timedelta(days=5)).isoformat(), 

115 to_date=(today() + timedelta(days=10)).isoformat(), 

116 description="Visiting town!", 

117 ) 

118 ) 

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

120 assert e.value.details() == "Community not found." 

121 

122 

123def test_create_public_trip_community_too_broad(db): 

124 _, token = generate_user() 

125 node_id = _make_node(node_type=NodeType.macroregion) 

126 

127 with public_trips_session(token) as api: 

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

129 api.CreatePublicTrip( 

130 public_trips_pb2.CreatePublicTripReq( 

131 node_id=node_id, 

132 from_date=(today() + timedelta(days=5)).isoformat(), 

133 to_date=(today() + timedelta(days=10)).isoformat(), 

134 description="Visiting town!", 

135 ) 

136 ) 

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

138 assert "too broad" in (e.value.details() or "") 

139 

140 

141@pytest.mark.parametrize( 

142 "node_type", 

143 [NodeType.region, NodeType.subregion, NodeType.locality, NodeType.sublocality], 

144) 

145def test_create_public_trip_allows_region_and_narrower(db, node_type): 

146 _, token = generate_user() 

147 node_id = _make_node(node_type=node_type) 

148 

149 with public_trips_session(token) as api: 

150 res = api.CreatePublicTrip( 

151 public_trips_pb2.CreatePublicTripReq( 

152 node_id=node_id, 

153 from_date=(today() + timedelta(days=5)).isoformat(), 

154 to_date=(today() + timedelta(days=10)).isoformat(), 

155 description=VALID_DESCRIPTION, 

156 ) 

157 ) 

158 assert res.trip_id > 0 

159 

160 

161def test_create_public_trip_date_errors(db): 

162 _, token = generate_user() 

163 node_id = _make_node() 

164 

165 with public_trips_session(token) as api: 

166 # from_date in the past 

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

168 api.CreatePublicTrip( 

169 public_trips_pb2.CreatePublicTripReq( 

170 node_id=node_id, 

171 from_date=(today() - timedelta(days=1)).isoformat(), 

172 to_date=(today() + timedelta(days=1)).isoformat(), 

173 description="Visiting town!", 

174 ) 

175 ) 

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

177 

178 # from_date after to_date 

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

180 api.CreatePublicTrip( 

181 public_trips_pb2.CreatePublicTripReq( 

182 node_id=node_id, 

183 from_date=(today() + timedelta(days=10)).isoformat(), 

184 to_date=(today() + timedelta(days=5)).isoformat(), 

185 description="Visiting town!", 

186 ) 

187 ) 

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

189 

190 # empty description 

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

192 api.CreatePublicTrip( 

193 public_trips_pb2.CreatePublicTripReq( 

194 node_id=node_id, 

195 from_date=(today() + timedelta(days=5)).isoformat(), 

196 to_date=(today() + timedelta(days=10)).isoformat(), 

197 description=" ", 

198 ) 

199 ) 

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

201 

202 # invalid date format 

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

204 api.CreatePublicTrip( 

205 public_trips_pb2.CreatePublicTripReq( 

206 node_id=node_id, 

207 from_date="not-a-date", 

208 to_date=(today() + timedelta(days=10)).isoformat(), 

209 description="Visiting town!", 

210 ) 

211 ) 

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

213 

214 

215def test_create_public_trip_overlap(db): 

216 user, token = generate_user() 

217 node_id = _make_node() 

218 

219 _create_trip_directly( 

220 user.id, 

221 node_id, 

222 today() + timedelta(days=5), 

223 today() + timedelta(days=10), 

224 ) 

225 

226 with public_trips_session(token) as api: 

227 # overlapping dates should fail 

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

229 api.CreatePublicTrip( 

230 public_trips_pb2.CreatePublicTripReq( 

231 node_id=node_id, 

232 from_date=(today() + timedelta(days=8)).isoformat(), 

233 to_date=(today() + timedelta(days=12)).isoformat(), 

234 description=VALID_DESCRIPTION, 

235 ) 

236 ) 

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

238 

239 # non-overlapping dates should succeed 

240 res = api.CreatePublicTrip( 

241 public_trips_pb2.CreatePublicTripReq( 

242 node_id=node_id, 

243 from_date=(today() + timedelta(days=20)).isoformat(), 

244 to_date=(today() + timedelta(days=25)).isoformat(), 

245 description=VALID_DESCRIPTION, 

246 ) 

247 ) 

248 assert res.trip_id > 0 

249 

250 

251def test_create_public_trip_closed_trip_allows_new_overlap(db): 

252 user, token = generate_user() 

253 node_id = _make_node() 

254 

255 _create_trip_directly( 

256 user.id, 

257 node_id, 

258 today() + timedelta(days=5), 

259 today() + timedelta(days=10), 

260 status=PublicTripStatus.closed, 

261 ) 

262 

263 with public_trips_session(token) as api: 

264 # closed trips shouldn't block new overlapping ones 

265 res = api.CreatePublicTrip( 

266 public_trips_pb2.CreatePublicTripReq( 

267 node_id=node_id, 

268 from_date=(today() + timedelta(days=7)).isoformat(), 

269 to_date=(today() + timedelta(days=12)).isoformat(), 

270 description=VALID_DESCRIPTION, 

271 ) 

272 ) 

273 assert res.trip_id > 0 

274 

275 

276def test_get_public_trip(db): 

277 user, token = generate_user() 

278 node_id = _make_node() 

279 trip_id = _create_trip_directly(user.id, node_id, today() + timedelta(days=5), today() + timedelta(days=10)) 

280 

281 with public_trips_session(token) as api: 

282 res = api.GetPublicTrip(public_trips_pb2.GetPublicTripReq(trip_id=trip_id)) 

283 assert res.trip_id == trip_id 

284 assert res.user.user_id == user.id 

285 

286 

287def test_get_public_trip_not_found(db): 

288 _, token = generate_user() 

289 with public_trips_session(token) as api: 

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

291 api.GetPublicTrip(public_trips_pb2.GetPublicTripReq(trip_id=999999)) 

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

293 

294 

295def test_list_public_trips(db): 

296 traveler, _ = generate_user() 

297 _, host_token = generate_user() 

298 node_id = _make_node() 

299 

300 trip1 = _create_trip_directly(traveler.id, node_id, today() + timedelta(days=5), today() + timedelta(days=10)) 

301 trip2 = _create_trip_directly(traveler.id, node_id, today() + timedelta(days=20), today() + timedelta(days=25)) 

302 

303 with public_trips_session(host_token) as api: 

304 res = api.ListPublicTrips(public_trips_pb2.ListPublicTripsReq(community_id=node_id)) 

305 returned_ids = {t.trip_id for t in res.public_trips} 

306 assert returned_ids == {trip1, trip2} 

307 

308 

309def test_list_public_trips_filters_closed_and_past(db): 

310 traveler, _ = generate_user() 

311 _, host_token = generate_user() 

312 node_id = _make_node() 

313 

314 active = _create_trip_directly(traveler.id, node_id, today() + timedelta(days=5), today() + timedelta(days=10)) 

315 # closed trip - should be hidden 

316 _create_trip_directly( 

317 traveler.id, 

318 node_id, 

319 today() + timedelta(days=15), 

320 today() + timedelta(days=20), 

321 status=PublicTripStatus.closed, 

322 ) 

323 # past trip (to_date < today) - should be hidden 

324 _create_trip_directly(traveler.id, node_id, today() - timedelta(days=10), today() - timedelta(days=1)) 

325 

326 with public_trips_session(host_token) as api: 

327 res = api.ListPublicTrips(public_trips_pb2.ListPublicTripsReq(community_id=node_id)) 

328 assert [t.trip_id for t in res.public_trips] == [active] 

329 

330 

331def test_list_public_trips_hides_invisible_user(db): 

332 traveler, _ = generate_user() 

333 _, host_token = generate_user() 

334 node_id = _make_node() 

335 

336 _create_trip_directly(traveler.id, node_id, today() + timedelta(days=5), today() + timedelta(days=10)) 

337 

338 # soft-delete the traveler 

339 with session_scope() as session: 

340 from couchers.models import User 

341 from couchers.utils import now 

342 

343 t = session.execute(select(User).where(User.id == traveler.id)).scalar_one() 

344 t.deleted_at = now() 

345 

346 with public_trips_session(host_token) as api: 

347 res = api.ListPublicTrips(public_trips_pb2.ListPublicTripsReq(community_id=node_id)) 

348 assert len(res.public_trips) == 0 

349 

350 

351def test_list_public_trips_pagination(db): 

352 traveler, _ = generate_user() 

353 _, host_token = generate_user() 

354 node_id = _make_node() 

355 

356 trip_ids = [ 

357 _create_trip_directly( 

358 traveler.id, node_id, today() + timedelta(days=5 + i * 10), today() + timedelta(days=10 + i * 10) 

359 ) 

360 for i in range(5) 

361 ] 

362 

363 with public_trips_session(host_token) as api: 

364 res = api.ListPublicTrips(public_trips_pb2.ListPublicTripsReq(community_id=node_id, page_size=2)) 

365 assert [t.trip_id for t in res.public_trips] == [trip_ids[4], trip_ids[3]] 

366 assert res.next_page_token 

367 

368 res2 = api.ListPublicTrips( 

369 public_trips_pb2.ListPublicTripsReq(community_id=node_id, page_size=2, page_token=res.next_page_token) 

370 ) 

371 assert [t.trip_id for t in res2.public_trips] == [trip_ids[2], trip_ids[1]] 

372 assert res2.next_page_token 

373 

374 res3 = api.ListPublicTrips( 

375 public_trips_pb2.ListPublicTripsReq(community_id=node_id, page_size=2, page_token=res2.next_page_token) 

376 ) 

377 assert [t.trip_id for t in res3.public_trips] == [trip_ids[0]] 

378 assert not res3.next_page_token 

379 

380 

381def test_list_public_trips_by_user_self_sees_all(db): 

382 user, token = generate_user() 

383 other, _ = generate_user() 

384 node_id = _make_node() 

385 

386 mine_active = _create_trip_directly(user.id, node_id, today() + timedelta(days=5), today() + timedelta(days=10)) 

387 mine_closed = _create_trip_directly( 

388 user.id, 

389 node_id, 

390 today() + timedelta(days=15), 

391 today() + timedelta(days=20), 

392 status=PublicTripStatus.closed, 

393 ) 

394 mine_past = _create_trip_directly(user.id, node_id, today() - timedelta(days=10), today() - timedelta(days=1)) 

395 # other user's trip should not be returned 

396 _create_trip_directly(other.id, node_id, today() + timedelta(days=5), today() + timedelta(days=10)) 

397 

398 with public_trips_session(token) as api: 

399 res = api.ListPublicTripsByUser(public_trips_pb2.ListPublicTripsByUserReq(user_id=user.id)) 

400 assert {t.trip_id for t in res.public_trips} == {mine_active, mine_closed, mine_past} 

401 

402 

403def test_list_public_trips_by_user_other_filters_inactive_and_past(db): 

404 traveler, _ = generate_user() 

405 _, viewer_token = generate_user() 

406 node_id = _make_node() 

407 

408 active = _create_trip_directly(traveler.id, node_id, today() + timedelta(days=5), today() + timedelta(days=10)) 

409 # closed trip - hidden from others 

410 _create_trip_directly( 

411 traveler.id, 

412 node_id, 

413 today() + timedelta(days=15), 

414 today() + timedelta(days=20), 

415 status=PublicTripStatus.closed, 

416 ) 

417 # past trip - hidden from others 

418 _create_trip_directly(traveler.id, node_id, today() - timedelta(days=10), today() - timedelta(days=1)) 

419 

420 with public_trips_session(viewer_token) as api: 

421 res = api.ListPublicTripsByUser(public_trips_pb2.ListPublicTripsByUserReq(user_id=traveler.id)) 

422 assert [t.trip_id for t in res.public_trips] == [active] 

423 

424 

425def test_list_public_trips_by_user_invisible_user(db): 

426 traveler, _ = generate_user() 

427 _, viewer_token = generate_user() 

428 node_id = _make_node() 

429 

430 _create_trip_directly(traveler.id, node_id, today() + timedelta(days=5), today() + timedelta(days=10)) 

431 

432 # soft-delete the traveler 

433 with session_scope() as session: 

434 from couchers.models import User 

435 from couchers.utils import now 

436 

437 t = session.execute(select(User).where(User.id == traveler.id)).scalar_one() 

438 t.deleted_at = now() 

439 

440 with public_trips_session(viewer_token) as api: 

441 res = api.ListPublicTripsByUser(public_trips_pb2.ListPublicTripsByUserReq(user_id=traveler.id)) 

442 assert len(res.public_trips) == 0 

443 

444 

445def test_update_public_trip_close(db): 

446 user, token = generate_user() 

447 node_id = _make_node() 

448 trip_id = _create_trip_directly(user.id, node_id, today() + timedelta(days=5), today() + timedelta(days=10)) 

449 

450 with public_trips_session(token) as api: 

451 res = api.UpdatePublicTrip( 

452 public_trips_pb2.UpdatePublicTripReq( 

453 trip_id=trip_id, 

454 status=public_trips_pb2.PUBLIC_TRIP_STATUS_CLOSED, 

455 ) 

456 ) 

457 assert res.status == public_trips_pb2.PUBLIC_TRIP_STATUS_CLOSED 

458 

459 with session_scope() as session: 

460 trip = session.execute(select(PublicTrip).where(PublicTrip.id == trip_id)).scalar_one() 

461 assert trip.status == PublicTripStatus.closed 

462 

463 

464def test_update_public_trip_cant_reopen(db): 

465 user, token = generate_user() 

466 node_id = _make_node() 

467 trip_id = _create_trip_directly( 

468 user.id, 

469 node_id, 

470 today() + timedelta(days=5), 

471 today() + timedelta(days=10), 

472 status=PublicTripStatus.closed, 

473 ) 

474 

475 with public_trips_session(token) as api: 

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

477 api.UpdatePublicTrip( 

478 public_trips_pb2.UpdatePublicTripReq( 

479 trip_id=trip_id, 

480 status=public_trips_pb2.PUBLIC_TRIP_STATUS_SEARCHING_FOR_HOST, 

481 ) 

482 ) 

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

484 

485 

486def test_update_public_trip_close_past_trip_allowed(db): 

487 # Closing a trip whose dates are in the past is allowed, even though content edits are not. 

488 user, token = generate_user() 

489 node_id = _make_node() 

490 trip_id = _create_trip_directly(user.id, node_id, today() - timedelta(days=10), today() - timedelta(days=1)) 

491 

492 with public_trips_session(token) as api: 

493 res = api.UpdatePublicTrip( 

494 public_trips_pb2.UpdatePublicTripReq( 

495 trip_id=trip_id, 

496 status=public_trips_pb2.PUBLIC_TRIP_STATUS_CLOSED, 

497 ) 

498 ) 

499 assert res.status == public_trips_pb2.PUBLIC_TRIP_STATUS_CLOSED 

500 

501 

502def test_update_public_trip_description_only(db): 

503 user, token = generate_user() 

504 node_id = _make_node() 

505 trip_id = _create_trip_directly( 

506 user.id, 

507 node_id, 

508 today() + timedelta(days=5), 

509 today() + timedelta(days=10), 

510 description="Original description", 

511 ) 

512 

513 updated = VALID_DESCRIPTION + " Updated plans." 

514 

515 with public_trips_session(token) as api: 

516 res = api.UpdatePublicTrip( 

517 public_trips_pb2.UpdatePublicTripReq( 

518 trip_id=trip_id, 

519 description=updated, 

520 ) 

521 ) 

522 assert res.trip_id == trip_id 

523 assert res.description == updated 

524 # dates should be unchanged 

525 assert res.from_date == (today() + timedelta(days=5)).isoformat() 

526 assert res.to_date == (today() + timedelta(days=10)).isoformat() 

527 

528 with session_scope() as session: 

529 trip = session.execute(select(PublicTrip).where(PublicTrip.id == trip_id)).scalar_one() 

530 assert trip.description == updated 

531 

532 

533def test_update_public_trip_dates(db): 

534 user, token = generate_user() 

535 node_id = _make_node() 

536 trip_id = _create_trip_directly(user.id, node_id, today() + timedelta(days=5), today() + timedelta(days=10)) 

537 

538 new_from = today() + timedelta(days=7) 

539 new_to = today() + timedelta(days=14) 

540 

541 with public_trips_session(token) as api: 

542 res = api.UpdatePublicTrip( 

543 public_trips_pb2.UpdatePublicTripReq( 

544 trip_id=trip_id, 

545 from_date=new_from.isoformat(), 

546 to_date=new_to.isoformat(), 

547 ) 

548 ) 

549 assert res.from_date == new_from.isoformat() 

550 assert res.to_date == new_to.isoformat() 

551 

552 

553def test_update_public_trip_not_owner(db): 

554 user, _ = generate_user() 

555 _, other_token = generate_user() 

556 node_id = _make_node() 

557 trip_id = _create_trip_directly(user.id, node_id, today() + timedelta(days=5), today() + timedelta(days=10)) 

558 

559 with public_trips_session(other_token) as api: 

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

561 api.UpdatePublicTrip( 

562 public_trips_pb2.UpdatePublicTripReq( 

563 trip_id=trip_id, 

564 description="I don't own this!", 

565 ) 

566 ) 

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

568 

569 

570def test_update_public_trip_in_past(db): 

571 user, token = generate_user() 

572 node_id = _make_node() 

573 trip_id = _create_trip_directly(user.id, node_id, today() - timedelta(days=10), today() - timedelta(days=1)) 

574 

575 with public_trips_session(token) as api: 

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

577 api.UpdatePublicTrip( 

578 public_trips_pb2.UpdatePublicTripReq( 

579 trip_id=trip_id, 

580 description="Too late!", 

581 ) 

582 ) 

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

584 

585 

586def test_update_public_trip_date_validation(db): 

587 user, token = generate_user() 

588 node_id = _make_node() 

589 trip_id = _create_trip_directly(user.id, node_id, today() + timedelta(days=5), today() + timedelta(days=10)) 

590 

591 with public_trips_session(token) as api: 

592 # from_date after to_date (using the stored to_date of today+10) 

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

594 api.UpdatePublicTrip( 

595 public_trips_pb2.UpdatePublicTripReq( 

596 trip_id=trip_id, 

597 from_date=(today() + timedelta(days=20)).isoformat(), 

598 ) 

599 ) 

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

601 

602 # Empty description 

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

604 api.UpdatePublicTrip( 

605 public_trips_pb2.UpdatePublicTripReq( 

606 trip_id=trip_id, 

607 description=" ", 

608 ) 

609 ) 

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

611 

612 

613def test_create_public_trip_description_too_short(db): 

614 _, token = generate_user() 

615 node_id = _make_node() 

616 

617 with public_trips_session(token) as api: 

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

619 api.CreatePublicTrip( 

620 public_trips_pb2.CreatePublicTripReq( 

621 node_id=node_id, 

622 from_date=(today() + timedelta(days=5)).isoformat(), 

623 to_date=(today() + timedelta(days=10)).isoformat(), 

624 description="Too short.", 

625 ) 

626 ) 

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

628 assert "150" in (e.value.details() or "") 

629 

630 

631def test_update_public_trip_description_too_short(db): 

632 user, token = generate_user() 

633 node_id = _make_node() 

634 trip_id = _create_trip_directly(user.id, node_id, today() + timedelta(days=5), today() + timedelta(days=10)) 

635 

636 with public_trips_session(token) as api: 

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

638 api.UpdatePublicTrip( 

639 public_trips_pb2.UpdatePublicTripReq( 

640 trip_id=trip_id, 

641 description="Too short.", 

642 ) 

643 ) 

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

645 assert "150" in (e.value.details() or "")