<commit_before>import calendar
import urllib
from datetime import timedelta
from typing import Any
from unittest.mock import patch

import orjson
from django.conf import settings
from django.http import HttpResponse
from django.utils.timezone import now as timezone_now

from corporate.models import Customer, CustomerPlan
from zerver.lib.actions import do_change_logo_source, do_create_user
from zerver.lib.events import add_realm_logo_fields
from zerver.lib.home import get_furthest_read_time
from zerver.lib.soft_deactivation import do_soft_deactivate_users
from zerver.lib.test_classes import ZulipTestCase
from zerver.lib.test_helpers import get_user_messages, queries_captured
from zerver.lib.users import compute_show_invites_and_add_streams
from zerver.models import (
    DefaultStream,
    Realm,
    UserActivity,
    UserProfile,
    flush_per_request_caches,
    get_realm,
    get_stream,
    get_system_bot,
    get_user,
)
from zerver.views.home import compute_navbar_logo_url
from zerver.worker.queue_processors import UserActivityWorker

logger_string = "zulip.soft_deactivation"


class HomeTest(ZulipTestCase):

    # Keep this list sorted!!!
    expected_page_params_keys = [
        "alert_words",
        "available_notification_sounds",
        "avatar_source",
        "avatar_url",
        "avatar_url_medium",
        "bot_types",
        "can_create_streams",
        "can_subscribe_other_users",
        "color_scheme",
        "cross_realm_bots",
        "custom_profile_field_types",
        "custom_profile_fields",
        "debug_mode",
        "default_language",
        "default_language_name",
        "default_view",
        "delivery_email",
        "demote_inactive_streams",
        "dense_mode",
        "desktop_icon_count_display",
        "development_environment",
        "email",
        "emojiset",
        "emojiset_choices",
        "enable_desktop_notifications",
        "enable_digest_emails",
        "enable_login_emails",
        "enable_offline_email_notifications",
        "enable_offline_push_notifications",
        "enable_online_push_notifications",
        "enable_sounds",
        "enable_stream_audible_notifications",
        "enable_stream_desktop_notifications",
        "enable_stream_email_notifications",
        "enable_stream_push_notifications",
        "enter_sends",
        "first_in_realm",
        "fluid_layout_width",
        "full_name",
        "furthest_read_time",
        "has_mobile_devices",
        "has_zoom_token",
        "high_contrast_mode",
        "hotspots",
        "initial_servertime",
        "insecure_desktop_app",
        "is_admin",
        "is_guest",
        "is_owner",
        "is_web_public_visitor",
        "jitsi_server_url",
        "language_list",
        "language_list_dbl_col",
        "last_event_id",
        "left_side_userlist",
        "login_page",
        "max_avatar_file_size_mib",
        "max_file_upload_size_mib",
        "max_icon_file_size",
        "max_logo_file_size",
        "max_message_id",
        "message_content_in_email_notifications",
        "muted_topics",
        "narrow",
        "narrow_stream",
        "needs_tutorial",
        "never_subscribed",
        "no_event_queue",
        "notification_sound",
        "<PASSWORD>",
        "<PASSWORD>",
        "pm_content_in_desktop_notifications",
        "poll_timeout",
        "presence_enabled",
        "presences",
        "prompt_for_invites",
        "queue_id",
        "realm_add_emoji_by_admins_only",
        "realm_allow_community_topic_editing",
        "realm_allow_edit_history",
        "realm_allow_message_deleting",
        "realm_allow_message_editing",
        "realm_authentication_methods",
        "realm_available_video_chat_providers",
        "realm_avatar_changes_disabled",
        "realm_bot_creation_policy",
        "realm_bot_domain",
        "realm_bots",
        "realm_community_topic_editing_limit_seconds",
        "realm_create_stream_policy",
        "realm_default_code_block_language",
        "realm_default_external_accounts",
        "realm_default_language",
        "realm_default_stream_groups",
        "realm_default_streams",
        "realm_default_twenty_four_hour_time",
        "realm_description",
        "realm_digest_emails_enabled",
        "realm_digest_weekday",
        "realm_disallow_disposable_email_addresses",
        "realm_domains",
        "realm_email_address_visibility",
        "realm_email_auth_enabled",
        "realm_email_changes_disabled",
        "realm_emails_restricted_to_domains",
        "realm_embedded_bots",
        "realm_emoji",
        "realm_filters",
        "realm_icon_source",
        "realm_icon_url",
        "realm_incoming_webhook_bots",
        "realm_inline_image_preview",
        "realm_inline_url_embed_preview",
        "realm_invite_by_admins_only",
        "realm_invite_required",
        "realm_invite_to_stream_policy",
        "realm_is_zephyr_mirror_realm",
        "realm_logo_source",
        "realm_logo_url",
        "realm_mandatory_topics",
        "realm_message_content_allowed_in_email_notifications",
        "realm_message_content_delete_limit_seconds",
        "realm_message_content_edit_limit_seconds",
        "realm_message_retention_days",
        "realm_name",
        "realm_name_changes_disabled",
        "realm_name_in_notifications",
        "realm_night_logo_source",
        "realm_night_logo_url",
        "realm_non_active_users",
        "realm_notifications_stream_id",
        "realm_password_auth_enabled",
        "realm_plan_type",
        "realm_presence_disabled",
        "realm_private_message_policy",
        "realm_push_notifications_enabled",
        "realm_send_welcome_emails",
        "realm_signup_notifications_stream_id",
        "realm_upload_quota",
        "realm_uri",
        "realm_user_group_edit_policy",
        "realm_user_groups",
        "realm_users",
        "realm_video_chat_provider",
        "realm_waiting_period_threshold",
        "realm_wildcard_mention_policy",
        "recent_private_conversations",
        "root_domain_uri",
        "save_stacktraces",
        "search_pills_enabled",
        "server_avatar_changes_disabled",
        "server_generation",
        "server_inline_image_preview",
        "server_inline_url_embed_preview",
        "server_name_changes_disabled",
        "settings_send_digest_emails",
        "starred_message_counts",
        "starred_messages",
        "stop_words",
        "stream_description_max_length",
        "stream_name_max_length",
        "subscriptions",
        "test_suite",
        "timezone",
        "translate_emoticons",
        "translation_data",
        "twenty_four_hour_time",
        "two_fa_enabled",
        "two_fa_enabled_user",
        "unread_msgs",
        "unsubscribed",
        "upgrade_text_for_wide_organization_logo",
        "user_id",
        "user_status",
        "warn_no_email",
        "webpack_public_path",
        "wildcard_mentions_notify",
        "zulip_feature_level",
        "zulip_plan_is_not_limited",
        "zulip_version",
    ]

    def test_home(self) -> None:
        # Keep this list sorted!!!
        html_bits = [
            "Compose your message here...",
            "Exclude messages with topic",
            "Keyboard shortcuts",
            "Loading...",
            "Manage streams",
            "Narrow to topic",
            "Next message",
            "Search streams",
            # Verify that the app styles get included
            "app-stubentry.js",
            "data-params",
        ]

        self.login("hamlet")

        # Create bot for realm_bots testing. Must be done before fetching home_page.
        bot_info = {
            "full_name": "<NAME>",
            "short_name": "hambot",
        }
        self.client_post("/json/bots", bot_info)

        # Verify succeeds once logged-in
        flush_per_request_caches()
        with queries_captured() as queries:
            with patch("zerver.lib.cache.cache_set") as cache_mock:
                result = self._get_home_page(stream="Denmark")
                self.check_rendered_logged_in_app(result)
        self.assertEqual(
            set(result["Cache-Control"].split(", ")), {"must-revalidate", "no-store", "no-cache"}
        )

        self.assert_length(queries, 39)
        self.assert_length(cache_mock.call_args_list, 5)

        html = result.content.decode("utf-8")

        for html_bit in html_bits:
            if html_bit not in html:
                raise AssertionError(f"{html_bit} not in result")

        page_params = self._get_page_params(result)

        actual_keys = sorted(str(k) for k in page_params.keys())

        self.assertEqual(actual_keys, self.expected_page_params_keys)

        # TODO: Inspect the page_params data further.
        # print(orjson.dumps(page_params, option=orjson.OPT_INDENT_2).decode())
        realm_bots_expected_keys = [
            "api_key",
            "avatar_url",
            "bot_type",
            "default_all_public_streams",
            "default_events_register_stream",
            "default_sending_stream",
            "email",
            "full_name",
            "is_active",
            "owner_id",
            "services",
            "user_id",
        ]

        realm_bots_actual_keys = sorted(str(key) for key in page_params["realm_bots"][0].keys())
        self.assertEqual(realm_bots_actual_keys, realm_bots_expected_keys)

    def test_logged_out_home(self) -> None:
        result = self.client_get("/")
        self.assertEqual(result.status_code, 200)

        page_params = self._get_page_params(result)
        actual_keys = sorted(str(k) for k in page_params.keys())
        removed_keys = [
            "last_event_id",
            "narrow",
            "narrow_stream",
        ]
        expected_keys = [i for i in self.expected_page_params_keys if i not in removed_keys]
        self.assertEqual(actual_keys, expected_keys)

    def test_home_under_2fa_without_otp_device(self) -> None:
        with self.settings(TWO_FACTOR_AUTHENTICATION_ENABLED=True):
            self.login("iago")
            result = self._get_home_page()
            # Should be successful because otp device is not configured.
            self.check_rendered_logged_in_app(result)

    def test_home_under_2fa_with_otp_device(self) -> None:
        with self.settings(TWO_FACTOR_AUTHENTICATION_ENABLED=True):
            user_profile = self.example_user("iago")
            self.create_default_device(user_profile)
            self.login_user(user_profile)
            result = self._get_home_page()
            # User should not log in because otp device is configured but
            # 2fa login function was not called.
            self.assertEqual(result.status_code, 302)

            self.login_2fa(user_profile)
            result = self._get_home_page()
            # Should be successful after calling 2fa login function.
            self.check_rendered_logged_in_app(result)

    def test_num_queries_for_realm_admin(self) -> None:
        # Verify number of queries for Realm admin isn't much higher than for normal users.
        self.login("iago")
        flush_per_request_caches()
        with queries_captured() as queries:
            with patch("zerver.lib.cache.cache_set") as cache_mock:
                result = self._get_home_page()
                self.check_rendered_logged_in_app(result)
                self.assert_length(cache_mock.call_args_list, 6)
            self.assert_length(queries, 36)

    def test_num_queries_with_streams(self) -> None:
        main_user = self.example_user("hamlet")
        other_user = self.example_user("cordelia")

        realm_id = main_user.realm_id

        self.login_user(main_user)

        # Try to make page-load do extra work for various subscribed
        # streams.
        for i in range(10):
            stream_name = "test_stream_" + str(i)
            stream = self.make_stream(stream_name)
            DefaultStream.objects.create(
                realm_id=realm_id,
                stream_id=stream.id,
            )
            for user in [main_user, other_user]:
                self.subscribe(user, stream_name)

        # Simulate hitting the page the first time to avoid some noise
        # related to initial logins.
        self._get_home_page()

        # Then for the second page load, measure the number of queries.
        flush_per_request_caches()
        with queries_captured() as queries2:
            result = self._get_home_page()

        self.assert_length(queries2, 34)

        # Do a sanity check that our new streams were in the payload.
        html = result.content.decode("utf-8")
        self.assertIn("test_stream_7", html)

    def _get_home_page(self, **kwargs: Any) -> HttpResponse:
        with patch("zerver.lib.events.request_event_queue", return_value=42), patch(
            "zerver.lib.events.get_user_events", return_value=[]
        ):
            result = self.client_get("/", dict(**kwargs))
        return result

    def _sanity_check(self, result: HttpResponse) -> None:
        """
        Use this for tests that are geared toward specific edge cases, but
        which still want the home page to load properly.
        """
        html = result.content.decode("utf-8")
        if "Compose your message" not in html:
            raise AssertionError("Home page probably did not load.")

    def test_terms_of_service(self) -> None:
        user = self.example_user("hamlet")
        self.login_user(user)

        for user_tos_version in [None, "1.1", "2.0.3.4"]:
            user.tos_version = user_tos_version
            user.save()

            with self.settings(TERMS_OF_SERVICE="whatever"), self.settings(TOS_VERSION="99.99"):

                result = self.client_get("/", dict(stream="Denmark"))

            html = result.content.decode("utf-8")
            self.assertIn("Accept the new Terms of Service", html)

    def test_banned_desktop_app_versions(self) -> None:
        user = self.example_user("hamlet")
        self.login_user(user)

        result = self.client_get("/", HTTP_USER_AGENT="ZulipElectron/2.3.82")
        html = result.content.decode("utf-8")
        self.assertIn("You are using old version of the Zulip desktop", html)

    def test_unsupported_browser(self) -> None:
        user = self.example_user("hamlet")
        self.login_user(user)

        # currently we don't support IE, so some of IE's user agents are added.
        unsupported_user_agents = [
            "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2)",
            "Mozilla/5.0 (Windows NT 10.0; Trident/7.0; rv:11.0) like Gecko",
            "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0)",
        ]
        for user_agent in unsupported_user_agents:
            result = self.client_get("/", HTTP_USER_AGENT=user_agent)
            html = result.content.decode("utf-8")
            self.assertIn("Internet Explorer is not supported by Zulip.", html)

    def test_terms_of_service_first_time_template(self) -> None:
        user = self.example_user("hamlet")
        self.login_user(user)

        user.tos_version = None
        user.save()

        with self.settings(FIRST_TIME_TOS_TEMPLATE="hello.html"), self.settings(
            TOS_VERSION="99.99"
        ):
            result = self.client_post("/accounts/accept_terms/")
            self.assertEqual(result.status_code, 200)
            self.assert_in_response("I agree to the", result)
            self.assert_in_response("Chat for distributed teams", result)

    def test_accept_terms_of_service(self) -> None:
        self.login("hamlet")

        result = self.client_post("/accounts/accept_terms/")
        self.assertEqual(result.status_code, 200)
        self.assert_in_response("I agree to the", result)

        result = self.client_post("/accounts/accept_terms/", {"terms": True})
        self.assertEqual(result.status_code, 302)
        self.assertEqual(result["Location"], "/")

    def test_bad_narrow(self) -> None:
        self.login("hamlet")
        with self.assertLogs(level="WARNING") as m:
            result = self._get_home_page(stream="Invalid Stream")
            self.assertEqual(m.output, ["WARNING:root:Invalid narrow requested, ignoring"])
        self._sanity_check(result)

    def test_topic_narrow(self) -> None:
        self.login("hamlet")
        result = self._get_home_page(stream="Denmark", topic="lunch")
        self._sanity_check(result)
        html = result.content.decode("utf-8")
        self.assertIn("lunch", html)
        self.assertEqual(
            set(result["Cache-Control"].split(", ")), {"must-revalidate", "no-store", "no-cache"}
        )

    def test_notifications_stream(self) -> None:
        realm = get_realm("zulip")
        realm.notifications_stream_id = get_stream("Denmark", realm).id
        realm.save()
        self.login("hamlet")
        result = self._get_home_page()
        page_params = self._get_page_params(result)
        self.assertEqual(
            page_params["realm_notifications_stream_id"], get_stream("Denmark", realm).id
        )

    def create_bot(self, owner: UserProfile, bot_email: str, bot_name: str) -> UserProfile:
        user = do_create_user(
            email=bot_email,
            password="<PASSWORD>",
            realm=owner.realm,
            full_name=bot_name,
            bot_type=UserProfile.DEFAULT_BOT,
            bot_owner=owner,
            acting_user=None,
        )
        return user

    def create_non_active_user(self, realm: Realm, email: str, name: str) -> UserProfile:
        user = do_create_user(
            email=email, password="<PASSWORD>", realm=realm, full_name=name, acting_user=None
        )

        # Doing a full-stack deactivation would be expensive here,
        # and we really only need to flip the flag to get a valid
        # test.
        user.is_active = False
        user.save()
        return user

    def test_signup_notifications_stream(self) -> None:
        realm = get_realm("zulip")
        realm.signup_notifications_stream = get_stream("Denmark", realm)
        realm.save()
        self.login("hamlet")
        result = self._get_home_page()
        page_params = self._get_page_params(result)
        self.assertEqual(
            page_params["realm_signup_notifications_stream_id"], get_stream("Denmark", realm).id
        )

    def test_people(self) -> None:
        hamlet = self.example_user("hamlet")
        realm = get_realm("zulip")
        self.login_user(hamlet)

        bots = {}
        for i in range(3):
            bots[i] = self.create_bot(
                owner=hamlet,
                bot_email=f"<EMAIL>",
                bot_name=f"Bot {i}",
            )

        for i in range(3):
            defunct_user = self.create_non_active_user(
                realm=realm,
                email=f"<EMAIL>",
                name=f"Defunct User {i}",
            )

        result = self._get_home_page()
        page_params = self._get_page_params(result)

        """
        We send three lists of users.  The first two below are disjoint
        lists of users, and the records we send for them have identical
        structure.

        The realm_bots bucket is somewhat redundant, since all bots will
        be in one of the first two buckets.  They do include fields, however,
        that normal users don't care about, such as default_sending_stream.
        """

        buckets = [
            "realm_users",
            "realm_non_active_users",
            "realm_bots",
        ]

        for field in buckets:
            users = page_params[field]
            self.assertTrue(len(users) >= 3, field)
            for rec in users:
                self.assertEqual(rec["user_id"], get_user(rec["email"], realm).id)
                if field == "realm_bots":
                    self.assertNotIn("is_bot", rec)
                    self.assertIn("is_active", rec)
                    self.assertIn("owner_id", rec)
                else:
                    self.assertIn("is_bot", rec)
                    self.assertNotIn("is_active", rec)

        active_ids = {p["user_id"] for p in page_params["realm_users"]}
        non_active_ids = {p["user_id"] for p in page_params["realm_non_active_users"]}
        bot_ids = {p["user_id"] for p in page_params["realm_bots"]}

        self.assertIn(hamlet.id, active_ids)
        self.assertIn(defunct_user.id, non_active_ids)

        # Bots can show up in multiple buckets.
        self.assertIn(bots[2].id, bot_ids)
        self.assertIn(bots[2].id, active_ids)

        # Make sure nobody got mis-bucketed.
        self.assertNotIn(hamlet.id, non_active_ids)
        self.assertNotIn(defunct_user.id, active_ids)

        cross_bots = page_params["cross_realm_bots"]
        self.assertEqual(len(cross_bots), 3)
        cross_bots.sort(key=lambda d: d["email"])
        for cross_bot in cross_bots:
            # These are either nondeterministic or boring
            del cross_bot["timezone"]
            del cross_bot["avatar_url"]
            del cross_bot["date_joined"]

        notification_bot = self.notification_bot()
        email_gateway_bot = get_system_bot(settings.EMAIL_GATEWAY_BOT)
        welcome_bot = get_system_bot(settings.WELCOME_BOT)

        by_email = lambda d: d["email"]

        self.assertEqual(
            sorted(cross_bots, key=by_email),
            sorted(
                [
                    dict(
                        avatar_version=email_gateway_bot.avatar_version,
                        bot_owner_id=None,
                        bot_type=1,
                        email=email_gateway_bot.email,
                        user_id=email_gateway_bot.id,
                        full_name=email_gateway_bot.full_name,
                        is_active=True,
                        is_bot=True,
                        is_admin=False,
                        is_owner=False,
                        is_cross_realm_bot=True,
                        is_guest=False,
                    ),
                    dict(
                        avatar_version=notification_bot.avatar_version,
                        bot_owner_id=None,
                        bot_type=1,
                        email=notification_bot.email,
                        user_id=notification_bot.id,
                        full_name=notification_bot.full_name,
                        is_active=True,
                        is_bot=True,
                        is_admin=False,
                        is_owner=False,
                        is_cross_realm_bot=True,
                        is_guest=False,
                    ),
                    dict(
                        avatar_version=welcome_bot.avatar_version,
                        bot_owner_id=None,
                        bot_type=1,
                        email=welcome_bot.email,
                        user_id=welcome_bot.id,
                        full_name=welcome_bot.full_name,
                        is_active=True,
                        is_bot=True,
                        is_admin=False,
                        is_owner=False,
                        is_cross_realm_bot=True,
                        is_guest=False,
                    ),
                ],
                key=by_email,
            ),
        )

    def test_new_stream(self) -> None:
        user_profile = self.example_user("hamlet")
        stream_name = "New stream"
        self.subscribe(user_profile, stream_name)
        self.login_user(user_profile)
        result = self._get_home_page(stream=stream_name)
        page_params = self._get_page_params(result)
        self.assertEqual(page_params["narrow_stream"], stream_name)
        self.assertEqual(page_params["narrow"], [dict(operator="stream", operand=stream_name)])
        self.assertEqual(page_params["max_message_id"], -1)

    def test_invites_by_admins_only(self) -> None:
        user_profile = self.example_user("hamlet")

        realm = user_profile.realm
        realm.invite_by_admins_only = True
        realm.save()

        self.login_user(user_profile)
        self.assertFalse(user_profile.is_realm_admin)
        result = self._get_home_page()
        html = result.content.decode("utf-8")
        self.assertNotIn("Invite more users", html)

        user_profile.role = UserProfile.ROLE_REALM_ADMINISTRATOR
        user_profile.save()
        result = self._get_home_page()
        html = result.content.decode("utf-8")
        self.assertIn("Invite more users", html)

    def test_show_invites_for_guest_users(self) -> None:
        user_profile = self.example_user("polonius")

        realm = user_profile.realm
        realm.invite_by_admins_only = False
        realm.save()

        self.login_user(user_profile)
        self.assertFalse(user_profile.is_realm_admin)
        self.assertFalse(get_realm("zulip").invite_by_admins_only)
        result = self._get_home_page()
        html = result.content.decode("utf-8")
        self.assertNotIn("Invite more users", html)

    def test_show_billing(self) -> None:
        customer = Customer.objects.create(realm=get_realm("zulip"), stripe_customer_id="cus_id")
        user = self.example_user("desdemona")

        # realm owner, but no CustomerPlan -> no billing link
        user.role = UserProfile.ROLE_REALM_OWNER
        user.save(update_fields=["role"])
        self.login_user(user)
        result_html = self._get_home_page().content.decode("utf-8")
        self.assertNotIn("Billing", result_html)

        # realm owner, with inactive CustomerPlan -> show billing link
        CustomerPlan.objects.create(
            customer=customer,
            billing_cycle_anchor=timezone_now(),
            billing_schedule=CustomerPlan.ANNUAL,
            next_invoice_date=timezone_now(),
            tier=CustomerPlan.STANDARD,
            status=CustomerPlan.ENDED,
        )
        result_html = self._get_home_page().content.decode("utf-8")
        self.assertIn("Billing", result_html)

        # realm admin, with CustomerPlan -> no billing link
        user.role = UserProfile.ROLE_REALM_ADMINISTRATOR
        user.save(update_fields=["role"])
        result_html = self._get_home_page().content.decode("utf-8")
        self.assertNotIn("Billing", result_html)

        # billing admin, with CustomerPlan -> show billing link
        user.role = UserProfile.ROLE_MEMBER
        user.is_billing_admin = True
        user.save(update_fields=["role", "is_billing_admin"])
        result_html = self._get_home_page().content.decode("utf-8")
        self.assertIn("Billing", result_html)

        # member, with CustomerPlan -> no billing link
        user.is_billing_admin = False
        user.save(update_fields=["is_billing_admin"])
        result_html = self._get_home_page().content.decode("utf-8")
        self.assertNotIn("Billing", result_html)

        # guest, with CustomerPlan -> no billing link
        user.role = UserProfile.ROLE_GUEST
        user.save(update_fields=["role"])
        result_html = self._get_home_page().content.decode("utf-8")
        self.assertNotIn("Billing", result_html)

        # billing admin, but no CustomerPlan -> no billing link
        user.role = UserProfile.ROLE_MEMBER
        user.is_billing_admin = True
        user.save(update_fields=["role", "is_billing_admin"])
        CustomerPlan.objects.all().delete()
        result_html = self._get_home_page().content.decode("utf-8")
        self.assertNotIn("Billing", result_html)

        # billing admin, with sponsorship pending -> show billing link
        customer.sponsorship_pending = True
        customer.save(update_fields=["sponsorship_pending"])
        result_html = self._get_home_page().content.decode("utf-8")
        self.assertIn("Billing", result_html)

        # billing admin, no customer object -> make sure it doesn't crash
        customer.delete()
        result = self._get_home_page()
        self.check_rendered_logged_in_app(result)

    def test_show_plans(self) -> None:
        realm = get_realm("zulip")

        # Don't show plans to guest users
        self.login("polonius")
        realm.plan_type = Realm.LIMITED
        realm.save(update_fields=["plan_type"])
        result_html = self._get_home_page().content.decode("utf-8")
        self.assertNotIn("Plans", result_html)

        # Show plans link to all other users if plan_type is LIMITED
        self.login("hamlet")
        result_html = self._get_home_page().content.decode("utf-8")
        self.assertIn("Plans", result_html)

        # Show plans link to no one, including admins, if SELF_HOSTED or STANDARD
        realm.plan_type = Realm.SELF_HOSTED
        realm.save(update_fields=["plan_type"])
        result_html = self._get_home_page().content.decode("utf-8")
        self.assertNotIn("Plans", result_html)

        realm.plan_type = Realm.STANDARD
        realm.save(update_fields=["plan_type"])
        result_html = self._get_home_page().content.decode("utf-8")
        self.assertNotIn("Plans", result_html)

    def test_desktop_home(self) -> None:
        self.login("hamlet")
        result = self.client_get("/desktop_home")
        self.assertEqual(result.status_code, 301)
        self.assertTrue(result["Location"].endswith("/desktop_home/"))
        result = self.client_get("/desktop_home/")
        self.assertEqual(result.status_code, 302)
        path = urllib.parse.urlparse(result["Location"]).path
        self.assertEqual(path, "/")

    def test_compute_navbar_logo_url(self) -> None:
        user_profile = self.example_user("hamlet")

        page_params = {"color_scheme": user_profile.COLOR_SCHEME_NIGHT}
        add_realm_logo_fields(page_params, user_profile.realm)
        self.assertEqual(
            compute_navbar_logo_url(page_params), "/static/images/logo/zulip-org-logo.svg?version=0"
        )

        page_params = {"color_scheme": user_profile.COLOR_SCHEME_LIGHT}
        add_realm_logo_fields(page_params, user_profile.realm)<commit_msg>tests: Use do_change_plan_type in test_show_plans.<commit_after>import calendar
import urllib
from datetime import timedelta
from typing import Any
from unittest.mock import patch

import orjson
from django.conf import settings
from django.http import HttpResponse
from django.utils.timezone import now as timezone_now

from corporate.models import Customer, CustomerPlan
from zerver.lib.actions import do_change_logo_source, do_create_user
from zerver.lib.events import add_realm_logo_fields
from zerver.lib.home import get_furthest_read_time
from zerver.lib.soft_deactivation import do_soft_deactivate_users
from zerver.lib.test_classes import ZulipTestCase
from zerver.lib.test_helpers import get_user_messages, queries_captured
from zerver.lib.users import compute_show_invites_and_add_streams
from zerver.models import (
    DefaultStream,
    Realm,
    UserActivity,
    UserProfile,
    flush_per_request_caches,
    get_realm,
    get_stream,
    get_system_bot,
    get_user,
)
from zerver.views.home import compute_navbar_logo_url
from zerver.worker.queue_processors import UserActivityWorker

logger_string = "zulip.soft_deactivation"


class HomeTest(ZulipTestCase):

    # Keep this list sorted!!!
    expected_page_params_keys = [
        "alert_words",
        "available_notification_sounds",
        "avatar_source",
        "avatar_url",
        "avatar_url_medium",
        "bot_types",
        "can_create_streams",
        "can_subscribe_other_users",
        "color_scheme",
        "cross_realm_bots",
        "custom_profile_field_types",
        "custom_profile_fields",
        "debug_mode",
        "default_language",
        "default_language_name",
        "default_view",
        "delivery_email",
        "demote_inactive_streams",
        "dense_mode",
        "desktop_icon_count_display",
        "development_environment",
        "email",
        "emojiset",
        "emojiset_choices",
        "enable_desktop_notifications",
        "enable_digest_emails",
        "enable_login_emails",
        "enable_offline_email_notifications",
        "enable_offline_push_notifications",
        "enable_online_push_notifications",
        "enable_sounds",
        "enable_stream_audible_notifications",
        "enable_stream_desktop_notifications",
        "enable_stream_email_notifications",
        "enable_stream_push_notifications",
        "enter_sends",
        "first_in_realm",
        "fluid_layout_width",
        "full_name",
        "furthest_read_time",
        "has_mobile_devices",
        "has_zoom_token",
        "high_contrast_mode",
        "hotspots",
        "initial_servertime",
        "insecure_desktop_app",
        "is_admin",
        "is_guest",
        "is_owner",
        "is_web_public_visitor",
        "jitsi_server_url",
        "language_list",
        "language_list_dbl_col",
        "last_event_id",
        "left_side_userlist",
        "login_page",
        "max_avatar_file_size_mib",
        "max_file_upload_size_mib",
        "max_icon_file_size",
        "max_logo_file_size",
        "max_message_id",
        "message_content_in_email_notifications",
        "muted_topics",
        "narrow",
        "narrow_stream",
        "needs_tutorial",
        "never_subscribed",
        "no_event_queue",
        "notification_sound",
        "password_<PASSWORD>guesses",
        "password_min_length",
        "pm_content_in_desktop_notifications",
        "poll_timeout",
        "presence_enabled",
        "presences",
        "prompt_for_invites",
        "queue_id",
        "realm_add_emoji_by_admins_only",
        "realm_allow_community_topic_editing",
        "realm_allow_edit_history",
        "realm_allow_message_deleting",
        "realm_allow_message_editing",
        "realm_authentication_methods",
        "realm_available_video_chat_providers",
        "realm_avatar_changes_disabled",
        "realm_bot_creation_policy",
        "realm_bot_domain",
        "realm_bots",
        "realm_community_topic_editing_limit_seconds",
        "realm_create_stream_policy",
        "realm_default_code_block_language",
        "realm_default_external_accounts",
        "realm_default_language",
        "realm_default_stream_groups",
        "realm_default_streams",
        "realm_default_twenty_four_hour_time",
        "realm_description",
        "realm_digest_emails_enabled",
        "realm_digest_weekday",
        "realm_disallow_disposable_email_addresses",
        "realm_domains",
        "realm_email_address_visibility",
        "realm_email_auth_enabled",
        "realm_email_changes_disabled",
        "realm_emails_restricted_to_domains",
        "realm_embedded_bots",
        "realm_emoji",
        "realm_filters",
        "realm_icon_source",
        "realm_icon_url",
        "realm_incoming_webhook_bots",
        "realm_inline_image_preview",
        "realm_inline_url_embed_preview",
        "realm_invite_by_admins_only",
        "realm_invite_required",
        "realm_invite_to_stream_policy",
        "realm_is_zephyr_mirror_realm",
        "realm_logo_source",
        "realm_logo_url",
        "realm_mandatory_topics",
        "realm_message_content_allowed_in_email_notifications",
        "realm_message_content_delete_limit_seconds",
        "realm_message_content_edit_limit_seconds",
        "realm_message_retention_days",
        "realm_name",
        "realm_name_changes_disabled",
        "realm_name_in_notifications",
        "realm_night_logo_source",
        "realm_night_logo_url",
        "realm_non_active_users",
        "realm_notifications_stream_id",
        "realm_password_auth_enabled",
        "realm_plan_type",
        "realm_presence_disabled",
        "realm_private_message_policy",
        "realm_push_notifications_enabled",
        "realm_send_welcome_emails",
        "realm_signup_notifications_stream_id",
        "realm_upload_quota",
        "realm_uri",
        "realm_user_group_edit_policy",
        "realm_user_groups",
        "realm_users",
        "realm_video_chat_provider",
        "realm_waiting_period_threshold",
        "realm_wildcard_mention_policy",
        "recent_private_conversations",
        "root_domain_uri",
        "save_stacktraces",
        "search_pills_enabled",
        "server_avatar_changes_disabled",
        "server_generation",
        "server_inline_image_preview",
        "server_inline_url_embed_preview",
        "server_name_changes_disabled",
        "settings_send_digest_emails",
        "starred_message_counts",
        "starred_messages",
        "stop_words",
        "stream_description_max_length",
        "stream_name_max_length",
        "subscriptions",
        "test_suite",
        "timezone",
        "translate_emoticons",
        "translation_data",
        "twenty_four_hour_time",
        "two_fa_enabled",
        "two_fa_enabled_user",
        "unread_msgs",
        "unsubscribed",
        "upgrade_text_for_wide_organization_logo",
        "user_id",
        "user_status",
        "warn_no_email",
        "webpack_public_path",
        "wildcard_mentions_notify",
        "zulip_feature_level",
        "zulip_plan_is_not_limited",
        "zulip_version",
    ]

    def test_home(self) -> None:
        # Keep this list sorted!!!
        html_bits = [
            "Compose your message here...",
            "Exclude messages with topic",
            "Keyboard shortcuts",
            "Loading...",
            "Manage streams",
            "Narrow to topic",
            "Next message",
            "Search streams",
            # Verify that the app styles get included
            "app-stubentry.js",
            "data-params",
        ]

        self.login("hamlet")

        # Create bot for realm_bots testing. Must be done before fetching home_page.
        bot_info = {
            "full_name": "The Bot <NAME>",
            "short_name": "hambot",
        }
        self.client_post("/json/bots", bot_info)

        # Verify succeeds once logged-in
        flush_per_request_caches()
        with queries_captured() as queries:
            with patch("zerver.lib.cache.cache_set") as cache_mock:
                result = self._get_home_page(stream="Denmark")
                self.check_rendered_logged_in_app(result)
        self.assertEqual(
            set(result["Cache-Control"].split(", ")), {"must-revalidate", "no-store", "no-cache"}
        )

        self.assert_length(queries, 39)
        self.assert_length(cache_mock.call_args_list, 5)

        html = result.content.decode("utf-8")

        for html_bit in html_bits:
            if html_bit not in html:
                raise AssertionError(f"{html_bit} not in result")

        page_params = self._get_page_params(result)

        actual_keys = sorted(str(k) for k in page_params.keys())

        self.assertEqual(actual_keys, self.expected_page_params_keys)

        # TODO: Inspect the page_params data further.
        # print(orjson.dumps(page_params, option=orjson.OPT_INDENT_2).decode())
        realm_bots_expected_keys = [
            "api_key",
            "avatar_url",
            "bot_type",
            "default_all_public_streams",
            "default_events_register_stream",
            "default_sending_stream",
            "email",
            "full_name",
            "is_active",
            "owner_id",
            "services",
            "user_id",
        ]

        realm_bots_actual_keys = sorted(str(key) for key in page_params["realm_bots"][0].keys())
        self.assertEqual(realm_bots_actual_keys, realm_bots_expected_keys)

    def test_logged_out_home(self) -> None:
        result = self.client_get("/")
        self.assertEqual(result.status_code, 200)

        page_params = self._get_page_params(result)
        actual_keys = sorted(str(k) for k in page_params.keys())
        removed_keys = [
            "last_event_id",
            "narrow",
            "narrow_stream",
        ]
        expected_keys = [i for i in self.expected_page_params_keys if i not in removed_keys]
        self.assertEqual(actual_keys, expected_keys)

    def test_home_under_2fa_without_otp_device(self) -> None:
        with self.settings(TWO_FACTOR_AUTHENTICATION_ENABLED=True):
            self.login("iago")
            result = self._get_home_page()
            # Should be successful because otp device is not configured.
            self.check_rendered_logged_in_app(result)

    def test_home_under_2fa_with_otp_device(self) -> None:
        with self.settings(TWO_FACTOR_AUTHENTICATION_ENABLED=True):
            user_profile = self.example_user("iago")
            self.create_default_device(user_profile)
            self.login_user(user_profile)
            result = self._get_home_page()
            # User should not log in because otp device is configured but
            # 2fa login function was not called.
            self.assertEqual(result.status_code, 302)

            self.login_2fa(user_profile)
            result = self._get_home_page()
            # Should be successful after calling 2fa login function.
            self.check_rendered_logged_in_app(result)

    def test_num_queries_for_realm_admin(self) -> None:
        # Verify number of queries for Realm admin isn't much higher than for normal users.
        self.login("iago")
        flush_per_request_caches()
        with queries_captured() as queries:
            with patch("zerver.lib.cache.cache_set") as cache_mock:
                result = self._get_home_page()
                self.check_rendered_logged_in_app(result)
                self.assert_length(cache_mock.call_args_list, 6)
            self.assert_length(queries, 36)

    def test_num_queries_with_streams(self) -> None:
        main_user = self.example_user("hamlet")
        other_user = self.example_user("cordelia")

        realm_id = main_user.realm_id

        self.login_user(main_user)

        # Try to make page-load do extra work for various subscribed
        # streams.
        for i in range(10):
            stream_name = "test_stream_" + str(i)
            stream = self.make_stream(stream_name)
            DefaultStream.objects.create(
                realm_id=realm_id,
                stream_id=stream.id,
            )
            for user in [main_user, other_user]:
                self.subscribe(user, stream_name)

        # Simulate hitting the page the first time to avoid some noise
        # related to initial logins.
        self._get_home_page()

        # Then for the second page load, measure the number of queries.
        flush_per_request_caches()
        with queries_captured() as queries2:
            result = self._get_home_page()

        self.assert_length(queries2, 34)

        # Do a sanity check that our new streams were in the payload.
        html = result.content.decode("utf-8")
        self.assertIn("test_stream_7", html)

    def _get_home_page(self, **kwargs: Any) -> HttpResponse:
        with patch("zerver.lib.events.request_event_queue", return_value=42), patch(
            "zerver.lib.events.get_user_events", return_value=[]
        ):
            result = self.client_get("/", dict(**kwargs))
        return result

    def _sanity_check(self, result: HttpResponse) -> None:
        """
        Use this for tests that are geared toward specific edge cases, but
        which still want the home page to load properly.
        """
        html = result.content.decode("utf-8")
        if "Compose your message" not in html:
            raise AssertionError("Home page probably did not load.")

    def test_terms_of_service(self) -> None:
        user = self.example_user("hamlet")
        self.login_user(user)

        for user_tos_version in [None, "1.1", "2.0.3.4"]:
            user.tos_version = user_tos_version
            user.save()

            with self.settings(TERMS_OF_SERVICE="whatever"), self.settings(TOS_VERSION="99.99"):

                result = self.client_get("/", dict(stream="Denmark"))

            html = result.content.decode("utf-8")
            self.assertIn("Accept the new Terms of Service", html)

    def test_banned_desktop_app_versions(self) -> None:
        user = self.example_user("hamlet")
        self.login_user(user)

        result = self.client_get("/", HTTP_USER_AGENT="ZulipElectron/2.3.82")
        html = result.content.decode("utf-8")
        self.assertIn("You are using old version of the Zulip desktop", html)

    def test_unsupported_browser(self) -> None:
        user = self.example_user("hamlet")
        self.login_user(user)

        # currently we don't support IE, so some of IE's user agents are added.
        unsupported_user_agents = [
            "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2)",
            "Mozilla/5.0 (Windows NT 10.0; Trident/7.0; rv:11.0) like Gecko",
            "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0)",
        ]
        for user_agent in unsupported_user_agents:
            result = self.client_get("/", HTTP_USER_AGENT=user_agent)
            html = result.content.decode("utf-8")
            self.assertIn("Internet Explorer is not supported by Zulip.", html)

    def test_terms_of_service_first_time_template(self) -> None:
        user = self.example_user("hamlet")
        self.login_user(user)

        user.tos_version = None
        user.save()

        with self.settings(FIRST_TIME_TOS_TEMPLATE="hello.html"), self.settings(
            TOS_VERSION="99.99"
        ):
            result = self.client_post("/accounts/accept_terms/")
            self.assertEqual(result.status_code, 200)
            self.assert_in_response("I agree to the", result)
            self.assert_in_response("Chat for distributed teams", result)

    def test_accept_terms_of_service(self) -> None:
        self.login("hamlet")

        result = self.client_post("/accounts/accept_terms/")
        self.assertEqual(result.status_code, 200)
        self.assert_in_response("I agree to the", result)

        result = self.client_post("/accounts/accept_terms/", {"terms": True})
        self.assertEqual(result.status_code, 302)
        self.assertEqual(result["Location"], "/")

    def test_bad_narrow(self) -> None:
        self.login("hamlet")
        with self.assertLogs(level="WARNING") as m:
            result = self._get_home_page(stream="Invalid Stream")
            self.assertEqual(m.output, ["WARNING:root:Invalid narrow requested, ignoring"])
        self._sanity_check(result)

    def test_topic_narrow(self) -> None:
        self.login("hamlet")
        result = self._get_home_page(stream="Denmark", topic="lunch")
        self._sanity_check(result)
        html = result.content.decode("utf-8")
        self.assertIn("lunch", html)
        self.assertEqual(
            set(result["Cache-Control"].split(", ")), {"must-revalidate", "no-store", "no-cache"}
        )

    def test_notifications_stream(self) -> None:
        realm = get_realm("zulip")
        realm.notifications_stream_id = get_stream("Denmark", realm).id
        realm.save()
        self.login("hamlet")
        result = self._get_home_page()
        page_params = self._get_page_params(result)
        self.assertEqual(
            page_params["realm_notifications_stream_id"], get_stream("Denmark", realm).id
        )

    def create_bot(self, owner: UserProfile, bot_email: str, bot_name: str) -> UserProfile:
        user = do_create_user(
            email=bot_email,
            password="<PASSWORD>",
            realm=owner.realm,
            full_name=bot_name,
            bot_type=UserProfile.DEFAULT_BOT,
            bot_owner=owner,
            acting_user=None,
        )
        return user

    def create_non_active_user(self, realm: Realm, email: str, name: str) -> UserProfile:
        user = do_create_user(
            email=email, password="<PASSWORD>", realm=realm, full_name=name, acting_user=None
        )

        # Doing a full-stack deactivation would be expensive here,
        # and we really only need to flip the flag to get a valid
        # test.
        user.is_active = False
        user.save()
        return user

    def test_signup_notifications_stream(self) -> None:
        realm = get_realm("zulip")
        realm.signup_notifications_stream = get_stream("Denmark", realm)
        realm.save()
        self.login("hamlet")
        result = self._get_home_page()
        page_params = self._get_page_params(result)
        self.assertEqual(
            page_params["realm_signup_notifications_stream_id"], get_stream("Denmark", realm).id
        )

    def test_people(self) -> None:
        hamlet = self.example_user("hamlet")
        realm = get_realm("zulip")
        self.login_user(hamlet)

        bots = {}
        for i in range(3):
            bots[i] = self.create_bot(
                owner=hamlet,
                bot_email=f"<EMAIL>",
                bot_name=f"Bot {i}",
            )

        for i in range(3):
            defunct_user = self.create_non_active_user(
                realm=realm,
                email=f"<EMAIL>",
                name=f"Defunct User {i}",
            )

        result = self._get_home_page()
        page_params = self._get_page_params(result)

        """
        We send three lists of users.  The first two below are disjoint
        lists of users, and the records we send for them have identical
        structure.

        The realm_bots bucket is somewhat redundant, since all bots will
        be in one of the first two buckets.  They do include fields, however,
        that normal users don't care about, such as default_sending_stream.
        """

        buckets = [
            "realm_users",
            "realm_non_active_users",
            "realm_bots",
        ]

        for field in buckets:
            users = page_params[field]
            self.assertTrue(len(users) >= 3, field)
            for rec in users:
                self.assertEqual(rec["user_id"], get_user(rec["email"], realm).id)
                if field == "realm_bots":
                    self.assertNotIn("is_bot", rec)
                    self.assertIn("is_active", rec)
                    self.assertIn("owner_id", rec)
                else:
                    self.assertIn("is_bot", rec)
                    self.assertNotIn("is_active", rec)

        active_ids = {p["user_id"] for p in page_params["realm_users"]}
        non_active_ids = {p["user_id"] for p in page_params["realm_non_active_users"]}
        bot_ids = {p["user_id"] for p in page_params["realm_bots"]}

        self.assertIn(hamlet.id, active_ids)
        self.assertIn(defunct_user.id, non_active_ids)

        # Bots can show up in multiple buckets.
        self.assertIn(bots[2].id, bot_ids)
        self.assertIn(bots[2].id, active_ids)

        # Make sure nobody got mis-bucketed.
        self.assertNotIn(hamlet.id, non_active_ids)
        self.assertNotIn(defunct_user.id, active_ids)

        cross_bots = page_params["cross_realm_bots"]
        self.assertEqual(len(cross_bots), 3)
        cross_bots.sort(key=lambda d: d["email"])
        for cross_bot in cross_bots:
            # These are either nondeterministic or boring
            del cross_bot["timezone"]
            del cross_bot["avatar_url"]
            del cross_bot["date_joined"]

        notification_bot = self.notification_bot()
        email_gateway_bot = get_system_bot(settings.EMAIL_GATEWAY_BOT)
        welcome_bot = get_system_bot(settings.WELCOME_BOT)

        by_email = lambda d: d["email"]

        self.assertEqual(
            sorted(cross_bots, key=by_email),
            sorted(
                [
                    dict(
                        avatar_version=email_gateway_bot.avatar_version,
                        bot_owner_id=None,
                        bot_type=1,
                        email=email_gateway_bot.email,
                        user_id=email_gateway_bot.id,
                        full_name=email_gateway_bot.full_name,
                        is_active=True,
                        is_bot=True,
                        is_admin=False,
                        is_owner=False,
                        is_cross_realm_bot=True,
                        is_guest=False,
                    ),
                    dict(
                        avatar_version=notification_bot.avatar_version,
                        bot_owner_id=None,
                        bot_type=1,
                        email=notification_bot.email,
                        user_id=notification_bot.id,
                        full_name=notification_bot.full_name,
                        is_active=True,
                        is_bot=True,
                        is_admin=False,
                        is_owner=False,
                        is_cross_realm_bot=True,
                        is_guest=False,
                    ),
                    dict(
                        avatar_version=welcome_bot.avatar_version,
                        bot_owner_id=None,
                        bot_type=1,
                        email=welcome_bot.email,
                        user_id=welcome_bot.id,
                        full_name=welcome_bot.full_name,
                        is_active=True,
                        is_bot=True,
                        is_admin=False,
                        is_owner=False,
                        is_cross_realm_bot=True,
                        is_guest=False,
                    ),
                ],
                key=by_email,
            ),
        )

    def test_new_stream(self) -> None:
        user_profile = self.example_user("hamlet")
        stream_name = "New stream"
        self.subscribe(user_profile, stream_name)
        self.login_user(user_profile)
        result = self._get_home_page(stream=stream_name)
        page_params = self._get_page_params(result)
        self.assertEqual(page_params["narrow_stream"], stream_name)
        self.assertEqual(page_params["narrow"], [dict(operator="stream", operand=stream_name)])
        self.assertEqual(page_params["max_message_id"], -1)

    def test_invites_by_admins_only(self) -> None:
        user_profile = self.example_user("hamlet")

        realm = user_profile.realm
        realm.invite_by_admins_only = True
        realm.save()

        self.login_user(user_profile)
        self.assertFalse(user_profile.is_realm_admin)
        result = self._get_home_page()
        html = result.content.decode("utf-8")
        self.assertNotIn("Invite more users", html)

        user_profile.role = UserProfile.ROLE_REALM_ADMINISTRATOR
        user_profile.save()
        result = self._get_home_page()
        html = result.content.decode("utf-8")
        self.assertIn("Invite more users", html)

    def test_show_invites_for_guest_users(self) -> None:
        user_profile = self.example_user("polonius")

        realm = user_profile.realm
        realm.invite_by_admins_only = False
        realm.save()

        self.login_user(user_profile)
        self.assertFalse(user_profile.is_realm_admin)
        self.assertFalse(get_realm("zulip").invite_by_admins_only)
        result = self._get_home_page()
        html = result.content.decode("utf-8")
        self.assertNotIn("Invite more users", html)

    def test_show_billing(self) -> None:
        customer = Customer.objects.create(realm=get_realm("zulip"), stripe_customer_id="cus_id")
        user = self.example_user("desdemona")

        # realm owner, but no CustomerPlan -> no billing link
        user.role = UserProfile.ROLE_REALM_OWNER
        user.save(update_fields=["role"])
        self.login_user(user)
        result_html = self._get_home_page().content.decode("utf-8")
        self.assertNotIn("Billing", result_html)

        # realm owner, with inactive CustomerPlan -> show billing link
        CustomerPlan.objects.create(
            customer=customer,
            billing_cycle_anchor=timezone_now(),
            billing_schedule=CustomerPlan.ANNUAL,
            next_invoice_date=timezone_now(),
            tier=CustomerPlan.STANDARD,
            status=CustomerPlan.ENDED,
        )
        result_html = self._get_home_page().content.decode("utf-8")
        self.assertIn("Billing", result_html)

        # realm admin, with CustomerPlan -> no billing link
        user.role = UserProfile.ROLE_REALM_ADMINISTRATOR
        user.save(update_fields=["role"])
        result_html = self._get_home_page().content.decode("utf-8")
        self.assertNotIn("Billing", result_html)

        # billing admin, with CustomerPlan -> show billing link
        user.role = UserProfile.ROLE_MEMBER
        user.is_billing_admin = True
        user.save(update_fields=["role", "is_billing_admin"])
        result_html = self._get_home_page().content.decode("utf-8")
        self.assertIn("Billing", result_html)

        # member, with CustomerPlan -> no billing link
        user.is_billing_admin = False
        user.save(update_fields=["is_billing_admin"])
        result_html = self._get_home_page().content.decode("utf-8")
        self.assertNotIn("Billing", result_html)

        # guest, with CustomerPlan -> no billing link
        user.role = UserProfile.ROLE_GUEST
        user.save(update_fields=["role"])
        result_html = self._get_home_page().content.decode("utf-8")
        self.assertNotIn("Billing", result_html)

        # billing admin, but no CustomerPlan -> no billing link
        user.role = UserProfile.ROLE_MEMBER
        user.is_billing_admin = True
        user.save(update_fields=["role", "is_billing_admin"])
        CustomerPlan.objects.all().delete()
        result_html = self._get_home_page().content.decode("utf-8")
        self.assertNotIn("Billing", result_html)

        # billing admin, with sponsorship pending -> show billing link
        customer.sponsorship_pending = True
        customer.save(update_fields=["sponsorship_pending"])
        result_html = self._get_home_page().content.decode("utf-8")
        self.assertIn("Billing", result_html)

        # billing admin, no customer object -> make sure it doesn't crash
        customer.delete()
        result = self._get_home_page()
        self.check_rendered_logged_in_app(result)

    def test_show_plans(self) -> None:
        realm = get_realm("zulip")

        # Don't show plans to guest users
        self.login("polonius")
        do_change_plan_type(realm, Realm.LIMITED)
        result_html = self._get_home_page().content.decode("utf-8")
        self.assertNotIn("Plans", result_html)

        # Show plans link to all other users if plan_type is LIMITED
        self.login("hamlet")
        result_html = self._get_home_page().content.decode("utf-8")
        self.assertIn("Plans", result_html)

        # Show plans link to no one, including admins, if SELF_HOSTED or STANDARD
        do_change_plan_type(realm, Realm.SELF_HOSTED)
        result_html = self._get_home_page().content.decode("utf-8")
        self.assertNotIn("Plans", result_html)

        do_change_plan_type(realm, Realm.STANDARD)
        result_html = self._get_home_page().content.decode("utf-8")
        self.assertNotIn("Plans", result_html)

    def test_desktop_home(self) -> None:
        self.login("hamlet")
        result = self.client_get("/desktop_home")
        self.assertEqual(result.status_code, 301)
        self.assertTrue(result["Location"].endswith("/desktop_home/"))
        result = self.client_get("/desktop_home/")
        self.assertEqual(result.status_code, 302)
        path = urllib.parse.urlparse(result["Location"]).path
        self.assertEqual(path, "/")

    def test_compute_navbar_logo_url(self) -> None:
        user_profile = self.example_user("hamlet")

        page_params = {"color_scheme": user_profile.COLOR_SCHEME_NIGHT}
        add_realm_logo_fields(page_params, user_profile.realm)
        self.assertEqual(
            compute_navbar_logo_url(page_params), "/static/images/logo/zulip-org-logo.svg?version=0"
        )

        page_params = {"color_scheme": user_profile.COLOR_SCHEME_LIGHT}
        add_realm_logo_fields(page_params, user_profile.realm)
{'fraction_non_alphanumeric': 0.0971211063461986, 'fraction_numerical': 0.003955985505801004, 'mean_word_length': 2.319154805252124, 'pattern_counts': {'":': 12, '<': 16, '<?xml version=': 0, '>': 90, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 2, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7359811', 'n_tokens_mistral': 18515, 'n_tokens_neox': 17936, 'n_words': 3082}