Coverage for src/tests/test_galleries.py: 100%

371 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2025-12-06 23:17 +0000

1import grpc 

2import pytest 

3from google.protobuf import empty_pb2 

4from sqlalchemy.exc import IntegrityError 

5 

6from couchers.db import session_scope 

7from couchers.jobs.handlers import DatabaseInconsistencyError, check_database_consistency 

8from couchers.models import PhotoGallery, PhotoGalleryItem, Upload, User 

9from couchers.proto import galleries_pb2 

10from couchers.sql import couchers_select as select 

11from tests.test_fixtures import ( # noqa 

12 db, 

13 galleries_session, 

14 generate_user, 

15 testconfig, 

16) 

17 

18 

19@pytest.fixture(autouse=True) 

20def _(testconfig): 

21 pass 

22 

23 

24def create_upload(session, user_id, filename="test.jpg"): 

25 """Helper to create an upload for testing""" 

26 upload = Upload( 

27 key=f"test_key_{filename}_{user_id}", 

28 filename=filename, 

29 creator_user_id=user_id, 

30 ) 

31 session.add(upload) 

32 session.commit() 

33 return upload.key 

34 

35 

36def test_user_has_profile_gallery(db): 

37 """Each user should have a profile gallery created automatically""" 

38 user1, token1 = generate_user() 

39 

40 with session_scope() as session: 

41 user = session.execute(select(User).where(User.id == user1.id)).scalar_one() 

42 assert user.profile_gallery_id is not None 

43 

44 gallery = session.execute(select(PhotoGallery).where(PhotoGallery.id == user.profile_gallery_id)).scalar_one() 

45 assert gallery.owner_user_id == user1.id 

46 

47 

48def test_database_consistency_check(db): 

49 """The database consistency check should pass with valid user/gallery setup""" 

50 # Create a few users (which auto-creates their profile galleries) 

51 generate_user() 

52 generate_user() 

53 generate_user() 

54 

55 # This should not raise any exceptions 

56 check_database_consistency(empty_pb2.Empty()) 

57 

58 # Now break consistency by removing a user's profile gallery 

59 with session_scope() as session: 

60 user = session.execute(select(User).where(User.is_deleted == False).limit(1)).scalar_one() 

61 user.profile_gallery_id = None 

62 

63 # This should now raise an exception 

64 with pytest.raises(DatabaseInconsistencyError): 

65 check_database_consistency(empty_pb2.Empty()) 

66 

67 

68def test_GetGalleryEditInfo(db): 

69 user1, token1 = generate_user() 

70 

71 with galleries_session(token1) as api: 

72 res = api.GetGalleryEditInfo(galleries_pb2.GetGalleryEditInfoReq(gallery_id=user1.profile_gallery_id)) 

73 assert res.gallery_id == user1.profile_gallery_id 

74 assert res.max_photos == 1 

75 assert res.current_photo_count == 0 

76 

77 

78def test_GetGalleryEditInfo_verified_user(db): 

79 user1, token1 = generate_user(strong_verification=True) 

80 

81 with galleries_session(token1) as api: 

82 res = api.GetGalleryEditInfo(galleries_pb2.GetGalleryEditInfoReq(gallery_id=user1.profile_gallery_id)) 

83 assert res.gallery_id == user1.profile_gallery_id 

84 assert res.max_photos == 4 

85 assert res.current_photo_count == 0 

86 

87 

88def test_GetGalleryEditInfo_not_owner(db): 

89 user1, token1 = generate_user() 

90 user2, token2 = generate_user() 

91 

92 with galleries_session(token2) as api: 

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

94 api.GetGalleryEditInfo(galleries_pb2.GetGalleryEditInfoReq(gallery_id=user1.profile_gallery_id)) 

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

96 assert e.value.details() == "You do not have permission to edit this gallery." 

97 

98 

99def test_GetGalleryEditInfo_not_found(db): 

100 user1, token1 = generate_user() 

101 

102 with galleries_session(token1) as api: 

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

104 api.GetGalleryEditInfo(galleries_pb2.GetGalleryEditInfoReq(gallery_id=999999)) 

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

106 assert e.value.details() == "Gallery not found." 

107 

108 

109def test_GetGalleryEditInfo_with_photos(db): 

110 user1, token1 = generate_user(strong_verification=True) 

111 

112 with session_scope() as session: 

113 keys = [create_upload(session, user1.id, f"photo{i}.jpg") for i in range(3)] 

114 

115 with galleries_session(token1) as api: 

116 for key in keys: 

117 api.AddPhotoToGallery( 

118 galleries_pb2.AddPhotoToGalleryReq(gallery_id=user1.profile_gallery_id, upload_key=key) 

119 ) 

120 

121 res = api.GetGalleryEditInfo(galleries_pb2.GetGalleryEditInfoReq(gallery_id=user1.profile_gallery_id)) 

122 assert res.max_photos == 4 

123 assert res.current_photo_count == 3 

124 

125 

126def test_GetGallery_as_owner(db): 

127 user1, token1 = generate_user() 

128 

129 with galleries_session(token1) as api: 

130 res = api.GetGallery(galleries_pb2.GetGalleryReq(gallery_id=user1.profile_gallery_id)) 

131 assert res.gallery_id == user1.profile_gallery_id 

132 assert res.can_edit is True 

133 assert len(res.photos) == 0 

134 

135 

136def test_GetGallery_as_non_owner(db): 

137 user1, token1 = generate_user() 

138 user2, token2 = generate_user() 

139 

140 with galleries_session(token2) as api: 

141 res = api.GetGallery(galleries_pb2.GetGalleryReq(gallery_id=user1.profile_gallery_id)) 

142 assert res.gallery_id == user1.profile_gallery_id 

143 assert res.can_edit is False 

144 assert len(res.photos) == 0 

145 

146 

147def test_GetGallery_not_found(db): 

148 user1, token1 = generate_user() 

149 

150 with galleries_session(token1) as api: 

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

152 api.GetGallery(galleries_pb2.GetGalleryReq(gallery_id=999999)) 

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

154 assert e.value.details() == "Gallery not found." 

155 

156 

157def test_AddPhotoToGallery_success(db): 

158 user1, token1 = generate_user() 

159 

160 with session_scope() as session: 

161 upload_key = create_upload(session, user1.id) 

162 

163 with galleries_session(token1) as api: 

164 res = api.AddPhotoToGallery( 

165 galleries_pb2.AddPhotoToGalleryReq( 

166 gallery_id=user1.profile_gallery_id, 

167 upload_key=upload_key, 

168 ) 

169 ) 

170 

171 assert len(res.photos) == 1 

172 photo = res.photos[0] 

173 assert photo.full_url 

174 assert photo.thumbnail_url 

175 assert photo.caption == "" 

176 

177 

178def test_AddPhotoToGallery_with_caption(db): 

179 user1, token1 = generate_user() 

180 

181 with session_scope() as session: 

182 upload_key = create_upload(session, user1.id) 

183 

184 with galleries_session(token1) as api: 

185 res = api.AddPhotoToGallery( 

186 galleries_pb2.AddPhotoToGalleryReq( 

187 gallery_id=user1.profile_gallery_id, 

188 upload_key=upload_key, 

189 caption="Test caption", 

190 ) 

191 ) 

192 

193 assert len(res.photos) == 1 

194 assert res.photos[0].caption == "Test caption" 

195 

196 

197def test_AddPhotoToGallery_multiple_photos(db): 

198 user1, token1 = generate_user(strong_verification=True) 

199 

200 with session_scope() as session: 

201 key1 = create_upload(session, user1.id, "photo1.jpg") 

202 key2 = create_upload(session, user1.id, "photo2.jpg") 

203 key3 = create_upload(session, user1.id, "photo3.jpg") 

204 

205 with galleries_session(token1) as api: 

206 res = api.AddPhotoToGallery( 

207 galleries_pb2.AddPhotoToGalleryReq(gallery_id=user1.profile_gallery_id, upload_key=key1) 

208 ) 

209 assert len(res.photos) == 1 

210 

211 res = api.AddPhotoToGallery( 

212 galleries_pb2.AddPhotoToGalleryReq(gallery_id=user1.profile_gallery_id, upload_key=key2) 

213 ) 

214 assert len(res.photos) == 2 

215 

216 res = api.AddPhotoToGallery( 

217 galleries_pb2.AddPhotoToGalleryReq(gallery_id=user1.profile_gallery_id, upload_key=key3) 

218 ) 

219 assert len(res.photos) == 3 

220 

221 

222def test_AddPhotoToGallery_not_owner(db): 

223 user1, token1 = generate_user() 

224 user2, token2 = generate_user() 

225 

226 with session_scope() as session: 

227 upload_key = create_upload(session, user2.id) 

228 

229 with galleries_session(token2) as api: 

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

231 api.AddPhotoToGallery( 

232 galleries_pb2.AddPhotoToGalleryReq( 

233 gallery_id=user1.profile_gallery_id, 

234 upload_key=upload_key, 

235 ) 

236 ) 

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

238 assert e.value.details() == "You do not have permission to edit this gallery." 

239 

240 

241def test_AddPhotoToGallery_upload_not_owned(db): 

242 user1, token1 = generate_user() 

243 user2, token2 = generate_user() 

244 

245 with session_scope() as session: 

246 upload_key = create_upload(session, user2.id) 

247 

248 with galleries_session(token1) as api: 

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

250 api.AddPhotoToGallery( 

251 galleries_pb2.AddPhotoToGalleryReq( 

252 gallery_id=user1.profile_gallery_id, 

253 upload_key=upload_key, 

254 ) 

255 ) 

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

257 assert e.value.details() == "Upload not found or you don't own it." 

258 

259 

260def test_AddPhotoToGallery_max_capacity(db): 

261 user1, token1 = generate_user(strong_verification=True) 

262 

263 with session_scope() as session: 

264 keys = [create_upload(session, user1.id, f"photo{i}.jpg") for i in range(5)] 

265 

266 with galleries_session(token1) as api: 

267 for i in range(4): 

268 api.AddPhotoToGallery( 

269 galleries_pb2.AddPhotoToGalleryReq( 

270 gallery_id=user1.profile_gallery_id, 

271 upload_key=keys[i], 

272 ) 

273 ) 

274 

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

276 api.AddPhotoToGallery( 

277 galleries_pb2.AddPhotoToGalleryReq( 

278 gallery_id=user1.profile_gallery_id, 

279 upload_key=keys[4], 

280 ) 

281 ) 

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

283 assert e.value.details() == "The gallery is at maximum capacity and cannot accept more photos." 

284 

285 

286def test_AddPhotoToGallery_duplicate_photo(db): 

287 user1, token1 = generate_user(strong_verification=True) 

288 

289 with session_scope() as session: 

290 upload_key = create_upload(session, user1.id) 

291 

292 with galleries_session(token1) as api: 

293 api.AddPhotoToGallery( 

294 galleries_pb2.AddPhotoToGalleryReq( 

295 gallery_id=user1.profile_gallery_id, 

296 upload_key=upload_key, 

297 ) 

298 ) 

299 

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

301 api.AddPhotoToGallery( 

302 galleries_pb2.AddPhotoToGalleryReq( 

303 gallery_id=user1.profile_gallery_id, 

304 upload_key=upload_key, 

305 ) 

306 ) 

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

308 assert e.value.details() == "This photo is already in the gallery." 

309 

310 

311def test_AddPhotoToGallery_gallery_not_found(db): 

312 user1, token1 = generate_user() 

313 

314 with session_scope() as session: 

315 upload_key = create_upload(session, user1.id) 

316 

317 with galleries_session(token1) as api: 

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

319 api.AddPhotoToGallery( 

320 galleries_pb2.AddPhotoToGalleryReq( 

321 gallery_id=999999, 

322 upload_key=upload_key, 

323 ) 

324 ) 

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

326 assert e.value.details() == "Gallery not found." 

327 

328 

329def test_RemovePhotoFromGallery_success(db): 

330 user1, token1 = generate_user(strong_verification=True) 

331 

332 with session_scope() as session: 

333 key1 = create_upload(session, user1.id, "photo1.jpg") 

334 key2 = create_upload(session, user1.id, "photo2.jpg") 

335 key3 = create_upload(session, user1.id, "photo3.jpg") 

336 

337 with galleries_session(token1) as api: 

338 api.AddPhotoToGallery(galleries_pb2.AddPhotoToGalleryReq(gallery_id=user1.profile_gallery_id, upload_key=key1)) 

339 api.AddPhotoToGallery(galleries_pb2.AddPhotoToGalleryReq(gallery_id=user1.profile_gallery_id, upload_key=key2)) 

340 res = api.AddPhotoToGallery( 

341 galleries_pb2.AddPhotoToGalleryReq(gallery_id=user1.profile_gallery_id, upload_key=key3) 

342 ) 

343 

344 item_id = res.photos[1].item_id 

345 

346 res = api.RemovePhotoFromGallery( 

347 galleries_pb2.RemovePhotoFromGalleryReq( 

348 gallery_id=user1.profile_gallery_id, 

349 item_id=item_id, 

350 ) 

351 ) 

352 

353 assert len(res.photos) == 2 

354 

355 

356def test_RemovePhotoFromGallery_not_owner(db): 

357 user1, token1 = generate_user() 

358 user2, token2 = generate_user() 

359 

360 with session_scope() as session: 

361 upload_key = create_upload(session, user1.id) 

362 

363 with galleries_session(token1) as api: 

364 res = api.AddPhotoToGallery( 

365 galleries_pb2.AddPhotoToGalleryReq(gallery_id=user1.profile_gallery_id, upload_key=upload_key) 

366 ) 

367 item_id = res.photos[0].item_id 

368 

369 with galleries_session(token2) as api: 

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

371 api.RemovePhotoFromGallery( 

372 galleries_pb2.RemovePhotoFromGalleryReq( 

373 gallery_id=user1.profile_gallery_id, 

374 item_id=item_id, 

375 ) 

376 ) 

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

378 assert e.value.details() == "You do not have permission to edit this gallery." 

379 

380 

381def test_RemovePhotoFromGallery_item_not_found(db): 

382 user1, token1 = generate_user() 

383 

384 with galleries_session(token1) as api: 

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

386 api.RemovePhotoFromGallery( 

387 galleries_pb2.RemovePhotoFromGalleryReq( 

388 gallery_id=user1.profile_gallery_id, 

389 item_id=999999, 

390 ) 

391 ) 

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

393 assert e.value.details() == "Gallery item not found." 

394 

395 

396def test_MovePhoto_to_first(db): 

397 user1, token1 = generate_user(strong_verification=True) 

398 

399 with session_scope() as session: 

400 keys = [create_upload(session, user1.id, f"photo{i}.jpg") for i in range(3)] 

401 

402 with galleries_session(token1) as api: 

403 for key in keys: 

404 api.AddPhotoToGallery( 

405 galleries_pb2.AddPhotoToGalleryReq(gallery_id=user1.profile_gallery_id, upload_key=key) 

406 ) 

407 

408 res = api.GetGallery(galleries_pb2.GetGalleryReq(gallery_id=user1.profile_gallery_id)) 

409 item_ids = [photo.item_id for photo in res.photos] 

410 

411 # Move last photo to first position 

412 res = api.MovePhoto( 

413 galleries_pb2.MovePhotoReq( 

414 gallery_id=user1.profile_gallery_id, 

415 item_id=item_ids[2], 

416 after_item_id=0, # 0 means first position 

417 ) 

418 ) 

419 

420 # Last photo should now be first 

421 assert res.photos[0].item_id == item_ids[2] 

422 assert res.photos[1].item_id == item_ids[0] 

423 assert res.photos[2].item_id == item_ids[1] 

424 

425 

426def test_MovePhoto_to_middle(db): 

427 user1, token1 = generate_user(strong_verification=True) 

428 

429 with session_scope() as session: 

430 keys = [create_upload(session, user1.id, f"photo{i}.jpg") for i in range(3)] 

431 

432 with galleries_session(token1) as api: 

433 for key in keys: 

434 api.AddPhotoToGallery( 

435 galleries_pb2.AddPhotoToGalleryReq(gallery_id=user1.profile_gallery_id, upload_key=key) 

436 ) 

437 

438 res = api.GetGallery(galleries_pb2.GetGalleryReq(gallery_id=user1.profile_gallery_id)) 

439 item_ids = [photo.item_id for photo in res.photos] 

440 

441 # Move first photo after second (to middle) 

442 res = api.MovePhoto( 

443 galleries_pb2.MovePhotoReq( 

444 gallery_id=user1.profile_gallery_id, 

445 item_id=item_ids[0], 

446 after_item_id=item_ids[1], 

447 ) 

448 ) 

449 

450 # Order should be: [1, 0, 2] 

451 assert res.photos[0].item_id == item_ids[1] 

452 assert res.photos[1].item_id == item_ids[0] 

453 assert res.photos[2].item_id == item_ids[2] 

454 

455 

456def test_MovePhoto_to_end(db): 

457 user1, token1 = generate_user(strong_verification=True) 

458 

459 with session_scope() as session: 

460 keys = [create_upload(session, user1.id, f"photo{i}.jpg") for i in range(3)] 

461 

462 with galleries_session(token1) as api: 

463 for key in keys: 

464 api.AddPhotoToGallery( 

465 galleries_pb2.AddPhotoToGalleryReq(gallery_id=user1.profile_gallery_id, upload_key=key) 

466 ) 

467 

468 res = api.GetGallery(galleries_pb2.GetGalleryReq(gallery_id=user1.profile_gallery_id)) 

469 item_ids = [photo.item_id for photo in res.photos] 

470 

471 # Move first photo to end (after last) 

472 res = api.MovePhoto( 

473 galleries_pb2.MovePhotoReq( 

474 gallery_id=user1.profile_gallery_id, 

475 item_id=item_ids[0], 

476 after_item_id=item_ids[2], 

477 ) 

478 ) 

479 

480 # Order should be: [1, 2, 0] 

481 assert res.photos[0].item_id == item_ids[1] 

482 assert res.photos[1].item_id == item_ids[2] 

483 assert res.photos[2].item_id == item_ids[0] 

484 

485 

486def test_MovePhoto_noop(db): 

487 user1, token1 = generate_user(strong_verification=True) 

488 

489 with session_scope() as session: 

490 keys = [create_upload(session, user1.id, f"photo{i}.jpg") for i in range(3)] 

491 

492 with galleries_session(token1) as api: 

493 for key in keys: 

494 api.AddPhotoToGallery( 

495 galleries_pb2.AddPhotoToGalleryReq(gallery_id=user1.profile_gallery_id, upload_key=key) 

496 ) 

497 

498 res = api.GetGallery(galleries_pb2.GetGalleryReq(gallery_id=user1.profile_gallery_id)) 

499 item_ids = [photo.item_id for photo in res.photos] 

500 

501 # Move photo after itself - should be a no-op 

502 res = api.MovePhoto( 

503 galleries_pb2.MovePhotoReq( 

504 gallery_id=user1.profile_gallery_id, 

505 item_id=item_ids[1], 

506 after_item_id=item_ids[1], 

507 ) 

508 ) 

509 

510 # Order should be unchanged 

511 assert res.photos[0].item_id == item_ids[0] 

512 assert res.photos[1].item_id == item_ids[1] 

513 assert res.photos[2].item_id == item_ids[2] 

514 

515 

516def test_MovePhoto_not_owner(db): 

517 user1, token1 = generate_user() 

518 user2, token2 = generate_user() 

519 

520 with session_scope() as session: 

521 upload_key = create_upload(session, user1.id) 

522 

523 with galleries_session(token1) as api: 

524 res = api.AddPhotoToGallery( 

525 galleries_pb2.AddPhotoToGalleryReq(gallery_id=user1.profile_gallery_id, upload_key=upload_key) 

526 ) 

527 item_id = res.photos[0].item_id 

528 

529 with galleries_session(token2) as api: 

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

531 api.MovePhoto( 

532 galleries_pb2.MovePhotoReq( 

533 gallery_id=user1.profile_gallery_id, 

534 item_id=item_id, 

535 after_item_id=0, 

536 ) 

537 ) 

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

539 assert e.value.details() == "You do not have permission to edit this gallery." 

540 

541 

542def test_MovePhoto_item_not_found(db): 

543 user1, token1 = generate_user() 

544 

545 with galleries_session(token1) as api: 

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

547 api.MovePhoto( 

548 galleries_pb2.MovePhotoReq( 

549 gallery_id=user1.profile_gallery_id, 

550 item_id=999999, 

551 after_item_id=0, 

552 ) 

553 ) 

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

555 assert e.value.details() == "Gallery item not found." 

556 

557 

558def test_MovePhoto_after_item_not_found(db): 

559 user1, token1 = generate_user() 

560 

561 with session_scope() as session: 

562 upload_key = create_upload(session, user1.id) 

563 

564 with galleries_session(token1) as api: 

565 res = api.AddPhotoToGallery( 

566 galleries_pb2.AddPhotoToGalleryReq(gallery_id=user1.profile_gallery_id, upload_key=upload_key) 

567 ) 

568 item_id = res.photos[0].item_id 

569 

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

571 api.MovePhoto( 

572 galleries_pb2.MovePhotoReq( 

573 gallery_id=user1.profile_gallery_id, 

574 item_id=item_id, 

575 after_item_id=999999, 

576 ) 

577 ) 

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

579 assert e.value.details() == "The item to place after was not found." 

580 

581 

582def test_UpdatePhotoCaption_success(db): 

583 user1, token1 = generate_user() 

584 

585 with session_scope() as session: 

586 upload_key = create_upload(session, user1.id) 

587 

588 with galleries_session(token1) as api: 

589 res = api.AddPhotoToGallery( 

590 galleries_pb2.AddPhotoToGalleryReq(gallery_id=user1.profile_gallery_id, upload_key=upload_key) 

591 ) 

592 item_id = res.photos[0].item_id 

593 

594 res = api.UpdatePhotoCaption( 

595 galleries_pb2.UpdatePhotoCaptionReq( 

596 gallery_id=user1.profile_gallery_id, 

597 item_id=item_id, 

598 caption="New caption", 

599 ) 

600 ) 

601 

602 assert len(res.photos) == 1 

603 assert res.photos[0].caption == "New caption" 

604 

605 

606def test_UpdatePhotoCaption_clear_caption(db): 

607 user1, token1 = generate_user() 

608 

609 with session_scope() as session: 

610 upload_key = create_upload(session, user1.id) 

611 

612 with galleries_session(token1) as api: 

613 res = api.AddPhotoToGallery( 

614 galleries_pb2.AddPhotoToGalleryReq( 

615 gallery_id=user1.profile_gallery_id, 

616 upload_key=upload_key, 

617 caption="Initial caption", 

618 ) 

619 ) 

620 item_id = res.photos[0].item_id 

621 

622 res = api.UpdatePhotoCaption( 

623 galleries_pb2.UpdatePhotoCaptionReq( 

624 gallery_id=user1.profile_gallery_id, 

625 item_id=item_id, 

626 caption="", 

627 ) 

628 ) 

629 

630 assert len(res.photos) == 1 

631 assert res.photos[0].caption == "" 

632 

633 

634def test_UpdatePhotoCaption_not_owner(db): 

635 user1, token1 = generate_user() 

636 user2, token2 = generate_user() 

637 

638 with session_scope() as session: 

639 upload_key = create_upload(session, user1.id) 

640 

641 with galleries_session(token1) as api: 

642 res = api.AddPhotoToGallery( 

643 galleries_pb2.AddPhotoToGalleryReq(gallery_id=user1.profile_gallery_id, upload_key=upload_key) 

644 ) 

645 item_id = res.photos[0].item_id 

646 

647 with galleries_session(token2) as api: 

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

649 api.UpdatePhotoCaption( 

650 galleries_pb2.UpdatePhotoCaptionReq( 

651 gallery_id=user1.profile_gallery_id, 

652 item_id=item_id, 

653 caption="Hacked!", 

654 ) 

655 ) 

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

657 assert e.value.details() == "You do not have permission to edit this gallery." 

658 

659 

660def test_UpdatePhotoCaption_item_not_found(db): 

661 user1, token1 = generate_user() 

662 

663 with galleries_session(token1) as api: 

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

665 api.UpdatePhotoCaption( 

666 galleries_pb2.UpdatePhotoCaptionReq( 

667 gallery_id=user1.profile_gallery_id, 

668 item_id=999999, 

669 caption="Test", 

670 ) 

671 ) 

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

673 assert e.value.details() == "Gallery item not found." 

674 

675 

676def test_remove_and_readd_photo(db): 

677 user1, token1 = generate_user() 

678 

679 with session_scope() as session: 

680 upload_key = create_upload(session, user1.id) 

681 

682 with galleries_session(token1) as api: 

683 res = api.AddPhotoToGallery( 

684 galleries_pb2.AddPhotoToGalleryReq(gallery_id=user1.profile_gallery_id, upload_key=upload_key) 

685 ) 

686 item_id = res.photos[0].item_id 

687 

688 res = api.RemovePhotoFromGallery( 

689 galleries_pb2.RemovePhotoFromGalleryReq(gallery_id=user1.profile_gallery_id, item_id=item_id) 

690 ) 

691 assert len(res.photos) == 0 

692 

693 res = api.AddPhotoToGallery( 

694 galleries_pb2.AddPhotoToGalleryReq(gallery_id=user1.profile_gallery_id, upload_key=upload_key) 

695 ) 

696 assert len(res.photos) == 1 

697 

698 

699def test_gallery_photo_ordering_preserved(db): 

700 user1, token1 = generate_user(strong_verification=True) 

701 

702 with session_scope() as session: 

703 keys = [create_upload(session, user1.id, f"photo{i}.jpg") for i in range(4)] 

704 

705 with galleries_session(token1) as api: 

706 item_ids = [] 

707 for key in keys: 

708 res = api.AddPhotoToGallery( 

709 galleries_pb2.AddPhotoToGalleryReq(gallery_id=user1.profile_gallery_id, upload_key=key) 

710 ) 

711 item_ids.append(res.photos[-1].item_id) 

712 

713 res = api.GetGallery(galleries_pb2.GetGalleryReq(gallery_id=user1.profile_gallery_id)) 

714 assert len(res.photos) == 4 

715 for i, photo in enumerate(res.photos): 

716 assert photo.item_id == item_ids[i] 

717 

718 

719def test_database_constraints_upload_uniqueness(db): 

720 user1, token1 = generate_user() 

721 

722 with session_scope() as session: 

723 user = session.execute(select(User).where(User.id == user1.id)).scalar_one() 

724 

725 upload = Upload(key="key1", filename="test.jpg", creator_user_id=user.id) 

726 session.add(upload) 

727 session.flush() 

728 

729 item1 = PhotoGalleryItem(gallery_id=user.profile_gallery_id, upload_key="key1", position=0.0) 

730 item2 = PhotoGalleryItem(gallery_id=user.profile_gallery_id, upload_key="key1", position=1.0) 

731 session.add_all([item1, item2]) 

732 

733 with pytest.raises(IntegrityError): 

734 session.flush() 

735 

736 session.rollback()