Repository: zulip/zulip

Issue:
Make muting work for deactivated users
Even after a user deactivated, it may be helpful to hide that user's messages, avatar, etc. We should therefore make it possible to mute deactivated users.

This will also fix a bug where if a user tries to mute a deactivated user, the action silently fails.


Discussion:

Resolving patch:
diff --git a/zerver/views/muting.py b/zerver/views/muting.py
--- a/zerver/views/muting.py
+++ b/zerver/views/muting.py
@@ -94,7 +94,9 @@ def mute_user(request: HttpRequest, user_profile: UserProfile, muted_user_id: in
     if user_profile.id == muted_user_id:
         raise JsonableError(_("Cannot mute self"))
 
-    muted_user = access_user_by_id(user_profile, muted_user_id, allow_bots=False, for_admin=False)
+    muted_user = access_user_by_id(
+        user_profile, muted_user_id, allow_bots=False, allow_deactivated=True, for_admin=False
+    )
     date_muted = timezone_now()
 
     if get_mute_object(user_profile, muted_user) is not None:
@@ -107,7 +109,9 @@ def mute_user(request: HttpRequest, user_profile: UserProfile, muted_user_id: in
 def unmute_user(
     request: HttpRequest, user_profile: UserProfile, muted_user_id: int
 ) -> HttpResponse:
-    muted_user = access_user_by_id(user_profile, muted_user_id, allow_bots=False, for_admin=False)
+    muted_user = access_user_by_id(
+        user_profile, muted_user_id, allow_bots=False, allow_deactivated=True, for_admin=False
+    )
     mute_object = get_mute_object(user_profile, muted_user)
 
     if mute_object is None:


Test patch:
diff --git a/zerver/tests/test_muting_users.py b/zerver/tests/test_muting_users.py
--- a/zerver/tests/test_muting_users.py
+++ b/zerver/tests/test_muting_users.py
@@ -3,6 +3,7 @@
 
 import orjson
 
+from zerver.lib.actions import do_deactivate_user
 from zerver.lib.cache import cache_get, get_muting_users_cache_key
 from zerver.lib.test_classes import ZulipTestCase
 from zerver.lib.timestamp import datetime_to_timestamp
@@ -76,12 +77,15 @@ def test_add_muted_user_mute_twice(self) -> None:
         result = self.api_post(hamlet, url)
         self.assert_json_error(result, "User already muted")
 
-    def test_add_muted_user_valid_data(self) -> None:
+    def _test_add_muted_user_valid_data(self, deactivate_user: bool = False) -> None:
         hamlet = self.example_user("hamlet")
         self.login_user(hamlet)
         cordelia = self.example_user("cordelia")
         mute_time = datetime(2021, 1, 1, tzinfo=timezone.utc)
 
+        if deactivate_user:
+            do_deactivate_user(cordelia, acting_user=None)
+
         with mock.patch("zerver.views.muting.timezone_now", return_value=mute_time):
             url = "/api/v1/users/me/muted_users/{}".format(cordelia.id)
             result = self.api_post(hamlet, url)
@@ -112,6 +116,12 @@ def test_add_muted_user_valid_data(self) -> None:
             ),
         )
 
+    def test_add_muted_user_valid_data(self) -> None:
+        self._test_add_muted_user_valid_data()
+
+    def test_add_muted_user_deactivated_user(self) -> None:
+        self._test_add_muted_user_valid_data(deactivate_user=True)
+
     def test_remove_muted_user_unmute_before_muting(self) -> None:
         hamlet = self.example_user("hamlet")
         self.login_user(hamlet)
@@ -121,12 +131,15 @@ def test_remove_muted_user_unmute_before_muting(self) -> None:
         result = self.api_delete(hamlet, url)
         self.assert_json_error(result, "User is not muted")
 
-    def test_remove_muted_user_valid_data(self) -> None:
+    def _test_remove_muted_user_valid_data(self, deactivate_user: bool = False) -> None:
         hamlet = self.example_user("hamlet")
         self.login_user(hamlet)
         cordelia = self.example_user("cordelia")
         mute_time = datetime(2021, 1, 1, tzinfo=timezone.utc)
 
+        if deactivate_user:
+            do_deactivate_user(cordelia, acting_user=None)
+
         with mock.patch("zerver.views.muting.timezone_now", return_value=mute_time):
             url = "/api/v1/users/me/muted_users/{}".format(cordelia.id)
             result = self.api_post(hamlet, url)
@@ -163,6 +176,12 @@ def test_remove_muted_user_valid_data(self) -> None:
             ),
         )
 
+    def test_remove_muted_user_valid_data(self) -> None:
+        self._test_remove_muted_user_valid_data()
+
+    def test_remove_muted_user_deactivated_user(self) -> None:
+        self._test_remove_muted_user_valid_data(deactivate_user=True)
+
     def test_get_muting_users(self) -> None:
         hamlet = self.example_user("hamlet")
         self.login_user(hamlet)