snapshot of https://github.com/zulip/zulip.git @ 48c69911c7dd4e03b20552eb3bd77ad8151c1357 (2026-09-15)
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
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
This commit is contained in:
158
zerver/views/development/integrations.py
Normal file
158
zerver/views/development/integrations.py
Normal file
@@ -0,0 +1,158 @@
|
||||
import os
|
||||
from contextlib import suppress
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
import orjson
|
||||
from django.http import HttpRequest, HttpResponse
|
||||
from django.http.response import HttpResponseBase
|
||||
from django.shortcuts import render
|
||||
from django.test import Client
|
||||
from pydantic import Json
|
||||
|
||||
from zerver.lib.exceptions import JsonableError, ResourceNotFoundError
|
||||
from zerver.lib.integrations import INCOMING_WEBHOOK_INTEGRATIONS
|
||||
from zerver.lib.response import json_success
|
||||
from zerver.lib.typed_endpoint import PathOnly, typed_endpoint
|
||||
from zerver.lib.webhooks.common import call_fixture_to_headers, standardize_headers
|
||||
from zerver.models import UserProfile
|
||||
from zerver.models.realms import get_realm
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from django.test.client import _MonkeyPatchedWSGIResponse as TestHttpResponse
|
||||
|
||||
ZULIP_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../../")
|
||||
|
||||
|
||||
def get_webhook_integrations() -> list[str]:
|
||||
return [integration.name for integration in INCOMING_WEBHOOK_INTEGRATIONS]
|
||||
|
||||
|
||||
def get_valid_integration_name(name: str) -> str | None:
|
||||
for integration_name in get_webhook_integrations():
|
||||
if name == integration_name:
|
||||
return integration_name
|
||||
return None
|
||||
|
||||
|
||||
def dev_panel(request: HttpRequest) -> HttpResponse:
|
||||
integrations = get_webhook_integrations()
|
||||
bots = UserProfile.objects.filter(is_bot=True, bot_type=UserProfile.INCOMING_WEBHOOK_BOT)
|
||||
context = {
|
||||
"integrations": integrations,
|
||||
"bots": bots,
|
||||
# We set isolated_page to avoid clutter from footer/header.
|
||||
"isolated_page": True,
|
||||
}
|
||||
return render(request, "zerver/development/integrations_dev_panel.html", context)
|
||||
|
||||
|
||||
def send_webhook_fixture_message(
|
||||
url: str, body: str, is_json: bool, custom_headers: dict[str, Any]
|
||||
) -> "TestHttpResponse":
|
||||
client = Client()
|
||||
realm = get_realm("zulip")
|
||||
standardized_headers = standardize_headers(custom_headers)
|
||||
http_host = standardized_headers.pop("HTTP_HOST", realm.host)
|
||||
if is_json:
|
||||
content_type = standardized_headers.pop("HTTP_CONTENT_TYPE", "application/json")
|
||||
else:
|
||||
content_type = standardized_headers.pop("HTTP_CONTENT_TYPE", "text/plain")
|
||||
return client.post(
|
||||
url,
|
||||
body,
|
||||
content_type=content_type,
|
||||
follow=False,
|
||||
secure=False,
|
||||
headers=None,
|
||||
query_params=None,
|
||||
HTTP_HOST=http_host,
|
||||
**standardized_headers,
|
||||
)
|
||||
|
||||
|
||||
@typed_endpoint
|
||||
def get_fixtures(request: HttpRequest, *, integration_name: PathOnly[str]) -> HttpResponse:
|
||||
valid_integration_name = get_valid_integration_name(integration_name)
|
||||
if not valid_integration_name:
|
||||
raise ResourceNotFoundError(f'"{integration_name}" is not a valid webhook integration.')
|
||||
|
||||
fixtures = {}
|
||||
fixtures_dir = os.path.join(ZULIP_PATH, f"zerver/webhooks/{valid_integration_name}/fixtures")
|
||||
if not os.path.exists(fixtures_dir):
|
||||
msg = f'The integration "{valid_integration_name}" does not have fixtures.'
|
||||
raise ResourceNotFoundError(msg)
|
||||
|
||||
for fixture in os.listdir(fixtures_dir):
|
||||
fixture_path = os.path.join(fixtures_dir, fixture)
|
||||
with open(fixture_path) as f:
|
||||
body = f.read()
|
||||
# The file extension will be used to determine the type.
|
||||
with suppress(orjson.JSONDecodeError):
|
||||
body = orjson.loads(body)
|
||||
|
||||
headers_raw = call_fixture_to_headers(
|
||||
valid_integration_name, "".join(fixture.split(".")[:-1])
|
||||
)
|
||||
|
||||
def fix_name(header: str) -> str: # nocoverage
|
||||
return header.removeprefix("HTTP_") # HTTP_ is a prefix intended for Django.
|
||||
|
||||
headers = {fix_name(k): v for k, v in headers_raw.items()}
|
||||
fixtures[fixture] = {"body": body, "headers": headers}
|
||||
|
||||
return json_success(request, data={"fixtures": fixtures})
|
||||
|
||||
|
||||
@typed_endpoint
|
||||
def check_send_webhook_fixture_message(
|
||||
request: HttpRequest,
|
||||
*,
|
||||
url: str,
|
||||
body: str,
|
||||
is_json: Json[bool],
|
||||
custom_headers: str,
|
||||
) -> HttpResponseBase:
|
||||
try:
|
||||
custom_headers_dict = orjson.loads(custom_headers)
|
||||
except orjson.JSONDecodeError as ve: # nocoverage
|
||||
raise JsonableError(f"Custom HTTP headers are not in a valid JSON format. {ve}") # nolint
|
||||
|
||||
response = send_webhook_fixture_message(url, body, is_json, custom_headers_dict)
|
||||
if response.status_code == 200:
|
||||
responses = [{"status_code": response.status_code, "message": response.content.decode()}]
|
||||
return json_success(request, data={"responses": responses})
|
||||
else: # nocoverage
|
||||
return response
|
||||
|
||||
|
||||
@typed_endpoint
|
||||
def send_all_webhook_fixture_messages(
|
||||
request: HttpRequest, *, url: str, integration_name: str
|
||||
) -> HttpResponse:
|
||||
valid_integration_name = get_valid_integration_name(integration_name)
|
||||
if not valid_integration_name: # nocoverage
|
||||
raise ResourceNotFoundError(f'"{integration_name}" is not a valid webhook integration.')
|
||||
|
||||
fixtures_dir = os.path.join(ZULIP_PATH, f"zerver/webhooks/{valid_integration_name}/fixtures")
|
||||
if not os.path.exists(fixtures_dir):
|
||||
msg = f'The integration "{valid_integration_name}" does not have fixtures.'
|
||||
raise ResourceNotFoundError(msg)
|
||||
|
||||
responses = []
|
||||
for fixture in os.listdir(fixtures_dir):
|
||||
fixture_path = os.path.join(fixtures_dir, fixture)
|
||||
with open(fixture_path) as f:
|
||||
content = f.read()
|
||||
x = fixture.split(".")
|
||||
fixture_name, fixture_format = "".join(_ for _ in x[:-1]), x[-1]
|
||||
headers = call_fixture_to_headers(valid_integration_name, fixture_name)
|
||||
is_json = fixture_format == "json"
|
||||
response = send_webhook_fixture_message(url, content, is_json, headers)
|
||||
responses.append(
|
||||
{
|
||||
"status_code": response.status_code,
|
||||
"fixture_name": fixture,
|
||||
"message": response.content.decode(),
|
||||
}
|
||||
)
|
||||
return json_success(request, data={"responses": responses})
|
||||
Reference in New Issue
Block a user