mirror of
				https://github.com/zulip/zulip.git
				synced 2025-11-04 05:53:43 +00:00 
			
		
		
		
	screenshot webhooks: Add support for multipart/form-data fixtures.
Add a common function for webhooks to convert multipart strings to dict. This facilitates loading a multipart/form-data fixture as a file string, and converting it. This will allow testing integrations that use multipart/form-data, and generating their example screenshots using a script. Note that this only supports text fields, accommodation for binary files is not included at the moment.
This commit is contained in:
		@@ -50,7 +50,7 @@ from zerver.lib.integrations import (
 | 
				
			|||||||
from zerver.lib.storage import static_path
 | 
					from zerver.lib.storage import static_path
 | 
				
			||||||
from zerver.lib.streams import create_stream_if_needed
 | 
					from zerver.lib.streams import create_stream_if_needed
 | 
				
			||||||
from zerver.lib.upload import upload_avatar_image
 | 
					from zerver.lib.upload import upload_avatar_image
 | 
				
			||||||
from zerver.lib.webhooks.common import get_fixture_http_headers
 | 
					from zerver.lib.webhooks.common import get_fixture_http_headers, parse_multipart_string
 | 
				
			||||||
from zerver.models import Message, UserProfile
 | 
					from zerver.models import Message, UserProfile
 | 
				
			||||||
from zerver.models.realms import get_realm
 | 
					from zerver.models.realms import get_realm
 | 
				
			||||||
from zerver.models.users import get_user_by_delivery_email
 | 
					from zerver.models.users import get_user_by_delivery_email
 | 
				
			||||||
@@ -94,8 +94,9 @@ def create_integration_stream(integration: Integration, bot: UserProfile) -> Non
 | 
				
			|||||||
    bulk_add_subscriptions(realm, [stream], [bot, bot.bot_owner], acting_user=bot)
 | 
					    bulk_add_subscriptions(realm, [stream], [bot, bot.bot_owner], acting_user=bot)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_fixture_info(fixture_path: str) -> tuple[Any, bool, str]:
 | 
					def get_fixture_info(fixture_path: str) -> tuple[Any, bool, bool, str]:
 | 
				
			||||||
    json_fixture = fixture_path.endswith(".json")
 | 
					    json_fixture = fixture_path.endswith(".json")
 | 
				
			||||||
 | 
					    multipart_fixture = fixture_path.endswith(".multipart")
 | 
				
			||||||
    _, fixture_name = split_fixture_path(fixture_path)
 | 
					    _, fixture_name = split_fixture_path(fixture_path)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if fixture_name:
 | 
					    if fixture_name:
 | 
				
			||||||
@@ -105,10 +106,12 @@ def get_fixture_info(fixture_path: str) -> tuple[Any, bool, str]:
 | 
				
			|||||||
        else:
 | 
					        else:
 | 
				
			||||||
            with open(fixture_path) as f:
 | 
					            with open(fixture_path) as f:
 | 
				
			||||||
                data = f.read().strip()
 | 
					                data = f.read().strip()
 | 
				
			||||||
 | 
					            if multipart_fixture:
 | 
				
			||||||
 | 
					                data = parse_multipart_string(data)
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
        data = ""
 | 
					        data = ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return data, json_fixture, fixture_name
 | 
					    return data, json_fixture, multipart_fixture, fixture_name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_integration(integration_name: str) -> Integration:
 | 
					def get_integration(integration_name: str) -> Integration:
 | 
				
			||||||
@@ -142,7 +145,7 @@ def send_bot_mock_message(
 | 
				
			|||||||
) -> None:
 | 
					) -> None:
 | 
				
			||||||
    # Delete all messages, so new message is the only one it's message group
 | 
					    # Delete all messages, so new message is the only one it's message group
 | 
				
			||||||
    Message.objects.filter(realm_id=bot.realm_id, sender=bot).delete()
 | 
					    Message.objects.filter(realm_id=bot.realm_id, sender=bot).delete()
 | 
				
			||||||
    data, _, _ = get_fixture_info(fixture_path)
 | 
					    data, _, _, _ = get_fixture_info(fixture_path)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    assert bot.bot_owner is not None
 | 
					    assert bot.bot_owner is not None
 | 
				
			||||||
    url = f"{bot.bot_owner.realm.url}"
 | 
					    url = f"{bot.bot_owner.realm.url}"
 | 
				
			||||||
@@ -169,7 +172,7 @@ def send_bot_payload_message(
 | 
				
			|||||||
) -> bool:
 | 
					) -> bool:
 | 
				
			||||||
    # Delete all messages, so new message is the only one it's message group
 | 
					    # Delete all messages, so new message is the only one it's message group
 | 
				
			||||||
    Message.objects.filter(realm_id=bot.realm_id, sender=bot).delete()
 | 
					    Message.objects.filter(realm_id=bot.realm_id, sender=bot).delete()
 | 
				
			||||||
    data, json_fixture, fixture_name = get_fixture_info(fixture_path)
 | 
					    data, json_fixture, multipart_fixture, fixture_name = get_fixture_info(fixture_path)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    headers = get_requests_headers(integration.name, fixture_name)
 | 
					    headers = get_requests_headers(integration.name, fixture_name)
 | 
				
			||||||
    if config.custom_headers:
 | 
					    if config.custom_headers:
 | 
				
			||||||
@@ -186,7 +189,10 @@ def send_bot_payload_message(
 | 
				
			|||||||
    params.update(config.extra_params)
 | 
					    params.update(config.extra_params)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    extra_args = {}
 | 
					    extra_args = {}
 | 
				
			||||||
    if not json_fixture and data:
 | 
					    if multipart_fixture:
 | 
				
			||||||
 | 
					        extra_args = {"data": data}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    elif not json_fixture and data:
 | 
				
			||||||
        # We overwrite any params in fixture with our params. stream name, for
 | 
					        # We overwrite any params in fixture with our params. stream name, for
 | 
				
			||||||
        # example, may be defined in the fixture.
 | 
					        # example, may be defined in the fixture.
 | 
				
			||||||
        assert isinstance(data, str)
 | 
					        assert isinstance(data, str)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -249,3 +249,27 @@ def unix_milliseconds_to_timestamp(milliseconds: Any, webhook: str) -> datetime:
 | 
				
			|||||||
        raise JsonableError(
 | 
					        raise JsonableError(
 | 
				
			||||||
            _("The {webhook} webhook expects time in milliseconds.").format(webhook=webhook)
 | 
					            _("The {webhook} webhook expects time in milliseconds.").format(webhook=webhook)
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def parse_multipart_string(body: str) -> dict[str, str]:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Converts multipart/form-data string (fixture) to dict
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    boundary = body.split("\n")[0][2:]
 | 
				
			||||||
 | 
					    parts = body.split(f"--{boundary}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    data = {}
 | 
				
			||||||
 | 
					    for part in parts:
 | 
				
			||||||
 | 
					        if part.strip() in ["", "--"]:
 | 
				
			||||||
 | 
					            continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        headers, body = part.split("\n\n", 1)
 | 
				
			||||||
 | 
					        body = body.removesuffix("\n--")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        content_disposition = next(
 | 
				
			||||||
 | 
					            (line for line in headers.splitlines() if "Content-Disposition" in line), ""
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        field_name = content_disposition.split('name="')[1].split('"')[0]
 | 
				
			||||||
 | 
					        data[field_name] = body
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return data
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user