diff --git a/corporate/tests/test_remote_billing.py b/corporate/tests/test_remote_billing.py index e7548e43e7..4a8d136be1 100644 --- a/corporate/tests/test_remote_billing.py +++ b/corporate/tests/test_remote_billing.py @@ -24,6 +24,7 @@ from corporate.models import ( ) from corporate.views.remote_billing_page import generate_confirmation_link_for_server_deactivation from zerver.actions.realm_settings import do_deactivate_realm +from zerver.lib.exceptions import RemoteRealmServerMismatchError from zerver.lib.remote_server import send_server_data_to_push_bouncer from zerver.lib.test_classes import BouncerTestCase from zerver.lib.timestamp import datetime_to_timestamp @@ -175,6 +176,57 @@ class RemoteRealmBillingTestCase(BouncerTestCase): return result +@override_settings(PUSH_NOTIFICATION_BOUNCER_URL="https://push.zulip.org.example.com") +class SelfHostedBillingEndpointBasicTest(RemoteRealmBillingTestCase): + @responses.activate + def test_self_hosted_billing_endpoints(self) -> None: + self.login("desdemona") + + self.add_mock_response() + + self_hosted_billing_url = "/self-hosted-billing/" + self_hosted_billing_json_url = "/json/self-hosted-billing" + + with self.settings(PUSH_NOTIFICATION_BOUNCER_URL=None): + result = self.client_get(self_hosted_billing_url) + self.assertEqual(result.status_code, 404) + self.assert_in_response("Page not found (404)", result) + + result = self.client_get(self_hosted_billing_json_url) + self.assert_json_error(result, "Server doesn't use the push notification service", 404) + + with mock.patch( + "zerver.views.push_notifications.send_to_push_bouncer", + side_effect=RemoteRealmServerMismatchError, + ): + result = self.client_get(self_hosted_billing_url) + self.assertEqual(result.status_code, 403) + self.assert_in_response("Unexpected Zulip server registration", result) + + result = self.client_get(self_hosted_billing_json_url) + self.assert_json_error( + result, + "Your organization is registered to a different Zulip server. Please contact Zulip support for assistance in resolving this issue.", + 403, + ) + + # Now test successes. We only check that an url for accessing the remote billing system + # is returned (in the appropriate format - redirect or json data, depending on the endpoint). + # We don't need to test that returned URL beyond that, because that's just the full auth flow, + # which gets tested properly in other tests. + result = self.client_get(self_hosted_billing_url) + self.assertEqual(result.status_code, 302) + self.assertIn("http://selfhosting.testserver/remote-billing-login/", result["Location"]) + + result = self.client_get(self_hosted_billing_json_url) + self.assert_json_success(result) + data = result.json() + self.assertEqual(sorted(data.keys()), ["billing_access_url", "msg", "result"]) + self.assertIn( + "http://selfhosting.testserver/remote-billing-login/", data["billing_access_url"] + ) + + @override_settings(PUSH_NOTIFICATION_BOUNCER_URL="https://push.zulip.org.example.com") class RemoteBillingAuthenticationTest(RemoteRealmBillingTestCase): @responses.activate diff --git a/web/src/desktop_integration.js b/web/src/desktop_integration.js index 047cd005d0..3cf194b651 100644 --- a/web/src/desktop_integration.js +++ b/web/src/desktop_integration.js @@ -2,6 +2,8 @@ import $ from "jquery"; import * as browser_history from "./browser_history"; import * as channel from "./channel"; +import * as feedback_widget from "./feedback_widget"; +import {$t} from "./i18n"; import * as message_store from "./message_store"; import * as narrow from "./narrow"; import * as stream_data from "./stream_data"; @@ -63,3 +65,32 @@ if (window.electron_bridge !== undefined) { }); }); } + +export function initialize() { + if (window.electron_bridge === undefined) { + return; + } + + $(document).on("click", "#open-self-hosted-billing", (event) => { + event.preventDefault(); + + const url = "/json/self-hosted-billing"; + + channel.get({ + url, + success(data) { + window.open(data.billing_access_url, "_blank", "noopener,noreferrer"); + }, + error(xhr) { + if (xhr.responseJSON?.msg) { + feedback_widget.show({ + populate($container) { + $container.text(xhr.responseJSON.msg); + }, + title_text: $t({defaultMessage: "Error"}), + }); + } + }, + }); + }); +} diff --git a/web/src/ui_init.js b/web/src/ui_init.js index 4a58a6a693..fdf580366d 100644 --- a/web/src/ui_init.js +++ b/web/src/ui_init.js @@ -34,6 +34,7 @@ import * as composebox_typeahead from "./composebox_typeahead"; import * as condense from "./condense"; import * as copy_and_paste from "./copy_and_paste"; import * as dark_theme from "./dark_theme"; +import * as desktop_integration from "./desktop_integration"; import * as desktop_notifications from "./desktop_notifications"; import * as drafts from "./drafts"; import * as drafts_overlay_ui from "./drafts_overlay_ui"; @@ -701,6 +702,7 @@ export function initialize_everything() { fenced_code.initialize(generated_pygments_data); message_edit_history.initialize(); hotkey.initialize(); + desktop_integration.initialize(); $("#app-loading").addClass("loaded"); } diff --git a/web/templates/gear_menu_popover.hbs b/web/templates/gear_menu_popover.hbs index 39f20bb1a8..be4d909d7c 100644 --- a/web/templates/gear_menu_popover.hbs +++ b/web/templates/gear_menu_popover.hbs @@ -135,7 +135,7 @@ {{/if}} {{#if show_remote_billing }}