Files
upstream-zulip/corporate/tests/test_sponsorship.py
upstream-mirror 64898b2c79
Some checks failed
Code scanning / CodeQL (push) Failing after 15s
Zulip production suite / Ubuntu 22.04 production build (push) Failing after 26s
Zulip production suite / Debian 12 production install with custom db name and user (push) Has been skipped
Zulip production suite / Ubuntu 22.04 production install and PostgreSQL upgrade with pgroonga (push) Has been skipped
Zulip production suite / Ubuntu 24.04 production install (push) Has been skipped
Zulip production suite / Ubuntu 26.04 production install (push) Has been skipped
Zulip production suite / Debian 13 production install (push) Has been skipped
Zulip production suite / 7.0 Version Upgrade (push) Has been skipped
Zulip production suite / 8.0 Version Upgrade (push) Has been skipped
Zulip production suite / 6.0 Version Upgrade (push) Has been skipped
Zulip production suite / 10.0 Version Upgrade (push) Has been skipped
Zulip production suite / 9.0 Version Upgrade (push) Has been skipped
Zulip production suite / 12.0 Version Upgrade (push) Has been skipped
Zulip production suite / 11.0 Version Upgrade (push) Has been skipped
GitHub Actions Security Analysis with zizmor / zizmor (push) Failing after 3s
Zulip CI / Debian 12 (Python 3.11, backend + documentation) (push) Failing after 26s
Zulip CI / Ubuntu 22.04 (Python 3.10, backend + frontend) (push) Failing after 3s
Zulip CI / Ubuntu 24.04 (Python 3.12, backend) (push) Failing after 25s
Zulip CI / Ubuntu 26.04 (Python 3.14, backend) (push) Failing after 25s
Zulip CI / Debian 13 (Python 3.13, backend) (push) Failing after 28s
Zulip production suite / Required jobs (push) Has been cancelled
Zulip CI / Required jobs (push) Has been cancelled
API Documentation Update Check / check-feature-level-updated (push) Has been cancelled
API Documentation Update Check / notify-if-api-docs-changed (push) Has been cancelled
snapshot of https://github.com/zulip/zulip.git @ 48c69911c7dd4e03b20552eb3bd77ad8151c1357 (2026-09-15)
2026-09-15 15:05:30 +05:00

441 lines
20 KiB
Python

from datetime import timedelta
from unittest.mock import Mock
import responses
import time_machine
from django.conf import settings
from django.utils.timezone import now as timezone_now
from typing_extensions import override
from corporate.lib.stripe import RemoteRealmBillingSession, RemoteServerBillingSession
from corporate.lib.test_stripe_class import StripeTestCase, mock_stripe
from corporate.models.customers import get_customer_by_realm
from corporate.models.plans import CustomerPlan
from corporate.models.sponsorships import ZulipSponsorshipRequest
from corporate.tests.test_remote_billing import RemoteRealmBillingTestCase, RemoteServerTestCase
from zerver.lib.remote_server import send_server_data_to_push_bouncer
from zerver.lib.test_helpers import activate_push_notification_service
from zerver.models import Realm
from zerver.models.realms import get_realm
from zilencer.models import RemoteRealm, RemoteZulipServer
class TestRealmSponsorship(StripeTestCase):
def test_request_sponsorship_form_with_invalid_url(self) -> None:
user = self.example_user("hamlet")
self.login_user(user)
data = {
"organization_type": Realm.ORG_TYPES["opensource"]["id"],
"website": "invalid-url",
"description": "Infinispan is a distributed in-memory key/value data store with optional schema.",
"expected_total_users": "10 users",
"plan_to_use_zulip": "For communication on moon.",
"paid_users_count": "1 user",
"paid_users_description": "We have 1 paid user.",
}
response = self.client_billing_post("/billing/sponsorship", data)
self.assert_json_error(response, "Enter a valid URL.")
def test_request_sponsorship_form_with_blank_url(self) -> None:
user = self.example_user("hamlet")
self.login_user(user)
data = {
"organization_type": Realm.ORG_TYPES["opensource"]["id"],
"website": "",
"description": "Infinispan is a distributed in-memory key/value data store with optional schema.",
"expected_total_users": "10 users",
"plan_to_use_zulip": "For communication on moon.",
"paid_users_count": "1 user",
"paid_users_description": "We have 1 paid user.",
}
response = self.client_billing_post("/billing/sponsorship", data)
self.assert_json_success(response)
@mock_stripe()
def test_sponsorship_access_for_realms_on_paid_plan(self, *mocks: Mock) -> None:
user = self.example_user("hamlet")
self.login_user(user)
self.add_card_and_upgrade(user)
response = self.client_get("/sponsorship/")
self.assert_in_success_response(
[
"How many paid staff does your organization have?",
],
response,
)
def test_request_sponsorship(self) -> None:
user = self.example_user("hamlet")
self.assertIsNone(get_customer_by_realm(user.realm))
self.login_user(user)
data = {
"organization_type": Realm.ORG_TYPES["opensource"]["id"],
"website": "https://infinispan.org/",
"description": "Infinispan is a distributed in-memory key/value data store with optional schema.",
"expected_total_users": "10 users",
"plan_to_use_zulip": "For communication on moon.",
"paid_users_count": "1 user",
"paid_users_description": "We have 1 paid user.",
}
response = self.client_billing_post("/billing/sponsorship", data)
self.assert_json_success(response)
customer = get_customer_by_realm(user.realm)
assert customer is not None
sponsorship_request = ZulipSponsorshipRequest.objects.filter(
customer=customer, requested_by=user
).first()
assert sponsorship_request is not None
self.assertEqual(sponsorship_request.org_website, data["website"])
self.assertEqual(sponsorship_request.org_description, data["description"])
self.assertEqual(
sponsorship_request.org_type,
Realm.ORG_TYPES["opensource"]["id"],
)
customer = get_customer_by_realm(user.realm)
assert customer is not None
self.assertEqual(customer.sponsorship_pending, True)
from django.core.mail import outbox
self.assert_length(outbox, 1)
for message in outbox:
self.assert_length(message.to, 1)
self.assertEqual(message.to[0], "sales@zulip.com")
self.assertEqual(message.subject, "Sponsorship request for zulip")
self.assertEqual(message.reply_to, ["hamlet@zulip.com"])
self.assertEqual(self.email_envelope_from(message), settings.NOREPLY_EMAIL_ADDRESS)
self.assertIn("Zulip sponsorship request <noreply-", self.email_display_from(message))
self.assertIn("Requested by: King Hamlet (Organization owner)", message.body)
self.assertIn(
"Support URL: http://zulip.testserver/activity/support?q=zulip", message.body
)
self.assertIn("Website: https://infinispan.org", message.body)
self.assertIn("Organization type: Open-source", message.body)
self.assertIn("Description:\nInfinispan is a distributed in-memory", message.body)
response = self.client_get("/upgrade/")
self.assertEqual(response.status_code, 302)
self.assertEqual(response["Location"], "http://zulip.testserver/sponsorship")
response = self.client_get("/billing/")
self.assertEqual(response.status_code, 302)
self.assertEqual(response["Location"], "/sponsorship/")
response = self.client_get("/sponsorship/")
self.assert_in_success_response(
[
"This organization has requested sponsorship for a",
'<a href="/plans/">Zulip Cloud Standard</a>',
'plan.<br/><a href="mailto:support@zulip.com">Contact Zulip support</a> with any questions or updates.',
],
response,
)
self.login_user(self.example_user("othello"))
response = self.client_get("/billing/")
self.assert_in_success_response(
["You do not have permission to view this page."],
response,
)
response = self.client_get("/invoices/")
self.assertEqual(response.status_code, 302)
self.assertEqual(response["Location"], "/billing/")
response = self.client_get("/customer_portal/")
self.assertEqual(response.status_code, 302)
self.assertEqual(response["Location"], "/billing/")
user.realm.plan_type = Realm.PLAN_TYPE_PLUS
user.realm.save()
response = self.client_get("/sponsorship/")
self.assertEqual(response.status_code, 302)
self.assertEqual(response["Location"], "/billing/")
user.realm.plan_type = Realm.PLAN_TYPE_STANDARD_FREE
user.realm.save()
self.login_user(self.example_user("hamlet"))
response = self.client_get("/sponsorship/")
self.assert_in_success_response(
[
'Zulip is sponsoring a free <a href="/plans/">Zulip Cloud Standard</a> plan for this organization. 🎉'
],
response,
)
def test_sponsorship_page_for_demo_organizations(self) -> None:
user = self.example_user("hamlet")
user.realm.demo_organization_scheduled_deletion_date = timezone_now() + timedelta(days=30)
user.realm.save()
self.login_user(user)
response = self.client_get("/sponsorship/", follow=True)
self.assert_in_success_response(
["Demo organizations cannot apply for sponsorship."], response
)
@activate_push_notification_service()
class TestRemoteRealmSponsorship(StripeTestCase, RemoteRealmBillingTestCase):
@override
def setUp(self) -> None:
super().setUp()
self.remote_realm = RemoteRealm.objects.get(uuid=get_realm("zulip").uuid)
self.billing_session = RemoteRealmBillingSession(remote_realm=self.remote_realm)
@responses.activate
def test_request_sponsorship(self) -> None:
self.login("hamlet")
hamlet = self.example_user("hamlet")
realm = hamlet.realm
self.add_mock_response()
send_server_data_to_push_bouncer(consider_usage_statistics=False)
remote_realm = RemoteRealm.objects.get(uuid=hamlet.realm.uuid)
billing_base_url = self.billing_session.billing_base_url
self.assertEqual(remote_realm.plan_type, RemoteRealm.PLAN_TYPE_SELF_MANAGED)
self.assertIsNone(self.billing_session.get_customer())
result = self.execute_remote_billing_authentication_flow(hamlet)
# User has no plan, so we redirect to /plans by default.
self.assertEqual(result["Location"], f"/realm/{realm.uuid!s}/plans/")
# Check strings on plans page.
result = self.client_get(result["Location"], subdomain="selfhosting")
self.assert_not_in_success_response(["Sponsorship pending"], result)
# Navigate to request sponsorship page.
result = self.client_get(f"{billing_base_url}/sponsorship/", subdomain="selfhosting")
self.assert_in_success_response(
["Description of your organization", "Requested plan"], result
)
# Submit form data.
data = {
"organization_type": Realm.ORG_TYPES["opensource"]["id"],
"website": "https://infinispan.org/",
"description": "Infinispan is a distributed in-memory key/value data store with optional schema.",
"expected_total_users": "10 users",
"plan_to_use_zulip": "For communication on moon.",
"paid_users_count": "1 user",
"paid_users_description": "We have 1 paid user.",
"requested_plan": "Community",
}
response = self.client_billing_post("/billing/sponsorship", data)
self.assert_json_success(response)
customer = self.billing_session.get_customer()
assert customer is not None
sponsorship_request = ZulipSponsorshipRequest.objects.get(customer=customer)
self.assertEqual(sponsorship_request.requested_plan, data["requested_plan"])
self.assertEqual(sponsorship_request.org_website, data["website"])
self.assertEqual(sponsorship_request.org_description, data["description"])
self.assertEqual(
sponsorship_request.org_type,
Realm.ORG_TYPES["opensource"]["id"],
)
from django.core.mail import outbox
# First email is remote user email confirmation, second email is for sponsorship
message = outbox[1]
self.assert_length(outbox, 2)
self.assert_length(message.to, 1)
self.assertEqual(message.to[0], "sales@zulip.com")
self.assertEqual(message.subject, "Sponsorship request for Zulip Dev")
self.assertEqual(message.reply_to, ["hamlet@zulip.com"])
self.assertEqual(self.email_envelope_from(message), settings.NOREPLY_EMAIL_ADDRESS)
self.assertIn("Zulip sponsorship request <noreply-", self.email_display_from(message))
self.assertIn(
f"Support URL: http://zulip.testserver/activity/remote/support?q={remote_realm.uuid!s}",
message.body,
)
self.assertIn("Website: https://infinispan.org", message.body)
self.assertIn("Organization type: Open-source", message.body)
self.assertIn("Description:\nInfinispan is a distributed in-memory", message.body)
# Check /billing redirects you to sponsorship page.
response = self.client_get(f"{billing_base_url}/billing/", subdomain="selfhosting")
self.assertEqual(response.status_code, 302)
self.assertEqual(response["Location"], f"/realm/{realm.uuid!s}/sponsorship/")
# Check sponsorship page shows sponsorship pending banner.
result = self.client_get(f"{billing_base_url}/sponsorship/", subdomain="selfhosting")
self.assert_in_success_response(
["This organization has requested sponsorship for a", "Community"], result
)
# Approve sponsorship
billing_session = RemoteRealmBillingSession(
remote_realm=remote_realm, support_staff=self.example_user("iago")
)
billing_session.approve_sponsorship()
remote_realm.refresh_from_db()
self.assertEqual(remote_realm.plan_type, RemoteRealm.PLAN_TYPE_COMMUNITY)
# Assert such a plan exists
CustomerPlan.objects.get(
customer=customer,
tier=CustomerPlan.TIER_SELF_HOSTED_COMMUNITY,
status=CustomerPlan.ACTIVE,
next_invoice_date=None,
price_per_license=0,
)
# Check email sent.
expected_message = (
"Your request for Zulip sponsorship has been approved! Your organization has been upgraded to the Zulip Community plan."
"\n\nIf you could list Zulip as a sponsor on your website, we would really appreciate it!"
)
self.assert_length(outbox, 3)
message = outbox[2]
self.assert_length(message.to, 1)
self.assertEqual(message.to[0], "hamlet@zulip.com")
self.assertEqual(message.subject, "Community plan sponsorship approved for Zulip Dev!")
self.assertEqual(message.from_email, "noreply@testserver")
self.assertIn(expected_message[0], message.body)
self.assertIn(expected_message[1], message.body)
# Check sponsorship approved banner.
result = self.client_get(f"{billing_base_url}/sponsorship/", subdomain="selfhosting")
self.assert_in_success_response(["Zulip is sponsoring a free", "Community"], result)
@activate_push_notification_service()
class TestRemoteServerSponsorship(StripeTestCase, RemoteServerTestCase):
@override
def setUp(self) -> None:
super().setUp()
self.remote_server = RemoteZulipServer.objects.get(hostname="demo.example.com")
self.billing_session = RemoteServerBillingSession(remote_server=self.remote_server)
@responses.activate
def test_request_sponsorship(self) -> None:
hamlet = self.example_user("hamlet")
now = timezone_now()
with time_machine.travel(now, tick=False):
result = self.execute_remote_billing_authentication_flow(
hamlet.delivery_email, hamlet.full_name, expect_tos=True, confirm_tos=True
)
self.add_mock_response()
send_server_data_to_push_bouncer(consider_usage_statistics=False)
billing_base_url = self.billing_session.billing_base_url
self.assertEqual(self.remote_server.plan_type, RemoteZulipServer.PLAN_TYPE_SELF_MANAGED)
self.assertIsNone(self.billing_session.get_customer())
# User has no plan, so we redirect to /plans by default.
self.assertEqual(result["Location"], f"/server/{self.remote_server.uuid!s}/plans/")
# Check strings on plans page.
result = self.client_get(result["Location"], subdomain="selfhosting")
self.assert_not_in_success_response(["Sponsorship pending"], result)
# Navigate to request sponsorship page.
result = self.client_get(f"{billing_base_url}/sponsorship/", subdomain="selfhosting")
self.assert_in_success_response(
["Description of your organization", "Requested plan"], result
)
# Submit form data.
data = {
"organization_type": Realm.ORG_TYPES["opensource"]["id"],
"website": "https://infinispan.org/",
"description": "Infinispan is a distributed in-memory key/value data store with optional schema.",
"expected_total_users": "10 users",
"plan_to_use_zulip": "For communication on moon.",
"paid_users_count": "1 user",
"paid_users_description": "We have 1 paid user.",
"requested_plan": "Community",
}
response = self.client_billing_post("/billing/sponsorship", data)
self.assert_json_success(response)
customer = self.billing_session.get_customer()
assert customer is not None
sponsorship_request = ZulipSponsorshipRequest.objects.get(customer=customer)
self.assertEqual(sponsorship_request.requested_plan, data["requested_plan"])
self.assertEqual(sponsorship_request.org_website, data["website"])
self.assertEqual(sponsorship_request.org_description, data["description"])
self.assertEqual(
sponsorship_request.org_type,
Realm.ORG_TYPES["opensource"]["id"],
)
from django.core.mail import outbox
# First email is remote user email confirmation, second email is for sponsorship
message = outbox[1]
self.assert_length(outbox, 2)
self.assert_length(message.to, 1)
self.assertEqual(message.to[0], "sales@zulip.com")
self.assertEqual(message.subject, "Sponsorship request for demo.example.com")
self.assertEqual(message.reply_to, ["hamlet@zulip.com"])
self.assertEqual(self.email_envelope_from(message), settings.NOREPLY_EMAIL_ADDRESS)
self.assertIn("Zulip sponsorship request <noreply-", self.email_display_from(message))
self.assertIn(
f"Support URL: http://zulip.testserver/activity/remote/support?q={self.remote_server.uuid!s}",
message.body,
)
self.assertIn("Website: https://infinispan.org", message.body)
self.assertIn("Organization type: Open-source", message.body)
self.assertIn("Description:\nInfinispan is a distributed in-memory", message.body)
# Check /billing redirects you to sponsorship page.
response = self.client_get(f"{billing_base_url}/billing/", subdomain="selfhosting")
self.assertEqual(response.status_code, 302)
self.assertEqual(response["Location"], f"/server/{self.remote_server.uuid!s}/sponsorship/")
# Check sponsorship page shows sponsorship pending banner.
result = self.client_get(f"{billing_base_url}/sponsorship/", subdomain="selfhosting")
self.assert_in_success_response(
["This organization has requested sponsorship for a", "Community"], result
)
# Approve sponsorship
billing_session = RemoteServerBillingSession(
remote_server=self.remote_server, support_staff=self.example_user("iago")
)
billing_session.approve_sponsorship()
self.remote_server.refresh_from_db()
self.assertEqual(self.remote_server.plan_type, RemoteZulipServer.PLAN_TYPE_COMMUNITY)
# Assert such a plan exists
CustomerPlan.objects.get(
customer=customer,
tier=CustomerPlan.TIER_SELF_HOSTED_COMMUNITY,
status=CustomerPlan.ACTIVE,
next_invoice_date=None,
price_per_license=0,
)
# Check email sent.
expected_message = (
"Your request for Zulip sponsorship has been approved! Your organization has been upgraded to the Zulip Community plan."
"\n\nIf you could list Zulip as a sponsor on your website, we would really appreciate it!"
)
self.assert_length(outbox, 3)
message = outbox[2]
self.assert_length(message.to, 1)
self.assertEqual(message.to[0], "hamlet@zulip.com")
self.assertEqual(
message.subject, "Community plan sponsorship approved for demo.example.com!"
)
self.assertEqual(message.from_email, "noreply@testserver")
self.assertIn(expected_message[0], message.body)
self.assertIn(expected_message[1], message.body)
# Check sponsorship approved banner.
result = self.client_get(f"{billing_base_url}/sponsorship/", subdomain="selfhosting")
self.assert_in_success_response(["Zulip is sponsoring a free", "Community"], result)