Coverage for ivatar/ivataraccount/test_views.py: 100%
807 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-20 05:10 +0000
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-20 05:10 +0000
1"""
2Test our views in ivatar.ivataraccount.views and ivatar.views
3"""
5import contextlib
7# pylint: disable=too-many-lines
8from urllib.parse import urlsplit
9from io import BytesIO
10import io
11import os
12import gzip
13import xml.etree.ElementTree
14import base64
15import django
16from django.test import TestCase
17from django.test import Client
18from django.test import override_settings
19from django.urls import reverse
20from django.core import mail
21from django.contrib.auth.models import User
22from django.contrib.auth import authenticate
23import hashlib
25from libravatar import libravatar_url
27from PIL import Image
29os.environ["DJANGO_SETTINGS_MODULE"] = "ivatar.settings"
30django.setup()
32# pylint: disable=wrong-import-position
33from ivatar import settings
34from ivatar.ivataraccount.forms import MAX_NUM_UNCONFIRMED_EMAILS_DEFAULT
35from ivatar.ivataraccount.models import Photo, ConfirmedOpenId, ConfirmedEmail
36from ivatar.utils import random_string
38# pylint: enable=wrong-import-position
40TEST_IMAGE_FILE = os.path.join(settings.STATIC_ROOT, "img", "deadbeef.png")
42# Disable the default cache for this class via a scoped override. The previous
43# in-place `settings.CACHES["default"] = DummyCache` leaked into every test
44# module that ran afterwards (no cache), breaking tests that rely on it.
45_DUMMY_CACHES = {
46 **settings.CACHES,
47 "default": {"BACKEND": "django.core.cache.backends.dummy.DummyCache"},
48}
51@override_settings(CACHES=_DUMMY_CACHES)
52class Tester(TestCase): # pylint: disable=too-many-public-methods
53 """
54 Main test class
55 """
57 client = Client()
58 user = None
59 username = random_string()
60 password = random_string()
61 email = "{}@{}.org".format(username, random_string())
62 # Dunno why random tld doesn't work, but I'm too lazy now to investigate
63 openid = "http://{}.{}.{}/".format(username, random_string(), "org")
64 first_name = random_string()
65 last_name = random_string()
67 def login(self):
68 """
69 Login as user
70 """
71 self.client.login(username=self.username, password=self.password)
73 def setUp(self):
74 """
75 Prepare for tests.
76 - Create user
77 """
78 self.user = User.objects.create_user(
79 username=self.username,
80 password=self.password,
81 first_name=self.first_name,
82 last_name=self.last_name,
83 )
85 def test_new_user(self):
86 """
87 Create a new user
88 """
89 response = self.client.get(reverse("new_account"))
90 self.assertEqual(response.status_code, 200, "no 200 ok?")
91 # Empty database / eliminate existing users
92 User.objects.all().delete()
93 url = reverse("new_account")
94 response = self.client.post(
95 url,
96 {
97 "username": self.username,
98 "password1": self.password,
99 "password2": self.password,
100 },
101 follow=True,
102 )
103 self.assertEqual(response.status_code, 200, "unable to create user?")
104 self.assertEqual(response.context[0]["user"].username, self.username)
106 def test_new_user_twice(self):
107 """
108 Try to create a user that already exists
109 """
110 response = self.client.get(reverse("new_account"))
111 self.assertEqual(response.status_code, 200, "no 200 ok?")
112 # Due to setUp(), we already have this user!
113 url = reverse("new_account")
114 response = self.client.post(
115 url,
116 {
117 "username": self.username,
118 "password1": self.password,
119 "password2": self.password,
120 },
121 follow=True,
122 )
123 self.assertEqual(response.status_code, 200, "unable to create user?")
124 self.assertEqual(response.context[0]["user"].username, "")
125 self.assertContains(
126 response,
127 "A user with that username already exists.",
128 1,
129 200,
130 "can we create a user a second time???",
131 )
133 def test_set_password(self):
134 """
135 Change the user password
136 """
137 self.login()
138 response = self.client.get(reverse("password_set"))
139 self.assertEqual(response.status_code, 200, "no 200 ok?")
140 self.password = random_string()
141 response = self.client.post(
142 reverse("password_set"),
143 {
144 "new_password1": self.password,
145 "new_password2": self.password,
146 },
147 follow=True,
148 )
150 self.assertEqual(response.status_code, 200, "cannot change password?")
151 self.assertEqual(
152 str(list(response.context[0]["messages"])[0]),
153 "password changed successfully - please login again",
154 "password change not successful?",
155 )
157 self.assertIsNotNone(
158 authenticate(
159 username=self.username,
160 password=self.password,
161 ),
162 "cannot authenticate with new password!?",
163 )
165 self.login()
166 response = self.client.get(reverse("profile"))
167 self.assertEqual(response.context[0]["user"].is_anonymous, False)
169 def test_add_email(self):
170 """
171 Add e-mail address
172 """
173 self.login()
174 response = self.client.get(reverse("add_email"))
175 self.assertEqual(response.status_code, 200, "no 200 ok?")
176 # Avoid sending out mails
177 settings.EMAIL_BACKEND = "django.core.mail.backends.dummy.EmailBackend"
178 response = self.client.post(
179 reverse("add_email"),
180 {
181 "email": self.email,
182 },
183 follow=True,
184 )
185 self.assertEqual(response.status_code, 200, "cannot add email?")
186 self.assertEqual(
187 len(response.context[0]["messages"]),
188 1,
189 "there must not be more or less than ONE (1) message",
190 )
191 self.assertEqual(
192 str(list(response.context[0]["messages"])[0]),
193 "Address added successfully",
194 "unable to add mail address?",
195 )
197 def test_confirm_email(self):
198 """
199 Confirm unconfirmed email
200 """
201 self.login()
202 # Avoid sending out mails
203 settings.EMAIL_BACKEND = "django.core.mail.backends.dummy.EmailBackend"
204 response = self.client.post(
205 reverse("add_email"),
206 {
207 "email": self.email,
208 },
209 follow=True,
210 )
211 unconfirmed = self.user.unconfirmedemail_set.first()
212 verification_key = unconfirmed.verification_key
213 url = reverse("confirm_email", args=[verification_key])
214 response = self.client.get(url)
215 self.assertEqual(response.status_code, 200, "unable to confirm mail address?")
217 self.assertEqual(
218 self.user.unconfirmedemail_set.count(),
219 0,
220 "there must not be any unconfirmed address, after confirming it",
221 )
222 self.assertEqual(
223 self.user.confirmedemail_set.count(),
224 1,
225 "there must not be more or less than ONE (1) confirmed address!",
226 )
228 def test_confirm_email_w_invalid_auth_key(self): # pylint: disable=invalid-name
229 """
230 Test confirmation with invalid auth key
231 """
232 self.login()
233 # Avoid sending out mails
234 settings.EMAIL_BACKEND = "django.core.mail.backends.dummy.EmailBackend"
235 response = self.client.post(
236 reverse("add_email"),
237 {
238 "email": self.email,
239 },
240 follow=True,
241 )
242 url = reverse("confirm_email", args=["x"])
243 response = self.client.get(url, follow=True)
244 self.assertEqual(
245 response.status_code,
246 200,
247 "Not able to request confirmation - without verification key?",
248 )
249 self.assertEqual(
250 str(list(response.context[0]["messages"])[-1]),
251 "Verification key incorrect",
252 "Confirm w/o verification key does not produce error message?",
253 )
255 def test_confirm_email_w_non_existing_auth_key(
256 self,
257 ): # pylint: disable=invalid-name
258 """
259 Test confirmation with non existing auth key
260 """
261 self.login()
262 # Avoid sending out mails
263 settings.EMAIL_BACKEND = "django.core.mail.backends.dummy.EmailBackend"
264 response = self.client.post(
265 reverse("add_email"),
266 {
267 "email": self.email,
268 },
269 follow=True,
270 )
271 url = reverse("confirm_email", args=["x" * 64])
272 response = self.client.get(url, follow=True)
273 self.assertEqual(
274 response.status_code,
275 200,
276 "Not able to request confirmation - without verification key?",
277 )
278 self.assertEqual(
279 str(list(response.context[0]["messages"])[-1]),
280 "Verification key does not exist",
281 "Confirm w/o non existing key does not produce error message?",
282 )
284 def test_remove_confirmed_email(self):
285 """
286 Remove confirmed email
287 """
288 self.login()
289 # Avoid sending out mails
290 settings.EMAIL_BACKEND = "django.core.mail.backends.dummy.EmailBackend"
291 response = self.client.post(
292 reverse("add_email"),
293 {
294 "email": self.email,
295 },
296 ) # Create test address
297 unconfirmed = self.user.unconfirmedemail_set.first()
298 verification_key = unconfirmed.verification_key
299 url = reverse("confirm_email", args=[verification_key])
300 self.client.get(url) # Confirm
301 url = reverse(
302 "remove_confirmed_email", args=[self.user.confirmedemail_set.first().id]
303 )
304 response = self.client.post(url, follow=True)
305 self.assertEqual(
306 response.status_code, 200, "unable to remove confirmed address?"
307 )
308 self.assertEqual(
309 str(list(response.context[0]["messages"])[-1]),
310 "Address removed",
311 "Removing confirmed mail does not work?",
312 )
314 def test_remove_not_existing_confirmed_email(self): # pylint: disable=invalid-name
315 """
316 Try removing confirmed mail that doesn't exist
317 """
318 self.login()
319 url = reverse("remove_confirmed_email", args=[1234])
320 response = self.client.post(url, follow=True)
321 self.assertEqual(
322 response.status_code, 200, "removing email does not redirect to profile?"
323 )
324 self.assertEqual(
325 str(list(response.context[0]["messages"])[0]),
326 "Address does not exist",
327 "Removing not existing (confirmed) address, should produce an\
328 error message!",
329 )
331 def test_remove_unconfirmed_email(self):
332 """
333 Remove unconfirmed email
334 """
335 self.login()
336 # Avoid sending out mails
337 settings.EMAIL_BACKEND = "django.core.mail.backends.dummy.EmailBackend"
338 response = self.client.post(
339 reverse("add_email"),
340 {
341 "email": self.email,
342 },
343 ) # Create test address
344 url = reverse(
345 "remove_unconfirmed_email", args=[self.user.unconfirmedemail_set.first().id]
346 )
347 response = self.client.post(url, follow=True)
348 self.assertEqual(
349 response.status_code, 200, "unable to remove unconfirmed address?"
350 )
351 # Take care, since we do not fetch any page now, the message we need
352 # to check is the _second_ (aka [1], since first is [0])
353 self.assertEqual(
354 str(list(response.context[0]["messages"])[1]),
355 "Address removed",
356 "Removing unconfirmed mail does not work?",
357 )
359 def test_gravatar_photo_import(self):
360 """
361 import photo from Gravatar (with known mail address)
362 """
363 self.login()
364 # Avoid sending out mails
365 settings.EMAIL_BACKEND = "django.core.mail.backends.dummy.EmailBackend"
366 response = self.client.post(
367 reverse("add_email"),
368 {
369 "email": "oliver@linux-kernel.at", # Wow, static :-[
370 },
371 ) # Create test address
372 unconfirmed = self.user.unconfirmedemail_set.first()
373 verification_key = unconfirmed.verification_key
374 url = reverse("confirm_email", args=[verification_key])
375 self.client.get(url) # Confirm
377 url = reverse("import_photo", args=[self.user.confirmedemail_set.first().id])
378 response = self.client.post(
379 url,
380 {
381 "photo_Gravatar": 1,
382 },
383 follow=True,
384 )
385 self.assertEqual(
386 response.status_code, 200, "unable to import photo from Gravatar?"
387 )
388 self.assertEqual(
389 str(list(response.context[0]["messages"])[-1]),
390 "Gravatar image successfully imported",
391 "Importing gravatar photo did not work?",
392 )
393 self.assertIsInstance(
394 self.user.photo_set.first(), Photo, "why is there no Photo (instance)?"
395 )
397 def test_raw_image(self):
398 """
399 test raw image view (as seen in profile <img src=
400 """
402 # Ensure we have a photo
403 self.test_gravatar_photo_import()
404 photo_id = self.user.photo_set.first().id
405 response = self.client.get(
406 reverse("raw_image", args=[photo_id])
407 )
408 self.assertEqual(response.status_code, 200, "cannot fetch photo?")
409 # Probably not the best way to access the content type
410 self.assertEqual(response["Content-Type"], "image/jpg", "Content type wrong!?")
411 self.assertIn("ETag", response)
412 self.assertIn("Cache-Control", response)
413 self.assertIn("private", response["Cache-Control"])
415 self.assertEqual(
416 bytes(response.content),
417 bytes(self.user.photo_set.first().data),
418 "raw_image should return the same content as if we\
419 read it directly from the DB",
420 )
422 def test_raw_image_resized(self):
423 """
424 Test raw image view with size query parameter returns resized thumbnail
425 """
426 self.test_gravatar_photo_import()
427 photo_id = self.user.photo_set.first().id
429 # Test with size parameter
430 response = self.client.get(
431 reverse("raw_image", args=[photo_id]) + "?size=100"
432 )
433 self.assertEqual(response.status_code, 200)
434 img = Image.open(BytesIO(response.content))
435 self.assertTrue(max(img.size) <= 100)
436 self.assertIn("ETag", response)
438 # Test with s parameter
439 response_s = self.client.get(
440 reverse("raw_image", args=[photo_id]) + "?s=48"
441 )
442 self.assertEqual(response_s.status_code, 200)
443 img_s = Image.open(BytesIO(response_s.content))
444 self.assertTrue(max(img_s.size) <= 48)
446 def test_raw_image_conditional_request(self):
447 """
448 Test raw image view handles If-None-Match (304 Not Modified)
449 """
450 self.test_gravatar_photo_import()
451 photo_id = self.user.photo_set.first().id
453 response = self.client.get(
454 reverse("raw_image", args=[photo_id]) + "?size=120"
455 )
456 self.assertEqual(response.status_code, 200)
457 etag = response["ETag"]
459 # Conditional request with matching ETag
460 cond_response = self.client.get(
461 reverse("raw_image", args=[photo_id]) + "?size=120",
462 HTTP_IF_NONE_MATCH=etag,
463 )
464 self.assertEqual(cond_response.status_code, 304)
465 self.assertEqual(cond_response["ETag"], etag)
467 def test_raw_image_invalid_size(self):
468 """
469 Test raw image view handles invalid size parameter gracefully
470 """
471 self.test_gravatar_photo_import()
472 photo_id = self.user.photo_set.first().id
474 # Non-integer size falls back to full image
475 response = self.client.get(
476 reverse("raw_image", args=[photo_id]) + "?size=invalid"
477 )
478 self.assertEqual(response.status_code, 200)
479 self.assertEqual(bytes(response.content), bytes(self.user.photo_set.first().data))
481 def test_delete_photo(self):
482 """
483 test deleting the photo
484 """
486 # Ensure we have a photo
487 self.test_gravatar_photo_import()
489 url = reverse("delete_photo", args=[self.user.photo_set.first().id])
490 response = self.client.get(url, follow=True)
491 self.assertEqual(response.status_code, 200, "deleting photo does not work?")
492 self.assertEqual(
493 str(list(response.context[0]["messages"])[-1]),
494 "Photo deleted successfully",
495 "Photo deletion did not work?",
496 )
498 def test_delete_non_existing_photo(self):
499 """
500 test deleting the photo
501 """
503 # Ensure we have a photo
504 self.test_gravatar_photo_import()
506 url = reverse("delete_photo", args=[1234])
507 response = self.client.get(url, follow=True)
508 self.assertEqual(response.status_code, 200, "post to delete does not work?")
509 self.assertEqual(
510 str(list(response.context[0]["messages"])[-1]),
511 "No such image or no permission to delete it",
512 "Deleting photo that does not exist, should return error message",
513 )
515 def test_too_many_unconfirmed_email(self):
516 """
517 Request too many unconfirmed email addresses, make sure we
518 cannot add more
519 """
520 self.login()
521 # Avoid sending out mails
522 settings.EMAIL_BACKEND = "django.core.mail.backends.dummy.EmailBackend"
524 max_num_unconfirmed = getattr(
525 settings, "MAX_NUM_UNCONFIRMED_EMAILS", MAX_NUM_UNCONFIRMED_EMAILS_DEFAULT
526 )
528 for i in range(max_num_unconfirmed + 1):
529 response = self.client.post( # noqa: F841
530 reverse("add_email"),
531 {
532 "email": "%i.%s" % (i, self.email),
533 },
534 follow=True,
535 ) # Create test addresses + 1 too much
536 return self._check_form_validity(
537 response, "Too many unconfirmed mail addresses!", "__all__"
538 )
540 def test_add_mail_address_twice(self):
541 """
542 Request the same mail address two times, should not lead to
543 having the same address twice
544 """
545 self.login()
546 # Avoid sending out mails
547 settings.EMAIL_BACKEND = "django.core.mail.backends.dummy.EmailBackend"
549 for _ in range(2):
550 response = self.client.post( # noqa: F841
551 reverse("add_email"),
552 {
553 "email": self.email,
554 },
555 follow=True,
556 )
557 return self._check_form_validity(
558 response, "Address already added, currently unconfirmed", "email"
559 )
561 def test_add_already_confirmed_email_self(self): # pylint: disable=invalid-name
562 """
563 Request adding mail address that is already confirmed (by someone)
564 """
565 # Create test mail and confirm it, reuse test code
566 # Should set EMAIL_BACKEND, so no need to do it here
567 self.test_confirm_email()
569 response = self.client.post( # noqa: F841
570 reverse("add_email"),
571 {
572 "email": self.email,
573 },
574 follow=True,
575 )
577 return self._check_form_validity(
578 response, "Address already confirmed (by you)", "email"
579 )
581 def test_add_already_confirmed_email_other(self): # pylint: disable=invalid-name
582 """
583 Request adding mail address that is already confirmed (by someone)
584 """
585 # Create test mail and confirm it, reuse test code
586 # Should set EMAIL_BACKEND, so no need to do it here
587 self.test_confirm_email()
589 # Create another user and assign the mail address to that one
590 # in order to test the correct error message
591 otheruser = User.objects.create(username="otheruser")
592 confirmedemail = ConfirmedEmail.objects.last()
593 confirmedemail.user = otheruser
594 confirmedemail.save()
596 response = self.client.post( # noqa: F841
597 reverse("add_email"),
598 {
599 "email": self.email,
600 },
601 follow=True,
602 )
604 return self._check_form_validity(
605 response, "Address already confirmed (by someone else)", "email"
606 )
608 def test_remove_unconfirmed_non_existing_email(
609 self,
610 ): # pylint: disable=invalid-name
611 """
612 Remove unconfirmed email that doesn't exist
613 """
614 self.login()
615 url = reverse("remove_unconfirmed_email", args=[1234])
616 response = self.client.post(url, follow=True)
617 self.assertEqual(
618 response.status_code, 200, "unable to remove non existing address?"
619 )
620 self.assertEqual(
621 str(list(response.context[0]["messages"])[0]),
622 "Address does not exist",
623 "Removing address that does not\
624 exist, should return error message!",
625 )
627 def test_upload_image(
628 self, test_only_one=True
629 ): # pylint: disable=inconsistent-return-statements
630 """
631 Test uploading image
632 """
633 self.login()
634 url = reverse("upload_photo")
635 # rb => Read binary
636 with open(TEST_IMAGE_FILE, "rb") as photo_file:
637 photo_data = photo_file.read()
639 from django.core.files.uploadedfile import SimpleUploadedFile
641 uploaded_file = SimpleUploadedFile(
642 "deadbeef.png", photo_data, content_type="image/png"
643 )
644 response = self.client.post(
645 url,
646 {
647 "photo": uploaded_file,
648 "not_porn": True,
649 "can_distribute": True,
650 },
651 follow=True,
652 )
653 if not test_only_one:
654 return response
655 self.assertEqual(
656 self.user.photo_set.count(), 1, "there must be exactly one photo now!"
657 )
658 self.assertEqual(
659 str(list(response.context[0]["messages"])[-1]),
660 "Successfully uploaded",
661 "A valid image should return a success message!",
662 )
663 self.assertEqual(
664 self.user.photo_set.first().format,
665 "png",
666 "Format must be png, since we uploaded a png!",
667 )
669 def test_upload_too_many_images(self):
670 """
671 Test uploading more images than we are allowed
672 """
673 for _ in range(settings.MAX_NUM_PHOTOS + 1):
674 response = self.test_upload_image(test_only_one=False)
675 self.assertEqual(
676 self.user.photo_set.count(),
677 settings.MAX_NUM_PHOTOS,
678 "there may not be more photos than allowed!",
679 )
680 # Take care we need to check the last message
681 self.assertEqual(
682 str(list(response.context[0]["messages"])[-1]),
683 "Maximum number of photos (%i) reached" % settings.MAX_NUM_PHOTOS,
684 "Adding more than allowed images, should return error message!",
685 )
687 def test_upload_too_big_image(self):
688 """
689 Test uploading image that is too big
690 """
691 self.login()
692 url = reverse("upload_photo")
693 # rb => Read binary
694 response = self.client.post(
695 url,
696 {
697 "photo": io.StringIO("x" * (settings.MAX_PHOTO_SIZE + 1)),
698 "not_porn": True,
699 "can_distribute": True,
700 },
701 follow=True,
702 )
703 self.assertEqual(
704 str(list(response.context[0]["messages"])[0]),
705 "Image too big",
706 "Uploading too big image, should return error message!",
707 )
709 def test_upload_invalid_image(self):
710 """
711 Test invalid image data
712 """
713 self.login()
714 url = reverse("upload_photo")
715 # rb => Read binary
716 response = self.client.post(
717 url,
718 {
719 "photo": io.StringIO("x"),
720 "not_porn": True,
721 "can_distribute": True,
722 },
723 follow=True,
724 )
725 self.assertEqual(
726 str(list(response.context[0]["messages"])[0]),
727 "Invalid Format",
728 "Invalid img data should return error message!",
729 )
731 def test_upload_invalid_image_format(self): # pylint: disable=invalid-name
732 """
733 Test if invalid format is correctly detected
734 """
735 self.login()
736 url = reverse("upload_photo")
737 # rb => Read binary
738 with open(os.path.join(settings.STATIC_ROOT, "img", "mm.svg"), "rb") as photo:
739 response = self.client.post(
740 url,
741 {
742 "photo": photo,
743 "not_porn": True,
744 "can_distribute": True,
745 },
746 follow=True,
747 )
748 self.assertEqual(
749 str(list(response.context[0]["messages"])[0]),
750 "Invalid Format",
751 "Invalid img data should return error message!",
752 )
754 def test_upload_gif_image(self):
755 """
756 Test if gif is correctly detected and can be viewed
757 """
758 self._extracted_from_test_upload_webp_image_5(
759 "broken.gif",
760 "GIF upload failed?!",
761 "gif",
762 "Format must be gif, since we uploaded a GIF!",
763 )
765 def test_upload_jpg_image(self):
766 """
767 Test if jpg is correctly detected and can be viewed
768 """
769 self._extracted_from_test_upload_webp_image_5(
770 "broken.jpg",
771 "JPEG upload failed?!",
772 "jpg",
773 "Format must be jpeg, since we uploaded a jpeg!",
774 )
776 def test_upload_webp_image(self):
777 """
778 Test if webp is correctly detected and can be viewed
779 """
780 self._extracted_from_test_upload_webp_image_5(
781 "broken.webp",
782 "WEBP upload failed?!",
783 "webp",
784 "Format must be webp, since we uploaded a webp!",
785 )
787 def _extracted_from_test_upload_webp_image_5(
788 self, filename, message1, format, message2
789 ):
790 """
791 Helper function for common checks for gif, jpg, webp
792 """
793 self.login()
794 url = reverse("upload_photo")
795 with open(os.path.join(settings.STATIC_ROOT, "img", filename), "rb") as photo:
796 response = self.client.post(
797 url,
798 {"photo": photo, "not_porn": True, "can_distribute": True},
799 follow=True,
800 )
801 self.assertEqual(
802 str(list(response.context[0]["messages"])[0]),
803 "Successfully uploaded",
804 message1,
805 )
806 self.assertEqual(self.user.photo_set.first().format, format, message2)
807 self.test_confirm_email()
808 self.user.confirmedemail_set.first().photo = self.user.photo_set.first()
809 urlobj = urlsplit(
810 libravatar_url(email=self.user.confirmedemail_set.first().email)
811 )
812 url = f"{urlobj.path}?{urlobj.query}"
813 response = self.client.get(url, follow=True)
814 self.assertEqual(response.status_code, 200, "unable to fetch avatar?")
816 def test_upload_unsupported_tif_image(self): # pylint: disable=invalid-name
817 """
818 Test if unsupported format is correctly detected
819 """
820 self.login()
821 url = reverse("upload_photo")
822 # rb => Read binary
823 with open(
824 os.path.join(settings.STATIC_ROOT, "img", "broken.tif"), "rb"
825 ) as photo:
826 response = self.client.post(
827 url,
828 {
829 "photo": photo,
830 "not_porn": True,
831 "can_distribute": True,
832 },
833 follow=True,
834 )
835 self.assertEqual(
836 str(list(response.context[0]["messages"])[0]),
837 "Invalid Format",
838 "Invalid img data should return error message!",
839 )
841 def test_automatic_photo_assign_to_confirmed_mail(
842 self,
843 ): # pylint: disable=invalid-name
844 """
845 Test if automatic assignment of photo works
846 """
847 self.test_upload_image()
848 self.test_confirm_email()
849 confirmed = self.user.confirmedemail_set.first()
850 self.assertEqual(confirmed.photo, self.user.photo_set.first())
852 def test_assign_photo_to_email(self):
853 """
854 Test assigning photo to mail address
855 """
856 self.test_confirm_email()
857 self.test_upload_image()
858 self.assertIsNone(self.user.confirmedemail_set.first().photo)
859 url = reverse(
860 "assign_photo_email", args=[self.user.confirmedemail_set.first().id]
861 )
862 # The get is for the view - test context data
863 self.client.get(
864 url,
865 {
866 "photo_id": self.user.photo_set.first().id,
867 },
868 )
869 # The post is for the actual assigning
870 response = self.client.post(
871 url,
872 {
873 "photo_id": self.user.photo_set.first().id,
874 },
875 follow=True,
876 )
877 self.assertEqual(response.status_code, 200, "cannot assign photo?")
878 self.assertEqual(
879 self.user.confirmedemail_set.first().photo, self.user.photo_set.first()
880 )
882 def test_no_photo_to_email(self):
883 """
884 Test assigning photo to mail address
885 """
886 self.test_confirm_email()
887 url = reverse(
888 "assign_photo_email", args=[self.user.confirmedemail_set.first().id]
889 )
890 response = self.client.post(
891 url,
892 {
893 "photoNone": True,
894 },
895 follow=True,
896 )
897 self.assertEqual(response.status_code, 200, "cannot un-assign photo?")
898 self.assertEqual(self.user.confirmedemail_set.first().photo, None)
900 def test_assign_photo_to_email_wo_photo_for_testing_template(
901 self,
902 ): # pylint: disable=invalid-name
903 """
904 Test assign photo template
905 """
906 self.test_confirm_email()
907 url = reverse(
908 "assign_photo_email", args=[self.user.confirmedemail_set.first().id]
909 )
910 # The get is for the view - test context data
911 response = self.client.get(url)
912 self.assertEqual(response.status_code, 200, "cannot fetch page?")
914 def test_assign_invalid_photo_id_to_email(self): # pylint: disable=invalid-name
915 """
916 Test if assigning an invalid photo id returns the correct error message
917 """
918 self.test_confirm_email()
919 self.test_upload_image()
920 self.assertIsNone(self.user.confirmedemail_set.first().photo)
921 url = reverse(
922 "assign_photo_email", args=[self.user.confirmedemail_set.first().id]
923 )
924 response = self.client.post(
925 url,
926 {
927 "photo_id": 1234,
928 },
929 follow=True,
930 )
931 self.assertEqual(response.status_code, 200, "cannot post assign photo request?")
932 self.assertEqual(
933 str(list(response.context[0]["messages"])[-1]),
934 "Photo does not exist",
935 "Assign non existing photo, does not return error message?",
936 )
938 def test_post_to_assign_photo_without_photo_id(
939 self,
940 ): # pylint: disable=invalid-name
941 """
942 Test if assigning photo without id returns the correct error message
943 """
944 self.test_confirm_email()
945 self.test_upload_image()
946 self.assertIsNone(self.user.confirmedemail_set.first().photo)
947 url = reverse(
948 "assign_photo_email", args=[self.user.confirmedemail_set.first().id]
949 )
950 response = self.client.post(url, {}, follow=True)
951 self.assertEqual(response.status_code, 200, "cannot post assign photo request?")
952 self.assertEqual(
953 str(list(response.context[0]["messages"])[-1]),
954 "Invalid request [photo_id] missing",
955 "Assign non existing photo, does not return error message?",
956 )
958 def test_assign_photo_to_non_existing_mail(self): # pylint: disable=invalid-name
959 """
960 Test if assigning photo to mail address that doesn't exist returns
961 the correct error message
962 """
963 self.test_upload_image()
964 url = reverse("assign_photo_email", args=[1234])
965 response = self.client.post(
966 url,
967 {
968 "photo_id": self.user.photo_set.first().id,
969 },
970 follow=True,
971 )
972 self.assertEqual(response.status_code, 200, "cannot post assign photo request?")
973 self.assertEqual(
974 str(list(response.context[0]["messages"])[-1]),
975 "Invalid request",
976 "Assign non existing photo, does not return error message?",
977 )
979 def test_import_photo_with_non_existing_email(self): # pylint: disable=invalid-name
980 """
981 Test if import with non existing mail address returns
982 the correct error message
983 """
984 self.login()
985 url = reverse("import_photo", args=[1234])
986 response = self.client.post(url, {}, follow=True)
987 self.assertEqual(response.status_code, 200, "cannot post import photo request?")
988 self.assertEqual(
989 str(list(response.context[0]["messages"])[0]),
990 "Address does not exist",
991 "Import photo with non existing mail id,\
992 does not return error message?",
993 )
995 def test_import_nothing(self):
996 """
997 Test if importing nothing causes the correct
998 error message to be returned
999 """
1000 self.test_confirm_email()
1001 url = reverse("import_photo", args=[self.user.confirmedemail_set.first().id])
1002 response = self.client.post(url, {}, follow=True)
1003 self.assertEqual(response.status_code, 200, "cannot post import photo request?")
1004 self.assertEqual(
1005 str(list(response.context[0]["messages"])[-1]),
1006 "Nothing importable",
1007 "Importing with email that does not exist in Gravatar,\
1008 should return an error message!",
1009 )
1011 def _manual_confirm(self):
1012 """
1013 Helper method to confirm manually, because testing is really hard
1014 """
1015 # Manual confirm, since testing is _really_ hard!
1016 unconfirmed = self.user.unconfirmedopenid_set.first()
1017 confirmed = ConfirmedOpenId()
1018 confirmed.user = unconfirmed.user
1019 confirmed.ip_address = "127.0.0.1"
1020 confirmed.openid = unconfirmed.openid
1021 confirmed.save()
1022 unconfirmed.delete()
1024 def test_add_openid(self, confirm=True):
1025 """
1026 Test if adding an OpenID works
1027 """
1029 self.login()
1030 # Get page
1031 response = self.client.get(reverse("add_openid"))
1032 self.assertEqual(
1033 response.status_code, 200, "Fetching page to add OpenID fails?"
1034 )
1036 response = self.client.post(
1037 reverse("add_openid"),
1038 {
1039 "openid": self.openid,
1040 },
1041 )
1042 self.assertEqual(response.status_code, 302, "OpenID must redirect")
1044 if confirm:
1045 self._manual_confirm()
1047 def test_add_openid_twice(self):
1048 """
1049 Test if adding OpenID a second time works - it shouldn't
1050 """
1051 self.login()
1052 # Get page
1053 response = self.client.get(reverse("add_openid"))
1054 self.assertEqual(
1055 response.status_code, 200, "Fetching page to add OpenID fails?"
1056 )
1058 response = self.client.post(
1059 reverse("add_openid"),
1060 {
1061 "openid": self.openid,
1062 },
1063 )
1064 self.assertEqual(response.status_code, 302, "OpenID must redirect")
1065 response = self.client.post(
1066 reverse("add_openid"),
1067 {
1068 "openid": self.openid,
1069 },
1070 follow=True,
1071 )
1072 self.assertEqual(
1073 self.user.unconfirmedopenid_set.count(),
1074 1,
1075 "There must only be one unconfirmed ID!",
1076 )
1078 self._check_form_validity(
1079 response, "OpenID already added, but not confirmed yet!", "openid"
1080 )
1081 # Manual confirm, since testing is _really_ hard!
1082 unconfirmed = self.user.unconfirmedopenid_set.first()
1083 confirmed = ConfirmedOpenId()
1084 confirmed.user = unconfirmed.user
1085 confirmed.ip_address = "127.0.0.1"
1086 confirmed.openid = unconfirmed.openid
1087 confirmed.save()
1088 unconfirmed.delete()
1090 # Try adding it again - although already confirmed
1091 response = self.client.post(
1092 reverse("add_openid"),
1093 {
1094 "openid": self.openid,
1095 },
1096 follow=True,
1097 )
1099 return self._check_form_validity(
1100 response, "OpenID already added and confirmed!", "openid"
1101 )
1103 def _check_form_validity(self, response, message, field):
1104 """
1105 Helper method to check form, used in several test functions,
1106 deduplicating code
1107 """
1109 self.assertTrue(
1110 hasattr(response, "context"), "Response does not have a context"
1111 )
1112 result = response.context.get("form")
1113 self.assertIsNotNone(result, "No form found in response context")
1114 self.assertFalse(result.is_valid(), "Form should not be valid")
1115 self.assertIn(message, result.errors.get(field, []))
1116 return result
1118 def test_assign_photo_to_openid(self):
1119 """
1120 Test assignment of photo to openid
1121 """
1122 self.test_add_openid()
1123 self.test_upload_image()
1124 self.assertIsNone(self.user.confirmedopenid_set.first().photo)
1125 url = reverse(
1126 "assign_photo_openid", args=[self.user.confirmedopenid_set.first().id]
1127 )
1128 # The get is for the view - test context data
1129 self.client.get(
1130 url,
1131 {
1132 "photo_id": self.user.photo_set.first().id,
1133 },
1134 )
1135 # The post is for the actual assigning
1136 response = self.client.post(
1137 url,
1138 {
1139 "photo_id": self.user.photo_set.first().id,
1140 },
1141 follow=True,
1142 )
1143 self.assertEqual(response.status_code, 200, "cannot assign photo?")
1144 self.assertEqual(
1145 self.user.confirmedopenid_set.first().photo, self.user.photo_set.first()
1146 )
1148 def test_assign_photo_to_openid_wo_photo_for_testing_template(
1149 self,
1150 ): # pylint: disable=invalid-name
1151 """
1152 Test openid/photo assignment template
1153 """
1154 self.test_add_openid()
1155 url = reverse(
1156 "assign_photo_openid", args=[self.user.confirmedopenid_set.first().id]
1157 )
1158 response = self.client.get(url)
1159 self.assertEqual(response.status_code, 200, "cannot fetch page?")
1161 def test_assign_invalid_photo_id_to_openid(self): # pylint: disable=invalid-name
1162 """
1163 Test assigning invalid photo to openid returns
1164 the correct error message
1165 """
1166 self.test_add_openid()
1167 self.assertIsNone(self.user.confirmedopenid_set.first().photo)
1168 url = reverse(
1169 "assign_photo_openid", args=[self.user.confirmedopenid_set.first().id]
1170 )
1171 response = self.client.post(
1172 url,
1173 {
1174 "photo_id": 1234,
1175 },
1176 follow=True,
1177 )
1178 self.assertEqual(response.status_code, 200, "cannot post assign photo request?")
1179 self.assertEqual(
1180 str(list(response.context[0]["messages"])[-1]),
1181 "Photo does not exist",
1182 "Assign non existing photo, does not return error message?",
1183 )
1185 def test_post_to_assign_photo_openid_without_photo_id(
1186 self,
1187 ): # pylint: disable=invalid-name
1188 """
1189 Test POST assign photo to openid without photo id
1190 returns the correct error message
1191 """
1192 self.test_add_openid()
1193 self.test_upload_image()
1194 self.assertIsNone(self.user.confirmedopenid_set.first().photo)
1195 url = reverse(
1196 "assign_photo_openid", args=[self.user.confirmedopenid_set.first().id]
1197 )
1198 response = self.client.post(url, {}, follow=True)
1199 self.assertEqual(response.status_code, 200, "cannot post assign photo request?")
1200 self.assertEqual(
1201 str(list(response.context[0]["messages"])[-1]),
1202 "Invalid request [photo_id] missing",
1203 "Assign non existing photo, does not return error message?",
1204 )
1206 def test_assign_photo_to_openid_non_existing_openid(
1207 self,
1208 ): # pylint: disable=invalid-name
1209 """
1210 Test assigning photo to openid that doesn't exist
1211 returns the correct error message.
1212 """
1213 self.test_upload_image()
1214 url = reverse("assign_photo_openid", args=[1234])
1215 response = self.client.post(
1216 url,
1217 {
1218 "photo_id": self.user.photo_set.first().id,
1219 },
1220 follow=True,
1221 )
1222 self.assertEqual(response.status_code, 200, "cannot post assign photo request?")
1223 self.assertEqual(
1224 str(list(response.context[0]["messages"])[-1]),
1225 "Invalid request",
1226 "Assign non existing photo, does not return error message?",
1227 )
1229 def test_remove_confirmed_openid(self): # pylint: disable=invalid-name
1230 """
1231 Remove confirmed openid
1232 """
1233 self.test_add_openid()
1234 url = reverse(
1235 "remove_confirmed_openid", args=[self.user.confirmedopenid_set.first().id]
1236 )
1237 response = self.client.post(url, follow=True)
1238 self.assertEqual(
1239 response.status_code, 200, "unable to remove confirmed openid?"
1240 )
1241 self.assertEqual(
1242 str(list(response.context[0]["messages"])[-1]),
1243 "ID removed",
1244 "Removing confirmed openid does not work?",
1245 )
1247 def test_remove_not_existing_confirmed_openid(self): # pylint: disable=invalid-name
1248 """
1249 Try removing confirmed openid that doesn't exist
1250 """
1251 self.login()
1252 url = reverse("remove_confirmed_openid", args=[1234])
1253 response = self.client.post(url, follow=True)
1254 self.assertEqual(
1255 response.status_code, 200, "removing id does not redirect to profile?"
1256 )
1257 self.assertEqual(
1258 str(list(response.context[0]["messages"])[0]),
1259 "ID does not exist",
1260 "Removing not existing (confirmed) address, should produce an\
1261 error message!",
1262 )
1264 def test_remove_unconfirmed_openid(self):
1265 """
1266 Remove unconfirmed openid
1267 """
1268 self.test_add_openid(confirm=False)
1269 url = reverse(
1270 "remove_unconfirmed_openid",
1271 args=[self.user.unconfirmedopenid_set.first().id],
1272 )
1273 response = self.client.post(url, follow=True)
1274 self.assertEqual(
1275 response.status_code, 200, "unable to remove unconfirmed address?"
1276 )
1277 self.assertEqual(
1278 str(list(response.context[0]["messages"])[-1]),
1279 "ID removed",
1280 "Removing unconfirmed mail does not work?",
1281 )
1283 def test_remove_unconfirmed_non_existing_openid(
1284 self,
1285 ): # pylint: disable=invalid-name
1286 """
1287 Remove unconfirmed openid that doesn't exist
1288 """
1289 self.login()
1290 url = reverse("remove_unconfirmed_openid", args=[1234])
1291 response = self.client.post(url, follow=True)
1292 self.assertEqual(
1293 response.status_code, 200, "unable to remove unconfirmed address?"
1294 )
1295 self.assertEqual(
1296 str(list(response.context[0]["messages"])[0]),
1297 "ID does not exist",
1298 "Removing an non existing openid should return an error message",
1299 )
1301 def test_openid_redirect_view(self):
1302 """
1303 Test redirect view
1304 """
1305 self.test_add_openid(confirm=False)
1306 url = reverse(
1307 "openid_redirection", args=[self.user.unconfirmedopenid_set.first().id]
1308 )
1309 response = self.client.get(url, follow=True)
1310 self.assertEqual(
1311 response.status_code, 200, "unable to remove unconfirmed address?"
1312 )
1313 # self.assertContains(
1314 # response,
1315 # 'OpenID discovery failed: ', 1, 200,
1316 # 'This request must return an error in test mode'
1317 # )
1319 def test_set_photo_on_openid(self):
1320 """
1321 Test the set_photo function on our ConfirmedOpenId model.
1322 """
1323 self.test_add_openid()
1324 self.test_upload_image()
1325 self.assertIsNone(self.user.confirmedopenid_set.first().photo)
1326 self.user.confirmedopenid_set.first().set_photo(self.user.photo_set.first())
1327 self.assertEqual(
1328 self.user.confirmedopenid_set.first().photo,
1329 self.user.photo_set.first(),
1330 "set_photo did not work!?",
1331 )
1333 def test_avatar_url_mail(self, do_upload_and_confirm=True, size=(80, 80)):
1334 """
1335 Test fetching avatar via mail
1336 """
1337 if do_upload_and_confirm:
1338 self.test_upload_image()
1339 self.test_confirm_email()
1340 urlobj = urlsplit(
1341 libravatar_url(
1342 email=self.user.confirmedemail_set.first().email,
1343 size=size[0],
1344 )
1345 )
1346 url = f"{urlobj.path}?{urlobj.query}"
1347 response = self.client.get(url, follow=True)
1348 self.assertEqual(response.status_code, 200, "unable to fetch avatar?")
1349 photodata = Image.open(BytesIO(response.content))
1350 self.assertEqual(photodata.size, size, "Why is this not the correct size?")
1352 def test_avatar_url_openid(self):
1353 """
1354 Test fetching avatar via openid
1355 """
1356 self.test_assign_photo_to_openid()
1357 urlobj = urlsplit(
1358 libravatar_url(
1359 openid=self.user.confirmedopenid_set.first().openid,
1360 size=80,
1361 )
1362 )
1363 url = f"{urlobj.path}?{urlobj.query}"
1364 response = self.client.get(url, follow=True)
1365 self.assertEqual(response.status_code, 200, "unable to fetch avatar?")
1366 photodata = Image.open(BytesIO(response.content))
1367 self.assertEqual(photodata.size, (80, 80), "Why is this not the correct size?")
1369 def test_avatar_url_non_existing_mail_digest(self): # pylint: disable=invalid-name
1370 """
1371 Test fetching avatar via non existing mail digest
1372 """
1373 self.test_upload_image()
1374 self.test_confirm_email()
1375 urlobj = urlsplit(
1376 libravatar_url(
1377 email=self.user.confirmedemail_set.first().email,
1378 size=80,
1379 )
1380 )
1381 # Simply delete it, then it's digest is 'correct', but
1382 # the hash is no longer there
1383 addr = self.user.confirmedemail_set.first().email
1384 hashlib.md5(addr.strip().lower().encode("utf-8")).hexdigest()
1386 self.user.confirmedemail_set.first().delete()
1387 url = f"{urlobj.path}?{urlobj.query}"
1388 self.client.get(url, follow=True)
1389 # TODO: All these tests still fails under some circumstances - it needs further investigation
1390 # self.assertEqual(
1391 # response.redirect_chain[0][0],
1392 # f"/gravatarproxy/{digest}?s=80",
1393 # "Doesn't redirect to Gravatar?",
1394 # )
1395 # self.assertEqual(
1396 # response.redirect_chain[0][1], 302, "Doesn't redirect with 302?"
1397 # )
1398 # self.assertEqual(
1399 # response.redirect_chain[1][0],
1400 # f"/avatar/{digest}?s=80&forcedefault=y",
1401 # "Doesn't redirect with default forced on?",
1402 # )
1403 # self.assertEqual(
1404 # response.redirect_chain[1][1], 302, "Doesn't redirect with 302?"
1405 # )
1406 # self.assertEqual(
1407 # response.redirect_chain[2][0],
1408 # "/static/img/nobody/80.png",
1409 # "Doesn't redirect to static?",
1410 # )
1411 # self.assertRedirects(
1412 # response=response,
1413 # expected_url="/static/img/nobody/80.png",
1414 # msg_prefix="Why does this not redirect to Gravatar?",
1415 # )
1416 # Eventually one should check if the data is the same
1418 def test_avatar_url_non_existing_mail_digest_gravatarproxy_disabled(
1419 self,
1420 ): # pylint: disable=invalid-name
1421 """
1422 Test fetching avatar via non existing mail digest
1423 """
1424 self.test_upload_image()
1425 self.test_confirm_email()
1426 urlobj = urlsplit(
1427 libravatar_url(
1428 email=self.user.confirmedemail_set.first().email,
1429 size=80,
1430 )
1431 )
1432 # Simply delete it, then it digest is 'correct', but
1433 # the hash is no longer there
1434 self.user.confirmedemail_set.first().delete()
1435 url = f"{urlobj.path}?{urlobj.query}&gravatarproxy=n"
1436 response = self.client.get(url, follow=True)
1437 self.assertEqual(
1438 response.redirect_chain[0][0],
1439 "/static/img/nobody/80.png",
1440 "Doesn't redirect to static?",
1441 )
1443 # self.assertRedirects(
1444 # response=response,
1445 # expected_url="/static/img/nobody/80.png",
1446 # msg_prefix="Why does this not redirect to the default img?",
1447 # )
1448 # Eventually one should check if the data is the same
1450 def test_avatar_url_non_existing_mail_digest_w_default_mm(
1451 self,
1452 ): # pylint: disable=invalid-name
1453 """
1454 Test fetching avatar via non existing mail digest and default 'mm'
1455 """
1456 urlobj = urlsplit(
1457 libravatar_url(
1458 email="asdf@company.local",
1459 size=80,
1460 default="mm",
1461 )
1462 )
1463 url = f"{urlobj.path}?{urlobj.query}"
1464 self.client.get(url, follow=False)
1466 def test_avatar_url_non_existing_mail_digest_w_default_mm_gravatarproxy_disabled(
1467 self,
1468 ): # pylint: disable=invalid-name
1469 """
1470 Test fetching avatar via non existing mail digest and default 'mm'
1471 """
1472 urlobj = urlsplit(
1473 libravatar_url(
1474 email="asdf@company.local",
1475 size=80,
1476 default="mm",
1477 )
1478 )
1479 url = f"{urlobj.path}?{urlobj.query}&gravatarproxy=n"
1480 response = self.client.get(url, follow=True)
1481 self.assertEqual(
1482 response.redirect_chain[0][0],
1483 "/static/img/mm/80.png",
1484 "Doesn't redirect to static?",
1485 )
1487 # self.assertRedirects(
1488 # response=response,
1489 # expected_url="/static/img/mm/80.png",
1490 # msg_prefix="Why does this not redirect to the default img?",
1491 # )
1492 # Eventually one should check if the data is the same
1494 def test_avatar_url_non_existing_mail_digest_wo_default(
1495 self,
1496 ): # pylint: disable=invalid-name
1497 """
1498 Test fetching avatar via non existing mail digest and default 'mm'
1499 """
1500 urlobj = urlsplit(
1501 libravatar_url(
1502 email="asdf@company.local",
1503 size=80,
1504 )
1505 )
1506 digest = hashlib.md5("asdf@company.local".lower().encode("utf-8")).hexdigest()
1507 url = f"{urlobj.path}?{urlobj.query}"
1508 response = self.client.get(url, follow=True)
1509 self.assertEqual(
1510 response.redirect_chain[0][0],
1511 f"/gravatarproxy/{digest}?s=80",
1512 "Doesn't redirect to Gravatar?",
1513 )
1514 self.assertEqual(
1515 response.redirect_chain[0][1], 302, "Doesn't redirect with 302?"
1516 )
1517 self.assertEqual(
1518 response.redirect_chain[1][0],
1519 f"/avatar/{digest}?s=80&forcedefault=y",
1520 "Doesn't redirect with default forced on?",
1521 )
1522 self.assertEqual(
1523 response.redirect_chain[1][1], 302, "Doesn't redirect with 302?"
1524 )
1525 self.assertEqual(
1526 response.redirect_chain[2][0],
1527 "/static/img/nobody/80.png",
1528 "Doesn't redirect to static?",
1529 )
1531 # self.assertRedirects(
1532 # response=response,
1533 # expected_url="/static/img/nobody/80.png",
1534 # msg_prefix="Why does this not redirect to the default img?",
1535 # )
1536 # Eventually one should check if the data is the same
1538 def test_avatar_url_non_existing_mail_digest_wo_default_gravatarproxy_disabled(
1539 self,
1540 ): # pylint: disable=invalid-name
1541 """
1542 Test fetching avatar via non existing mail digest and default 'mm'
1543 """
1544 urlobj = urlsplit(
1545 libravatar_url(
1546 email="asdf@company.local",
1547 size=80,
1548 )
1549 )
1550 url = f"{urlobj.path}?{urlobj.query}&gravatarproxy=n"
1551 response = self.client.get(url, follow=True)
1552 self.assertEqual(
1553 response.redirect_chain[0][0],
1554 "/static/img/nobody/80.png",
1555 "Doesn't redirect to static?",
1556 )
1558 # self.assertRedirects(
1559 # response=response,
1560 # expected_url="/static/img/nobody/80.png",
1561 # msg_prefix="Why does this not redirect to the default img?",
1562 # )
1563 # Eventually one should check if the data is the same
1565 def test_avatar_url_default(self): # pylint: disable=invalid-name
1566 """
1567 Test fetching avatar for not existing mail with default specified
1568 """
1569 urlobj = urlsplit(
1570 libravatar_url(
1571 "xxx@xxx.xxx",
1572 size=80,
1573 default="/static/img/nobody.png",
1574 )
1575 )
1576 url = f"{urlobj.path}?{urlobj.query}"
1577 url += "&gravatarproxy=n"
1578 response = self.client.get(url, follow=False)
1579 self.assertEqual(response.status_code, 302, "Doesn't redirect with 302?")
1580 self.assertEqual(
1581 response["Location"],
1582 "/static/img/nobody.png",
1583 "Doesn't redirect to static img?",
1584 )
1586 def test_avatar_url_default_gravatarproxy_disabled(
1587 self,
1588 ): # pylint: disable=invalid-name
1589 """
1590 Test fetching avatar for not existing mail with default specified
1591 """
1592 urlobj = urlsplit(
1593 libravatar_url(
1594 "xxx@xxx.xxx",
1595 size=80,
1596 default="/static/img/nobody.png",
1597 )
1598 )
1599 url = f"{urlobj.path}?{urlobj.query}&gravatarproxy=n"
1600 response = self.client.get(url, follow=True)
1601 self.assertEqual(
1602 response.redirect_chain[0][0],
1603 "/static/img/nobody.png",
1604 "Doesn't redirect to static?",
1605 )
1607 def test_avatar_url_default_external(self): # pylint: disable=invalid-name
1608 """
1609 Test fetching avatar for not existing mail with external default specified
1610 This shall *not* redirect to the external site (CWE-601)!
1611 """
1612 default = "http://host.tld/img.png"
1613 size = 80
1614 urlobj = urlsplit(
1615 libravatar_url(
1616 "xxx@xxx.xxx",
1617 size=size,
1618 default=default,
1619 )
1620 )
1621 url = f"{urlobj.path}?{urlobj.query}"
1622 response = self.client.get(url, follow=False)
1623 self.assertRedirects(
1624 response=response,
1625 expected_url=f"/gravatarproxy/fb7a6d7f11365642d44ba66dc57df56f?s={size}",
1626 fetch_redirect_response=False,
1627 msg_prefix="Why does this not redirect to the default img?",
1628 )
1630 def test_avatar_url_default_external_trusted(self): # pylint: disable=invalid-name
1631 """
1632 Test fetching avatar for not existing mail with external default specified
1633 """
1634 default = "https://ui-avatars.com/api/blah"
1635 urlobj = urlsplit(
1636 libravatar_url(
1637 "xxx@xxx.xxx",
1638 size=80,
1639 default=default,
1640 )
1641 )
1642 url = f"{urlobj.path}?{urlobj.query}"
1643 response = self.client.get(url, follow=False)
1644 self.assertRedirects(
1645 response=response,
1646 expected_url="/gravatarproxy/fb7a6d7f11365642d44ba66dc57df56f?s=80&default=https://ui-avatars.com/api/blah",
1647 fetch_redirect_response=False,
1648 msg_prefix="Why does this not redirect to the default img?",
1649 )
1651 def test_avatar_url_default_external_gravatarproxy_disabled(
1652 self,
1653 ): # pylint: disable=invalid-name
1654 """
1655 Test fetching avatar for not existing mail with external default specified
1656 This shall *not* redirect to the external site (CWE-601)!
1657 """
1658 default = "http://host.tld/img.png"
1659 urlobj = urlsplit(
1660 libravatar_url(
1661 "xxx@xxx.xxx",
1662 size=80,
1663 default=default,
1664 )
1665 )
1666 url = f"{urlobj.path}?{urlobj.query}&gravatarproxy=n"
1667 response = self.client.get(url, follow=False)
1668 self.assertRedirects(
1669 response=response,
1670 expected_url="/static/img/nobody/80.png",
1671 fetch_redirect_response=False,
1672 msg_prefix="Why does this not redirect to the default img?",
1673 )
1675 def test_crop_photo(self):
1676 """
1677 Test cropping photo
1678 """
1679 self.test_upload_image()
1680 self.test_confirm_email()
1681 url = reverse("crop_photo", args=[self.user.photo_set.first().pk])
1682 response = self.client.post(
1683 url,
1684 {
1685 "x": 10,
1686 "y": 10,
1687 "w": 20,
1688 "h": 20,
1689 },
1690 follow=True,
1691 )
1692 self.assertEqual(response.status_code, 200, "unable to crop?")
1693 self.test_avatar_url_mail(do_upload_and_confirm=False, size=(20, 20))
1694 img = Image.open(BytesIO(self.user.photo_set.first().data))
1695 self.assertEqual(
1696 img.size, (20, 20), "cropped to 20x20, but resulting image isn't 20x20!?"
1697 )
1699 def test_password_change_view(self):
1700 """
1701 Test password change view
1702 """
1703 self.login()
1704 url = reverse("password_change")
1705 response = self.client.get(url)
1706 self.assertEqual(
1707 response.status_code, 200, "unable to view password change view?"
1708 )
1710 def test_password_change_view_post_wrong_old_pw(self):
1711 """
1712 Test password change view post
1713 """
1714 self.login()
1715 response = self.client.post(
1716 reverse("password_change"),
1717 {
1718 "old_password": "xxx",
1719 "new_password1": self.password,
1720 "new_password2": self.password,
1721 },
1722 follow=True,
1723 )
1725 self.assertContains(
1726 response,
1727 "Your old password was entered incorrectly. Please enter it again.",
1728 1,
1729 200,
1730 "Old password as entered incorrectly, site should raise an error",
1731 )
1733 def test_password_change_view_post_wrong_new_password1(self):
1734 """
1735 Test password change view post
1736 """
1737 self.login()
1738 response = self.client.post(
1739 reverse("password_change"),
1740 {
1741 "old_password": self.password,
1742 "new_password1": f"{self.password}.",
1743 "new_password2": self.password,
1744 },
1745 follow=True,
1746 )
1747 self.assertContains(
1748 response,
1749 "The two password fields did",
1750 1,
1751 200,
1752 "Old password was entered incorrectly, site should raise an error",
1753 )
1755 def test_password_change_view_post_wrong_new_password2(self):
1756 """
1757 Test password change view post
1758 """
1759 self.login()
1760 response = self.client.post(
1761 reverse("password_change"),
1762 {
1763 "old_password": self.password,
1764 "new_password1": self.password,
1765 "new_password2": f"{self.password}.",
1766 },
1767 follow=True,
1768 )
1770 self.assertContains(
1771 response,
1772 "The two password fields did",
1773 1,
1774 200,
1775 "Old password as entered incorrectly, site should raise an error",
1776 )
1778 def test_password_change_view_post_common_password(self):
1779 """
1780 Test password change view post
1781 """
1782 self.login()
1783 response = self.client.post(
1784 reverse("password_change"),
1785 {
1786 "old_password": self.password,
1787 "new_password1": "Hallo",
1788 "new_password2": "Hallo",
1789 },
1790 follow=True,
1791 )
1793 self.assertContains(
1794 response,
1795 "This password is too common.",
1796 1,
1797 200,
1798 "Common password, site should raise an error",
1799 )
1801 def test_profile_must_list_first_and_lastname(self):
1802 """
1803 Test if profile view correctly lists first -/last name
1804 """
1805 self.login()
1806 response = self.client.get(reverse("profile"))
1807 self.assertContains(
1808 response,
1809 self.first_name,
1810 1,
1811 200,
1812 "First name not listed in profile page",
1813 )
1814 self.assertContains(
1815 response,
1816 self.last_name,
1817 1,
1818 200,
1819 "Last name not listed in profile page",
1820 )
1821 self.assertContains(
1822 response,
1823 f"{self.first_name} {self.last_name}",
1824 1,
1825 200,
1826 "First and last name not correctly listed in profile page",
1827 )
1829 def test_password_reset_page(self):
1830 """
1831 Just test if the password reset page come up correctly
1832 """
1833 response = self.client.get(reverse("password_reset"))
1834 self.assertEqual(response.status_code, 200, "no 200 ok?")
1836 def test_password_reset_wo_mail(self):
1837 """
1838 Test if the password reset doesn't error out
1839 if the mail address doesn't exist
1840 """
1841 # Avoid sending out mails
1842 settings.EMAIL_BACKEND = "django.core.mail.backends.locmem.EmailBackend"
1844 # Empty database / eliminate existing users
1845 User.objects.all().delete()
1846 url = reverse("password_reset")
1847 response = self.client.post(
1848 url,
1849 {
1850 "email": "asdf@asdf.local",
1851 },
1852 follow=True,
1853 )
1854 self.assertEqual(response.status_code, 200, "password reset page not working?")
1855 self.assertEqual(
1856 len(mail.outbox),
1857 0,
1858 "user does not exist, there should be no mail in the outbox!",
1859 )
1861 def test_password_reset_w_mail(self):
1862 """
1863 Test if the password reset works correctly with email in
1864 User object
1865 """
1866 # Avoid sending out mails
1867 settings.EMAIL_BACKEND = "django.core.mail.backends.locmem.EmailBackend"
1869 url = reverse("password_reset")
1870 # Our test user doesn't have an email address by default - but we need one set
1871 self.user.email = "asdf@asdf.local"
1872 self.user.save()
1873 response = self.client.post(
1874 url,
1875 {
1876 "email": self.user.email,
1877 },
1878 follow=True,
1879 )
1880 self.assertEqual(response.status_code, 200, "password reset page not working?")
1881 self.assertEqual(
1882 len(mail.outbox), 1, "User exists, there should be a mail in the outbox!"
1883 )
1884 self.assertEqual(
1885 mail.outbox[0].to[0],
1886 self.user.email,
1887 "Sending mails to the wrong \
1888 mail address?",
1889 )
1891 def test_password_reset_w_confirmed_mail(self):
1892 """
1893 Test if the password reset works correctly with confirmed
1894 mail
1895 """
1896 # Avoid sending out mails
1897 settings.EMAIL_BACKEND = "django.core.mail.backends.locmem.EmailBackend"
1899 url = reverse("password_reset")
1900 # Our test user doesn't have a confirmed mail identity - add one
1901 self.user.confirmedemail_set.create(email="asdf@asdf.local")
1902 self.user.save()
1904 response = self.client.post(
1905 url,
1906 {
1907 "email": self.user.confirmedemail_set.first().email,
1908 },
1909 follow=True,
1910 )
1911 # Since the object is touched in another process, we need to refresh it
1912 self.user.refresh_from_db()
1913 self.assertEqual(response.status_code, 200, "password reset page not working?")
1914 self.assertEqual(
1915 self.user.email,
1916 self.user.confirmedemail_set.first().email,
1917 "The password reset view, should have corrected this!",
1918 )
1919 self.assertEqual(
1920 len(mail.outbox), 1, "user exists, there should be a mail in the outbox!"
1921 )
1922 self.assertEqual(
1923 mail.outbox[0].to[0],
1924 self.user.email,
1925 "why are we sending mails to the wrong mail address?",
1926 )
1928 def test_password_reset_w_confirmed_mail_no_password(self):
1929 """
1930 Test password reset for user with confirmed email but no password set.
1931 This tests the specific case that was failing with Django 4.2+ where
1932 User.objects.make_random_password() was deprecated.
1934 Reproduces the scenario where a user has a confirmed email address
1935 but their password field is empty or starts with "!" (unusable password).
1936 """
1937 # Avoid sending out mails
1938 settings.EMAIL_BACKEND = "django.core.mail.backends.locmem.EmailBackend"
1940 # Create a user with no usable password (starts with "!")
1941 test_user = User.objects.create_user(
1942 username="testuser_no_pass",
1943 email="", # No email in User model
1944 )
1945 # Set an unusable password (starts with "!")
1946 test_user.set_unusable_password()
1947 test_user.save()
1949 # Add a confirmed email (this is the key scenario)
1950 confirmed_email = test_user.confirmedemail_set.create(email="test@example.com")
1952 # Verify the user has no usable password
1953 self.assertTrue(
1954 test_user.password.startswith("!"),
1955 "Test user should have unusable password starting with '!'",
1956 )
1958 url = reverse("password_reset")
1960 # Attempt password reset - this should work without AttributeError
1961 response = self.client.post(
1962 url,
1963 {
1964 "email": confirmed_email.email,
1965 },
1966 follow=True,
1967 )
1969 # Refresh user from database to see changes
1970 test_user.refresh_from_db()
1972 # Verify the request succeeded
1973 self.assertEqual(response.status_code, 200, "password reset page not working?")
1975 # Verify that the user now has a usable password (no longer starts with "!")
1976 self.assertFalse(
1977 test_user.password.startswith("!"),
1978 "User should now have a usable password after reset",
1979 )
1981 # Verify the email was set on the user object
1982 self.assertEqual(
1983 test_user.email,
1984 confirmed_email.email,
1985 "The password reset view should have set the email on user object",
1986 )
1988 # Verify a reset email was sent
1989 self.assertEqual(
1990 len(mail.outbox), 1, "user exists, there should be a mail in the outbox!"
1991 )
1992 self.assertEqual(
1993 mail.outbox[0].to[0],
1994 test_user.email,
1995 "reset email should be sent to the correct address",
1996 )
1998 # Clean up
1999 test_user.delete()
2001 def test_export(self):
2002 """
2003 Test if export works
2004 """
2006 # Create well known strings to check if export
2007 # works as expected
2008 self.user.confirmedemail_set.create(email="asdf@asdf.local")
2009 self.user.confirmedopenid_set.create(openid="http://asdf.asdf.local")
2010 self.user.save()
2012 # Ensure we have a photo uploaded
2013 self.test_upload_image()
2015 self.login()
2016 self.client.get(reverse("export"))
2017 response = self.client.post(
2018 reverse("export"),
2019 {},
2020 follow=False,
2021 )
2022 self.assertIsInstance(response.content, bytes)
2023 fh = gzip.open(BytesIO(response.content), "rb")
2024 content = fh.read()
2025 fh.close()
2026 root = xml.etree.ElementTree.fromstring(content)
2027 self.assertEqual(root.tag, "{%s}user" % settings.SCHEMAROOT)
2028 self.assertEqual(
2029 root.findall("{%s}account" % settings.SCHEMAROOT)[0].items()[0][1],
2030 self.user.username,
2031 )
2032 self.assertEqual(
2033 root.findall("{%s}account" % settings.SCHEMAROOT)[0].items()[1][1],
2034 self.user.password,
2035 )
2037 self.assertEqual(
2038 root.findall("{%s}emails" % settings.SCHEMAROOT)[0][0].text,
2039 self.user.confirmedemail_set.first().email,
2040 )
2041 self.assertEqual(
2042 root.findall("{%s}openids" % settings.SCHEMAROOT)[0][0].text,
2043 self.user.confirmedopenid_set.first().openid,
2044 )
2046 data = root.findall("{%s}photos" % settings.SCHEMAROOT)[0][0].text
2048 data = data.strip("'")
2049 data = data.strip("\\n")
2050 data = data.lstrip("b'")
2051 bindata = base64.decodebytes(bytes(data, "utf-8"))
2052 image = Image.open(BytesIO(bindata))
2053 self.assertTrue(hasattr(image, "png"))
2055 def test_upload_export(self):
2056 """
2057 Test if uploading export works
2058 """
2060 # Ensure we have data in place
2061 self.test_export()
2063 self.login()
2064 self.client.get(reverse("export"))
2065 response = self.client.post(
2066 reverse("export"),
2067 {},
2068 follow=False,
2069 )
2070 self.assertIsInstance(response.content, bytes)
2072 fh_gzip = gzip.open(BytesIO(response.content), "rb")
2073 fh = BytesIO(response.content)
2075 response = self._uploading_export_check(
2076 fh_gzip, "Unable to parse file: Not a gzipped file"
2077 )
2078 response = self._uploading_export_check(fh, "Choose items to be imported")
2079 self.assertContains(
2080 response,
2081 "asdf@asdf.local",
2082 2,
2083 200,
2084 "Upload didn't work?",
2085 )
2087 def _uploading_export_check(self, fh, message):
2088 """
2089 Helper function to upload an export
2090 """
2091 result = self.client.post(
2092 reverse("upload_export"),
2093 data={"not_porn": "on", "can_distribute": "on", "export_file": fh},
2094 follow=True,
2095 )
2096 fh.close()
2097 self.assertEqual(result.status_code, 200, "Upload worked")
2098 self.assertContains(result, message, 1, 200, "Upload didn't work?")
2100 return result
2102 def test_preferences_page(self):
2103 """
2104 Test if preferences page works
2105 """
2107 self.login()
2108 self.client.get(reverse("user_preference"))
2110 def test_delete_user(self):
2111 """
2112 Test if deleting user profile works
2113 """
2115 self.login()
2116 self.client.get(reverse("delete"))
2117 response = self.client.post(
2118 reverse("delete"),
2119 data={"password": self.password},
2120 follow=True,
2121 )
2122 self.assertEqual(response.status_code, 200, "Deletion worked")
2123 self.assertEqual(User.objects.count(), 0, "No user there any more")
2125 def test_confirm_already_confirmed(self):
2126 """
2127 Try to confirm a mail address that has been confirmed (by another user)
2128 """
2130 # Add mail address (stays unconfirmed)
2131 self.test_add_email()
2133 # Create a second user that will conflict
2134 user2 = User.objects.create_user(
2135 username=f"{self.username}1",
2136 password=self.password,
2137 first_name=self.first_name,
2138 last_name=self.last_name,
2139 )
2140 ConfirmedEmail.objects.create(
2141 email=self.email,
2142 user=user2,
2143 )
2145 # Just to be sure
2146 self.assertEqual(
2147 self.user.unconfirmedemail_set.first().email,
2148 user2.confirmedemail_set.first().email,
2149 "Mail not the same?",
2150 )
2152 # This needs to be caught
2153 with contextlib.suppress(AssertionError):
2154 self.test_confirm_email()
2155 # Request a random page, so we can access the messages
2156 response = self.client.get(reverse("profile"))
2157 self.assertEqual(
2158 str(list(response.context[0]["messages"])[0]),
2159 "This mail address has been taken already and cannot be confirmed",
2160 "This should return an error message!",
2161 )
2164class OpenIDErrorHandlingTestCase(TestCase):
2165 """
2166 Test cases for OpenID error handling and error.html template coverage
2167 """
2169 def setUp(self):
2170 """Set up test user and client"""
2171 self.username = random_string()
2172 self.password = random_string()
2173 self.user = User.objects.create_user(
2174 username=self.username,
2175 password=self.password,
2176 )
2177 self.client = Client()
2179 def login(self):
2180 """Login as test user"""
2181 self.client.login(username=self.username, password=self.password)
2183 def test_openid_discovery_failure_renders_error_template(self):
2184 """
2185 Test that OpenID discovery failure renders error.html template
2186 """
2187 from unittest.mock import patch, MagicMock
2188 from openid.consumer import consumer
2189 from ivatar.ivataraccount.models import UnconfirmedOpenId
2191 self.login()
2193 # Create an unconfirmed OpenID
2194 unconfirmed = UnconfirmedOpenId.objects.create(
2195 user=self.user,
2196 openid="http://invalid-openid-provider.example.com/",
2197 )
2199 # Mock the OpenID consumer to raise DiscoveryFailure
2200 with patch(
2201 "ivatar.ivataraccount.views.consumer.Consumer"
2202 ) as mock_consumer_class:
2203 mock_consumer = MagicMock()
2204 mock_consumer_class.return_value = mock_consumer
2205 # Create a proper DiscoveryFailure with required http_response parameter
2206 mock_response = MagicMock()
2207 mock_response.status_code = 404
2208 discovery_failure = consumer.DiscoveryFailure(
2209 "Invalid provider", mock_response
2210 )
2211 mock_consumer.begin.side_effect = discovery_failure
2213 # Make request to openid_redirection view
2214 response = self.client.get(
2215 reverse("openid_redirection", args=[unconfirmed.id]), follow=True
2216 )
2218 # Verify we get redirected to profile with error message
2219 self.assertEqual(response.status_code, 200)
2220 self.assertRedirects(response, reverse("profile"))
2222 # Check that error message is in the response
2223 messages = list(response.context[0]["messages"])
2224 self.assertTrue(
2225 any("OpenID discovery failed" in str(msg) for msg in messages)
2226 )
2228 def test_openid_confirmation_failure_renders_error_template(self):
2229 """
2230 Test that OpenID confirmation failure renders error.html template
2231 """
2232 from unittest.mock import patch, MagicMock
2233 from openid.consumer import consumer
2234 from ivatar.ivataraccount.models import UnconfirmedOpenId
2236 self.login()
2238 # Create an unconfirmed OpenID
2239 unconfirmed = UnconfirmedOpenId.objects.create(
2240 user=self.user,
2241 openid="http://test-provider.example.com/",
2242 )
2244 # Mock the OpenID consumer to return FAILURE status
2245 with patch(
2246 "ivatar.ivataraccount.views.consumer.Consumer"
2247 ) as mock_consumer_class:
2248 mock_consumer = MagicMock()
2249 mock_consumer_class.return_value = mock_consumer
2251 # Create a mock response with FAILURE status
2252 mock_response = MagicMock()
2253 mock_response.status = consumer.FAILURE
2254 mock_response.message = "Authentication failed"
2255 mock_consumer.complete.return_value = mock_response
2257 # Make request to confirm_openid view
2258 response = self.client.get(
2259 reverse("confirm_openid", args=[unconfirmed.id]), follow=True
2260 )
2262 # Verify we get redirected to profile with error message
2263 self.assertEqual(response.status_code, 200)
2264 self.assertRedirects(response, reverse("profile"))
2266 # Check that error message is in the response
2267 messages = list(response.context[0]["messages"])
2268 self.assertTrue(any("Confirmation failed" in str(msg) for msg in messages))
2270 def test_openid_cancellation_renders_error_template(self):
2271 """
2272 Test that OpenID cancellation renders error.html template
2273 """
2274 from unittest.mock import patch, MagicMock
2275 from openid.consumer import consumer
2276 from ivatar.ivataraccount.models import UnconfirmedOpenId
2278 self.login()
2280 # Create an unconfirmed OpenID
2281 unconfirmed = UnconfirmedOpenId.objects.create(
2282 user=self.user,
2283 openid="http://test-provider.example.com/",
2284 )
2286 # Mock the OpenID consumer to return CANCEL status
2287 with patch(
2288 "ivatar.ivataraccount.views.consumer.Consumer"
2289 ) as mock_consumer_class:
2290 mock_consumer = MagicMock()
2291 mock_consumer_class.return_value = mock_consumer
2293 # Create a mock response with CANCEL status
2294 mock_response = MagicMock()
2295 mock_response.status = consumer.CANCEL
2296 mock_consumer.complete.return_value = mock_response
2298 # Make request to confirm_openid view
2299 response = self.client.get(
2300 reverse("confirm_openid", args=[unconfirmed.id]), follow=True
2301 )
2303 # Verify we get redirected to profile with error message
2304 self.assertEqual(response.status_code, 200)
2305 self.assertRedirects(response, reverse("profile"))
2307 # Check that error message is in the response
2308 messages = list(response.context[0]["messages"])
2309 self.assertTrue(any("Cancelled by user" in str(msg) for msg in messages))
2311 def test_openid_unknown_error_renders_error_template(self):
2312 """
2313 Test that unknown OpenID verification error renders error.html template
2314 """
2315 from unittest.mock import patch, MagicMock
2316 from ivatar.ivataraccount.models import UnconfirmedOpenId
2318 self.login()
2320 # Create an unconfirmed OpenID
2321 unconfirmed = UnconfirmedOpenId.objects.create(
2322 user=self.user,
2323 openid="http://test-provider.example.com/",
2324 )
2326 # Mock the OpenID consumer to return unknown status
2327 with patch(
2328 "ivatar.ivataraccount.views.consumer.Consumer"
2329 ) as mock_consumer_class:
2330 mock_consumer = MagicMock()
2331 mock_consumer_class.return_value = mock_consumer
2333 # Create a mock response with unknown status
2334 mock_response = MagicMock()
2335 mock_response.status = "UNKNOWN_STATUS"
2336 mock_consumer.complete.return_value = mock_response
2338 # Make request to confirm_openid view
2339 response = self.client.get(
2340 reverse("confirm_openid", args=[unconfirmed.id]), follow=True
2341 )
2343 # Verify we get redirected to profile with error message
2344 self.assertEqual(response.status_code, 200)
2345 self.assertRedirects(response, reverse("profile"))
2347 # Check that error message is in the response
2348 messages = list(response.context[0]["messages"])
2349 self.assertTrue(
2350 any("Unknown verification error" in str(msg) for msg in messages)
2351 )
2353 def test_openid_nonexistent_id_error(self):
2354 """
2355 Test that accessing non-existent OpenID ID shows error message
2356 """
2357 self.login()
2359 # Try to access a non-existent OpenID ID
2360 response = self.client.get(
2361 reverse("openid_redirection", args=[99999]), follow=True
2362 )
2364 # Verify we get redirected to profile with error message
2365 self.assertEqual(response.status_code, 200)
2366 self.assertRedirects(response, reverse("profile"))
2368 # Check that error message is in the response
2369 messages = list(response.context[0]["messages"])
2370 self.assertTrue(any("ID does not exist" in str(msg) for msg in messages))
2372 def test_django_openid_auth_failure_template_coverage(self):
2373 """
2374 Test that django-openid-auth failure template uses error.html
2375 This test verifies the OpenID login page renders correctly
2376 """
2377 # Try to access the OpenID login page
2378 response = self.client.get(reverse("openid-login"))
2379 self.assertEqual(response.status_code, 200)
2381 # The login page should render successfully
2382 self.assertContains(response, "OpenID Login")
2384 def test_error_template_direct_rendering(self):
2385 """
2386 Test error.html template directly to ensure it renders correctly
2387 """
2388 from django.test import RequestFactory
2389 from django.template import Context, Template
2390 from django.contrib.auth.models import AnonymousUser
2392 # Test with authenticated user
2393 factory = RequestFactory()
2394 request = factory.get("/")
2395 request.user = self.user
2397 # Test template with error message
2398 template_content = """
2399 {% extends 'error.html' %}
2400 {% load i18n %}
2401 {% block errormessage %}
2402 {% trans 'Test error message:' %} {{ errormessage }}
2403 {% endblock errormessage %}
2404 """
2406 template = Template(template_content)
2407 context = Context(
2408 {
2409 "request": request,
2410 "errormessage": "This is a test error",
2411 "user": self.user,
2412 }
2413 )
2415 rendered = template.render(context)
2417 # Verify the template renders without errors
2418 self.assertIn("Error!", rendered)
2419 self.assertIn("This is a test error", rendered)
2420 # Check for the profile link in the navbar (not in the backlink block)
2421 self.assertIn("/accounts/profile/", rendered)
2423 # Test with anonymous user
2424 request.user = AnonymousUser()
2425 context = Context(
2426 {
2427 "request": request,
2428 "errormessage": "This is a test error",
2429 "user": AnonymousUser(),
2430 }
2431 )
2433 rendered = template.render(context)
2435 # Verify the template renders without errors for anonymous users
2436 self.assertIn("Error!", rendered)
2437 self.assertIn("This is a test error", rendered)
2438 # Should not contain profile link for anonymous users
2439 self.assertNotIn("/accounts/profile/", rendered)
2441 def test_openid_failure_template_inheritance(self):
2442 """
2443 Test that openid/failure.html properly extends error.html
2444 """
2445 from django.test import RequestFactory
2446 from django.template import Context, Template
2448 factory = RequestFactory()
2449 request = factory.get("/")
2450 request.user = self.user
2452 # Test the openid/failure.html template
2453 template_content = """
2454 {% extends 'error.html' %}
2455 {% load i18n %}
2456 {% block errormessage %}
2457 {% trans 'OpenID error:' %} {{ message }}
2458 {% endblock errormessage %}
2459 """
2461 template = Template(template_content)
2462 context = Context(
2463 {
2464 "request": request,
2465 "message": "Authentication failed",
2466 "user": self.user,
2467 }
2468 )
2470 rendered = template.render(context)
2472 # Verify the template renders correctly
2473 self.assertIn("Error!", rendered)
2474 self.assertIn("OpenID error:", rendered)
2475 self.assertIn("Authentication failed", rendered)
2476 # Check for the profile link in the navbar (not in the backlink block)
2477 self.assertIn("/accounts/profile/", rendered)