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

640 statements  

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

1# -*- coding: utf-8 -*- 

2""" 

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

4""" 

5 

6import contextlib 

7 

8# pylint: disable=too-many-lines 

9from urllib.parse import urlsplit 

10from io import BytesIO 

11from contextlib import suppress 

12import io 

13import os 

14import gzip 

15import xml.etree.ElementTree 

16import base64 

17import django 

18from django.test import TestCase 

19from django.test import Client 

20from django.test import override_settings 

21from django.urls import reverse 

22from django.core import mail 

23from django.core.cache import caches 

24from django.contrib.auth.models import User 

25from django.contrib.auth import authenticate 

26import hashlib 

27 

28from libravatar import libravatar_url 

29 

30from PIL import Image 

31 

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

33django.setup() 

34 

35# pylint: disable=wrong-import-position 

36from ivatar import settings 

37from ivatar.ivataraccount.forms import MAX_NUM_UNCONFIRMED_EMAILS_DEFAULT 

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

39from ivatar.utils import random_string 

40 

41# pylint: enable=wrong-import-position 

42 

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

44 

45 

46@override_settings() 

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

48 """ 

49 Main test class 

50 """ 

51 

52 client = Client() 

53 user = None 

54 username = random_string() 

55 password = random_string() 

56 email = "%s@%s.org" % (username, random_string()) 

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

58 openid = "http://%s.%s.%s/" % (username, random_string(), "org") 

59 first_name = random_string() 

60 last_name = random_string() 

61 

62 def login(self): 

63 """ 

64 Login as user 

65 """ 

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

67 

68 def setUp(self): 

69 """ 

70 Prepare for tests. 

71 - Create user 

72 """ 

73 self.user = User.objects.create_user( 

74 username=self.username, 

75 password=self.password, 

76 first_name=self.first_name, 

77 last_name=self.last_name, 

78 ) 

79 # Disable caching 

80 settings.CACHES["default"] = { 

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

82 } 

83 caches._settings = None 

84 with suppress(AttributeError): 

85 # clear the existing cache connection 

86 delattr(caches._connections, "default") 

87 

88 def test_new_user(self): 

89 """ 

90 Create a new user 

91 """ 

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

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

94 # Empty database / eliminate existing users 

95 User.objects.all().delete() 

96 url = reverse("new_account") 

97 response = self.client.post( 

98 url, 

99 { 

100 "username": self.username, 

101 "password1": self.password, 

102 "password2": self.password, 

103 }, 

104 follow=True, 

105 ) 

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

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

108 

109 def test_new_user_twice(self): 

110 """ 

111 Try to create a user that already exists 

112 """ 

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

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

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

116 url = reverse("new_account") 

117 response = self.client.post( 

118 url, 

119 { 

120 "username": self.username, 

121 "password1": self.password, 

122 "password2": self.password, 

123 }, 

124 follow=True, 

125 ) 

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

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

128 self.assertContains( 

129 response, 

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

131 1, 

132 200, 

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

134 ) 

135 

136 def test_set_password(self): 

137 """ 

138 Change the user password 

139 """ 

140 self.login() 

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

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

143 self.password = random_string() 

144 response = self.client.post( 

145 reverse("password_set"), 

146 { 

147 "new_password1": self.password, 

148 "new_password2": self.password, 

149 }, 

150 follow=True, 

151 ) 

152 

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

154 self.assertEqual( 

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

156 "password changed successfully - please login again", 

157 "password change not successful?", 

158 ) 

159 

160 self.assertIsNotNone( 

161 authenticate( 

162 username=self.username, 

163 password=self.password, 

164 ), 

165 "cannot authenticate with new password!?", 

166 ) 

167 

168 self.login() 

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

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

171 

172 def test_add_email(self): 

173 """ 

174 Add e-mail address 

175 """ 

176 self.login() 

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

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

179 # Avoid sending out mails 

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

181 response = self.client.post( 

182 reverse("add_email"), 

183 { 

184 "email": self.email, 

185 }, 

186 follow=True, 

187 ) 

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

189 self.assertEqual( 

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

191 1, 

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

193 ) 

194 self.assertEqual( 

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

196 "Address added successfully", 

197 "unable to add mail address?", 

198 ) 

199 

200 def test_confirm_email(self): 

201 """ 

202 Confirm unconfirmed email 

203 """ 

204 self.login() 

205 # Avoid sending out mails 

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

207 response = self.client.post( 

208 reverse("add_email"), 

209 { 

210 "email": self.email, 

211 }, 

212 follow=True, 

213 ) 

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

215 verification_key = unconfirmed.verification_key 

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

217 response = self.client.get(url) 

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

219 

220 self.assertEqual( 

221 self.user.unconfirmedemail_set.count(), 

222 0, 

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

224 ) 

225 self.assertEqual( 

226 self.user.confirmedemail_set.count(), 

227 1, 

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

229 ) 

230 

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

232 """ 

233 Test confirmation with invalid auth key 

234 """ 

235 self.login() 

236 # Avoid sending out mails 

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

238 response = self.client.post( 

239 reverse("add_email"), 

240 { 

241 "email": self.email, 

242 }, 

243 follow=True, 

244 ) 

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

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

247 self.assertEqual( 

248 response.status_code, 

249 200, 

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

251 ) 

252 self.assertEqual( 

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

254 "Verification key incorrect", 

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

256 ) 

257 

258 def test_confirm_email_w_non_existing_auth_key( 

259 self, 

260 ): # pylint: disable=invalid-name 

261 """ 

262 Test confirmation with non existing auth key 

263 """ 

264 self.login() 

265 # Avoid sending out mails 

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

267 response = self.client.post( 

268 reverse("add_email"), 

269 { 

270 "email": self.email, 

271 }, 

272 follow=True, 

273 ) 

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

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

276 self.assertEqual( 

277 response.status_code, 

278 200, 

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

280 ) 

281 self.assertEqual( 

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

283 "Verification key does not exist", 

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

285 ) 

286 

287 def test_remove_confirmed_email(self): 

288 """ 

289 Remove confirmed email 

290 """ 

291 self.login() 

292 # Avoid sending out mails 

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

294 response = self.client.post( 

295 reverse("add_email"), 

296 { 

297 "email": self.email, 

298 }, 

299 ) # Create test address 

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

301 verification_key = unconfirmed.verification_key 

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

303 self.client.get(url) # Confirm 

304 url = reverse( 

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

306 ) 

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

308 self.assertEqual( 

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

310 ) 

311 self.assertEqual( 

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

313 "Address removed", 

314 "Removing confirmed mail does not work?", 

315 ) 

316 

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

318 """ 

319 Try removing confirmed mail that doesn't exist 

320 """ 

321 self.login() 

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

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

324 self.assertEqual( 

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

326 ) 

327 self.assertEqual( 

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

329 "Address does not exist", 

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

331 error message!", 

332 ) 

333 

334 def test_remove_unconfirmed_email(self): 

335 """ 

336 Remove unconfirmed email 

337 """ 

338 self.login() 

339 # Avoid sending out mails 

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

341 response = self.client.post( 

342 reverse("add_email"), 

343 { 

344 "email": self.email, 

345 }, 

346 ) # Create test address 

347 url = reverse( 

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

349 ) 

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

351 self.assertEqual( 

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

353 ) 

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

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

356 self.assertEqual( 

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

358 "Address removed", 

359 "Removing unconfirmed mail does not work?", 

360 ) 

361 

362 def test_gravatar_photo_import(self): 

363 """ 

364 import photo from Gravatar (with known mail address) 

365 """ 

366 self.login() 

367 # Avoid sending out mails 

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

369 response = self.client.post( 

370 reverse("add_email"), 

371 { 

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

373 }, 

374 ) # Create test address 

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

376 verification_key = unconfirmed.verification_key 

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

378 self.client.get(url) # Confirm 

379 

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

381 response = self.client.post( 

382 url, 

383 { 

384 "photo_Gravatar": 1, 

385 }, 

386 follow=True, 

387 ) 

388 self.assertEqual( 

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

390 ) 

391 self.assertEqual( 

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

393 "Gravatar image successfully imported", 

394 "Importing gravatar photo did not work?", 

395 ) 

396 self.assertIsInstance( 

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

398 ) 

399 

400 def test_raw_image(self): 

401 """ 

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

403 """ 

404 

405 # Ensure we have a photo 

406 self.test_gravatar_photo_import() 

407 response = self.client.get( 

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

409 ) 

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

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

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

413 

414 self.assertEqual( 

415 bytes(response.content), 

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

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

418 read it directly from the DB", 

419 ) 

420 

421 def test_delete_photo(self): 

422 """ 

423 test deleting the photo 

424 """ 

425 

426 # Ensure we have a photo 

427 self.test_gravatar_photo_import() 

428 

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

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

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

432 self.assertEqual( 

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

434 "Photo deleted successfully", 

435 "Photo deletion did not work?", 

436 ) 

437 

438 def test_delete_non_existing_photo(self): 

439 """ 

440 test deleting the photo 

441 """ 

442 

443 # Ensure we have a photo 

444 self.test_gravatar_photo_import() 

445 

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

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

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

449 self.assertEqual( 

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

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

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

453 ) 

454 

455 def test_too_many_unconfirmed_email(self): 

456 """ 

457 Request too many unconfirmed email addresses, make sure we 

458 cannot add more 

459 """ 

460 self.login() 

461 # Avoid sending out mails 

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

463 

464 max_num_unconfirmed = getattr( 

465 settings, "MAX_NUM_UNCONFIRMED_EMAILS", MAX_NUM_UNCONFIRMED_EMAILS_DEFAULT 

466 ) 

467 

468 for i in range(max_num_unconfirmed + 1): 

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

470 reverse("add_email"), 

471 { 

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

473 }, 

474 follow=True, 

475 ) # Create test addresses + 1 too much 

476 return self._check_form_validity( 

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

478 ) 

479 

480 def test_add_mail_address_twice(self): 

481 """ 

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

483 having the same address twice 

484 """ 

485 self.login() 

486 # Avoid sending out mails 

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

488 

489 for _ in range(2): 

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

491 reverse("add_email"), 

492 { 

493 "email": self.email, 

494 }, 

495 follow=True, 

496 ) 

497 return self._check_form_validity( 

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

499 ) 

500 

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

502 """ 

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

504 """ 

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

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

507 self.test_confirm_email() 

508 

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

510 reverse("add_email"), 

511 { 

512 "email": self.email, 

513 }, 

514 follow=True, 

515 ) 

516 

517 return self._check_form_validity( 

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

519 ) 

520 

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

522 """ 

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

524 """ 

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

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

527 self.test_confirm_email() 

528 

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

530 # in order to test the correct error message 

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

532 confirmedemail = ConfirmedEmail.objects.last() 

533 confirmedemail.user = otheruser 

534 confirmedemail.save() 

535 

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

537 reverse("add_email"), 

538 { 

539 "email": self.email, 

540 }, 

541 follow=True, 

542 ) 

543 

544 return self._check_form_validity( 

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

546 ) 

547 

548 def test_remove_unconfirmed_non_existing_email( 

549 self, 

550 ): # pylint: disable=invalid-name 

551 """ 

552 Remove unconfirmed email that doesn't exist 

553 """ 

554 self.login() 

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

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

557 self.assertEqual( 

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

559 ) 

560 self.assertEqual( 

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

562 "Address does not exist", 

563 "Removing address that does not\ 

564 exist, should return error message!", 

565 ) 

566 

567 def test_upload_image( 

568 self, test_only_one=True 

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

570 """ 

571 Test uploading image 

572 """ 

573 self.login() 

574 url = reverse("upload_photo") 

575 # rb => Read binary 

576 with open(TEST_IMAGE_FILE, "rb") as photo: 

577 response = self.client.post( 

578 url, 

579 { 

580 "photo": photo, 

581 "not_porn": True, 

582 "can_distribute": True, 

583 }, 

584 follow=True, 

585 ) 

586 if not test_only_one: 

587 return response 

588 self.assertEqual( 

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

590 ) 

591 self.assertEqual( 

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

593 "Successfully uploaded", 

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

595 ) 

596 self.assertEqual( 

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

598 "png", 

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

600 ) 

601 

602 def test_upload_too_many_images(self): 

603 """ 

604 Test uploading more images than we are allowed 

605 """ 

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

607 response = self.test_upload_image(test_only_one=False) 

608 self.assertEqual( 

609 self.user.photo_set.count(), 

610 settings.MAX_NUM_PHOTOS, 

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

612 ) 

613 # Take care we need to check the last message 

614 self.assertEqual( 

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

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

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

618 ) 

619 

620 def test_upload_too_big_image(self): 

621 """ 

622 Test uploading image that is too big 

623 """ 

624 self.login() 

625 url = reverse("upload_photo") 

626 # rb => Read binary 

627 response = self.client.post( 

628 url, 

629 { 

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

631 "not_porn": True, 

632 "can_distribute": True, 

633 }, 

634 follow=True, 

635 ) 

636 self.assertEqual( 

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

638 "Image too big", 

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

640 ) 

641 

642 def test_upload_invalid_image(self): 

643 """ 

644 Test invalid image data 

645 """ 

646 self.login() 

647 url = reverse("upload_photo") 

648 # rb => Read binary 

649 response = self.client.post( 

650 url, 

651 { 

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

653 "not_porn": True, 

654 "can_distribute": True, 

655 }, 

656 follow=True, 

657 ) 

658 self.assertEqual( 

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

660 "Invalid Format", 

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

662 ) 

663 

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

665 """ 

666 Test if invalid format is correctly detected 

667 """ 

668 self.login() 

669 url = reverse("upload_photo") 

670 # rb => Read binary 

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

672 response = self.client.post( 

673 url, 

674 { 

675 "photo": photo, 

676 "not_porn": True, 

677 "can_distribute": True, 

678 }, 

679 follow=True, 

680 ) 

681 self.assertEqual( 

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

683 "Invalid Format", 

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

685 ) 

686 

687 def test_upload_gif_image(self): 

688 """ 

689 Test if gif is correctly detected and can be viewed 

690 """ 

691 self._extracted_from_test_upload_webp_image_5( 

692 "broken.gif", 

693 "GIF upload failed?!", 

694 "gif", 

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

696 ) 

697 

698 def test_upload_jpg_image(self): 

699 """ 

700 Test if jpg is correctly detected and can be viewed 

701 """ 

702 self._extracted_from_test_upload_webp_image_5( 

703 "broken.jpg", 

704 "JPEG upload failed?!", 

705 "jpg", 

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

707 ) 

708 

709 def test_upload_webp_image(self): 

710 """ 

711 Test if webp is correctly detected and can be viewed 

712 """ 

713 self._extracted_from_test_upload_webp_image_5( 

714 "broken.webp", 

715 "WEBP upload failed?!", 

716 "webp", 

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

718 ) 

719 

720 def _extracted_from_test_upload_webp_image_5( 

721 self, filename, message1, format, message2 

722 ): 

723 """ 

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

725 """ 

726 self.login() 

727 url = reverse("upload_photo") 

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

729 response = self.client.post( 

730 url, 

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

732 follow=True, 

733 ) 

734 self.assertEqual( 

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

736 "Successfully uploaded", 

737 message1, 

738 ) 

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

740 self.test_confirm_email() 

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

742 urlobj = urlsplit( 

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

744 ) 

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

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

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

748 

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

750 """ 

751 Test if unsupported format is correctly detected 

752 """ 

753 self.login() 

754 url = reverse("upload_photo") 

755 # rb => Read binary 

756 with open( 

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

758 ) as photo: 

759 response = self.client.post( 

760 url, 

761 { 

762 "photo": photo, 

763 "not_porn": True, 

764 "can_distribute": True, 

765 }, 

766 follow=True, 

767 ) 

768 self.assertEqual( 

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

770 "Invalid Format", 

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

772 ) 

773 

774 def test_automatic_photo_assign_to_confirmed_mail( 

775 self, 

776 ): # pylint: disable=invalid-name 

777 """ 

778 Test if automatic assignment of photo works 

779 """ 

780 self.test_upload_image() 

781 self.test_confirm_email() 

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

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

784 

785 def test_assign_photo_to_email(self): 

786 """ 

787 Test assigning photo to mail address 

788 """ 

789 self.test_confirm_email() 

790 self.test_upload_image() 

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

792 url = reverse( 

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

794 ) 

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

796 self.client.get( 

797 url, 

798 { 

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

800 }, 

801 ) 

802 # The post is for the actual assigning 

803 response = self.client.post( 

804 url, 

805 { 

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

807 }, 

808 follow=True, 

809 ) 

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

811 self.assertEqual( 

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

813 ) 

814 

815 def test_no_photo_to_email(self): 

816 """ 

817 Test assigning photo to mail address 

818 """ 

819 self.test_confirm_email() 

820 url = reverse( 

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

822 ) 

823 response = self.client.post( 

824 url, 

825 { 

826 "photoNone": True, 

827 }, 

828 follow=True, 

829 ) 

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

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

832 

833 def test_assign_photo_to_email_wo_photo_for_testing_template( 

834 self, 

835 ): # pylint: disable=invalid-name 

836 """ 

837 Test assign photo template 

838 """ 

839 self.test_confirm_email() 

840 url = reverse( 

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

842 ) 

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

844 response = self.client.get(url) 

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

846 

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

848 """ 

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

850 """ 

851 self.test_confirm_email() 

852 self.test_upload_image() 

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

854 url = reverse( 

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

856 ) 

857 response = self.client.post( 

858 url, 

859 { 

860 "photo_id": 1234, 

861 }, 

862 follow=True, 

863 ) 

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

865 self.assertEqual( 

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

867 "Photo does not exist", 

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

869 ) 

870 

871 def test_post_to_assign_photo_without_photo_id( 

872 self, 

873 ): # pylint: disable=invalid-name 

874 """ 

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

876 """ 

877 self.test_confirm_email() 

878 self.test_upload_image() 

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

880 url = reverse( 

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

882 ) 

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

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

885 self.assertEqual( 

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

887 "Invalid request [photo_id] missing", 

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

889 ) 

890 

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

892 """ 

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

894 the correct error message 

895 """ 

896 self.test_upload_image() 

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

898 response = self.client.post( 

899 url, 

900 { 

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

902 }, 

903 follow=True, 

904 ) 

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

906 self.assertEqual( 

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

908 "Invalid request", 

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

910 ) 

911 

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

913 """ 

914 Test if import with non existing mail address returns 

915 the correct error message 

916 """ 

917 self.login() 

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

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

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

921 self.assertEqual( 

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

923 "Address does not exist", 

924 "Import photo with non existing mail id,\ 

925 does not return error message?", 

926 ) 

927 

928 def test_import_nothing(self): 

929 """ 

930 Test if importing nothing causes the correct 

931 error message to be returned 

932 """ 

933 self.test_confirm_email() 

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

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

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

937 self.assertEqual( 

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

939 "Nothing importable", 

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

941 should return an error message!", 

942 ) 

943 

944 def _manual_confirm(self): 

945 """ 

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

947 """ 

948 # Manual confirm, since testing is _really_ hard! 

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

950 confirmed = ConfirmedOpenId() 

951 confirmed.user = unconfirmed.user 

952 confirmed.ip_address = "127.0.0.1" 

953 confirmed.openid = unconfirmed.openid 

954 confirmed.save() 

955 unconfirmed.delete() 

956 

957 def test_add_openid(self, confirm=True): 

958 """ 

959 Test if adding an OpenID works 

960 """ 

961 

962 self.login() 

963 # Get page 

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

965 self.assertEqual( 

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

967 ) 

968 

969 response = self.client.post( 

970 reverse("add_openid"), 

971 { 

972 "openid": self.openid, 

973 }, 

974 ) 

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

976 

977 if confirm: 

978 self._manual_confirm() 

979 

980 def test_add_openid_twice(self): 

981 """ 

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

983 """ 

984 self.login() 

985 # Get page 

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

987 self.assertEqual( 

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

989 ) 

990 

991 response = self.client.post( 

992 reverse("add_openid"), 

993 { 

994 "openid": self.openid, 

995 }, 

996 ) 

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

998 response = self.client.post( 

999 reverse("add_openid"), 

1000 { 

1001 "openid": self.openid, 

1002 }, 

1003 follow=True, 

1004 ) 

1005 self.assertEqual( 

1006 self.user.unconfirmedopenid_set.count(), 

1007 1, 

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

1009 ) 

1010 

1011 self._check_form_validity( 

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

1013 ) 

1014 # Manual confirm, since testing is _really_ hard! 

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

1016 confirmed = ConfirmedOpenId() 

1017 confirmed.user = unconfirmed.user 

1018 confirmed.ip_address = "127.0.0.1" 

1019 confirmed.openid = unconfirmed.openid 

1020 confirmed.save() 

1021 unconfirmed.delete() 

1022 

1023 # Try adding it again - although already confirmed 

1024 response = self.client.post( 

1025 reverse("add_openid"), 

1026 { 

1027 "openid": self.openid, 

1028 }, 

1029 follow=True, 

1030 ) 

1031 

1032 return self._check_form_validity( 

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

1034 ) 

1035 

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

1037 """ 

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

1039 deduplicating code 

1040 """ 

1041 

1042 self.assertTrue( 

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

1044 ) 

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

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

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

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

1049 return result 

1050 

1051 def test_assign_photo_to_openid(self): 

1052 """ 

1053 Test assignment of photo to openid 

1054 """ 

1055 self.test_add_openid() 

1056 self.test_upload_image() 

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

1058 url = reverse( 

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

1060 ) 

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

1062 self.client.get( 

1063 url, 

1064 { 

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

1066 }, 

1067 ) 

1068 # The post is for the actual assigning 

1069 response = self.client.post( 

1070 url, 

1071 { 

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

1073 }, 

1074 follow=True, 

1075 ) 

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

1077 self.assertEqual( 

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

1079 ) 

1080 

1081 def test_assign_photo_to_openid_wo_photo_for_testing_template( 

1082 self, 

1083 ): # pylint: disable=invalid-name 

1084 """ 

1085 Test openid/photo assignment template 

1086 """ 

1087 self.test_add_openid() 

1088 url = reverse( 

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

1090 ) 

1091 response = self.client.get(url) 

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

1093 

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

1095 """ 

1096 Test assigning invalid photo to openid returns 

1097 the correct error message 

1098 """ 

1099 self.test_add_openid() 

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

1101 url = reverse( 

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

1103 ) 

1104 response = self.client.post( 

1105 url, 

1106 { 

1107 "photo_id": 1234, 

1108 }, 

1109 follow=True, 

1110 ) 

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

1112 self.assertEqual( 

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

1114 "Photo does not exist", 

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

1116 ) 

1117 

1118 def test_post_to_assign_photo_openid_without_photo_id( 

1119 self, 

1120 ): # pylint: disable=invalid-name 

1121 """ 

1122 Test POST assign photo to openid without photo id 

1123 returns the correct error message 

1124 """ 

1125 self.test_add_openid() 

1126 self.test_upload_image() 

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

1128 url = reverse( 

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

1130 ) 

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

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

1133 self.assertEqual( 

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

1135 "Invalid request [photo_id] missing", 

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

1137 ) 

1138 

1139 def test_assign_photo_to_openid_non_existing_openid( 

1140 self, 

1141 ): # pylint: disable=invalid-name 

1142 """ 

1143 Test assigning photo to openid that doesn't exist 

1144 returns the correct error message. 

1145 """ 

1146 self.test_upload_image() 

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

1148 response = self.client.post( 

1149 url, 

1150 { 

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

1152 }, 

1153 follow=True, 

1154 ) 

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

1156 self.assertEqual( 

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

1158 "Invalid request", 

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

1160 ) 

1161 

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

1163 """ 

1164 Remove confirmed openid 

1165 """ 

1166 self.test_add_openid() 

1167 url = reverse( 

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

1169 ) 

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

1171 self.assertEqual( 

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

1173 ) 

1174 self.assertEqual( 

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

1176 "ID removed", 

1177 "Removing confirmed openid does not work?", 

1178 ) 

1179 

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

1181 """ 

1182 Try removing confirmed openid that doesn't exist 

1183 """ 

1184 self.login() 

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

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

1187 self.assertEqual( 

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

1189 ) 

1190 self.assertEqual( 

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

1192 "ID does not exist", 

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

1194 error message!", 

1195 ) 

1196 

1197 def test_remove_unconfirmed_openid(self): 

1198 """ 

1199 Remove unconfirmed openid 

1200 """ 

1201 self.test_add_openid(confirm=False) 

1202 url = reverse( 

1203 "remove_unconfirmed_openid", 

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

1205 ) 

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

1207 self.assertEqual( 

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

1209 ) 

1210 self.assertEqual( 

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

1212 "ID removed", 

1213 "Removing unconfirmed mail does not work?", 

1214 ) 

1215 

1216 def test_remove_unconfirmed_non_existing_openid( 

1217 self, 

1218 ): # pylint: disable=invalid-name 

1219 """ 

1220 Remove unconfirmed openid that doesn't exist 

1221 """ 

1222 self.login() 

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

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

1225 self.assertEqual( 

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

1227 ) 

1228 self.assertEqual( 

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

1230 "ID does not exist", 

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

1232 ) 

1233 

1234 def test_openid_redirect_view(self): 

1235 """ 

1236 Test redirect view 

1237 """ 

1238 self.test_add_openid(confirm=False) 

1239 url = reverse( 

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

1241 ) 

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

1243 self.assertEqual( 

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

1245 ) 

1246 # self.assertContains( 

1247 # response, 

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

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

1250 # ) 

1251 

1252 def test_set_photo_on_openid(self): 

1253 """ 

1254 Test the set_photo function on our ConfirmedOpenId model. 

1255 """ 

1256 self.test_add_openid() 

1257 self.test_upload_image() 

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

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

1260 self.assertEqual( 

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

1262 self.user.photo_set.first(), 

1263 "set_photo did not work!?", 

1264 ) 

1265 

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

1267 """ 

1268 Test fetching avatar via mail 

1269 """ 

1270 if do_upload_and_confirm: 

1271 self.test_upload_image() 

1272 self.test_confirm_email() 

1273 urlobj = urlsplit( 

1274 libravatar_url( 

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

1276 size=size[0], 

1277 ) 

1278 ) 

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

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

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

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

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

1284 

1285 def test_avatar_url_openid(self): 

1286 """ 

1287 Test fetching avatar via openid 

1288 """ 

1289 self.test_assign_photo_to_openid() 

1290 urlobj = urlsplit( 

1291 libravatar_url( 

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

1293 size=80, 

1294 ) 

1295 ) 

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

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

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

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

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

1301 

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

1303 """ 

1304 Test fetching avatar via non existing mail digest 

1305 """ 

1306 self.test_upload_image() 

1307 self.test_confirm_email() 

1308 urlobj = urlsplit( 

1309 libravatar_url( 

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

1311 size=80, 

1312 ) 

1313 ) 

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

1315 # the hash is no longer there 

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

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

1318 

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

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

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

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

1323 # self.assertEqual( 

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

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

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

1327 # ) 

1328 # self.assertEqual( 

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

1330 # ) 

1331 # self.assertEqual( 

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

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

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

1335 # ) 

1336 # self.assertEqual( 

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

1338 # ) 

1339 # self.assertEqual( 

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

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

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

1343 # ) 

1344 # self.assertRedirects( 

1345 # response=response, 

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

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

1348 # ) 

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

1350 

1351 def test_avatar_url_non_existing_mail_digest_gravatarproxy_disabled( 

1352 self, 

1353 ): # pylint: disable=invalid-name 

1354 """ 

1355 Test fetching avatar via non existing mail digest 

1356 """ 

1357 self.test_upload_image() 

1358 self.test_confirm_email() 

1359 urlobj = urlsplit( 

1360 libravatar_url( 

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

1362 size=80, 

1363 ) 

1364 ) 

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

1366 # the hash is no longer there 

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

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

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

1370 self.assertEqual( 

1371 response.redirect_chain[0][0], 

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

1373 "Doesn't redirect to static?", 

1374 ) 

1375 

1376 # self.assertRedirects( 

1377 # response=response, 

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

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

1380 # ) 

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

1382 

1383 def test_avatar_url_non_existing_mail_digest_w_default_mm( 

1384 self, 

1385 ): # pylint: disable=invalid-name 

1386 """ 

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

1388 """ 

1389 urlobj = urlsplit( 

1390 libravatar_url( 

1391 email="asdf@company.local", 

1392 size=80, 

1393 default="mm", 

1394 ) 

1395 ) 

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

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

1398 

1399 def test_avatar_url_non_existing_mail_digest_w_default_mm_gravatarproxy_disabled( 

1400 self, 

1401 ): # pylint: disable=invalid-name 

1402 """ 

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

1404 """ 

1405 urlobj = urlsplit( 

1406 libravatar_url( 

1407 email="asdf@company.local", 

1408 size=80, 

1409 default="mm", 

1410 ) 

1411 ) 

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

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

1414 self.assertEqual( 

1415 response.redirect_chain[0][0], 

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

1417 "Doesn't redirect to static?", 

1418 ) 

1419 

1420 # self.assertRedirects( 

1421 # response=response, 

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

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

1424 # ) 

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

1426 

1427 def test_avatar_url_non_existing_mail_digest_wo_default( 

1428 self, 

1429 ): # pylint: disable=invalid-name 

1430 """ 

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

1432 """ 

1433 urlobj = urlsplit( 

1434 libravatar_url( 

1435 email="asdf@company.local", 

1436 size=80, 

1437 ) 

1438 ) 

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

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

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

1442 self.assertEqual( 

1443 response.redirect_chain[0][0], 

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

1445 "Doesn't redirect to Gravatar?", 

1446 ) 

1447 self.assertEqual( 

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

1449 ) 

1450 self.assertEqual( 

1451 response.redirect_chain[1][0], 

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

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

1454 ) 

1455 self.assertEqual( 

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

1457 ) 

1458 self.assertEqual( 

1459 response.redirect_chain[2][0], 

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

1461 "Doesn't redirect to static?", 

1462 ) 

1463 

1464 # self.assertRedirects( 

1465 # response=response, 

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

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

1468 # ) 

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

1470 

1471 def test_avatar_url_non_existing_mail_digest_wo_default_gravatarproxy_disabled( 

1472 self, 

1473 ): # pylint: disable=invalid-name 

1474 """ 

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

1476 """ 

1477 urlobj = urlsplit( 

1478 libravatar_url( 

1479 email="asdf@company.local", 

1480 size=80, 

1481 ) 

1482 ) 

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

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

1485 self.assertEqual( 

1486 response.redirect_chain[0][0], 

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

1488 "Doesn't redirect to static?", 

1489 ) 

1490 

1491 # self.assertRedirects( 

1492 # response=response, 

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

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

1495 # ) 

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

1497 

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

1499 """ 

1500 Test fetching avatar for not existing mail with default specified 

1501 """ 

1502 urlobj = urlsplit( 

1503 libravatar_url( 

1504 "xxx@xxx.xxx", 

1505 size=80, 

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

1507 ) 

1508 ) 

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

1510 url += "&gravatarproxy=n" 

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

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

1513 self.assertEqual( 

1514 response["Location"], 

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

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

1517 ) 

1518 

1519 def test_avatar_url_default_gravatarproxy_disabled( 

1520 self, 

1521 ): # pylint: disable=invalid-name 

1522 """ 

1523 Test fetching avatar for not existing mail with default specified 

1524 """ 

1525 urlobj = urlsplit( 

1526 libravatar_url( 

1527 "xxx@xxx.xxx", 

1528 size=80, 

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

1530 ) 

1531 ) 

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

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

1534 self.assertEqual( 

1535 response.redirect_chain[0][0], 

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

1537 "Doesn't redirect to static?", 

1538 ) 

1539 

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

1541 """ 

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

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

1544 """ 

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

1546 size = 80 

1547 urlobj = urlsplit( 

1548 libravatar_url( 

1549 "xxx@xxx.xxx", 

1550 size=size, 

1551 default=default, 

1552 ) 

1553 ) 

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

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

1556 self.assertRedirects( 

1557 response=response, 

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

1559 fetch_redirect_response=False, 

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

1561 ) 

1562 

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

1564 """ 

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

1566 """ 

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

1568 urlobj = urlsplit( 

1569 libravatar_url( 

1570 "xxx@xxx.xxx", 

1571 size=80, 

1572 default=default, 

1573 ) 

1574 ) 

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

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

1577 self.assertRedirects( 

1578 response=response, 

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

1580 fetch_redirect_response=False, 

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

1582 ) 

1583 

1584 def test_avatar_url_default_external_gravatarproxy_disabled( 

1585 self, 

1586 ): # pylint: disable=invalid-name 

1587 """ 

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

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

1590 """ 

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

1592 urlobj = urlsplit( 

1593 libravatar_url( 

1594 "xxx@xxx.xxx", 

1595 size=80, 

1596 default=default, 

1597 ) 

1598 ) 

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

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

1601 self.assertRedirects( 

1602 response=response, 

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

1604 fetch_redirect_response=False, 

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

1606 ) 

1607 

1608 def test_crop_photo(self): 

1609 """ 

1610 Test cropping photo 

1611 """ 

1612 self.test_upload_image() 

1613 self.test_confirm_email() 

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

1615 response = self.client.post( 

1616 url, 

1617 { 

1618 "x": 10, 

1619 "y": 10, 

1620 "w": 20, 

1621 "h": 20, 

1622 }, 

1623 follow=True, 

1624 ) 

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

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

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

1628 self.assertEqual( 

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

1630 ) 

1631 

1632 def test_password_change_view(self): 

1633 """ 

1634 Test password change view 

1635 """ 

1636 self.login() 

1637 url = reverse("password_change") 

1638 response = self.client.get(url) 

1639 self.assertEqual( 

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

1641 ) 

1642 

1643 def test_password_change_view_post_wrong_old_pw(self): 

1644 """ 

1645 Test password change view post 

1646 """ 

1647 self.login() 

1648 response = self.client.post( 

1649 reverse("password_change"), 

1650 { 

1651 "old_password": "xxx", 

1652 "new_password1": self.password, 

1653 "new_password2": self.password, 

1654 }, 

1655 follow=True, 

1656 ) 

1657 

1658 self.assertContains( 

1659 response, 

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

1661 1, 

1662 200, 

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

1664 ) 

1665 

1666 def test_password_change_view_post_wrong_new_password1(self): 

1667 """ 

1668 Test password change view post 

1669 """ 

1670 self.login() 

1671 response = self.client.post( 

1672 reverse("password_change"), 

1673 { 

1674 "old_password": self.password, 

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

1676 "new_password2": self.password, 

1677 }, 

1678 follow=True, 

1679 ) 

1680 self.assertContains( 

1681 response, 

1682 "The two password fields did", 

1683 1, 

1684 200, 

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

1686 ) 

1687 

1688 def test_password_change_view_post_wrong_new_password2(self): 

1689 """ 

1690 Test password change view post 

1691 """ 

1692 self.login() 

1693 response = self.client.post( 

1694 reverse("password_change"), 

1695 { 

1696 "old_password": self.password, 

1697 "new_password1": self.password, 

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

1699 }, 

1700 follow=True, 

1701 ) 

1702 

1703 self.assertContains( 

1704 response, 

1705 "The two password fields did", 

1706 1, 

1707 200, 

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

1709 ) 

1710 

1711 def test_password_change_view_post_common_password(self): 

1712 """ 

1713 Test password change view post 

1714 """ 

1715 self.login() 

1716 response = self.client.post( 

1717 reverse("password_change"), 

1718 { 

1719 "old_password": self.password, 

1720 "new_password1": "Hallo", 

1721 "new_password2": "Hallo", 

1722 }, 

1723 follow=True, 

1724 ) 

1725 

1726 self.assertContains( 

1727 response, 

1728 "This password is too common.", 

1729 1, 

1730 200, 

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

1732 ) 

1733 

1734 def test_profile_must_list_first_and_lastname(self): 

1735 """ 

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

1737 """ 

1738 self.login() 

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

1740 self.assertContains( 

1741 response, 

1742 self.first_name, 

1743 1, 

1744 200, 

1745 "First name not listed in profile page", 

1746 ) 

1747 self.assertContains( 

1748 response, 

1749 self.last_name, 

1750 1, 

1751 200, 

1752 "Last name not listed in profile page", 

1753 ) 

1754 self.assertContains( 

1755 response, 

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

1757 1, 

1758 200, 

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

1760 ) 

1761 

1762 def test_password_reset_page(self): 

1763 """ 

1764 Just test if the password reset page come up correctly 

1765 """ 

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

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

1768 

1769 def test_password_reset_wo_mail(self): 

1770 """ 

1771 Test if the password reset doesn't error out 

1772 if the mail address doesn't exist 

1773 """ 

1774 # Avoid sending out mails 

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

1776 

1777 # Empty database / eliminate existing users 

1778 User.objects.all().delete() 

1779 url = reverse("password_reset") 

1780 response = self.client.post( 

1781 url, 

1782 { 

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

1784 }, 

1785 follow=True, 

1786 ) 

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

1788 self.assertEqual( 

1789 len(mail.outbox), 

1790 0, 

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

1792 ) 

1793 

1794 def test_password_reset_w_mail(self): 

1795 """ 

1796 Test if the password reset works correctly with email in 

1797 User object 

1798 """ 

1799 # Avoid sending out mails 

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

1801 

1802 url = reverse("password_reset") 

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

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

1805 self.user.save() 

1806 response = self.client.post( 

1807 url, 

1808 { 

1809 "email": self.user.email, 

1810 }, 

1811 follow=True, 

1812 ) 

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

1814 self.assertEqual( 

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

1816 ) 

1817 self.assertEqual( 

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

1819 self.user.email, 

1820 "Sending mails to the wrong \ 

1821 mail address?", 

1822 ) 

1823 

1824 def test_password_reset_w_confirmed_mail(self): 

1825 """ 

1826 Test if the password reset works correctly with confirmed 

1827 mail 

1828 """ 

1829 # Avoid sending out mails 

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

1831 

1832 url = reverse("password_reset") 

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

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

1835 self.user.save() 

1836 

1837 response = self.client.post( 

1838 url, 

1839 { 

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

1841 }, 

1842 follow=True, 

1843 ) 

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

1845 self.user.refresh_from_db() 

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

1847 self.assertEqual( 

1848 self.user.email, 

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

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

1851 ) 

1852 self.assertEqual( 

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

1854 ) 

1855 self.assertEqual( 

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

1857 self.user.email, 

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

1859 ) 

1860 

1861 def test_export(self): 

1862 """ 

1863 Test if export works 

1864 """ 

1865 

1866 # Create well known strings to check if export 

1867 # works as expected 

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

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

1870 self.user.save() 

1871 

1872 # Ensure we have a photo uploaded 

1873 self.test_upload_image() 

1874 

1875 self.login() 

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

1877 response = self.client.post( 

1878 reverse("export"), 

1879 {}, 

1880 follow=False, 

1881 ) 

1882 self.assertIsInstance(response.content, bytes) 

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

1884 content = fh.read() 

1885 fh.close() 

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

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

1888 self.assertEqual( 

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

1890 self.user.username, 

1891 ) 

1892 self.assertEqual( 

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

1894 self.user.password, 

1895 ) 

1896 

1897 self.assertEqual( 

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

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

1900 ) 

1901 self.assertEqual( 

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

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

1904 ) 

1905 

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

1907 

1908 data = data.strip("'") 

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

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

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

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

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

1914 

1915 def test_upload_export(self): 

1916 """ 

1917 Test if uploading export works 

1918 """ 

1919 

1920 # Ensure we have data in place 

1921 self.test_export() 

1922 

1923 self.login() 

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

1925 response = self.client.post( 

1926 reverse("export"), 

1927 {}, 

1928 follow=False, 

1929 ) 

1930 self.assertIsInstance(response.content, bytes) 

1931 

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

1933 fh = BytesIO(response.content) 

1934 

1935 response = self._uploading_export_check( 

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

1937 ) 

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

1939 self.assertContains( 

1940 response, 

1941 "asdf@asdf.local", 

1942 2, 

1943 200, 

1944 "Upload didn't work?", 

1945 ) 

1946 

1947 def _uploading_export_check(self, fh, message): 

1948 """ 

1949 Helper function to upload an export 

1950 """ 

1951 result = self.client.post( 

1952 reverse("upload_export"), 

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

1954 follow=True, 

1955 ) 

1956 fh.close() 

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

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

1959 

1960 return result 

1961 

1962 def test_preferences_page(self): 

1963 """ 

1964 Test if preferences page works 

1965 """ 

1966 

1967 self.login() 

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

1969 

1970 def test_delete_user(self): 

1971 """ 

1972 Test if deleting user profile works 

1973 """ 

1974 

1975 self.login() 

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

1977 response = self.client.post( 

1978 reverse("delete"), 

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

1980 follow=True, 

1981 ) 

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

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

1984 

1985 def test_confirm_already_confirmed(self): 

1986 """ 

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

1988 """ 

1989 

1990 # Add mail address (stays unconfirmed) 

1991 self.test_add_email() 

1992 

1993 # Create a second user that will conflict 

1994 user2 = User.objects.create_user( 

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

1996 password=self.password, 

1997 first_name=self.first_name, 

1998 last_name=self.last_name, 

1999 ) 

2000 ConfirmedEmail.objects.create( 

2001 email=self.email, 

2002 user=user2, 

2003 ) 

2004 

2005 # Just to be sure 

2006 self.assertEqual( 

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

2008 user2.confirmedemail_set.first().email, 

2009 "Mail not the same?", 

2010 ) 

2011 

2012 # This needs to be caught 

2013 with contextlib.suppress(AssertionError): 

2014 self.test_confirm_email() 

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

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

2017 self.assertEqual( 

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

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

2020 "This should return an error message!", 

2021 )