from datetime import datetime, timedelta, timezone
from decimal import Decimal
from typing import TYPE_CHECKING, Any, Optional
from unittest import mock
import orjson
import time_machine
from django.utils.timezone import now as timezone_now
from typing_extensions import override
from corporate.lib.stripe import RealmBillingSession, add_months
from corporate.models import (
Customer,
CustomerPlan,
LicenseLedger,
SponsoredPlanTypes,
ZulipSponsorshipRequest,
get_current_plan_by_customer,
get_customer_by_realm,
)
from zerver.actions.invites import do_create_multiuse_invite_link
from zerver.actions.realm_settings import do_change_realm_org_type, do_send_realm_reactivation_email
from zerver.actions.user_settings import do_change_user_setting
from zerver.lib.test_classes import ZulipTestCase
from zerver.lib.test_helpers import reset_email_visibility_to_everyone_in_zulip_realm
from zerver.models import MultiuseInvite, PreregistrationUser, Realm, UserMessage, UserProfile
from zerver.models.realms import OrgTypeEnum, get_org_type_display_name, get_realm
from zilencer.lib.remote_counts import MissingDataError
if TYPE_CHECKING:
from django.test.client import _MonkeyPatchedWSGIResponse as TestHttpResponse
import uuid
from zilencer.models import RemoteZulipServer
class TestRemoteServerSupportEndpoint(ZulipTestCase):
@override
def setUp(self) -> None:
def add_sponsorship_request(
hostname: str, org_type: int, website: str, paid_users: str, plan: str
) -> None:
remote_server = RemoteZulipServer.objects.get(hostname=hostname)
customer = Customer.objects.create(
remote_server=remote_server, sponsorship_pending=True
)
ZulipSponsorshipRequest.objects.create(
customer=customer,
org_type=org_type,
org_website=website,
org_description="We help people.",
expected_total_users="20-35",
paid_users_count=paid_users,
paid_users_description="",
requested_plan=plan,
)
super().setUp()
# Set up some initial example data.
for i in range(20):
hostname = f"zulip-{i}.example.com"
RemoteZulipServer.objects.create(
hostname=hostname, contact_email=f"admin@{hostname}", plan_type=1, uuid=uuid.uuid4()
)
# Add example sponsorship request data
add_sponsorship_request(
hostname="zulip-1.example.com",
org_type=OrgTypeEnum.Community.value,
website="",
paid_users="None",
plan=SponsoredPlanTypes.BUSINESS.value,
)
add_sponsorship_request(
hostname="zulip-2.example.com",
org_type=OrgTypeEnum.OpenSource.value,
website="example.org",
paid_users="",
plan=SponsoredPlanTypes.COMMUNITY.value,
)
def test_search(self) -> None:
self.login("cordelia")
result = self.client_get("/activity/remote/support")
self.assertEqual(result.status_code, 302)
self.assertEqual(result["Location"], "/login/")
# Iago is the user with the appropriate permissions to access this page.
self.login("iago")
assert self.example_user("iago").is_staff
result = self.client_get("/activity/remote/support")
self.assert_in_success_response(
[
'input type="text" name="q" class="input-xxlarge search-query" placeholder="hostname or contact email"'
],
result,
)
with mock.patch("analytics.views.support.compute_max_monthly_messages", return_value=1000):
result = self.client_get("/activity/remote/support", {"q": "zulip-1.example.com"})
self.assert_in_success_response(["
zulip-1.example.com
"], result)
self.assert_in_success_response(["Max monthly messages: 1000"], result)
self.assert_not_in_success_response(["zulip-2.example.com
"], result)
# Sponsorship request information
self.assert_in_success_response(["Organization type: Community"], result)
self.assert_in_success_response(
["Organization website: No website submitted"], result
)
self.assert_in_success_response(["Paid users: None"], result)
self.assert_in_success_response(["Requested plan: Business"], result)
self.assert_in_success_response(
["Organization description: We help people."], result
)
self.assert_in_success_response(["Estimated total users: 20-35"], result)
self.assert_in_success_response(["Description of paid users: "], result)
with mock.patch(
"analytics.views.support.compute_max_monthly_messages", side_effect=MissingDataError
):
result = self.client_get("/activity/remote/support", {"q": "zulip-1.example.com"})
self.assert_in_success_response(["zulip-1.example.com
"], result)
self.assert_in_success_response(
["Max monthly messages: Recent data missing"], result
)
self.assert_not_in_success_response(["zulip-2.example.com
"], result)
result = self.client_get("/activity/remote/support", {"q": "example.com"})
for i in range(20):
self.assert_in_success_response([f"zulip-{i}.example.com
"], result)
result = self.client_get("/activity/remote/support", {"q": "admin@zulip-2.example.com"})
self.assert_in_success_response(["zulip-2.example.com
"], result)
self.assert_in_success_response(["Contact email: admin@zulip-2.example.com"], result)
self.assert_not_in_success_response(["zulip-1.example.com
"], result)
# Sponsorship request information
self.assert_in_success_response(
["Organization type: Open-source project"], result
)
self.assert_in_success_response(
["Organization website: example.org"], result
)
self.assert_in_success_response(["Paid users: "], result)
self.assert_in_success_response(["Requested plan: Community"], result)
self.assert_in_success_response(
["Organization description: We help people."], result
)
self.assert_in_success_response(["Estimated total users: 20-35"], result)
self.assert_in_success_response(["Description of paid users: "], result)
result = self.client_get("/activity/remote/support", {"q": "admin@zulip-3.example.com"})
self.assert_in_success_response(["zulip-3.example.com
"], result)
self.assert_in_success_response(["Contact email: admin@zulip-3.example.com"], result)
self.assert_not_in_success_response(["zulip-1.example.com
"], result)
# Sponsorship request information
self.assert_not_in_success_response(
["Organization description: We help people."], result
)
self.assert_not_in_success_response(
["Estimated total users: 20-35"], result
)
self.assert_not_in_success_response(["Description of paid users: "], result)
class TestSupportEndpoint(ZulipTestCase):
def create_customer_and_plan(self, realm: Realm, monthly: bool = False) -> Customer:
now = datetime(2016, 1, 2, tzinfo=timezone.utc)
billing_schedule = CustomerPlan.BILLING_SCHEDULE_ANNUAL
price_per_license = 8000
months = 12
if monthly:
billing_schedule = CustomerPlan.BILLING_SCHEDULE_MONTHLY
price_per_license = 800
months = 1
customer = Customer.objects.create(realm=realm)
plan = CustomerPlan.objects.create(
customer=customer,
billing_cycle_anchor=now,
billing_schedule=billing_schedule,
tier=CustomerPlan.TIER_CLOUD_STANDARD,
price_per_license=price_per_license,
next_invoice_date=add_months(now, months),
)
LicenseLedger.objects.create(
licenses=10,
licenses_at_next_renewal=10,
event_time=timezone_now(),
is_renewal=True,
plan=plan,
)
return customer
def test_search(self) -> None:
reset_email_visibility_to_everyone_in_zulip_realm()
lear_user = self.lear_user("king")
lear_user.is_staff = True
lear_user.save(update_fields=["is_staff"])
lear_realm = get_realm("lear")
def assert_user_details_in_html_response(
html_response: "TestHttpResponse", full_name: str, email: str, role: str
) -> None:
self.assert_in_success_response(
[
'user\n',
f"{full_name}
",
f"Email: {email}",
"Is active: True
",
f"Role: {role}
",
],
html_response,
)
def create_invitation(
stream: str, invitee_email: str, realm: Optional[Realm] = None
) -> None:
invite_expires_in_minutes = 10 * 24 * 60
self.client_post(
"/json/invites",
{
"invitee_emails": [invitee_email],
"stream_ids": orjson.dumps([self.get_stream_id(stream, realm)]).decode(),
"invite_expires_in_minutes": invite_expires_in_minutes,
"invite_as": PreregistrationUser.INVITE_AS["MEMBER"],
},
subdomain=realm.string_id if realm is not None else "zulip",
)
def check_hamlet_user_query_result(result: "TestHttpResponse") -> None:
assert_user_details_in_html_response(
result, "King Hamlet", self.example_email("hamlet"), "Member"
)
self.assert_in_success_response(
[
f"Admins: {self.example_email('iago')}\n",
f"Owners: {self.example_email('desdemona')}\n",
'class="copy-button" data-copytext="{}">'.format(self.example_email("iago")),
'class="copy-button" data-copytext="{}">'.format(
self.example_email("desdemona")
),
],
result,
)
def check_lear_user_query_result(result: "TestHttpResponse") -> None:
assert_user_details_in_html_response(
result, lear_user.full_name, lear_user.email, "Member"
)
def check_othello_user_query_result(result: "TestHttpResponse") -> None:
assert_user_details_in_html_response(
result, "Othello, the Moor of Venice", self.example_email("othello"), "Member"
)
def check_polonius_user_query_result(result: "TestHttpResponse") -> None:
assert_user_details_in_html_response(
result, "Polonius", self.example_email("polonius"), "Guest"
)
def check_zulip_realm_query_result(result: "TestHttpResponse") -> None:
zulip_realm = get_realm("zulip")
first_human_user = zulip_realm.get_first_human_user()
assert first_human_user is not None
self.assert_in_success_response(
[
f"First human user: {first_human_user.delivery_email}\n",
f'",
'',
'',
'input type="number" name="discount" value="None"',
'',
'',
f'',
'',
'input type="number" name="discount" value="None"',
'',
'',
'scrub-realm-button">',
'data-string-id="lear"',
"Plan name: Zulip Cloud Standard",
"Status: Active",
"Billing schedule: Annual",
"Licenses: 2/10 (Manual)",
"Price per license: $80.0",
"Next invoice date: 02 January 2017",
'