mirror of
				https://github.com/zulip/zulip.git
				synced 2025-11-04 05:53:43 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			159 lines
		
	
	
		
			5.8 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			159 lines
		
	
	
		
			5.8 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
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 WEBHOOK_INTEGRATIONS
 | 
						|
from zerver.lib.response import json_success
 | 
						|
from zerver.lib.typed_endpoint import PathOnly, typed_endpoint
 | 
						|
from zerver.lib.webhooks.common import get_fixture_http_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 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 = get_fixture_http_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 = get_fixture_http_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})
 |