Coverage for ivatar/ivataraccount/test_views.py: 100%

765 statements  

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

1""" 

2Test our views in ivatar.ivataraccount.views and ivatar.views 

3""" 

4 

5import contextlib 

6 

7# pylint: disable=too-many-lines 

8from urllib.parse import urlsplit 

9from io import BytesIO 

10from contextlib import suppress 

11import io 

12import os 

13import gzip 

14import xml.etree.ElementTree 

15import base64 

16import django 

17from django.test import TestCase 

18from django.test import Client 

19from django.test import override_settings 

20from django.urls import reverse 

21from django.core import mail 

22from django.core.cache import caches 

23from django.contrib.auth.models import User 

24from django.contrib.auth import authenticate 

25import hashlib 

26 

27from libravatar import libravatar_url 

28 

29from PIL import Image 

30 

31os.environ["DJANGO_SETTINGS_MODULE"] = "ivatar.settings" 

32django.setup() 

33 

34# pylint: disable=wrong-import-position 

35from ivatar import settings 

36from ivatar.ivataraccount.forms import MAX_NUM_UNCONFIRMED_EMAILS_DEFAULT 

37from ivatar.ivataraccount.models import Photo, ConfirmedOpenId, ConfirmedEmail 

38from ivatar.utils import random_string 

39 

40# pylint: enable=wrong-import-position 

41 

42TEST_IMAGE_FILE = os.path.join(settings.STATIC_ROOT, "img", "deadbeef.png") 

43 

44 

45@override_settings() 

46class Tester(TestCase): # pylint: disable=too-many-public-methods 

47 """ 

48 Main test class 

49 """ 

50 

51 client = Client() 

52 user = None 

53 username = random_string() 

54 password = random_string() 

55 email = "{}@{}.org".format(username, random_string()) 

56 # Dunno why random tld doesn't work, but I'm too lazy now to investigate 

57 openid = "http://{}.{}.{}/".format(username, random_string(), "org") 

58 first_name = random_string() 

59 last_name = random_string() 

60 

61 def login(self): 

62 """ 

63 Login as user 

64 """ 

65 self.client.login(username=self.username, password=self.password) 

66 

67 def setUp(self): 

68 """ 

69 Prepare for tests. 

70 - Create user 

71 """ 

72 self.user = User.objects.create_user( 

73 username=self.username, 

74 password=self.password, 

75 first_name=self.first_name, 

76 last_name=self.last_name, 

77 ) 

78 # Disable caching 

79 settings.CACHES["default"] = { 

80 "BACKEND": "django.core.cache.backends.dummy.DummyCache", 

81 } 

82 caches._settings = None 

83 with suppress(AttributeError): 

84 # clear the existing cache connection 

85 delattr(caches._connections, "default") 

86 

87 def test_new_user(self): 

88 """ 

89 Create a new user 

90 """ 

91 response = self.client.get(reverse("new_account")) 

92 self.assertEqual(response.status_code, 200, "no 200 ok?") 

93 # Empty database / eliminate existing users 

94 User.objects.all().delete() 

95 url = reverse("new_account") 

96 response = self.client.post( 

97 url, 

98 { 

99 "username": self.username, 

100 "password1": self.password, 

101 "password2": self.password, 

102 }, 

103 follow=True, 

104 ) 

105 self.assertEqual(response.status_code, 200, "unable to create user?") 

106 self.assertEqual(response.context[0]["user"].username, self.username) 

107 

108 def test_new_user_twice(self): 

109 """ 

110 Try to create a user that already exists 

111 """ 

112 response = self.client.get(reverse("new_account")) 

113 self.assertEqual(response.status_code, 200, "no 200 ok?") 

114 # Due to setUp(), we already have this user! 

115 url = reverse("new_account") 

116 response = self.client.post( 

117 url, 

118 { 

119 "username": self.username, 

120 "password1": self.password, 

121 "password2": self.password, 

122 }, 

123 follow=True, 

124 ) 

125 self.assertEqual(response.status_code, 200, "unable to create user?") 

126 self.assertEqual(response.context[0]["user"].username, "") 

127 self.assertContains( 

128 response, 

129 "A user with that username already exists.", 

130 1, 

131 200, 

132 "can we create a user a second time???", 

133 ) 

134 

135 def test_set_password(self): 

136 """ 

137 Change the user password 

138 """ 

139 self.login() 

140 response = self.client.get(reverse("password_set")) 

141 self.assertEqual(response.status_code, 200, "no 200 ok?") 

142 self.password = random_string() 

143 response = self.client.post( 

144 reverse("password_set"), 

145 { 

146 "new_password1": self.password, 

147 "new_password2": self.password, 

148 }, 

149 follow=True, 

150 ) 

151 

152 self.assertEqual(response.status_code, 200, "cannot change password?") 

153 self.assertEqual( 

154 str(list(response.context[0]["messages"])[0]), 

155 "password changed successfully - please login again", 

156 "password change not successful?", 

157 ) 

158 

159 self.assertIsNotNone( 

160 authenticate( 

161 username=self.username, 

162 password=self.password, 

163 ), 

164 "cannot authenticate with new password!?", 

165 ) 

166 

167 self.login() 

168 response = self.client.get(reverse("profile")) 

169 self.assertEqual(response.context[0]["user"].is_anonymous, False) 

170 

171 def test_add_email(self): 

172 """ 

173 Add e-mail address 

174 """ 

175 self.login() 

176 response = self.client.get(reverse("add_email")) 

177 self.assertEqual(response.status_code, 200, "no 200 ok?") 

178 # Avoid sending out mails 

179 settings.EMAIL_BACKEND = "django.core.mail.backends.dummy.EmailBackend" 

180 response = self.client.post( 

181 reverse("add_email"), 

182 { 

183 "email": self.email, 

184 }, 

185 follow=True, 

186 ) 

187 self.assertEqual(response.status_code, 200, "cannot add email?") 

188 self.assertEqual( 

189 len(response.context[0]["messages"]), 

190 1, 

191 "there must not be more or less than ONE (1) message", 

192 ) 

193 self.assertEqual( 

194 str(list(response.context[0]["messages"])[0]), 

195 "Address added successfully", 

196 "unable to add mail address?", 

197 ) 

198 

199 def test_confirm_email(self): 

200 """ 

201 Confirm unconfirmed email 

202 """ 

203 self.login() 

204 # Avoid sending out mails 

205 settings.EMAIL_BACKEND = "django.core.mail.backends.dummy.EmailBackend" 

206 response = self.client.post( 

207 reverse("add_email"), 

208 { 

209 "email": self.email, 

210 }, 

211 follow=True, 

212 ) 

213 unconfirmed = self.user.unconfirmedemail_set.first() 

214 verification_key = unconfirmed.verification_key 

215 url = reverse("confirm_email", args=[verification_key]) 

216 response = self.client.get(url) 

217 self.assertEqual(response.status_code, 200, "unable to confirm mail address?") 

218 

219 self.assertEqual( 

220 self.user.unconfirmedemail_set.count(), 

221 0, 

222 "there must not be any unconfirmed address, after confirming it", 

223 ) 

224 self.assertEqual( 

225 self.user.confirmedemail_set.count(), 

226 1, 

227 "there must not be more or less than ONE (1) confirmed address!", 

228 ) 

229 

230 def test_confirm_email_w_invalid_auth_key(self): # pylint: disable=invalid-name 

231 """ 

232 Test confirmation with invalid auth key 

233 """ 

234 self.login() 

235 # Avoid sending out mails 

236 settings.EMAIL_BACKEND = "django.core.mail.backends.dummy.EmailBackend" 

237 response = self.client.post( 

238 reverse("add_email"), 

239 { 

240 "email": self.email, 

241 }, 

242 follow=True, 

243 ) 

244 url = reverse("confirm_email", args=["x"]) 

245 response = self.client.get(url, follow=True) 

246 self.assertEqual( 

247 response.status_code, 

248 200, 

249 "Not able to request confirmation - without verification key?", 

250 ) 

251 self.assertEqual( 

252 str(list(response.context[0]["messages"])[-1]), 

253 "Verification key incorrect", 

254 "Confirm w/o verification key does not produce error message?", 

255 ) 

256 

257 def test_confirm_email_w_non_existing_auth_key( 

258 self, 

259 ): # pylint: disable=invalid-name 

260 """ 

261 Test confirmation with non existing auth key 

262 """ 

263 self.login() 

264 # Avoid sending out mails 

265 settings.EMAIL_BACKEND = "django.core.mail.backends.dummy.EmailBackend" 

266 response = self.client.post( 

267 reverse("add_email"), 

268 { 

269 "email": self.email, 

270 }, 

271 follow=True, 

272 ) 

273 url = reverse("confirm_email", args=["x" * 64]) 

274 response = self.client.get(url, follow=True) 

275 self.assertEqual( 

276 response.status_code, 

277 200, 

278 "Not able to request confirmation - without verification key?", 

279 ) 

280 self.assertEqual( 

281 str(list(response.context[0]["messages"])[-1]), 

282 "Verification key does not exist", 

283 "Confirm w/o non existing key does not produce error message?", 

284 ) 

285 

286 def test_remove_confirmed_email(self): 

287 """ 

288 Remove confirmed email 

289 """ 

290 self.login() 

291 # Avoid sending out mails 

292 settings.EMAIL_BACKEND = "django.core.mail.backends.dummy.EmailBackend" 

293 response = self.client.post( 

294 reverse("add_email"), 

295 { 

296 "email": self.email, 

297 }, 

298 ) # Create test address 

299 unconfirmed = self.user.unconfirmedemail_set.first() 

300 verification_key = unconfirmed.verification_key 

301 url = reverse("confirm_email", args=[verification_key]) 

302 self.client.get(url) # Confirm 

303 url = reverse( 

304 "remove_confirmed_email", args=[self.user.confirmedemail_set.first().id] 

305 ) 

306 response = self.client.post(url, follow=True) 

307 self.assertEqual( 

308 response.status_code, 200, "unable to remove confirmed address?" 

309 ) 

310 self.assertEqual( 

311 str(list(response.context[0]["messages"])[-1]), 

312 "Address removed", 

313 "Removing confirmed mail does not work?", 

314 ) 

315 

316 def test_remove_not_existing_confirmed_email(self): # pylint: disable=invalid-name 

317 """ 

318 Try removing confirmed mail that doesn't exist 

319 """ 

320 self.login() 

321 url = reverse("remove_confirmed_email", args=[1234]) 

322 response = self.client.post(url, follow=True) 

323 self.assertEqual( 

324 response.status_code, 200, "removing email does not redirect to profile?" 

325 ) 

326 self.assertEqual( 

327 str(list(response.context[0]["messages"])[0]), 

328 "Address does not exist", 

329 "Removing not existing (confirmed) address, should produce an\ 

330 error message!", 

331 ) 

332 

333 def test_remove_unconfirmed_email(self): 

334 """ 

335 Remove unconfirmed email 

336 """ 

337 self.login() 

338 # Avoid sending out mails 

339 settings.EMAIL_BACKEND = "django.core.mail.backends.dummy.EmailBackend" 

340 response = self.client.post( 

341 reverse("add_email"), 

342 { 

343 "email": self.email, 

344 }, 

345 ) # Create test address 

346 url = reverse( 

347 "remove_unconfirmed_email", args=[self.user.unconfirmedemail_set.first().id] 

348 ) 

349 response = self.client.post(url, follow=True) 

350 self.assertEqual( 

351 response.status_code, 200, "unable to remove unconfirmed address?" 

352 ) 

353 # Take care, since we do not fetch any page now, the message we need 

354 # to check is the _second_ (aka [1], since first is [0]) 

355 self.assertEqual( 

356 str(list(response.context[0]["messages"])[1]), 

357 "Address removed", 

358 "Removing unconfirmed mail does not work?", 

359 ) 

360 

361 def test_gravatar_photo_import(self): 

362 """ 

363 import photo from Gravatar (with known mail address) 

364 """ 

365 self.login() 

366 # Avoid sending out mails 

367 settings.EMAIL_BACKEND = "django.core.mail.backends.dummy.EmailBackend" 

368 response = self.client.post( 

369 reverse("add_email"), 

370 { 

371 "email": "oliver@linux-kernel.at", # Wow, static :-[ 

372 }, 

373 ) # Create test address 

374 unconfirmed = self.user.unconfirmedemail_set.first() 

375 verification_key = unconfirmed.verification_key 

376 url = reverse("confirm_email", args=[verification_key]) 

377 self.client.get(url) # Confirm 

378 

379 url = reverse("import_photo", args=[self.user.confirmedemail_set.first().id]) 

380 response = self.client.post( 

381 url, 

382 { 

383 "photo_Gravatar": 1, 

384 }, 

385 follow=True, 

386 ) 

387 self.assertEqual( 

388 response.status_code, 200, "unable to import photo from Gravatar?" 

389 ) 

390 self.assertEqual( 

391 str(list(response.context[0]["messages"])[-1]), 

392 "Gravatar image successfully imported", 

393 "Importing gravatar photo did not work?", 

394 ) 

395 self.assertIsInstance( 

396 self.user.photo_set.first(), Photo, "why is there no Photo (instance)?" 

397 ) 

398 

399 def test_raw_image(self): 

400 """ 

401 test raw image view (as seen in profile <img src= 

402 """ 

403 

404 # Ensure we have a photo 

405 self.test_gravatar_photo_import() 

406 response = self.client.get( 

407 reverse("raw_image", args=[self.user.photo_set.first().id]) 

408 ) 

409 self.assertEqual(response.status_code, 200, "cannot fetch photo?") 

410 # Probably not the best way to access the content type 

411 self.assertEqual(response["Content-Type"], "image/jpg", "Content type wrong!?") 

412 

413 self.assertEqual( 

414 bytes(response.content), 

415 bytes(self.user.photo_set.first().data), 

416 "raw_image should return the same content as if we\ 

417 read it directly from the DB", 

418 ) 

419 

420 def test_delete_photo(self): 

421 """ 

422 test deleting the photo 

423 """ 

424 

425 # Ensure we have a photo 

426 self.test_gravatar_photo_import() 

427 

428 url = reverse("delete_photo", args=[self.user.photo_set.first().id]) 

429 response = self.client.get(url, follow=True) 

430 self.assertEqual(response.status_code, 200, "deleting photo does not work?") 

431 self.assertEqual( 

432 str(list(response.context[0]["messages"])[-1]), 

433 "Photo deleted successfully", 

434 "Photo deletion did not work?", 

435 ) 

436 

437 def test_delete_non_existing_photo(self): 

438 """ 

439 test deleting the photo 

440 """ 

441 

442 # Ensure we have a photo 

443 self.test_gravatar_photo_import() 

444 

445 url = reverse("delete_photo", args=[1234]) 

446 response = self.client.get(url, follow=True) 

447 self.assertEqual(response.status_code, 200, "post to delete does not work?") 

448 self.assertEqual( 

449 str(list(response.context[0]["messages"])[-1]), 

450 "No such image or no permission to delete it", 

451 "Deleting photo that does not exist, should return error message", 

452 ) 

453 

454 def test_too_many_unconfirmed_email(self): 

455 """ 

456 Request too many unconfirmed email addresses, make sure we 

457 cannot add more 

458 """ 

459 self.login() 

460 # Avoid sending out mails 

461 settings.EMAIL_BACKEND = "django.core.mail.backends.dummy.EmailBackend" 

462 

463 max_num_unconfirmed = getattr( 

464 settings, "MAX_NUM_UNCONFIRMED_EMAILS", MAX_NUM_UNCONFIRMED_EMAILS_DEFAULT 

465 ) 

466 

467 for i in range(max_num_unconfirmed + 1): 

468 response = self.client.post( # noqa: F841 

469 reverse("add_email"), 

470 { 

471 "email": "%i.%s" % (i, self.email), 

472 }, 

473 follow=True, 

474 ) # Create test addresses + 1 too much 

475 return self._check_form_validity( 

476 response, "Too many unconfirmed mail addresses!", "__all__" 

477 ) 

478 

479 def test_add_mail_address_twice(self): 

480 """ 

481 Request the same mail address two times, should not lead to 

482 having the same address twice 

483 """ 

484 self.login() 

485 # Avoid sending out mails 

486 settings.EMAIL_BACKEND = "django.core.mail.backends.dummy.EmailBackend" 

487 

488 for _ in range(2): 

489 response = self.client.post( # noqa: F841 

490 reverse("add_email"), 

491 { 

492 "email": self.email, 

493 }, 

494 follow=True, 

495 ) 

496 return self._check_form_validity( 

497 response, "Address already added, currently unconfirmed", "email" 

498 ) 

499 

500 def test_add_already_confirmed_email_self(self): # pylint: disable=invalid-name 

501 """ 

502 Request adding mail address that is already confirmed (by someone) 

503 """ 

504 # Create test mail and confirm it, reuse test code 

505 # Should set EMAIL_BACKEND, so no need to do it here 

506 self.test_confirm_email() 

507 

508 response = self.client.post( # noqa: F841 

509 reverse("add_email"), 

510 { 

511 "email": self.email, 

512 }, 

513 follow=True, 

514 ) 

515 

516 return self._check_form_validity( 

517 response, "Address already confirmed (by you)", "email" 

518 ) 

519 

520 def test_add_already_confirmed_email_other(self): # pylint: disable=invalid-name 

521 """ 

522 Request adding mail address that is already confirmed (by someone) 

523 """ 

524 # Create test mail and confirm it, reuse test code 

525 # Should set EMAIL_BACKEND, so no need to do it here 

526 self.test_confirm_email() 

527 

528 # Create another user and assign the mail address to that one 

529 # in order to test the correct error message 

530 otheruser = User.objects.create(username="otheruser") 

531 confirmedemail = ConfirmedEmail.objects.last() 

532 confirmedemail.user = otheruser 

533 confirmedemail.save() 

534 

535 response = self.client.post( # noqa: F841 

536 reverse("add_email"), 

537 { 

538 "email": self.email, 

539 }, 

540 follow=True, 

541 ) 

542 

543 return self._check_form_validity( 

544 response, "Address already confirmed (by someone else)", "email" 

545 ) 

546 

547 def test_remove_unconfirmed_non_existing_email( 

548 self, 

549 ): # pylint: disable=invalid-name 

550 """ 

551 Remove unconfirmed email that doesn't exist 

552 """ 

553 self.login() 

554 url = reverse("remove_unconfirmed_email", args=[1234]) 

555 response = self.client.post(url, follow=True) 

556 self.assertEqual( 

557 response.status_code, 200, "unable to remove non existing address?" 

558 ) 

559 self.assertEqual( 

560 str(list(response.context[0]["messages"])[0]), 

561 "Address does not exist", 

562 "Removing address that does not\ 

563 exist, should return error message!", 

564 ) 

565 

566 def test_upload_image( 

567 self, test_only_one=True 

568 ): # pylint: disable=inconsistent-return-statements 

569 """ 

570 Test uploading image 

571 """ 

572 self.login() 

573 url = reverse("upload_photo") 

574 # rb => Read binary 

575 with open(TEST_IMAGE_FILE, "rb") as photo_file: 

576 photo_data = photo_file.read() 

577 

578 from django.core.files.uploadedfile import SimpleUploadedFile 

579 

580 uploaded_file = SimpleUploadedFile( 

581 "deadbeef.png", photo_data, content_type="image/png" 

582 ) 

583 response = self.client.post( 

584 url, 

585 { 

586 "photo": uploaded_file, 

587 "not_porn": True, 

588 "can_distribute": True, 

589 }, 

590 follow=True, 

591 ) 

592 if not test_only_one: 

593 return response 

594 self.assertEqual( 

595 self.user.photo_set.count(), 1, "there must be exactly one photo now!" 

596 ) 

597 self.assertEqual( 

598 str(list(response.context[0]["messages"])[-1]), 

599 "Successfully uploaded", 

600 "A valid image should return a success message!", 

601 ) 

602 self.assertEqual( 

603 self.user.photo_set.first().format, 

604 "png", 

605 "Format must be png, since we uploaded a png!", 

606 ) 

607 

608 def test_upload_too_many_images(self): 

609 """ 

610 Test uploading more images than we are allowed 

611 """ 

612 for _ in range(settings.MAX_NUM_PHOTOS + 1): 

613 response = self.test_upload_image(test_only_one=False) 

614 self.assertEqual( 

615 self.user.photo_set.count(), 

616 settings.MAX_NUM_PHOTOS, 

617 "there may not be more photos than allowed!", 

618 ) 

619 # Take care we need to check the last message 

620 self.assertEqual( 

621 str(list(response.context[0]["messages"])[-1]), 

622 "Maximum number of photos (%i) reached" % settings.MAX_NUM_PHOTOS, 

623 "Adding more than allowed images, should return error message!", 

624 ) 

625 

626 def test_upload_too_big_image(self): 

627 """ 

628 Test uploading image that is too big 

629 """ 

630 self.login() 

631 url = reverse("upload_photo") 

632 # rb => Read binary 

633 response = self.client.post( 

634 url, 

635 { 

636 "photo": io.StringIO("x" * (settings.MAX_PHOTO_SIZE + 1)), 

637 "not_porn": True, 

638 "can_distribute": True, 

639 }, 

640 follow=True, 

641 ) 

642 self.assertEqual( 

643 str(list(response.context[0]["messages"])[0]), 

644 "Image too big", 

645 "Uploading too big image, should return error message!", 

646 ) 

647 

648 def test_upload_invalid_image(self): 

649 """ 

650 Test invalid image data 

651 """ 

652 self.login() 

653 url = reverse("upload_photo") 

654 # rb => Read binary 

655 response = self.client.post( 

656 url, 

657 { 

658 "photo": io.StringIO("x"), 

659 "not_porn": True, 

660 "can_distribute": True, 

661 }, 

662 follow=True, 

663 ) 

664 self.assertEqual( 

665 str(list(response.context[0]["messages"])[0]), 

666 "Invalid Format", 

667 "Invalid img data should return error message!", 

668 ) 

669 

670 def test_upload_invalid_image_format(self): # pylint: disable=invalid-name 

671 """ 

672 Test if invalid format is correctly detected 

673 """ 

674 self.login() 

675 url = reverse("upload_photo") 

676 # rb => Read binary 

677 with open(os.path.join(settings.STATIC_ROOT, "img", "mm.svg"), "rb") as photo: 

678 response = self.client.post( 

679 url, 

680 { 

681 "photo": photo, 

682 "not_porn": True, 

683 "can_distribute": True, 

684 }, 

685 follow=True, 

686 ) 

687 self.assertEqual( 

688 str(list(response.context[0]["messages"])[0]), 

689 "Invalid Format", 

690 "Invalid img data should return error message!", 

691 ) 

692 

693 def test_upload_gif_image(self): 

694 """ 

695 Test if gif is correctly detected and can be viewed 

696 """ 

697 self._extracted_from_test_upload_webp_image_5( 

698 "broken.gif", 

699 "GIF upload failed?!", 

700 "gif", 

701 "Format must be gif, since we uploaded a GIF!", 

702 ) 

703 

704 def test_upload_jpg_image(self): 

705 """ 

706 Test if jpg is correctly detected and can be viewed 

707 """ 

708 self._extracted_from_test_upload_webp_image_5( 

709 "broken.jpg", 

710 "JPEG upload failed?!", 

711 "jpg", 

712 "Format must be jpeg, since we uploaded a jpeg!", 

713 ) 

714 

715 def test_upload_webp_image(self): 

716 """ 

717 Test if webp is correctly detected and can be viewed 

718 """ 

719 self._extracted_from_test_upload_webp_image_5( 

720 "broken.webp", 

721 "WEBP upload failed?!", 

722 "webp", 

723 "Format must be webp, since we uploaded a webp!", 

724 ) 

725 

726 def _extracted_from_test_upload_webp_image_5( 

727 self, filename, message1, format, message2 

728 ): 

729 """ 

730 Helper function for common checks for gif, jpg, webp 

731 """ 

732 self.login() 

733 url = reverse("upload_photo") 

734 with open(os.path.join(settings.STATIC_ROOT, "img", filename), "rb") as photo: 

735 response = self.client.post( 

736 url, 

737 {"photo": photo, "not_porn": True, "can_distribute": True}, 

738 follow=True, 

739 ) 

740 self.assertEqual( 

741 str(list(response.context[0]["messages"])[0]), 

742 "Successfully uploaded", 

743 message1, 

744 ) 

745 self.assertEqual(self.user.photo_set.first().format, format, message2) 

746 self.test_confirm_email() 

747 self.user.confirmedemail_set.first().photo = self.user.photo_set.first() 

748 urlobj = urlsplit( 

749 libravatar_url(email=self.user.confirmedemail_set.first().email) 

750 ) 

751 url = f"{urlobj.path}?{urlobj.query}" 

752 response = self.client.get(url, follow=True) 

753 self.assertEqual(response.status_code, 200, "unable to fetch avatar?") 

754 

755 def test_upload_unsupported_tif_image(self): # pylint: disable=invalid-name 

756 """ 

757 Test if unsupported format is correctly detected 

758 """ 

759 self.login() 

760 url = reverse("upload_photo") 

761 # rb => Read binary 

762 with open( 

763 os.path.join(settings.STATIC_ROOT, "img", "broken.tif"), "rb" 

764 ) as photo: 

765 response = self.client.post( 

766 url, 

767 { 

768 "photo": photo, 

769 "not_porn": True, 

770 "can_distribute": True, 

771 }, 

772 follow=True, 

773 ) 

774 self.assertEqual( 

775 str(list(response.context[0]["messages"])[0]), 

776 "Invalid Format", 

777 "Invalid img data should return error message!", 

778 ) 

779 

780 def test_automatic_photo_assign_to_confirmed_mail( 

781 self, 

782 ): # pylint: disable=invalid-name 

783 """ 

784 Test if automatic assignment of photo works 

785 """ 

786 self.test_upload_image() 

787 self.test_confirm_email() 

788 confirmed = self.user.confirmedemail_set.first() 

789 self.assertEqual(confirmed.photo, self.user.photo_set.first()) 

790 

791 def test_assign_photo_to_email(self): 

792 """ 

793 Test assigning photo to mail address 

794 """ 

795 self.test_confirm_email() 

796 self.test_upload_image() 

797 self.assertIsNone(self.user.confirmedemail_set.first().photo) 

798 url = reverse( 

799 "assign_photo_email", args=[self.user.confirmedemail_set.first().id] 

800 ) 

801 # The get is for the view - test context data 

802 self.client.get( 

803 url, 

804 { 

805 "photo_id": self.user.photo_set.first().id, 

806 }, 

807 ) 

808 # The post is for the actual assigning 

809 response = self.client.post( 

810 url, 

811 { 

812 "photo_id": self.user.photo_set.first().id, 

813 }, 

814 follow=True, 

815 ) 

816 self.assertEqual(response.status_code, 200, "cannot assign photo?") 

817 self.assertEqual( 

818 self.user.confirmedemail_set.first().photo, self.user.photo_set.first() 

819 ) 

820 

821 def test_no_photo_to_email(self): 

822 """ 

823 Test assigning photo to mail address 

824 """ 

825 self.test_confirm_email() 

826 url = reverse( 

827 "assign_photo_email", args=[self.user.confirmedemail_set.first().id] 

828 ) 

829 response = self.client.post( 

830 url, 

831 { 

832 "photoNone": True, 

833 }, 

834 follow=True, 

835 ) 

836 self.assertEqual(response.status_code, 200, "cannot un-assign photo?") 

837 self.assertEqual(self.user.confirmedemail_set.first().photo, None) 

838 

839 def test_assign_photo_to_email_wo_photo_for_testing_template( 

840 self, 

841 ): # pylint: disable=invalid-name 

842 """ 

843 Test assign photo template 

844 """ 

845 self.test_confirm_email() 

846 url = reverse( 

847 "assign_photo_email", args=[self.user.confirmedemail_set.first().id] 

848 ) 

849 # The get is for the view - test context data 

850 response = self.client.get(url) 

851 self.assertEqual(response.status_code, 200, "cannot fetch page?") 

852 

853 def test_assign_invalid_photo_id_to_email(self): # pylint: disable=invalid-name 

854 """ 

855 Test if assigning an invalid photo id returns the correct error message 

856 """ 

857 self.test_confirm_email() 

858 self.test_upload_image() 

859 self.assertIsNone(self.user.confirmedemail_set.first().photo) 

860 url = reverse( 

861 "assign_photo_email", args=[self.user.confirmedemail_set.first().id] 

862 ) 

863 response = self.client.post( 

864 url, 

865 { 

866 "photo_id": 1234, 

867 }, 

868 follow=True, 

869 ) 

870 self.assertEqual(response.status_code, 200, "cannot post assign photo request?") 

871 self.assertEqual( 

872 str(list(response.context[0]["messages"])[-1]), 

873 "Photo does not exist", 

874 "Assign non existing photo, does not return error message?", 

875 ) 

876 

877 def test_post_to_assign_photo_without_photo_id( 

878 self, 

879 ): # pylint: disable=invalid-name 

880 """ 

881 Test if assigning photo without id returns the correct error message 

882 """ 

883 self.test_confirm_email() 

884 self.test_upload_image() 

885 self.assertIsNone(self.user.confirmedemail_set.first().photo) 

886 url = reverse( 

887 "assign_photo_email", args=[self.user.confirmedemail_set.first().id] 

888 ) 

889 response = self.client.post(url, {}, follow=True) 

890 self.assertEqual(response.status_code, 200, "cannot post assign photo request?") 

891 self.assertEqual( 

892 str(list(response.context[0]["messages"])[-1]), 

893 "Invalid request [photo_id] missing", 

894 "Assign non existing photo, does not return error message?", 

895 ) 

896 

897 def test_assign_photo_to_non_existing_mail(self): # pylint: disable=invalid-name 

898 """ 

899 Test if assigning photo to mail address that doesn't exist returns 

900 the correct error message 

901 """ 

902 self.test_upload_image() 

903 url = reverse("assign_photo_email", args=[1234]) 

904 response = self.client.post( 

905 url, 

906 { 

907 "photo_id": self.user.photo_set.first().id, 

908 }, 

909 follow=True, 

910 ) 

911 self.assertEqual(response.status_code, 200, "cannot post assign photo request?") 

912 self.assertEqual( 

913 str(list(response.context[0]["messages"])[-1]), 

914 "Invalid request", 

915 "Assign non existing photo, does not return error message?", 

916 ) 

917 

918 def test_import_photo_with_non_existing_email(self): # pylint: disable=invalid-name 

919 """ 

920 Test if import with non existing mail address returns 

921 the correct error message 

922 """ 

923 self.login() 

924 url = reverse("import_photo", args=[1234]) 

925 response = self.client.post(url, {}, follow=True) 

926 self.assertEqual(response.status_code, 200, "cannot post import photo request?") 

927 self.assertEqual( 

928 str(list(response.context[0]["messages"])[0]), 

929 "Address does not exist", 

930 "Import photo with non existing mail id,\ 

931 does not return error message?", 

932 ) 

933 

934 def test_import_nothing(self): 

935 """ 

936 Test if importing nothing causes the correct 

937 error message to be returned 

938 """ 

939 self.test_confirm_email() 

940 url = reverse("import_photo", args=[self.user.confirmedemail_set.first().id]) 

941 response = self.client.post(url, {}, follow=True) 

942 self.assertEqual(response.status_code, 200, "cannot post import photo request?") 

943 self.assertEqual( 

944 str(list(response.context[0]["messages"])[-1]), 

945 "Nothing importable", 

946 "Importing with email that does not exist in Gravatar,\ 

947 should return an error message!", 

948 ) 

949 

950 def _manual_confirm(self): 

951 """ 

952 Helper method to confirm manually, because testing is really hard 

953 """ 

954 # Manual confirm, since testing is _really_ hard! 

955 unconfirmed = self.user.unconfirmedopenid_set.first() 

956 confirmed = ConfirmedOpenId() 

957 confirmed.user = unconfirmed.user 

958 confirmed.ip_address = "127.0.0.1" 

959 confirmed.openid = unconfirmed.openid 

960 confirmed.save() 

961 unconfirmed.delete() 

962 

963 def test_add_openid(self, confirm=True): 

964 """ 

965 Test if adding an OpenID works 

966 """ 

967 

968 self.login() 

969 # Get page 

970 response = self.client.get(reverse("add_openid")) 

971 self.assertEqual( 

972 response.status_code, 200, "Fetching page to add OpenID fails?" 

973 ) 

974 

975 response = self.client.post( 

976 reverse("add_openid"), 

977 { 

978 "openid": self.openid, 

979 }, 

980 ) 

981 self.assertEqual(response.status_code, 302, "OpenID must redirect") 

982 

983 if confirm: 

984 self._manual_confirm() 

985 

986 def test_add_openid_twice(self): 

987 """ 

988 Test if adding OpenID a second time works - it shouldn't 

989 """ 

990 self.login() 

991 # Get page 

992 response = self.client.get(reverse("add_openid")) 

993 self.assertEqual( 

994 response.status_code, 200, "Fetching page to add OpenID fails?" 

995 ) 

996 

997 response = self.client.post( 

998 reverse("add_openid"), 

999 { 

1000 "openid": self.openid, 

1001 }, 

1002 ) 

1003 self.assertEqual(response.status_code, 302, "OpenID must redirect") 

1004 response = self.client.post( 

1005 reverse("add_openid"), 

1006 { 

1007 "openid": self.openid, 

1008 }, 

1009 follow=True, 

1010 ) 

1011 self.assertEqual( 

1012 self.user.unconfirmedopenid_set.count(), 

1013 1, 

1014 "There must only be one unconfirmed ID!", 

1015 ) 

1016 

1017 self._check_form_validity( 

1018 response, "OpenID already added, but not confirmed yet!", "openid" 

1019 ) 

1020 # Manual confirm, since testing is _really_ hard! 

1021 unconfirmed = self.user.unconfirmedopenid_set.first() 

1022 confirmed = ConfirmedOpenId() 

1023 confirmed.user = unconfirmed.user 

1024 confirmed.ip_address = "127.0.0.1" 

1025 confirmed.openid = unconfirmed.openid 

1026 confirmed.save() 

1027 unconfirmed.delete() 

1028 

1029 # Try adding it again - although already confirmed 

1030 response = self.client.post( 

1031 reverse("add_openid"), 

1032 { 

1033 "openid": self.openid, 

1034 }, 

1035 follow=True, 

1036 ) 

1037 

1038 return self._check_form_validity( 

1039 response, "OpenID already added and confirmed!", "openid" 

1040 ) 

1041 

1042 def _check_form_validity(self, response, message, field): 

1043 """ 

1044 Helper method to check form, used in several test functions, 

1045 deduplicating code 

1046 """ 

1047 

1048 self.assertTrue( 

1049 hasattr(response, "context"), "Response does not have a context" 

1050 ) 

1051 result = response.context.get("form") 

1052 self.assertIsNotNone(result, "No form found in response context") 

1053 self.assertFalse(result.is_valid(), "Form should not be valid") 

1054 self.assertIn(message, result.errors.get(field, [])) 

1055 return result 

1056 

1057 def test_assign_photo_to_openid(self): 

1058 """ 

1059 Test assignment of photo to openid 

1060 """ 

1061 self.test_add_openid() 

1062 self.test_upload_image() 

1063 self.assertIsNone(self.user.confirmedopenid_set.first().photo) 

1064 url = reverse( 

1065 "assign_photo_openid", args=[self.user.confirmedopenid_set.first().id] 

1066 ) 

1067 # The get is for the view - test context data 

1068 self.client.get( 

1069 url, 

1070 { 

1071 "photo_id": self.user.photo_set.first().id, 

1072 }, 

1073 ) 

1074 # The post is for the actual assigning 

1075 response = self.client.post( 

1076 url, 

1077 { 

1078 "photo_id": self.user.photo_set.first().id, 

1079 }, 

1080 follow=True, 

1081 ) 

1082 self.assertEqual(response.status_code, 200, "cannot assign photo?") 

1083 self.assertEqual( 

1084 self.user.confirmedopenid_set.first().photo, self.user.photo_set.first() 

1085 ) 

1086 

1087 def test_assign_photo_to_openid_wo_photo_for_testing_template( 

1088 self, 

1089 ): # pylint: disable=invalid-name 

1090 """ 

1091 Test openid/photo assignment template 

1092 """ 

1093 self.test_add_openid() 

1094 url = reverse( 

1095 "assign_photo_openid", args=[self.user.confirmedopenid_set.first().id] 

1096 ) 

1097 response = self.client.get(url) 

1098 self.assertEqual(response.status_code, 200, "cannot fetch page?") 

1099 

1100 def test_assign_invalid_photo_id_to_openid(self): # pylint: disable=invalid-name 

1101 """ 

1102 Test assigning invalid photo to openid returns 

1103 the correct error message 

1104 """ 

1105 self.test_add_openid() 

1106 self.assertIsNone(self.user.confirmedopenid_set.first().photo) 

1107 url = reverse( 

1108 "assign_photo_openid", args=[self.user.confirmedopenid_set.first().id] 

1109 ) 

1110 response = self.client.post( 

1111 url, 

1112 { 

1113 "photo_id": 1234, 

1114 }, 

1115 follow=True, 

1116 ) 

1117 self.assertEqual(response.status_code, 200, "cannot post assign photo request?") 

1118 self.assertEqual( 

1119 str(list(response.context[0]["messages"])[-1]), 

1120 "Photo does not exist", 

1121 "Assign non existing photo, does not return error message?", 

1122 ) 

1123 

1124 def test_post_to_assign_photo_openid_without_photo_id( 

1125 self, 

1126 ): # pylint: disable=invalid-name 

1127 """ 

1128 Test POST assign photo to openid without photo id 

1129 returns the correct error message 

1130 """ 

1131 self.test_add_openid() 

1132 self.test_upload_image() 

1133 self.assertIsNone(self.user.confirmedopenid_set.first().photo) 

1134 url = reverse( 

1135 "assign_photo_openid", args=[self.user.confirmedopenid_set.first().id] 

1136 ) 

1137 response = self.client.post(url, {}, follow=True) 

1138 self.assertEqual(response.status_code, 200, "cannot post assign photo request?") 

1139 self.assertEqual( 

1140 str(list(response.context[0]["messages"])[-1]), 

1141 "Invalid request [photo_id] missing", 

1142 "Assign non existing photo, does not return error message?", 

1143 ) 

1144 

1145 def test_assign_photo_to_openid_non_existing_openid( 

1146 self, 

1147 ): # pylint: disable=invalid-name 

1148 """ 

1149 Test assigning photo to openid that doesn't exist 

1150 returns the correct error message. 

1151 """ 

1152 self.test_upload_image() 

1153 url = reverse("assign_photo_openid", args=[1234]) 

1154 response = self.client.post( 

1155 url, 

1156 { 

1157 "photo_id": self.user.photo_set.first().id, 

1158 }, 

1159 follow=True, 

1160 ) 

1161 self.assertEqual(response.status_code, 200, "cannot post assign photo request?") 

1162 self.assertEqual( 

1163 str(list(response.context[0]["messages"])[-1]), 

1164 "Invalid request", 

1165 "Assign non existing photo, does not return error message?", 

1166 ) 

1167 

1168 def test_remove_confirmed_openid(self): # pylint: disable=invalid-name 

1169 """ 

1170 Remove confirmed openid 

1171 """ 

1172 self.test_add_openid() 

1173 url = reverse( 

1174 "remove_confirmed_openid", args=[self.user.confirmedopenid_set.first().id] 

1175 ) 

1176 response = self.client.post(url, follow=True) 

1177 self.assertEqual( 

1178 response.status_code, 200, "unable to remove confirmed openid?" 

1179 ) 

1180 self.assertEqual( 

1181 str(list(response.context[0]["messages"])[-1]), 

1182 "ID removed", 

1183 "Removing confirmed openid does not work?", 

1184 ) 

1185 

1186 def test_remove_not_existing_confirmed_openid(self): # pylint: disable=invalid-name 

1187 """ 

1188 Try removing confirmed openid that doesn't exist 

1189 """ 

1190 self.login() 

1191 url = reverse("remove_confirmed_openid", args=[1234]) 

1192 response = self.client.post(url, follow=True) 

1193 self.assertEqual( 

1194 response.status_code, 200, "removing id does not redirect to profile?" 

1195 ) 

1196 self.assertEqual( 

1197 str(list(response.context[0]["messages"])[0]), 

1198 "ID does not exist", 

1199 "Removing not existing (confirmed) address, should produce an\ 

1200 error message!", 

1201 ) 

1202 

1203 def test_remove_unconfirmed_openid(self): 

1204 """ 

1205 Remove unconfirmed openid 

1206 """ 

1207 self.test_add_openid(confirm=False) 

1208 url = reverse( 

1209 "remove_unconfirmed_openid", 

1210 args=[self.user.unconfirmedopenid_set.first().id], 

1211 ) 

1212 response = self.client.post(url, follow=True) 

1213 self.assertEqual( 

1214 response.status_code, 200, "unable to remove unconfirmed address?" 

1215 ) 

1216 self.assertEqual( 

1217 str(list(response.context[0]["messages"])[-1]), 

1218 "ID removed", 

1219 "Removing unconfirmed mail does not work?", 

1220 ) 

1221 

1222 def test_remove_unconfirmed_non_existing_openid( 

1223 self, 

1224 ): # pylint: disable=invalid-name 

1225 """ 

1226 Remove unconfirmed openid that doesn't exist 

1227 """ 

1228 self.login() 

1229 url = reverse("remove_unconfirmed_openid", args=[1234]) 

1230 response = self.client.post(url, follow=True) 

1231 self.assertEqual( 

1232 response.status_code, 200, "unable to remove unconfirmed address?" 

1233 ) 

1234 self.assertEqual( 

1235 str(list(response.context[0]["messages"])[0]), 

1236 "ID does not exist", 

1237 "Removing an non existing openid should return an error message", 

1238 ) 

1239 

1240 def test_openid_redirect_view(self): 

1241 """ 

1242 Test redirect view 

1243 """ 

1244 self.test_add_openid(confirm=False) 

1245 url = reverse( 

1246 "openid_redirection", args=[self.user.unconfirmedopenid_set.first().id] 

1247 ) 

1248 response = self.client.get(url, follow=True) 

1249 self.assertEqual( 

1250 response.status_code, 200, "unable to remove unconfirmed address?" 

1251 ) 

1252 # self.assertContains( 

1253 # response, 

1254 # 'OpenID discovery failed: ', 1, 200, 

1255 # 'This request must return an error in test mode' 

1256 # ) 

1257 

1258 def test_set_photo_on_openid(self): 

1259 """ 

1260 Test the set_photo function on our ConfirmedOpenId model. 

1261 """ 

1262 self.test_add_openid() 

1263 self.test_upload_image() 

1264 self.assertIsNone(self.user.confirmedopenid_set.first().photo) 

1265 self.user.confirmedopenid_set.first().set_photo(self.user.photo_set.first()) 

1266 self.assertEqual( 

1267 self.user.confirmedopenid_set.first().photo, 

1268 self.user.photo_set.first(), 

1269 "set_photo did not work!?", 

1270 ) 

1271 

1272 def test_avatar_url_mail(self, do_upload_and_confirm=True, size=(80, 80)): 

1273 """ 

1274 Test fetching avatar via mail 

1275 """ 

1276 if do_upload_and_confirm: 

1277 self.test_upload_image() 

1278 self.test_confirm_email() 

1279 urlobj = urlsplit( 

1280 libravatar_url( 

1281 email=self.user.confirmedemail_set.first().email, 

1282 size=size[0], 

1283 ) 

1284 ) 

1285 url = f"{urlobj.path}?{urlobj.query}" 

1286 response = self.client.get(url, follow=True) 

1287 self.assertEqual(response.status_code, 200, "unable to fetch avatar?") 

1288 photodata = Image.open(BytesIO(response.content)) 

1289 self.assertEqual(photodata.size, size, "Why is this not the correct size?") 

1290 

1291 def test_avatar_url_openid(self): 

1292 """ 

1293 Test fetching avatar via openid 

1294 """ 

1295 self.test_assign_photo_to_openid() 

1296 urlobj = urlsplit( 

1297 libravatar_url( 

1298 openid=self.user.confirmedopenid_set.first().openid, 

1299 size=80, 

1300 ) 

1301 ) 

1302 url = f"{urlobj.path}?{urlobj.query}" 

1303 response = self.client.get(url, follow=True) 

1304 self.assertEqual(response.status_code, 200, "unable to fetch avatar?") 

1305 photodata = Image.open(BytesIO(response.content)) 

1306 self.assertEqual(photodata.size, (80, 80), "Why is this not the correct size?") 

1307 

1308 def test_avatar_url_non_existing_mail_digest(self): # pylint: disable=invalid-name 

1309 """ 

1310 Test fetching avatar via non existing mail digest 

1311 """ 

1312 self.test_upload_image() 

1313 self.test_confirm_email() 

1314 urlobj = urlsplit( 

1315 libravatar_url( 

1316 email=self.user.confirmedemail_set.first().email, 

1317 size=80, 

1318 ) 

1319 ) 

1320 # Simply delete it, then it's digest is 'correct', but 

1321 # the hash is no longer there 

1322 addr = self.user.confirmedemail_set.first().email 

1323 hashlib.md5(addr.strip().lower().encode("utf-8")).hexdigest() 

1324 

1325 self.user.confirmedemail_set.first().delete() 

1326 url = f"{urlobj.path}?{urlobj.query}" 

1327 self.client.get(url, follow=True) 

1328 # TODO: All these tests still fails under some circumstances - it needs further investigation 

1329 # self.assertEqual( 

1330 # response.redirect_chain[0][0], 

1331 # f"/gravatarproxy/{digest}?s=80", 

1332 # "Doesn't redirect to Gravatar?", 

1333 # ) 

1334 # self.assertEqual( 

1335 # response.redirect_chain[0][1], 302, "Doesn't redirect with 302?" 

1336 # ) 

1337 # self.assertEqual( 

1338 # response.redirect_chain[1][0], 

1339 # f"/avatar/{digest}?s=80&forcedefault=y", 

1340 # "Doesn't redirect with default forced on?", 

1341 # ) 

1342 # self.assertEqual( 

1343 # response.redirect_chain[1][1], 302, "Doesn't redirect with 302?" 

1344 # ) 

1345 # self.assertEqual( 

1346 # response.redirect_chain[2][0], 

1347 # "/static/img/nobody/80.png", 

1348 # "Doesn't redirect to static?", 

1349 # ) 

1350 # self.assertRedirects( 

1351 # response=response, 

1352 # expected_url="/static/img/nobody/80.png", 

1353 # msg_prefix="Why does this not redirect to Gravatar?", 

1354 # ) 

1355 # Eventually one should check if the data is the same 

1356 

1357 def test_avatar_url_non_existing_mail_digest_gravatarproxy_disabled( 

1358 self, 

1359 ): # pylint: disable=invalid-name 

1360 """ 

1361 Test fetching avatar via non existing mail digest 

1362 """ 

1363 self.test_upload_image() 

1364 self.test_confirm_email() 

1365 urlobj = urlsplit( 

1366 libravatar_url( 

1367 email=self.user.confirmedemail_set.first().email, 

1368 size=80, 

1369 ) 

1370 ) 

1371 # Simply delete it, then it digest is 'correct', but 

1372 # the hash is no longer there 

1373 self.user.confirmedemail_set.first().delete() 

1374 url = f"{urlobj.path}?{urlobj.query}&gravatarproxy=n" 

1375 response = self.client.get(url, follow=True) 

1376 self.assertEqual( 

1377 response.redirect_chain[0][0], 

1378 "/static/img/nobody/80.png", 

1379 "Doesn't redirect to static?", 

1380 ) 

1381 

1382 # self.assertRedirects( 

1383 # response=response, 

1384 # expected_url="/static/img/nobody/80.png", 

1385 # msg_prefix="Why does this not redirect to the default img?", 

1386 # ) 

1387 # Eventually one should check if the data is the same 

1388 

1389 def test_avatar_url_non_existing_mail_digest_w_default_mm( 

1390 self, 

1391 ): # pylint: disable=invalid-name 

1392 """ 

1393 Test fetching avatar via non existing mail digest and default 'mm' 

1394 """ 

1395 urlobj = urlsplit( 

1396 libravatar_url( 

1397 email="asdf@company.local", 

1398 size=80, 

1399 default="mm", 

1400 ) 

1401 ) 

1402 url = f"{urlobj.path}?{urlobj.query}" 

1403 self.client.get(url, follow=False) 

1404 

1405 def test_avatar_url_non_existing_mail_digest_w_default_mm_gravatarproxy_disabled( 

1406 self, 

1407 ): # pylint: disable=invalid-name 

1408 """ 

1409 Test fetching avatar via non existing mail digest and default 'mm' 

1410 """ 

1411 urlobj = urlsplit( 

1412 libravatar_url( 

1413 email="asdf@company.local", 

1414 size=80, 

1415 default="mm", 

1416 ) 

1417 ) 

1418 url = f"{urlobj.path}?{urlobj.query}&gravatarproxy=n" 

1419 response = self.client.get(url, follow=True) 

1420 self.assertEqual( 

1421 response.redirect_chain[0][0], 

1422 "/static/img/mm/80.png", 

1423 "Doesn't redirect to static?", 

1424 ) 

1425 

1426 # self.assertRedirects( 

1427 # response=response, 

1428 # expected_url="/static/img/mm/80.png", 

1429 # msg_prefix="Why does this not redirect to the default img?", 

1430 # ) 

1431 # Eventually one should check if the data is the same 

1432 

1433 def test_avatar_url_non_existing_mail_digest_wo_default( 

1434 self, 

1435 ): # pylint: disable=invalid-name 

1436 """ 

1437 Test fetching avatar via non existing mail digest and default 'mm' 

1438 """ 

1439 urlobj = urlsplit( 

1440 libravatar_url( 

1441 email="asdf@company.local", 

1442 size=80, 

1443 ) 

1444 ) 

1445 digest = hashlib.md5("asdf@company.local".lower().encode("utf-8")).hexdigest() 

1446 url = f"{urlobj.path}?{urlobj.query}" 

1447 response = self.client.get(url, follow=True) 

1448 self.assertEqual( 

1449 response.redirect_chain[0][0], 

1450 f"/gravatarproxy/{digest}?s=80", 

1451 "Doesn't redirect to Gravatar?", 

1452 ) 

1453 self.assertEqual( 

1454 response.redirect_chain[0][1], 302, "Doesn't redirect with 302?" 

1455 ) 

1456 self.assertEqual( 

1457 response.redirect_chain[1][0], 

1458 f"/avatar/{digest}?s=80&forcedefault=y", 

1459 "Doesn't redirect with default forced on?", 

1460 ) 

1461 self.assertEqual( 

1462 response.redirect_chain[1][1], 302, "Doesn't redirect with 302?" 

1463 ) 

1464 self.assertEqual( 

1465 response.redirect_chain[2][0], 

1466 "/static/img/nobody/80.png", 

1467 "Doesn't redirect to static?", 

1468 ) 

1469 

1470 # self.assertRedirects( 

1471 # response=response, 

1472 # expected_url="/static/img/nobody/80.png", 

1473 # msg_prefix="Why does this not redirect to the default img?", 

1474 # ) 

1475 # Eventually one should check if the data is the same 

1476 

1477 def test_avatar_url_non_existing_mail_digest_wo_default_gravatarproxy_disabled( 

1478 self, 

1479 ): # pylint: disable=invalid-name 

1480 """ 

1481 Test fetching avatar via non existing mail digest and default 'mm' 

1482 """ 

1483 urlobj = urlsplit( 

1484 libravatar_url( 

1485 email="asdf@company.local", 

1486 size=80, 

1487 ) 

1488 ) 

1489 url = f"{urlobj.path}?{urlobj.query}&gravatarproxy=n" 

1490 response = self.client.get(url, follow=True) 

1491 self.assertEqual( 

1492 response.redirect_chain[0][0], 

1493 "/static/img/nobody/80.png", 

1494 "Doesn't redirect to static?", 

1495 ) 

1496 

1497 # self.assertRedirects( 

1498 # response=response, 

1499 # expected_url="/static/img/nobody/80.png", 

1500 # msg_prefix="Why does this not redirect to the default img?", 

1501 # ) 

1502 # Eventually one should check if the data is the same 

1503 

1504 def test_avatar_url_default(self): # pylint: disable=invalid-name 

1505 """ 

1506 Test fetching avatar for not existing mail with default specified 

1507 """ 

1508 urlobj = urlsplit( 

1509 libravatar_url( 

1510 "xxx@xxx.xxx", 

1511 size=80, 

1512 default="/static/img/nobody.png", 

1513 ) 

1514 ) 

1515 url = f"{urlobj.path}?{urlobj.query}" 

1516 url += "&gravatarproxy=n" 

1517 response = self.client.get(url, follow=False) 

1518 self.assertEqual(response.status_code, 302, "Doesn't redirect with 302?") 

1519 self.assertEqual( 

1520 response["Location"], 

1521 "/static/img/nobody.png", 

1522 "Doesn't redirect to static img?", 

1523 ) 

1524 

1525 def test_avatar_url_default_gravatarproxy_disabled( 

1526 self, 

1527 ): # pylint: disable=invalid-name 

1528 """ 

1529 Test fetching avatar for not existing mail with default specified 

1530 """ 

1531 urlobj = urlsplit( 

1532 libravatar_url( 

1533 "xxx@xxx.xxx", 

1534 size=80, 

1535 default="/static/img/nobody.png", 

1536 ) 

1537 ) 

1538 url = f"{urlobj.path}?{urlobj.query}&gravatarproxy=n" 

1539 response = self.client.get(url, follow=True) 

1540 self.assertEqual( 

1541 response.redirect_chain[0][0], 

1542 "/static/img/nobody.png", 

1543 "Doesn't redirect to static?", 

1544 ) 

1545 

1546 def test_avatar_url_default_external(self): # pylint: disable=invalid-name 

1547 """ 

1548 Test fetching avatar for not existing mail with external default specified 

1549 This shall *not* redirect to the external site (CWE-601)! 

1550 """ 

1551 default = "http://host.tld/img.png" 

1552 size = 80 

1553 urlobj = urlsplit( 

1554 libravatar_url( 

1555 "xxx@xxx.xxx", 

1556 size=size, 

1557 default=default, 

1558 ) 

1559 ) 

1560 url = f"{urlobj.path}?{urlobj.query}" 

1561 response = self.client.get(url, follow=False) 

1562 self.assertRedirects( 

1563 response=response, 

1564 expected_url=f"/gravatarproxy/fb7a6d7f11365642d44ba66dc57df56f?s={size}", 

1565 fetch_redirect_response=False, 

1566 msg_prefix="Why does this not redirect to the default img?", 

1567 ) 

1568 

1569 def test_avatar_url_default_external_trusted(self): # pylint: disable=invalid-name 

1570 """ 

1571 Test fetching avatar for not existing mail with external default specified 

1572 """ 

1573 default = "https://ui-avatars.com/api/blah" 

1574 urlobj = urlsplit( 

1575 libravatar_url( 

1576 "xxx@xxx.xxx", 

1577 size=80, 

1578 default=default, 

1579 ) 

1580 ) 

1581 url = f"{urlobj.path}?{urlobj.query}" 

1582 response = self.client.get(url, follow=False) 

1583 self.assertRedirects( 

1584 response=response, 

1585 expected_url="/gravatarproxy/fb7a6d7f11365642d44ba66dc57df56f?s=80&default=https://ui-avatars.com/api/blah", 

1586 fetch_redirect_response=False, 

1587 msg_prefix="Why does this not redirect to the default img?", 

1588 ) 

1589 

1590 def test_avatar_url_default_external_gravatarproxy_disabled( 

1591 self, 

1592 ): # pylint: disable=invalid-name 

1593 """ 

1594 Test fetching avatar for not existing mail with external default specified 

1595 This shall *not* redirect to the external site (CWE-601)! 

1596 """ 

1597 default = "http://host.tld/img.png" 

1598 urlobj = urlsplit( 

1599 libravatar_url( 

1600 "xxx@xxx.xxx", 

1601 size=80, 

1602 default=default, 

1603 ) 

1604 ) 

1605 url = f"{urlobj.path}?{urlobj.query}&gravatarproxy=n" 

1606 response = self.client.get(url, follow=False) 

1607 self.assertRedirects( 

1608 response=response, 

1609 expected_url="/static/img/nobody/80.png", 

1610 fetch_redirect_response=False, 

1611 msg_prefix="Why does this not redirect to the default img?", 

1612 ) 

1613 

1614 def test_crop_photo(self): 

1615 """ 

1616 Test cropping photo 

1617 """ 

1618 self.test_upload_image() 

1619 self.test_confirm_email() 

1620 url = reverse("crop_photo", args=[self.user.photo_set.first().pk]) 

1621 response = self.client.post( 

1622 url, 

1623 { 

1624 "x": 10, 

1625 "y": 10, 

1626 "w": 20, 

1627 "h": 20, 

1628 }, 

1629 follow=True, 

1630 ) 

1631 self.assertEqual(response.status_code, 200, "unable to crop?") 

1632 self.test_avatar_url_mail(do_upload_and_confirm=False, size=(20, 20)) 

1633 img = Image.open(BytesIO(self.user.photo_set.first().data)) 

1634 self.assertEqual( 

1635 img.size, (20, 20), "cropped to 20x20, but resulting image isn't 20x20!?" 

1636 ) 

1637 

1638 def test_password_change_view(self): 

1639 """ 

1640 Test password change view 

1641 """ 

1642 self.login() 

1643 url = reverse("password_change") 

1644 response = self.client.get(url) 

1645 self.assertEqual( 

1646 response.status_code, 200, "unable to view password change view?" 

1647 ) 

1648 

1649 def test_password_change_view_post_wrong_old_pw(self): 

1650 """ 

1651 Test password change view post 

1652 """ 

1653 self.login() 

1654 response = self.client.post( 

1655 reverse("password_change"), 

1656 { 

1657 "old_password": "xxx", 

1658 "new_password1": self.password, 

1659 "new_password2": self.password, 

1660 }, 

1661 follow=True, 

1662 ) 

1663 

1664 self.assertContains( 

1665 response, 

1666 "Your old password was entered incorrectly. Please enter it again.", 

1667 1, 

1668 200, 

1669 "Old password as entered incorrectly, site should raise an error", 

1670 ) 

1671 

1672 def test_password_change_view_post_wrong_new_password1(self): 

1673 """ 

1674 Test password change view post 

1675 """ 

1676 self.login() 

1677 response = self.client.post( 

1678 reverse("password_change"), 

1679 { 

1680 "old_password": self.password, 

1681 "new_password1": f"{self.password}.", 

1682 "new_password2": self.password, 

1683 }, 

1684 follow=True, 

1685 ) 

1686 self.assertContains( 

1687 response, 

1688 "The two password fields did", 

1689 1, 

1690 200, 

1691 "Old password was entered incorrectly, site should raise an error", 

1692 ) 

1693 

1694 def test_password_change_view_post_wrong_new_password2(self): 

1695 """ 

1696 Test password change view post 

1697 """ 

1698 self.login() 

1699 response = self.client.post( 

1700 reverse("password_change"), 

1701 { 

1702 "old_password": self.password, 

1703 "new_password1": self.password, 

1704 "new_password2": f"{self.password}.", 

1705 }, 

1706 follow=True, 

1707 ) 

1708 

1709 self.assertContains( 

1710 response, 

1711 "The two password fields did", 

1712 1, 

1713 200, 

1714 "Old password as entered incorrectly, site should raise an error", 

1715 ) 

1716 

1717 def test_password_change_view_post_common_password(self): 

1718 """ 

1719 Test password change view post 

1720 """ 

1721 self.login() 

1722 response = self.client.post( 

1723 reverse("password_change"), 

1724 { 

1725 "old_password": self.password, 

1726 "new_password1": "Hallo", 

1727 "new_password2": "Hallo", 

1728 }, 

1729 follow=True, 

1730 ) 

1731 

1732 self.assertContains( 

1733 response, 

1734 "This password is too common.", 

1735 1, 

1736 200, 

1737 "Common password, site should raise an error", 

1738 ) 

1739 

1740 def test_profile_must_list_first_and_lastname(self): 

1741 """ 

1742 Test if profile view correctly lists first -/last name 

1743 """ 

1744 self.login() 

1745 response = self.client.get(reverse("profile")) 

1746 self.assertContains( 

1747 response, 

1748 self.first_name, 

1749 1, 

1750 200, 

1751 "First name not listed in profile page", 

1752 ) 

1753 self.assertContains( 

1754 response, 

1755 self.last_name, 

1756 1, 

1757 200, 

1758 "Last name not listed in profile page", 

1759 ) 

1760 self.assertContains( 

1761 response, 

1762 f"{self.first_name} {self.last_name}", 

1763 1, 

1764 200, 

1765 "First and last name not correctly listed in profile page", 

1766 ) 

1767 

1768 def test_password_reset_page(self): 

1769 """ 

1770 Just test if the password reset page come up correctly 

1771 """ 

1772 response = self.client.get(reverse("password_reset")) 

1773 self.assertEqual(response.status_code, 200, "no 200 ok?") 

1774 

1775 def test_password_reset_wo_mail(self): 

1776 """ 

1777 Test if the password reset doesn't error out 

1778 if the mail address doesn't exist 

1779 """ 

1780 # Avoid sending out mails 

1781 settings.EMAIL_BACKEND = "django.core.mail.backends.locmem.EmailBackend" 

1782 

1783 # Empty database / eliminate existing users 

1784 User.objects.all().delete() 

1785 url = reverse("password_reset") 

1786 response = self.client.post( 

1787 url, 

1788 { 

1789 "email": "asdf@asdf.local", 

1790 }, 

1791 follow=True, 

1792 ) 

1793 self.assertEqual(response.status_code, 200, "password reset page not working?") 

1794 self.assertEqual( 

1795 len(mail.outbox), 

1796 0, 

1797 "user does not exist, there should be no mail in the outbox!", 

1798 ) 

1799 

1800 def test_password_reset_w_mail(self): 

1801 """ 

1802 Test if the password reset works correctly with email in 

1803 User object 

1804 """ 

1805 # Avoid sending out mails 

1806 settings.EMAIL_BACKEND = "django.core.mail.backends.locmem.EmailBackend" 

1807 

1808 url = reverse("password_reset") 

1809 # Our test user doesn't have an email address by default - but we need one set 

1810 self.user.email = "asdf@asdf.local" 

1811 self.user.save() 

1812 response = self.client.post( 

1813 url, 

1814 { 

1815 "email": self.user.email, 

1816 }, 

1817 follow=True, 

1818 ) 

1819 self.assertEqual(response.status_code, 200, "password reset page not working?") 

1820 self.assertEqual( 

1821 len(mail.outbox), 1, "User exists, there should be a mail in the outbox!" 

1822 ) 

1823 self.assertEqual( 

1824 mail.outbox[0].to[0], 

1825 self.user.email, 

1826 "Sending mails to the wrong \ 

1827 mail address?", 

1828 ) 

1829 

1830 def test_password_reset_w_confirmed_mail(self): 

1831 """ 

1832 Test if the password reset works correctly with confirmed 

1833 mail 

1834 """ 

1835 # Avoid sending out mails 

1836 settings.EMAIL_BACKEND = "django.core.mail.backends.locmem.EmailBackend" 

1837 

1838 url = reverse("password_reset") 

1839 # Our test user doesn't have a confirmed mail identity - add one 

1840 self.user.confirmedemail_set.create(email="asdf@asdf.local") 

1841 self.user.save() 

1842 

1843 response = self.client.post( 

1844 url, 

1845 { 

1846 "email": self.user.confirmedemail_set.first().email, 

1847 }, 

1848 follow=True, 

1849 ) 

1850 # Since the object is touched in another process, we need to refresh it 

1851 self.user.refresh_from_db() 

1852 self.assertEqual(response.status_code, 200, "password reset page not working?") 

1853 self.assertEqual( 

1854 self.user.email, 

1855 self.user.confirmedemail_set.first().email, 

1856 "The password reset view, should have corrected this!", 

1857 ) 

1858 self.assertEqual( 

1859 len(mail.outbox), 1, "user exists, there should be a mail in the outbox!" 

1860 ) 

1861 self.assertEqual( 

1862 mail.outbox[0].to[0], 

1863 self.user.email, 

1864 "why are we sending mails to the wrong mail address?", 

1865 ) 

1866 

1867 def test_export(self): 

1868 """ 

1869 Test if export works 

1870 """ 

1871 

1872 # Create well known strings to check if export 

1873 # works as expected 

1874 self.user.confirmedemail_set.create(email="asdf@asdf.local") 

1875 self.user.confirmedopenid_set.create(openid="http://asdf.asdf.local") 

1876 self.user.save() 

1877 

1878 # Ensure we have a photo uploaded 

1879 self.test_upload_image() 

1880 

1881 self.login() 

1882 self.client.get(reverse("export")) 

1883 response = self.client.post( 

1884 reverse("export"), 

1885 {}, 

1886 follow=False, 

1887 ) 

1888 self.assertIsInstance(response.content, bytes) 

1889 fh = gzip.open(BytesIO(response.content), "rb") 

1890 content = fh.read() 

1891 fh.close() 

1892 root = xml.etree.ElementTree.fromstring(content) 

1893 self.assertEqual(root.tag, "{%s}user" % settings.SCHEMAROOT) 

1894 self.assertEqual( 

1895 root.findall("{%s}account" % settings.SCHEMAROOT)[0].items()[0][1], 

1896 self.user.username, 

1897 ) 

1898 self.assertEqual( 

1899 root.findall("{%s}account" % settings.SCHEMAROOT)[0].items()[1][1], 

1900 self.user.password, 

1901 ) 

1902 

1903 self.assertEqual( 

1904 root.findall("{%s}emails" % settings.SCHEMAROOT)[0][0].text, 

1905 self.user.confirmedemail_set.first().email, 

1906 ) 

1907 self.assertEqual( 

1908 root.findall("{%s}openids" % settings.SCHEMAROOT)[0][0].text, 

1909 self.user.confirmedopenid_set.first().openid, 

1910 ) 

1911 

1912 data = root.findall("{%s}photos" % settings.SCHEMAROOT)[0][0].text 

1913 

1914 data = data.strip("'") 

1915 data = data.strip("\\n") 

1916 data = data.lstrip("b'") 

1917 bindata = base64.decodebytes(bytes(data, "utf-8")) 

1918 image = Image.open(BytesIO(bindata)) 

1919 self.assertTrue(hasattr(image, "png")) 

1920 

1921 def test_upload_export(self): 

1922 """ 

1923 Test if uploading export works 

1924 """ 

1925 

1926 # Ensure we have data in place 

1927 self.test_export() 

1928 

1929 self.login() 

1930 self.client.get(reverse("export")) 

1931 response = self.client.post( 

1932 reverse("export"), 

1933 {}, 

1934 follow=False, 

1935 ) 

1936 self.assertIsInstance(response.content, bytes) 

1937 

1938 fh_gzip = gzip.open(BytesIO(response.content), "rb") 

1939 fh = BytesIO(response.content) 

1940 

1941 response = self._uploading_export_check( 

1942 fh_gzip, "Unable to parse file: Not a gzipped file" 

1943 ) 

1944 response = self._uploading_export_check(fh, "Choose items to be imported") 

1945 self.assertContains( 

1946 response, 

1947 "asdf@asdf.local", 

1948 2, 

1949 200, 

1950 "Upload didn't work?", 

1951 ) 

1952 

1953 def _uploading_export_check(self, fh, message): 

1954 """ 

1955 Helper function to upload an export 

1956 """ 

1957 result = self.client.post( 

1958 reverse("upload_export"), 

1959 data={"not_porn": "on", "can_distribute": "on", "export_file": fh}, 

1960 follow=True, 

1961 ) 

1962 fh.close() 

1963 self.assertEqual(result.status_code, 200, "Upload worked") 

1964 self.assertContains(result, message, 1, 200, "Upload didn't work?") 

1965 

1966 return result 

1967 

1968 def test_preferences_page(self): 

1969 """ 

1970 Test if preferences page works 

1971 """ 

1972 

1973 self.login() 

1974 self.client.get(reverse("user_preference")) 

1975 

1976 def test_delete_user(self): 

1977 """ 

1978 Test if deleting user profile works 

1979 """ 

1980 

1981 self.login() 

1982 self.client.get(reverse("delete")) 

1983 response = self.client.post( 

1984 reverse("delete"), 

1985 data={"password": self.password}, 

1986 follow=True, 

1987 ) 

1988 self.assertEqual(response.status_code, 200, "Deletion worked") 

1989 self.assertEqual(User.objects.count(), 0, "No user there any more") 

1990 

1991 def test_confirm_already_confirmed(self): 

1992 """ 

1993 Try to confirm a mail address that has been confirmed (by another user) 

1994 """ 

1995 

1996 # Add mail address (stays unconfirmed) 

1997 self.test_add_email() 

1998 

1999 # Create a second user that will conflict 

2000 user2 = User.objects.create_user( 

2001 username=f"{self.username}1", 

2002 password=self.password, 

2003 first_name=self.first_name, 

2004 last_name=self.last_name, 

2005 ) 

2006 ConfirmedEmail.objects.create( 

2007 email=self.email, 

2008 user=user2, 

2009 ) 

2010 

2011 # Just to be sure 

2012 self.assertEqual( 

2013 self.user.unconfirmedemail_set.first().email, 

2014 user2.confirmedemail_set.first().email, 

2015 "Mail not the same?", 

2016 ) 

2017 

2018 # This needs to be caught 

2019 with contextlib.suppress(AssertionError): 

2020 self.test_confirm_email() 

2021 # Request a random page, so we can access the messages 

2022 response = self.client.get(reverse("profile")) 

2023 self.assertEqual( 

2024 str(list(response.context[0]["messages"])[0]), 

2025 "This mail address has been taken already and cannot be confirmed", 

2026 "This should return an error message!", 

2027 ) 

2028 

2029 

2030class OpenIDErrorHandlingTestCase(TestCase): 

2031 """ 

2032 Test cases for OpenID error handling and error.html template coverage 

2033 """ 

2034 

2035 def setUp(self): 

2036 """Set up test user and client""" 

2037 self.username = random_string() 

2038 self.password = random_string() 

2039 self.user = User.objects.create_user( 

2040 username=self.username, 

2041 password=self.password, 

2042 ) 

2043 self.client = Client() 

2044 

2045 def login(self): 

2046 """Login as test user""" 

2047 self.client.login(username=self.username, password=self.password) 

2048 

2049 def test_openid_discovery_failure_renders_error_template(self): 

2050 """ 

2051 Test that OpenID discovery failure renders error.html template 

2052 """ 

2053 from unittest.mock import patch, MagicMock 

2054 from openid.consumer import consumer 

2055 from ivatar.ivataraccount.models import UnconfirmedOpenId 

2056 

2057 self.login() 

2058 

2059 # Create an unconfirmed OpenID 

2060 unconfirmed = UnconfirmedOpenId.objects.create( 

2061 user=self.user, 

2062 openid="http://invalid-openid-provider.example.com/", 

2063 ) 

2064 

2065 # Mock the OpenID consumer to raise DiscoveryFailure 

2066 with patch( 

2067 "ivatar.ivataraccount.views.consumer.Consumer" 

2068 ) as mock_consumer_class: 

2069 mock_consumer = MagicMock() 

2070 mock_consumer_class.return_value = mock_consumer 

2071 # Create a proper DiscoveryFailure with required http_response parameter 

2072 mock_response = MagicMock() 

2073 mock_response.status_code = 404 

2074 discovery_failure = consumer.DiscoveryFailure( 

2075 "Invalid provider", mock_response 

2076 ) 

2077 mock_consumer.begin.side_effect = discovery_failure 

2078 

2079 # Make request to openid_redirection view 

2080 response = self.client.get( 

2081 reverse("openid_redirection", args=[unconfirmed.id]), follow=True 

2082 ) 

2083 

2084 # Verify we get redirected to profile with error message 

2085 self.assertEqual(response.status_code, 200) 

2086 self.assertRedirects(response, reverse("profile")) 

2087 

2088 # Check that error message is in the response 

2089 messages = list(response.context[0]["messages"]) 

2090 self.assertTrue( 

2091 any("OpenID discovery failed" in str(msg) for msg in messages) 

2092 ) 

2093 

2094 def test_openid_confirmation_failure_renders_error_template(self): 

2095 """ 

2096 Test that OpenID confirmation failure renders error.html template 

2097 """ 

2098 from unittest.mock import patch, MagicMock 

2099 from openid.consumer import consumer 

2100 from ivatar.ivataraccount.models import UnconfirmedOpenId 

2101 

2102 self.login() 

2103 

2104 # Create an unconfirmed OpenID 

2105 unconfirmed = UnconfirmedOpenId.objects.create( 

2106 user=self.user, 

2107 openid="http://test-provider.example.com/", 

2108 ) 

2109 

2110 # Mock the OpenID consumer to return FAILURE status 

2111 with patch( 

2112 "ivatar.ivataraccount.views.consumer.Consumer" 

2113 ) as mock_consumer_class: 

2114 mock_consumer = MagicMock() 

2115 mock_consumer_class.return_value = mock_consumer 

2116 

2117 # Create a mock response with FAILURE status 

2118 mock_response = MagicMock() 

2119 mock_response.status = consumer.FAILURE 

2120 mock_response.message = "Authentication failed" 

2121 mock_consumer.complete.return_value = mock_response 

2122 

2123 # Make request to confirm_openid view 

2124 response = self.client.get( 

2125 reverse("confirm_openid", args=[unconfirmed.id]), follow=True 

2126 ) 

2127 

2128 # Verify we get redirected to profile with error message 

2129 self.assertEqual(response.status_code, 200) 

2130 self.assertRedirects(response, reverse("profile")) 

2131 

2132 # Check that error message is in the response 

2133 messages = list(response.context[0]["messages"]) 

2134 self.assertTrue(any("Confirmation failed" in str(msg) for msg in messages)) 

2135 

2136 def test_openid_cancellation_renders_error_template(self): 

2137 """ 

2138 Test that OpenID cancellation renders error.html template 

2139 """ 

2140 from unittest.mock import patch, MagicMock 

2141 from openid.consumer import consumer 

2142 from ivatar.ivataraccount.models import UnconfirmedOpenId 

2143 

2144 self.login() 

2145 

2146 # Create an unconfirmed OpenID 

2147 unconfirmed = UnconfirmedOpenId.objects.create( 

2148 user=self.user, 

2149 openid="http://test-provider.example.com/", 

2150 ) 

2151 

2152 # Mock the OpenID consumer to return CANCEL status 

2153 with patch( 

2154 "ivatar.ivataraccount.views.consumer.Consumer" 

2155 ) as mock_consumer_class: 

2156 mock_consumer = MagicMock() 

2157 mock_consumer_class.return_value = mock_consumer 

2158 

2159 # Create a mock response with CANCEL status 

2160 mock_response = MagicMock() 

2161 mock_response.status = consumer.CANCEL 

2162 mock_consumer.complete.return_value = mock_response 

2163 

2164 # Make request to confirm_openid view 

2165 response = self.client.get( 

2166 reverse("confirm_openid", args=[unconfirmed.id]), follow=True 

2167 ) 

2168 

2169 # Verify we get redirected to profile with error message 

2170 self.assertEqual(response.status_code, 200) 

2171 self.assertRedirects(response, reverse("profile")) 

2172 

2173 # Check that error message is in the response 

2174 messages = list(response.context[0]["messages"]) 

2175 self.assertTrue(any("Cancelled by user" in str(msg) for msg in messages)) 

2176 

2177 def test_openid_unknown_error_renders_error_template(self): 

2178 """ 

2179 Test that unknown OpenID verification error renders error.html template 

2180 """ 

2181 from unittest.mock import patch, MagicMock 

2182 from ivatar.ivataraccount.models import UnconfirmedOpenId 

2183 

2184 self.login() 

2185 

2186 # Create an unconfirmed OpenID 

2187 unconfirmed = UnconfirmedOpenId.objects.create( 

2188 user=self.user, 

2189 openid="http://test-provider.example.com/", 

2190 ) 

2191 

2192 # Mock the OpenID consumer to return unknown status 

2193 with patch( 

2194 "ivatar.ivataraccount.views.consumer.Consumer" 

2195 ) as mock_consumer_class: 

2196 mock_consumer = MagicMock() 

2197 mock_consumer_class.return_value = mock_consumer 

2198 

2199 # Create a mock response with unknown status 

2200 mock_response = MagicMock() 

2201 mock_response.status = "UNKNOWN_STATUS" 

2202 mock_consumer.complete.return_value = mock_response 

2203 

2204 # Make request to confirm_openid view 

2205 response = self.client.get( 

2206 reverse("confirm_openid", args=[unconfirmed.id]), follow=True 

2207 ) 

2208 

2209 # Verify we get redirected to profile with error message 

2210 self.assertEqual(response.status_code, 200) 

2211 self.assertRedirects(response, reverse("profile")) 

2212 

2213 # Check that error message is in the response 

2214 messages = list(response.context[0]["messages"]) 

2215 self.assertTrue( 

2216 any("Unknown verification error" in str(msg) for msg in messages) 

2217 ) 

2218 

2219 def test_openid_nonexistent_id_error(self): 

2220 """ 

2221 Test that accessing non-existent OpenID ID shows error message 

2222 """ 

2223 self.login() 

2224 

2225 # Try to access a non-existent OpenID ID 

2226 response = self.client.get( 

2227 reverse("openid_redirection", args=[99999]), follow=True 

2228 ) 

2229 

2230 # Verify we get redirected to profile with error message 

2231 self.assertEqual(response.status_code, 200) 

2232 self.assertRedirects(response, reverse("profile")) 

2233 

2234 # Check that error message is in the response 

2235 messages = list(response.context[0]["messages"]) 

2236 self.assertTrue(any("ID does not exist" in str(msg) for msg in messages)) 

2237 

2238 def test_django_openid_auth_failure_template_coverage(self): 

2239 """ 

2240 Test that django-openid-auth failure template uses error.html 

2241 This test verifies the OpenID login page renders correctly 

2242 """ 

2243 # Try to access the OpenID login page 

2244 response = self.client.get(reverse("openid-login")) 

2245 self.assertEqual(response.status_code, 200) 

2246 

2247 # The login page should render successfully 

2248 self.assertContains(response, "OpenID Login") 

2249 

2250 def test_error_template_direct_rendering(self): 

2251 """ 

2252 Test error.html template directly to ensure it renders correctly 

2253 """ 

2254 from django.test import RequestFactory 

2255 from django.template import Context, Template 

2256 from django.contrib.auth.models import AnonymousUser 

2257 

2258 # Test with authenticated user 

2259 factory = RequestFactory() 

2260 request = factory.get("/") 

2261 request.user = self.user 

2262 

2263 # Test template with error message 

2264 template_content = """ 

2265 {% extends 'error.html' %} 

2266 {% load i18n %} 

2267 {% block errormessage %} 

2268 {% trans 'Test error message:' %} {{ errormessage }} 

2269 {% endblock errormessage %} 

2270 """ 

2271 

2272 template = Template(template_content) 

2273 context = Context( 

2274 { 

2275 "request": request, 

2276 "errormessage": "This is a test error", 

2277 "user": self.user, 

2278 } 

2279 ) 

2280 

2281 rendered = template.render(context) 

2282 

2283 # Verify the template renders without errors 

2284 self.assertIn("Error!", rendered) 

2285 self.assertIn("This is a test error", rendered) 

2286 # Check for the profile link in the navbar (not in the backlink block) 

2287 self.assertIn("/accounts/profile/", rendered) 

2288 

2289 # Test with anonymous user 

2290 request.user = AnonymousUser() 

2291 context = Context( 

2292 { 

2293 "request": request, 

2294 "errormessage": "This is a test error", 

2295 "user": AnonymousUser(), 

2296 } 

2297 ) 

2298 

2299 rendered = template.render(context) 

2300 

2301 # Verify the template renders without errors for anonymous users 

2302 self.assertIn("Error!", rendered) 

2303 self.assertIn("This is a test error", rendered) 

2304 # Should not contain profile link for anonymous users 

2305 self.assertNotIn("/accounts/profile/", rendered) 

2306 

2307 def test_openid_failure_template_inheritance(self): 

2308 """ 

2309 Test that openid/failure.html properly extends error.html 

2310 """ 

2311 from django.test import RequestFactory 

2312 from django.template import Context, Template 

2313 

2314 factory = RequestFactory() 

2315 request = factory.get("/") 

2316 request.user = self.user 

2317 

2318 # Test the openid/failure.html template 

2319 template_content = """ 

2320 {% extends 'error.html' %} 

2321 {% load i18n %} 

2322 {% block errormessage %} 

2323 {% trans 'OpenID error:' %} {{ message }} 

2324 {% endblock errormessage %} 

2325 """ 

2326 

2327 template = Template(template_content) 

2328 context = Context( 

2329 { 

2330 "request": request, 

2331 "message": "Authentication failed", 

2332 "user": self.user, 

2333 } 

2334 ) 

2335 

2336 rendered = template.render(context) 

2337 

2338 # Verify the template renders correctly 

2339 self.assertIn("Error!", rendered) 

2340 self.assertIn("OpenID error:", rendered) 

2341 self.assertIn("Authentication failed", rendered) 

2342 # Check for the profile link in the navbar (not in the backlink block) 

2343 self.assertIn("/accounts/profile/", rendered)