From 0f8f5fafe15b9502ab37c7fcb14dba0926771d63 Mon Sep 17 00:00:00 2001 From: Steve Howell Date: Thu, 23 Jan 2025 11:13:17 +0000 Subject: [PATCH] test_example.py: Add new example with mock.patch. The original mocking example now uses time_machine, so I slimmed down a lot of its comments, and then I created another mocking example so that we still touch on mocking in terms of mock.patch. The new method reinforces the pattern of testing both the sad path and happy path inside the same test. --- zerver/tests/test_example.py | 102 ++++++++++++++++++++++++++--------- 1 file changed, 76 insertions(+), 26 deletions(-) diff --git a/zerver/tests/test_example.py b/zerver/tests/test_example.py index 215a2080f4..a1177bf569 100644 --- a/zerver/tests/test_example.py +++ b/zerver/tests/test_example.py @@ -5,14 +5,15 @@ import orjson import time_machine from django.utils.timezone import now as timezone_now +from zerver.actions.realm_settings import do_set_realm_property from zerver.actions.users import do_change_can_create_users, do_change_user_role -from zerver.lib.exceptions import JsonableError +from zerver.lib.exceptions import JsonableError, StreamWildcardMentionNotAllowedError from zerver.lib.streams import access_stream_for_send_message from zerver.lib.test_classes import ZulipTestCase from zerver.lib.test_helpers import most_recent_message from zerver.lib.users import is_administrator_role -from zerver.models import UserProfile, UserStatus -from zerver.models.realms import get_realm +from zerver.models import Realm, UserProfile, UserStatus +from zerver.models.realms import WildcardMentionPolicyEnum, get_realm from zerver.models.streams import get_stream from zerver.models.users import get_user_by_delivery_email @@ -433,13 +434,75 @@ class TestMocking(ZulipTestCase): # # Mocking is generally used in situations where # we want to avoid running original code for reasons - # like skipping HTTP requests, saving execution time etc. + # like skipping HTTP requests, saving execution time, + # or simulating convenient return values. # # Learn more about mocking in-depth at: # https://zulip.readthedocs.io/en/latest/testing/testing-with-django.html#testing-with-mocks - # - # The following test demonstrates a simple use case - # where mocking is helpful in saving test-run time. + def test_wildcard_mentions(self) -> None: + cordelia = self.example_user("cordelia") + self.login_user(cordelia) + self.subscribe(cordelia, "test_stream") + + # Let's explicitly set the policy for sending wildcard + # mentions to a stream. If a stream has too many + # subscribers, we won't allow any users to spam the stream. + do_set_realm_property( + cordelia.realm, + "wildcard_mention_policy", + WildcardMentionPolicyEnum.NOBODY, + acting_user=None, + ) + + # We will try the same message a couple times. + # Notice the content includes "@**all**". + all_mention = "@**all** test wildcard mention" + + # First, let's test the SAD PATH, where cordelia + # tries a wildcard method and gets rejected. + # + # Here we use both mock.patch to simulate a return value + # and assertRaisesRegex to verify our function raises + # an error. + with ( + mock.patch( + "zerver.lib.message.num_subscribers_for_stream_id", + return_value=Realm.WILDCARD_MENTION_THRESHOLD + 1, + ), + self.assertRaisesRegex( + StreamWildcardMentionNotAllowedError, + "You do not have permission to use channel wildcard mentions in this channel.", + ), + ): + self.send_stream_message( + sender=cordelia, + stream_name="test_stream", + content=all_mention, + ) + + # Verify the message was NOT sent. + message = most_recent_message(cordelia) + self.assertNotEqual(message.content, all_mention) + + # Now for the HAPPY PATH, we still mock the number of + # subscribers, but here we simulate that we are under + # the limit. We expect cordelia's message to go through. + with mock.patch( + "zerver.lib.message.num_subscribers_for_stream_id", + return_value=Realm.WILDCARD_MENTION_THRESHOLD - 1, + ): + self.send_stream_message( + sender=cordelia, + stream_name="test_stream", + content=all_mention, + ) + + # Verify the message WAS sent. + message = most_recent_message(cordelia) + self.assertEqual(message.content, all_mention) + + +class TestTimeTravel(ZulipTestCase): def test_edit_message(self) -> None: """ Verify if the time limit imposed on message editing is working correctly. @@ -484,28 +547,15 @@ class TestMocking(ZulipTestCase): # Now that we tested message editing works within the limit, # we want to verify it doesn't work beyond the limit. # - # To do that we'll have to wait for the time limit to pass which is - # 5 minutes here. Easy, use time.sleep() but mind that it slows down the - # test to a great extent which isn't good. This is when mocking comes to rescue. - # We can check what the original code does to determine whether the time limit - # exceeded and mock that here such that the code runs as if the time limit - # exceeded without actually waiting for that long! - # - # In this case, it is timezone_now, an alias to django.utils.timezone.now, - # to which the difference with message-sent-time is checked. So, we want - # that timezone_now() call to return `datetime` object representing time - # that is beyond the limit. - # - # Notice how mock.patch() is used here to do exactly the above mentioned. - # mock.patch() here makes any calls to `timezone_now` in `zerver.actions.message_edit` - # to return the value passed to `return_value` in the its context. - # You can also use mock.patch() as a decorator depending on the - # requirements. Read more at the documentation link provided above. - + # First, calculate the time we want to travel to, adding + # a little buffer of 100 seconds beyond the limit. time_beyond_edit_limit = message_sent_time + timedelta( seconds=MESSAGE_CONTENT_EDIT_LIMIT + 100 - ) # There's a buffer time applied to the limit, hence the extra 100s. + ) + # Now use time_machine.travel to simulate that we are in the future. + # + # See https://pypi.org/project/time-machine/ for more info. with time_machine.travel(time_beyond_edit_limit, tick=False): result = self.client_patch( f"/json/messages/{sent_message_id}", {"content": "I actually want pizza."}