from path import Path from PIL import Image from django.contrib.auth import get_user_model from django.core.exceptions import ValidationError from django.test import TestCase, override_settings from django.utils.crypto import get_random_string from misago.conf import settings from ..avatars import ( set_default_avatar, dynamic, gallery, gravatar, store, uploaded) from ..models import Avatar, AvatarGallery class AvatarsStoreTests(TestCase): def test_store(self): """store successfully stores and deletes avatar""" User = get_user_model() user = User.objects.create_user('Bob', 'bob@bob.com', 'pass123') test_image = Image.new("RGBA", (100, 100), 0) store.store_new_avatar(user, test_image) # reload user test_user = User.objects.get(pk=user.pk) # assert that avatars were stored in media avatars_dict = {} for avatar in user.avatar_set.all(): self.assertTrue(avatar.image.url) self.assertEqual(avatar.url, avatar.image.url) avatars_dict[avatar.size] = avatar # asserts that user.avatars cache was set self.assertEqual(len(avatars_dict), len(settings.MISAGO_AVATARS_SIZES)) self.assertEqual(len(user.avatars), len(settings.MISAGO_AVATARS_SIZES)) self.assertEqual(len(user.avatars), len(avatars_dict)) for avatar in user.avatars: self.assertIn(avatar['size'], settings.MISAGO_AVATARS_SIZES) self.assertEqual(avatar['url'], avatars_dict[avatar['size']].url) # another avatar change deleted old avatars store.store_new_avatar(user, test_image) for old_avatar in avatars_dict.values(): avatar_path = Path(old_avatar.image.path) self.assertFalse(avatar_path.exists()) self.assertFalse(avatar_path.isfile()) with self.assertRaises(Avatar.DoesNotExist): Avatar.objects.get(pk=old_avatar.pk) # and updated user avatars again new_avatars_dict = {} for size in settings.MISAGO_AVATARS_SIZES: avatar = user.avatar_set.get(size=size) self.assertTrue(avatar.image.url) self.assertEqual(avatar.url, avatar.image.url) new_avatars_dict[size] = avatar self.assertTrue(avatars_dict != new_avatars_dict) # asserts that user.avatars cache was updated self.assertEqual(len(user.avatars), len(settings.MISAGO_AVATARS_SIZES)) for avatar in user.avatars: self.assertIn(avatar['size'], settings.MISAGO_AVATARS_SIZES) self.assertEqual(avatar['url'], new_avatars_dict[avatar['size']].url) # delete avatar store.delete_avatar(user) for removed_avatar in new_avatars_dict.values(): avatar_path = Path(removed_avatar.image.path) self.assertFalse(avatar_path.exists()) self.assertFalse(avatar_path.isfile()) with self.assertRaises(Avatar.DoesNotExist): Avatar.objects.get(pk=removed_avatar.pk) class AvatarSetterTests(TestCase): def setUp(self): User = get_user_model() self.user = User.objects.create_user( 'Bob', 'kontakt@rpiton.com', 'pass123') self.user.avatars = None self.user.save() def tearDown(self): store.delete_avatar(self.user) def get_current_user(self): User = get_user_model() return User.objects.get(pk=self.user.pk) def assertNoAvatarIsSet(self): user = self.get_current_user() self.assertFalse(user.avatars) def assertAvatarWasSet(self): user = self.get_current_user() avatars_dict = {} for avatar in user.avatar_set.all(): avatar_path = Path(avatar.image.path) self.assertTrue(avatar_path.exists()) self.assertTrue(avatar_path.isfile()) avatars_dict[avatar.size] = avatar self.assertEqual(len(user.avatars), len(avatars_dict)) self.assertEqual(len(user.avatars), len(settings.MISAGO_AVATARS_SIZES)) def test_dynamic_avatar(self): """dynamic avatar gets created""" self.assertNoAvatarIsSet() dynamic.set_avatar(self.user) self.assertAvatarWasSet() def test_random_gallery_avatar_no_gallery(self): """runtime error is raised when no gallery exists""" with self.assertRaises(RuntimeError): gallery.set_random_avatar(self.user) def test_random_gallery_avatar(self): """dynamic avatar gets created""" gallery.load_avatar_galleries() self.assertNoAvatarIsSet() gallery.set_random_avatar(self.user) self.assertAvatarWasSet() def test_selected_gallery_avatar(self): """dynamic avatar gets created""" gallery.load_avatar_galleries() self.assertNoAvatarIsSet() test_avatar = AvatarGallery.objects.order_by('id').last() gallery.set_avatar(self.user, test_avatar) self.assertAvatarWasSet() def test_gravatar(self): """dynamic avatar gets created""" self.assertNoAvatarIsSet() gravatar.set_avatar(self.user) self.assertAvatarWasSet() def test_default_avatar_gravatar(self): """default gravatar gets set""" self.assertNoAvatarIsSet() set_default_avatar(self.user, 'gravatar', 'dynamic') self.assertAvatarWasSet() def test_default_avatar_gravatar_fallback_dynamic(self): """default gravatar fails but fallback dynamic works""" gibberish_email = '%s@%s.%s' % ( get_random_string(6), get_random_string(6), get_random_string(3)) self.user.set_email(gibberish_email) self.user.save() self.assertNoAvatarIsSet() set_default_avatar(self.user, 'gravatar', 'dynamic') self.assertAvatarWasSet() def test_default_avatar_gravatar_fallback_empty_gallery(self): """default both gravatar and fallback fail set""" gibberish_email = '%s@%s.%s' % ( get_random_string(6), get_random_string(6), get_random_string(3)) self.user.set_email(gibberish_email) self.user.save() self.assertNoAvatarIsSet() self.user.save() set_default_avatar(self.user, 'gravatar', 'gallery') self.assertAvatarWasSet() class MockAvatarFile(object): def __init__(self, size=None, name=None, mime=None): self.size = size self.name = name self.content_type = mime class UploadedAvatarTests(TestCase): def test_clean_crop(self): """crop validation and cleaning""" image = Image.new("RGBA", (200, 200), 0) with self.assertRaises(ValidationError): uploaded.clean_crop(image, "abc") with self.assertRaises(ValidationError): uploaded.clean_crop(image, {}) with self.assertRaises(ValidationError): uploaded.clean_crop(image, {'offset': {'x': 'ugabuga'}}) with self.assertRaises(ValidationError): uploaded.clean_crop(image, { 'offset': { 'x': 0, 'y': 0, }, 'zoom': -2 }) with self.assertRaises(ValidationError): uploaded.clean_crop(image, { 'offset': { 'x': 0, 'y': 0, }, 'zoom': 2 }) def test_uploaded_image_size_validation(self): """uploaded image size is validated""" image = MockAvatarFile(size=settings.avatar_upload_limit * 2024) with self.assertRaises(ValidationError): uploaded.validate_file_size(image) image = MockAvatarFile(size=settings.avatar_upload_limit * 1000) uploaded.validate_file_size(image) def test_uploaded_image_extension_validation(self): """uploaded image extension is validated""" for invalid_extension in ('.txt', '.zip', '.py', '.tiff'): with self.assertRaises(ValidationError): image = MockAvatarFile(name='test%s' % invalid_extension) uploaded.validate_extension(image) for valid_extension in uploaded.ALLOWED_EXTENSIONS: image = MockAvatarFile(name='test%s' % valid_extension) uploaded.validate_extension(image) def test_uploaded_image_mime_validation(self): """uploaded image mime type is validated""" image = MockAvatarFile(mime='fake/mime') with self.assertRaises(ValidationError): uploaded.validate_mime(image) for valid_mime in uploaded.ALLOWED_MIME_TYPES: image = MockAvatarFile(mime=valid_mime) uploaded.validate_mime(image)