ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Zulip Incoming Webhooks 参考指南:自定义 HTTP 头、URL 参数与负向测试的完整实现

Zulip Incoming Webhooks 参考指南:自定义 HTTP 头、URL 参数与负向测试的完整实现 Zulip Incoming Webhooks 参考指南自定义 HTTP 头、URL 参数与负向测试的完整实现【免费下载链接】zulipZulip server and web application. Open-source team chat that helps teams stay productive and focused.项目地址: https://gitcode.com/GitHub_Trending/zu/zulipZulip 的 incoming webhook入站 Webhook框架是连接第三方服务与 Zulip 对话的关键通道。本文以 docs/webhooks/incoming-webhooks-reference.md 为骨架深入讲解开发与配置 Zulip 入站 Webhook 时的三大进阶主题自定义 HTTP 头的事件类型提取与 fixture 编码、自定义 URL 查询参数url_options 与预设配置以及**负向测试Negative tests**的编写方法。读者将掌握如何在view.py中调用get_event_header与default_fixture_to_headers如何通过WebhookUrlOption.build_preset_config为 Web 端生成集成 URL弹窗配置 UI 选项以及如何绕过check_webhook手动构造错误场景测试同时结合仓库源码zerver/lib/webhooks/common.py、zerver/lib/integrations.py、zerver/lib/test_classes.py获得底层实现视角。如果你需要分步创建第一个入站 Webhook请先阅读 docs/webhooks/incoming-webhooks-walkthrough.md本文是面向进阶配置的参考手册。一、自定义 HTTP 头从请求中提取事件类型1.1 为什么需要自定义 HTTP 头部分第三方出站 Webhook API例如 GitHub 的并不会把事件的所有信息都放进 HTTP 请求体中。相反它们会把关键细节——触发本次 payload 的事件类型event type——放进一个独立的 HTTP 头中。例如 GitHub 使用X-GitHub-Event头Bitbucket 使用X-Event-Key头。这一点通常在该第三方服务的官方 API 文档中有明确说明你在创建 fixture测试夹具时也应参考该文档。1.2 使用get_event_header提取 HTTP 头值在你的view.py主 webhook 函数中可以从 zerver/lib/webhooks/common.py 导入并使用get_event_header函数来获取 HTTP 头值from zerver.lib.webhooks.common import get_event_header event get_event_header(request, header, integration_name)三个参数的含义参数说明request传入 webhook 主函数的 DjangoHttpRequest对象header你想提取的自定义头名称例如X-Event-Keyintegration_name第三方服务的名称例如GitHub用于错误提示文案源码级实现细节查看 common.py 的get_event_header实现可以看到它实际做了三件事通过request.headers.get(header)提取头值若头缺失使用MISSING_EVENT_HEADER_MESSAGE定义于 common.py模板构造一封说明邮件式的提示消息并通过send_rate_limited_pm_notification_to_bot_owner向 webhook 机器人的所有者发送一条直接消息告知其缺失了哪个头、该头为何重要抛出一个MissingHTTPEventHeaderErrorcommon.py其错误码为ErrorCode.MISSING_HTTP_EVENT_HEADER消息为Missing the HTTP event header {header}。之所以用直接消息而不是静默忽略是因为这类头的缺失通常意味着配置问题要么用户为集成配置了错误的 URL比如把 A 集成的 URL 填到了 B 集成上要么用户运行的是不发送该头的旧版本集成。让机器人所有者直接收到提示可以最快定位问题。1.3 在 fixture 中记录事件类型 HTTP 头为了测试 Zulip 对这类数据的处理你需要在捕获每个 fixture 时记录其对应的 HTTP 头。由于这是集成相关的行为Zulip 支持一种简单统一的命名格式把 HTTP 头的值编码在 fixture 文件名的第一部分与其余部分用双下划线__分隔。例如pull_request__opened.jsonpull_request是X-Github-Event头的值opened是该事件类型的子类型subtype两者用双下划线分隔这样每个段内部仍可以使用单下划线例如pull_request本身。在 zerver/webhooks/github/view.py 中可以看到 GitHub 集成正是这样声明的fixture_to_headers default_fixture_to_headers(HTTP_X_GITHUB_EVENT)1.4 从 fixture 文件名中提取事件类型 HTTP 头要在测试中从 fixture 文件名得到 HTTP 头值可以在你的view.py中定义一个fixture_to_headers函数直接复用 common.py 中的default_fixture_to_headersfixture_to_headers default_fixture_to_headers(HTTP_X_GITHUB_EVENT)HTTP_X_GITHUB_EVENT是你希望提取的自定义头名注意这里用的是 Django 规范化的头名格式大写、下划线、带HTTP_前缀。源码级实现细节default_fixture_to_headers的默认实现common.py逻辑很简单取出 fixture 文件名的第一部分以__分隔作为头值返回def fixture_to_headers(filename: str) - dict[str, str]: if __ in filename: event_type filename.split(__, 1)[0] else: event_type filename return {http_header_key: event_type}也就是说对于pull_request__opened.json返回{HTTP_X_GITHUB_EVENT: pull_request}。如果你需要用不同的方式编码头值例如一个 fixture 对应多个头或编码规则更复杂可以在view.py中自定义fixture_to_headers函数实现自己的解析逻辑替代default_fixture_to_headers。测试框架通过call_fixture_to_headerscommon.py按约定从zerver.webhooks.{integration_dir_name}.view模块动态导入这个函数——找不到就返回空 dict。这些头值最终如何进入测试请求在 zerver/lib/test_classes.py 的check_webhook中可以看到headers call_fixture_to_headers(self.webhook_dir_name, fixture_name) headers standardize_headers(headers) extra.update(headers)standardize_headerscommon.py会把类似X-GitHub-Event这样的头名规范化为 Django 期望的HTTP_X_GITHUB_EVENT大写加前缀格式再合并进 POST 请求。二、自定义 URL 查询参数url_options 与配置项2.1 注册需要自定义配置的 Webhook某些入站 Webhook 集成支持可选的 URL 参数。此时可以使用url_options特性——它是IncomingWebhookIntegration类的一个字段在 Web 端和桌面端生成集成 URL为机器人生成时被使用负责把用户对每个参数的输入编码进集成 URL 中。例如在 zerver/lib/integrations.py 中IncomingWebhookIntegration的构造函数接收url_options: Sequence[WebhookUrlOption]参数。声明方式如下IncomingWebhookIntegration( helloworld, ... url_options[ WebhookUrlOption( nameignore_private_repositories, labelExclude notifications from private repositories, input_typecheckbox, ), ], )url_options是一个列表描述 Web 应用 UI 在生成集成 URL 时应提供哪些参数字段含义name参数名用于把用户输入编码进集成的 webhook URLlabelWeb 应用 UI 中对该 URL 参数的简短描述标签input_type该选项在 UI 中对应的输入字段类型当前 Web 应用 UI 支持的input_type有三种checkbox复选框输入表示仅存在与否的值true 或缺失默认未选中checkbox_enabled复选框输入表示布尔参数默认已选中text文本框输入用于字符串值。如果需要在 UI 中支持其他输入类型可以更新 web/src/integration_url_modal.ts 中对应的实现。关于config_options字段在极少数情况下入站 Webhook 需要的用户配置超出了 POST URL 所能表达的范畴。典型场景是某些 API 要求客户端先回调获取细节而不仅仅是一个可以放进 Zulip 通知消息里的不透明对象 ID。IncomingWebhookIntegration类中的config_options字段就是为这种场景预留的——它对应 common.py 中的WebhookConfigOption数据类包含name、label和一个validator校验函数Callable[[str, str], str | bool | None]用于在集成代码中校验用户提供的配置值。2.2WebhookUrlOption预设PresetsWebhookUrlOption.build_preset_config方法common.py可以创建预配置字段的WebhookUrlOption对象。这些预设 URL 选项主要有两个用途构造多个入站 Webhook 集成中通用的WebhookUrlOption对象构造在 Web 应用中具有特殊 UI的WebhookUrlOption对象同样用于生成入站 webhook URL 的场景。其他用途下你可以直接使用WebhookUrlOption类。使用预设 URL 选项的示例参考 zerver/lib/integrations.py 中 GitHub 集成的真实写法# zerver/lib/integrations.py from zerver.lib.webhooks.common import PresetUrlOption, WebhookUrlOption # -- snip -- IncomingWebhookIntegration( github, # -- snip -- url_options[ WebhookUrlOption.build_preset_config(PresetUrlOption.BRANCHES), WebhookUrlOption.build_preset_config( PresetUrlOption.IGNORE_PRIVATE_REPOSITORIES ), ], ),当前可用的预设选项PresetUrlOption枚举定义于 common.py包含三个成员。build_preset_config通过match语句Python 3.10 的模式匹配把它们映射为具体的WebhookUrlOptionBRANCHESbranches预设用于版本控制类集成如 GitHub、GitLab为 UI 增加配置项让用户指定项目仓库的哪些分支会触发 Zulip 通知消息用户指定分支后branches参数会被追加到生成的集成 URL 中。例如用户输入仓库的main和dev两个分支URL 末尾会被追加branchesmain%2Cdev%2C是逗号的 URL 编码多个分支以逗号分隔其底层实现为WebhookUrlOption(namebranches, label, input_typetext)——一个文本框。IGNORE_PRIVATE_REPOSITORIESignore_private_repositories预设用于版本控制类集成为 UI 增加配置项让用户排除私有仓库触发 Zulip 通知消息用户勾选后ignore_private_repositories布尔参数会被追加到生成的集成 URL 中底层实现为WebhookUrlOption(nameignore_private_repositories, labelExclude notifications from private repositories, input_typecheckbox)。CHANNEL_MAPPINGmapping预设用于聊天应用类集成如 Slack为 Web 应用 UI 增加一个特殊选项Matching Zulip channel用于决定通知消息发送到哪个 Zulip 频道该特殊选项会把通知消息映射到与第三方服务中原始频道名匹配的 Zulip 频道选中该选项时要求为通知消息设置单一主题topic并向生成的集成 URL 追加mappingchannels底层实现为WebhookUrlOption(namemapping, label, input_typetext)。在 integrations.py 附近可以看到CHANNEL_MAPPING被用于 Slack 类通信集成的实例。这三个预设的name值与 URL 中追加的查询参数名一一对应branches、ignore_private_repositories、mapping便于在集成代码中解析。2.3 为自定义 URL 查询参数编写测试自定义 URL 查询参数在 webhook 代码中可以正常工作但在测试中需要特殊处理。从查询参数获取 stream 和 topic 的 Webhook 函数例如下面是一个同时从查询参数获取stream和topic的 webhook 函数定义webhook_view(Querytest) typed_endpoint def api_querytest_webhook( request: HttpRequest, user_profile: UserProfile, *, payload: Annotated[str, ApiParamConfig(argument_type_is_bodyTrue)], stream: str test, topic: str Default Alert, ) - HttpResponse:在实际使用中你可能会把第三方服务配置为使用类似这样的 URL 调用 Zulip 入站 webhookhttp://myhost/api/v1/external/querytest?api_keyabcdefghstreamalertstopicqueries它为stream和topic提供了值集成代码通过typed_endpoint无需任何特殊处理即可获取它们。那么在测试中如何构造这样的 URL 呢通过build_webhook_url传递查询参数WebhookTestCase基类zerver/lib/test_classes.py的build_webhook_urltest_classes.py支持把关键字参数拼接为 URL 查询参数。TOPIC类属性只存在于你的测试类中——要构造带topic查询参数的 URL可以把TOPIC属性作为关键字参数传给build_webhook_urlclass QuerytestHookTests(WebhookTestCase): TOPIC Default topic def test_querytest_test_one(self) - None: # construct the URL used for this test self.TOPIC Query test self.url self.build_webhook_url(topicself.TOPIC) # define the expected message contents expected_topic Query test expected_message This is a test of custom query parameters. self.check_webhook(test_one, expected_topic, expected_message, content_typeapplication/x-www-form-urlencoded)从源码看build_webhook_url的拼接逻辑是先用url_template填充webhook_dir_name、api_key、stream等占位符再把kwargs和args以keyvalue的形式追加到 URL 末尾test_classes.py。如果你的测试数据需要用非常规方式构造还可以覆写get_body或get_payloadget_payloadtest_classes.py默认委托给get_body通常返回字符串需要时也可以返回 dictget_bodytest_classes.py通过self.webhook_fixture_data(...)读取 fixture 文件内容并立即用orjson.loads(body)校验其是合法 JSON——如果没有可用 fixture 文件就需要覆写它。更多细节可查看WebhookTestCase基类的定义zerver/lib/test_classes.py或在仓库的 webhook 测试中搜索build_webhook_url的实际用例。三、负向测试Negative tests如何断言错误场景3.1 什么是负向测试负向测试是指预期会产生错误的测试例如第三方 payload 或头部数据不正确。要正确测试这些场景你必须显式地编写测试执行逻辑按需使用其他测试辅助函数而不是调用通常的check_webhook测试辅助函数。原因在于check_webhooktest_classes.py内部会调用send_webhook_payload而send_webhook_payload会用assert_json_success断言 webhook 返回成功结果——如果 webhook 返回错误测试反而会失败。因此负向测试需要手动模仿check_webhook的设置步骤然后自己检查错误结果。3.2 来自 WordPress 集成的完整示例下面是一个来自 WordPress 集成的负向测试示例def test_unknown_action_no_data(self) - None: # Mimic check_webhook() to manually execute a negative test. # Otherwise its call to send_webhook_payload() would assert on the non-success # we are testing. The value of result is the error message the webhook should # return if no params are sent. The fixture for this test is an empty file. # subscribe to the target channel self.subscribe(self.test_user, self.channel_name) # post to the webhook url post_params {stream_name: self.channel_name, content_type: application/x-www-form-urlencoded} result self.client_post(self.url, unknown_action, **post_params) # check that we got the expected error message self.assert_json_error(result, Unknown WordPress webhook action: WordPress action)3.3 三个关键测试辅助函数解析这个示例展示了三个关键辅助函数均来自ZulipTestCase/WebhookTestCase基类subscribe测试辅助函数使用基类的test_user和channel_name属性将用户注册为指定频道的接收者。如果该频道不存在会先创建它见 test_classes.py 中check_webhook对它的调用方式。client_post另一个辅助函数执行调用入站 webhook 的 HTTP POST 请求。只要self.url是正确的你就不需要自己构造 webhook URL大多数情况下它都是正确的。在WebhookTestCase.setUptest_classes.py中self.url已经通过build_webhook_url()基于类属性url_template自动构造好了。assert_json_error检查返回结果是否与预期错误匹配。如果你用的是check_webhook它内部会调用send_webhook_payload而后者用assert_json_success检查结果——这恰好与负向测试的目标相反。3.4 运行测试写完测试后从 Zulip 开发环境中运行测试./tools/test-backend zerver/webhooks/wordpress如果全部测试通过你会看到类似输出Running zerver.webhooks.wordpress.tests.WordPressHookTests.test_unknown_action_no_data DONE!四、实际集成中的典型组合用法以上三类进阶能力在真实集成中往往是组合出现的。以仓库中最复杂的 zerver/webhooks/github/view.py 为例头部提取get_event_header(request, X-GitHub-Event, GitHub)view.py在运行时提取事件类型fixture 编码fixture_to_headers default_fixture_to_headers(HTTP_X_GITHUB_EVENT)view.py让测试时能根据pull_request__opened.json这样的文件名还原头部URL 选项url_options[WebhookUrlOption.build_preset_config(PresetUrlOption.BRANCHES), ...]integrations.py在 Web 端为生成集成 URL 弹窗提供分支过滤、私有仓库排除等配置项。GitHub 集成同时用到了这三项能力可以作为阅读组合用法的第一手参考。五、结语与延伸阅读本文覆盖了 Zulip 入站 Webhook 参考指南的全部核心主题通过get_event_header从请求头提取事件类型、通过default_fixture_to_headers与 fixture 命名约定在测试中还原头部、通过url_options与build_preset_config为 Web 端 URL 生成 UI 提供配置项、以及通过显式断言来编写负向测试。文中所有关键函数都能在 zerver/lib/webhooks/common.py 中找到源码实现测试基础设施集中在 zerver/lib/test_classes.py 的WebhookTestCase基类中。进一步探索建议docs/webhooks/incoming-webhooks-walkthrough.md分步创建入站 Webhook 的完整教程docs/webhooks/incoming-webhooks-overview.mdURL 规范与集成总体概览docs/webhooks/incoming-webhooks-walkthrough.md#step-5-create-automated-testscheck_webhook与send_and_test_private_message的标准用法docs/webhooks/incoming-webhooks-walkthrough.md#command-line-toolssend_webhook_fixture_message管理命令及--custom-headers参数的手动测试方式zerver/lib/integrations.pyINCOMING_WEBHOOK_INTEGRATIONS列表中所有集成的url_options实际声明web/src/integration_url_modal.tsWeb 端生成集成 URL弹窗中input_type的前端实现。【免费下载链接】zulipZulip server and web application. Open-source team chat that helps teams stay productive and focused.项目地址: https://gitcode.com/GitHub_Trending/zu/zulip创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表