diff --git a/zerver/lib/actions.py b/zerver/lib/actions.py index 6adb94bf24..fbd0316a68 100644 --- a/zerver/lib/actions.py +++ b/zerver/lib/actions.py @@ -7385,9 +7385,13 @@ def notify_realm_emoji(realm: Realm) -> None: def check_add_realm_emoji( realm: Realm, name: str, author: UserProfile, image_file: IO[bytes] ) -> Optional[RealmEmoji]: - realm_emoji = RealmEmoji(realm=realm, name=name, author=author) - realm_emoji.full_clean() - realm_emoji.save() + try: + realm_emoji = RealmEmoji(realm=realm, name=name, author=author) + realm_emoji.full_clean() + realm_emoji.save() + except django.db.utils.IntegrityError: + # Match the string in upload_emoji. + raise JsonableError(_("A custom emoji with this name already exists.")) emoji_file_name = get_emoji_file_name(image_file.name, realm_emoji.id) diff --git a/zerver/migrations/0372_realmemoji_unique_realm_emoji_when_false_deactivated.py b/zerver/migrations/0372_realmemoji_unique_realm_emoji_when_false_deactivated.py new file mode 100644 index 0000000000..15baac4331 --- /dev/null +++ b/zerver/migrations/0372_realmemoji_unique_realm_emoji_when_false_deactivated.py @@ -0,0 +1,21 @@ +# Generated by Django 3.2.9 on 2021-12-09 19:46 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("zerver", "0371_invalid_characters_in_topics"), + ] + + operations = [ + migrations.AddConstraint( + model_name="realmemoji", + constraint=models.UniqueConstraint( + condition=models.Q(("deactivated", False)), + fields=("realm", "name"), + name="unique_realm_emoji_when_false_deactivated", + ), + ), + ] diff --git a/zerver/models.py b/zerver/models.py index c8e6508c9b..6db8776be7 100644 --- a/zerver/models.py +++ b/zerver/models.py @@ -1048,6 +1048,15 @@ class RealmEmoji(models.Model): def __str__(self) -> str: return f"" + class Meta: + constraints = [ + models.UniqueConstraint( + fields=["realm", "name"], + condition=Q(deactivated=False), + name="unique_realm_emoji_when_false_deactivated", + ), + ] + def get_realm_emoji_dicts( realm: Realm, only_active_emojis: bool = False diff --git a/zerver/tests/test_realm_emoji.py b/zerver/tests/test_realm_emoji.py index ed7cf05073..7b535baf55 100644 --- a/zerver/tests/test_realm_emoji.py +++ b/zerver/tests/test_realm_emoji.py @@ -7,6 +7,7 @@ from zerver.lib.actions import ( do_create_user, do_set_realm_property, ) +from zerver.lib.exceptions import JsonableError from zerver.lib.test_classes import ZulipTestCase from zerver.lib.test_helpers import get_test_image_file from zerver.models import Realm, RealmEmoji, UserProfile, get_realm @@ -343,3 +344,25 @@ class RealmEmojiTest(ZulipTestCase): self.login_user(emoji_author_2) result = self.client_delete("/json/realm/emoji/test_emoji") self.assert_json_success(result) + + def test_upload_already_existed_emoji_in_check_add_realm_emoji(self) -> None: + realm_1 = do_create_realm("test_realm", "test_realm") + emoji_author = do_create_user( + "abc@example.com", password="abc", realm=realm_1, full_name="abc", acting_user=None + ) + emoji_name = "emoji_test" + with get_test_image_file("img.png") as img_file: + # Because we want to verify the IntegrityError handling + # logic in check_add_realm_emoji rather than the primary + # check in upload_emoji, we need to make this request via + # that helper rather than via the API. + check_add_realm_emoji( + realm=emoji_author.realm, name=emoji_name, author=emoji_author, image_file=img_file + ) + with self.assertRaises(JsonableError): + check_add_realm_emoji( + realm=emoji_author.realm, + name=emoji_name, + author=emoji_author, + image_file=img_file, + ) diff --git a/zerver/tests/test_transfer.py b/zerver/tests/test_transfer.py index 85f8b5f1c7..ff3d677f3f 100644 --- a/zerver/tests/test_transfer.py +++ b/zerver/tests/test_transfer.py @@ -107,6 +107,8 @@ class TransferUploadsToS3Test(ZulipTestCase): self.assertEqual(image_data, original_key.get()["Body"].read()) self.assertEqual(resized_image_data, resized_key.get()["Body"].read()) + emoji_name = "emoji2.png" + with get_test_image_file("animated_img.gif") as image_file: emoji = check_add_realm_emoji(othello.realm, emoji_name, othello, image_file) if not emoji: