Skip to content

Commit 90aa45a

Browse files
odunybradtimabbott
authored andcommitted
emoji: Add database-level uniqueness constraint for RealmEmoji.
While races here are unlikely, it is most correct to enforce this invariant at the database layer, and having a database-level constraint makes the models file a bit more readable.
1 parent 9a39ca2 commit 90aa45a

File tree

5 files changed

+62
-3
lines changed

5 files changed

+62
-3
lines changed

zerver/lib/actions.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7385,9 +7385,13 @@ def notify_realm_emoji(realm: Realm) -> None:
73857385
def check_add_realm_emoji(
73867386
realm: Realm, name: str, author: UserProfile, image_file: IO[bytes]
73877387
) -> Optional[RealmEmoji]:
7388-
realm_emoji = RealmEmoji(realm=realm, name=name, author=author)
7389-
realm_emoji.full_clean()
7390-
realm_emoji.save()
7388+
try:
7389+
realm_emoji = RealmEmoji(realm=realm, name=name, author=author)
7390+
realm_emoji.full_clean()
7391+
realm_emoji.save()
7392+
except django.db.utils.IntegrityError:
7393+
# Match the string in upload_emoji.
7394+
raise JsonableError(_("A custom emoji with this name already exists."))
73917395

73927396
emoji_file_name = get_emoji_file_name(image_file.name, realm_emoji.id)
73937397

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Generated by Django 3.2.9 on 2021-12-09 19:46
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
("zerver", "0371_invalid_characters_in_topics"),
10+
]
11+
12+
operations = [
13+
migrations.AddConstraint(
14+
model_name="realmemoji",
15+
constraint=models.UniqueConstraint(
16+
condition=models.Q(("deactivated", False)),
17+
fields=("realm", "name"),
18+
name="unique_realm_emoji_when_false_deactivated",
19+
),
20+
),
21+
]

zerver/models.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1048,6 +1048,15 @@ class RealmEmoji(models.Model):
10481048
def __str__(self) -> str:
10491049
return f"<RealmEmoji({self.realm.string_id}): {self.id} {self.name} {self.deactivated} {self.file_name}>"
10501050

1051+
class Meta:
1052+
constraints = [
1053+
models.UniqueConstraint(
1054+
fields=["realm", "name"],
1055+
condition=Q(deactivated=False),
1056+
name="unique_realm_emoji_when_false_deactivated",
1057+
),
1058+
]
1059+
10511060

10521061
def get_realm_emoji_dicts(
10531062
realm: Realm, only_active_emojis: bool = False

zerver/tests/test_realm_emoji.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
do_create_user,
88
do_set_realm_property,
99
)
10+
from zerver.lib.exceptions import JsonableError
1011
from zerver.lib.test_classes import ZulipTestCase
1112
from zerver.lib.test_helpers import get_test_image_file
1213
from zerver.models import Realm, RealmEmoji, UserProfile, get_realm
@@ -343,3 +344,25 @@ def test_check_admin_different_realm_emoji(self) -> None:
343344
self.login_user(emoji_author_2)
344345
result = self.client_delete("/json/realm/emoji/test_emoji")
345346
self.assert_json_success(result)
347+
348+
def test_upload_already_existed_emoji_in_check_add_realm_emoji(self) -> None:
349+
realm_1 = do_create_realm("test_realm", "test_realm")
350+
emoji_author = do_create_user(
351+
"[email protected]", password="abc", realm=realm_1, full_name="abc", acting_user=None
352+
)
353+
emoji_name = "emoji_test"
354+
with get_test_image_file("img.png") as img_file:
355+
# Because we want to verify the IntegrityError handling
356+
# logic in check_add_realm_emoji rather than the primary
357+
# check in upload_emoji, we need to make this request via
358+
# that helper rather than via the API.
359+
check_add_realm_emoji(
360+
realm=emoji_author.realm, name=emoji_name, author=emoji_author, image_file=img_file
361+
)
362+
with self.assertRaises(JsonableError):
363+
check_add_realm_emoji(
364+
realm=emoji_author.realm,
365+
name=emoji_name,
366+
author=emoji_author,
367+
image_file=img_file,
368+
)

zerver/tests/test_transfer.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,8 @@ def test_transfer_emoji_to_s3(self) -> None:
107107
self.assertEqual(image_data, original_key.get()["Body"].read())
108108
self.assertEqual(resized_image_data, resized_key.get()["Body"].read())
109109

110+
emoji_name = "emoji2.png"
111+
110112
with get_test_image_file("animated_img.gif") as image_file:
111113
emoji = check_add_realm_emoji(othello.realm, emoji_name, othello, image_file)
112114
if not emoji:

0 commit comments

Comments
 (0)