Zulip Incoming Webhook 开发全指南:从设计原理到集成实现与测试发布

发布时间:2026/9/13 23:54:55
Zulip Incoming Webhook 开发全指南:从设计原理到集成实现与测试发布 Zulip Incoming Webhook 开发全指南从设计原理到集成实现与测试发布【免费下载链接】zulipZulip server and web application. Open-source team chat that helps teams stay productive and focused.项目地址: https://gitcode.com/GitHub_Trending/zu/zulip导读本文是 Zulip 开源团队聊天服务中入站 Webhookincoming webhook集成开发的完整实战指南。它系统讲解如何让第三方服务在事件发生时将数据推送到 Zulip 并自动格式化为聊天消息包括整体架构与 URL 规范、以 Hello World 为例的七步开发流程fixture 捕获、视图编写、端点注册、手动测试、自动化测试、文档与 PR 提交以及自定义 HTTP 头、自定义 URL 查询参数、负面测试等进阶主题。读完本文你将掌握在 Zulip 仓库zerver/webhooks/目录中从零编写、测试并发布一个官方级 Webhook 集成的完整方法论。关联文档本仓库的 docs/webhooks/index.md、incoming-webhooks-overview.md、incoming-webhooks-walkthrough.md 与 incoming-webhooks-reference.md 构成完整的 Webhook 开发文档体系本文以其为骨架并结合源码深度展开。一、什么是入站 Webhook三种接入方式与工作原理在 Zulip 中入站 Webhookincoming webhook允许第三方服务在发生事件时把数据推送到 Zulip。官方文档给出了三种接入方式详见 docs/webhooks/incoming-webhooks-overview.md直接使用 REST API 发送消息调用 Zulip [发送消息 REST 端点]适合内部工具或第三方工具希望完全控制 Zulip 中消息格式的场景。使用集成框架包括与 Slack 兼容的入站 Webhook、Zapier 集成、IFTTT 集成等适合希望借用既有生态的团队。实现一个真正的入站 Webhook 集成把格式化 Zulip 消息的全部逻辑放在 Zulip 服务端。这也是 Zulip 绝大多数官方集成的工作方式——因为许多第三方服务本身只有出站 Webhookoutgoing webhook能力由 Zulip 服务端承接这些请求第三方无需为 Zulip 做任何定制工作。工作原理在入站 Webhook 集成中第三方服务的出站 Webhook功能在事件发生时向一个特殊 URL 发送HTTP POST请求Zulip 的入站 Webhook集成接收这些传入数据经处理后格式化并发送一条 Zulip 消息。从仓库源码看这一注册与路由机制的实现在 zerver/lib/integrations.pyIncomingWebhookIntegration类源码class IncomingWebhookIntegration(Integration)位于该文件通过DEFAULT_URL api/v1/external/{name}与DEFAULT_FUNCTION_PATH zerver.webhooks.{dir_name}.view.api_{dir_name}_webhook把 URL 与视图函数绑定并用path(url, self.view)生成 Django 路由见url_objects属性。官方文档强调使用正确的方法一个新的官方 Webhook 集成含测试与文档通常只需数小时即可完成。二、Webhook URL 规范地址格式与查询参数基础 URL入站 Webhook 机器人的基础 URL 格式为INTEGRATION_NAME是具体集成名API_KEY是用户为集成创建的机器人 API Keyhttps://your-org.zulipchat.com/v1/external/INTEGRATION_NAME?api_keyAPI_KEY在开发环境中则为http://localhost:9991/api/v1/external/integration_name?api_keyapi_key。现有集成的完整列表可以在zerver/lib/integrations.py的INCOMING_WEBHOOK_INTEGRATIONS中查看源码见该文件的INCOMING_WEBHOOK_INTEGRATIONS: list[IncomingWebhookIntegration] [处。支持的查询参数参数是否必填说明api_key必填用户为集成创建的机器人 API Key用于身份认证。机器人 API Key 的获取方式参见 API keys 文档。stream可选集成发送通知的目标频道。可以是频道 ID也可以是 URL 编码后的频道名。默认情况下集成会向机器人所有者的直接消息私信发送通知。频道 ID 可在 Web 或桌面应用浏览频道时找到。topic可选指定频道内集成发送通知的主题。主题同样需要 URL 编码。默认情况下频道消息会有集成配置的默认主题。only_events/exclude_events可选部分集成支持用这两个参数过滤触发通知的事件。可追加only_events[event_a,event_b]或exclude_events[event_a,event_b]也可两者同时使用但事件不同事件数量不限。支持 UNIX 风格通配符如test*匹配所有以test开头的事件。事件过滤的底层实现only_events/exclude_events的过滤逻辑实现在 zerver/lib/webhooks/common.py 的check_send_webhook_message函数中当传入complete_event_type时函数会将其与用户配置的only_events/exclude_events列表用fnmatch.fnmatchUNIX shell 风格通配符逐一比对若事件不满足配置函数直接返回None而不发送任何消息。这段源码印证了文档中支持 UNIX 风格通配符的描述。消息发送的目标选择同一个check_send_webhook_message函数typed_endpoint装饰还实现了文档中描述的目标选择逻辑若 URL 中指定了stream查询参数发送频道消息。若stream参数是纯数字则视为频道 IDstream.isdecimal()分支否则视为频道名还支持unquote_url_parameters选项用于兼容 Jira 等第三方服务对 URL 双重转义%20未被正确解码的场景若未指定stream向 Webhook 机器人所有者发送私信若目标频道不存在会由check_message自动向机器人所有者发送一条私信告知例如测试用例 zerver/webhooks/helloworld/tests.py 中test_stream_error_pm_to_bot_owner所验证的Your botwebhook-botzulip.comtried to send a message to channel #nonexistent, but that channel does not exist.因此无需重新抛出异常以免污染webhook-errors.log。三、快速上手清单创建集成的七步路线开发环境准备先搭建 Zulip 开发环境。随后按以下顺序推进使用 Zulip 的 JSON 集成、https://webhook.site/ 或类似站点捕获第三方服务的出站 Webhook示例 payload在zerver/webhooks/mywebhook/fixtures/目录中存放捕获的 payload 作为测试 fixture。创建IncomingWebhookIntegration对象并加入zerver/lib/integrations.py的INCOMING_WEBHOOK_INTEGRATIONS列表。在zerver/webhooks/mywebhook/view.py编写 Webhook 处理器zerver/webhooks/目录下有大量可参考的示例。在zerver/webhooks/mywebhook/tests.py为 fixture 编写测试并运行tools/test-backend zerver/webhooks/mywebhook/迭代调试测试与处理器直至全部通过。捕获第三方服务其他常见类型的 payload并为它们补充测试。在zerver/webhooks/mywebhook/doc.md编写集成文档可参考 GitHub 集成文档 作为模板另有独立的集成文档编写指南。准备并提交 Pull Request。需要创建的文件清单以MyWebHook为例zerver/webhooks/mywebhook/__init__.py空文件Python 包的必备组成部分记得git add它。zerver/webhooks/mywebhook/view.py主 Webhook 处理器api_mywebhook_webhook及所需辅助函数。zerver/webhooks/mywebhook/fixtures/message_type.json来自第三方服务的 payload 样例数据供测试使用为集成支持的事件类型和条件添加 fixtures。zerver/webhooks/mywebhook/tests.pyWebhook 测试。zerver/webhooks/mywebhook/doc.md面向终端用户的集成配置文档。static/images/integrations/logos/mywebhook.svg第三方服务的方形 Logo用于文档页面。static/images/integrations/mywebhook/001.png集成发送消息的截图用于文档页可通过运行以下命令生成tools/screenshots/generate-integration-docs-screenshot --integration mywebhookstatic/images/integrations/bot_avatars/mywebhook.png第三方服务的方形 Logo用于生成示例截图时创建机器人的头像可由static/images/integrations/logos/mywebhook.svg自动生成tools/setup/generate_integration_bots_avatars.py需要更新的文件zerver/lib/integrations.py把集成的IncomingWebhookIntegration加入INCOMING_WEBHOOK_INTEGRATIONS。这会自动注册形如api/v1/external/mywebhook的 URL并将其关联到zerver/webhooks/mywebhook/view.py中名为api_mywebhook_webhook的函数。四、Hello World 实战逐步编写一个 Webhook本仓库自带一个完整可运行的示例集成——Zulip Hello World目录 zerver/webhooks/helloworld/。它接收一个虚构第三方服务发送的、包含维基百科每日精选文章信息的 HTTP POST JSON 数据将其格式化为 hello 消息并发送到 Zulip 指定会话。Step 0创建 fixtures第一步是研究第三方服务会向 Zulip 发送的数据。用 JSON 集成、webhook.site 或类似工具捕获出站 Webhook payload其用途有二确定集成代码的结构集成应支持哪些事件类型、以何种方式支持创建 fixtures测试 fixture 是包含某一类事件/场景测试数据的小文件使集成代码无需真正联系第三方服务即可被测试。每个集成支持的每一种事件类型都应有一个测试因此需要相应 fixtures。根据第三方数据格式不同fixtures 可以是 JSON、URL 编码文本或其他类型数据。Hello World 只做一件事因此只需一个 fixturezerver/webhooks/helloworld/fixtures/hello.json{ featured_title:Marilyn Monroe, featured_url:https://en.wikipedia.org/wiki/Marilyn_Monroe }Fixture 编写规范必须真实真实第三方服务的 fixtures 必须是实际捕获的 payload或来自该服务官方 API 文档的 payload绝不能手写、凭空编造或用 AI 生成。伪造的 payload 不能反映服务真实发送的数据据此开发的集成可能无法处理真实事件。只收集测试所需若想测试不同 URL 查询参数值优先写测试复用同一个 fixture而不是捕获多个几乎相同的 fixture。保护隐私不要使用不希望进入 fixture 的个人信息fixture 应原样使用、不要编辑内容。确需移除个人信息时先在本地提交捕获的 fixture再把个人信息替换为同样逼真的字符串并 amend 提交——这样脱敏就是一个可审查的 diff原始数据永不进入公开历史。命名与一致性fixture 数据会出现在集成产生的消息、主题以及文档的自动示例截图中因此配置第三方服务时请使用真实感强的项目、任务等实体名称尽量与zerver/webhooks/fixtureless_integrations.py中的共享示例内容保持一致。fixture 文件名用 snake_case 按事件及变体描述性命名如issue_created_with_assignee.json若事件类型等信息放在 HTTP 头中头的值编码在文件名首段与其余部分用双下划线分隔详见自定义 HTTP 头一节。无需频繁更新只要集成读取的字段仍是服务发送的字段即使服务多年后新增字段也不必更新 fixture。Step 1初始化 Python 包在zerver/webhooks/下为集成新建子目录示例为helloworld并创建空__init__.pytouch zerver/webhooks/helloworld/__init__.pyStep 2编写主 Webhook 代码集成的大部分代码在单个view.py文件中。Hello World 的完整实现见 zerver/webhooks/helloworld/view.pyfrom django.http import HttpRequest, HttpResponse from zerver.decorator import webhook_view from zerver.lib.response import json_success from zerver.lib.typed_endpoint import JsonBodyPayload, typed_endpoint from zerver.lib.validator import WildValue, check_string from zerver.lib.webhooks.common import check_send_webhook_message from zerver.models import UserProfile webhook_view(HelloWorld) typed_endpoint def api_helloworld_webhook( request: HttpRequest, user_profile: UserProfile, *, payload: JsonBodyPayload[WildValue], ) - HttpResponse: # construct the body of the message body Hello! I am happy to be here! :smile: # try to add the Wikipedia article of the day body_template ( \nThe Wikipedia featured article for today is **{featured_title}** ) body body_template.format( featured_titlepayload[featured_title].tame(check_string), featured_urlpayload[featured_url].tame(check_string), ) topic_name Hello World # send the message check_send_webhook_message(request, user_profile, topic_name, body) return json_success(request)装饰器Decoratorstyped_endpoint允许集成通过JsonBodyPayload[WildValue]访问请求变量更多关于JsonBodyPayload与请求变量的内容见编写视图教程。它还能把 URL 查询参数自动绑定为函数参数如后面自定义 URL 查询参数一节中的stream、topic参数。webhook_view必须传入集成名称该名称会用于 Zulip 分析页面中描述该集成。命名约定使用第三方服务名称的 camelCase按服务自身拼写为准唯一例外是首字母必须大写即使服务品牌首字母是小写。webhook_view表示第三方服务通过查询参数中的 API Key 完成授权若第三方服务使用 HTTP Basic 认证则应改用authenticated_rest_api_view装饰器。主处理函数函数命名如api_helloworld_webhook把helloworld换成集成名的小写形式。最少必须接受requestDjangoHttpRequest对象与user_profileZulip 用户对象还可通过typed_endpoint定义额外参数。Hello World 中还有一个payload参数由 HTTP POST 请求体填充。消息正文使用 Zulip 消息格式如:smile:表示 emojiJSON payload 数据用于维基百科文章链接。异常处理若 JSON payload 缺少集成所检查的必要键抛出的KeyError由 Zulip 服务端后端处理并生成合适响应。默认主题当 Webhook URL 通过stream参数指定了频道接收方但未指定主题时应定义一个默认主题名。发送消息check_send_webhook_message会校验消息并执行URL 指定stream则发频道消息否则向 Webhook 机器人所有者发私信。返回最后通过json_success(request)以 HTTP 200 JSON 格式返回成功消息。Step 3注册 API 端点入站 Webhook 必须映射到 URL 才能对外可用注册在zerver/lib/integrations.py。找到以如下代码开头的列表INCOMING_WEBHOOK_INTEGRATIONS: List[IncomingWebhookIntegration] [在其中找到 Hello World 的条目IncomingWebhookIntegration( helloworld, [misc], [WebhookScreenshotConfig(hello.json)], display_nameHello World ),参数含义第一个参数helloworld集成名告诉 Zulip API 在收到/api/v1/external/helloworld请求时调用zerver/webhooks/helloworld/view.py中的api_helloworld_webhook函数第二个参数[misc]定义集成在官方集成文档中所属的分类可用的分类常量见源码CATEGORIES字典如monitoring、version-control、communication、continuous-integration等第三个参数[WebhookScreenshotConfig(hello.json)]定义生成与更新文档页示例截图的配置。从源码看WebhookScreenshotConfig是 dataclass支持fixture_name、image_name默认001.png、image_dir、channel、extra_params、use_basic_auth、custom_headers等字段第四个参数display_nameHello World集成在文档中的显示名称。该条目同时把集成加入 Zulip 主集成文档。若集成支持可选 URL 参数可用url_options参数详见自定义 URL 查询参数一节在生成集成 URL的弹窗中创建对应 UI 元素。Step 4手动测试 Webhook在 Zulip 开发环境中手动测试。无论用哪种工具都需要先为入站 Webhook 机器人获取 API Key并替换下面命令中的api_key。方式一curl开发服务器在另一个控制台窗口运行时curl -X POST -H Content-Type: application/json -d { featured_title:Marilyn Monroe, featured_url:https://en.wikipedia.org/wiki/Marilyn_Monroe } http://localhost:9991/api/v1/external/helloworld\?api_key\api_key预期输出{msg:,result:success}以机器人所有者身份登录 Web 应用应能看到机器人发来的新私信。方式二send_webhook_fixture_message管理命令在 Zulip 开发环境中使用manage.py(zulip-server) vagrantvagrant:/srv/zulip$ ./manage.py send_webhook_fixture_message \ --fixturezerver/webhooks/helloworld/fixtures/hello.json \ --urlhttp://localhost:9991/api/v1/external/helloworld?api_keyapi_key预期输出类似2016-07-07 15:06:59,187 INFO 127.0.0.1 POST 200 143ms (mem: 6ms/13) (md: 43ms/1) (db: 20ms/9q) (start: 147ms) /api/v1/external/helloworld (helloworld-botzulip.com via ZulipHelloWorldWebhook)某些 Webhook 需要自定义 HTTP 头可用--custom-headers传入./manage.py send_webhook_fixture_message --custom-headers{X-Custom-Header: value}格式是 JSON 对象注意头名不能包含空格且务必使用上面所示的精确引号方式。更多manage.py命令见管理命令文档。方式三集成开发面板GUI运行./tools/run-dev浏览器访问http://localhost:9991/devtools/integrations/在下拉菜单选择机器人、集成与 fixture点击Send通知消息将发送到开发环境的默认 Zulip 组织。在一个标签页打开 Zulip、另一个打开该工具即可快速调整 Webhook 代码并针对不同 fixture 发送示例消息。自定义 HTTP 头需以 JSON 对象形式输入。与真实第三方服务联调:::warning 开发环境未针对恶意流量加固仅在积极测试时暴露它。 :::若要让第三方服务直接投递 Webhook服务需要能通过互联网访问你的开发服务器。无需重新配置开发服务器可用 UltraHook、localtunnel 等隧道工具把http://localhost:9991暴露为一个临时公网 URL。在第三方服务配置 Webhook 时使用该临时公网 URL 拼接集成路径例如temporary-public-url/api/v1/external/helloworld?api_keyapi_key真实数据的端到端测试有助于发现为什么没生效以及服务是否使用了集成需要处理的自定义 HTTP 头。Step 5编写自动化测试每个入站 Webhook 集成都应有对应的tests.py。Hello World 测试见 zerver/webhooks/helloworld/tests.py。测试类命名为WebhookNameHookTests继承WebhookTestCase基类定义于 zerver/lib/test_classes.pyclass HelloWorldHookTests(WebhookTestCase): DIRECT_MESSAGE_URL_TEMPLATE /api/v1/external/helloworld?api_key{api_key} # Note: Include a test function per each distinct message condition your integration supports def test_hello_message(self) - None: expected_topic_name Hello World expected_message Hello! I am happy to be here! :smile:\nThe Wikipedia featured article for today is **[Marilyn Monroe](https://en.wikipedia.org/wiki/Marilyn_Monroe)** # use fixture named hello.json self.check_webhook( hello, expected_topic_name, expected_message, content_typeapplication/x-www-form-urlencoded, ) def test_pm_to_bot_owner(self) - None: # Note that this is really just a test for check_send_webhook_message self.url_template self.DIRECT_MESSAGE_URL_TEMPLATE self.url self.build_webhook_url() expected_message Hello! I am happy to be here! :smile:\nThe Wikipedia featured article for today is **[Goodbye](https://en.wikipedia.org/wiki/Goodbye)** self.send_and_test_private_message( goodbye, expected_messageexpected_message, content_typeapplication/x-www-form-urlencoded, )规则为集成支持的每种事件类型与条件写一个测试函数。例如若为 Hello World 增加 goodbye 消息支持则新增test_goodbye_messagedef test_goodbye_message(self) - None: expected_topic_name Hello World expected_message Hello! I am happy to be here! :smile:\nThe Wikipedia featured article for today is **[Goodbye](https://en.wikipedia.org/wiki/Goodbye)** # use fixture named goodbye.json self.check_webhook( goodbye, expected_topic_name, expected_message, content_typeapplication/x-www-form-urlencoded, )以及新 fixturezerver/webhooks/helloworld/fixtures/goodbye.json{ featured_title:Goodbye, featured_url:https://en.wikipedia.org/wiki/Goodbye }另外应考虑是否需要负面测试数据应导致错误的测试详见负面测试一节。运行新测试./tools/test-backend zerver/webhooks/helloworld全部通过时输出Running zerver.webhooks.helloworld.tests.HelloWorldHookTests.test_goodbye_message Running zerver.webhooks.helloworld.tests.HelloWorldHookTests.test_hello_message DONE!除文档展示的示例外仓库中 Hello World 测试还覆盖了自定义主题test_custom_topic通过self.build_webhook_url(topic...)传入topic查询参数、以及频道不存在时机器人所有者收到通知test_stream_error_pm_to_bot_owner由settings.NOTIFICATION_BOT系统机器人发送错误私信等典型场景可作为你编写测试的参考模板。Step 6编写集成文档集成要能被使用必须有面向终端用户的文档所有入站 Webhook 都会进入 Zulip 主集成文档。用户可见的集成文档分两部分集成网格中的胶囊lozenge显示集成 Logo 与名称是通往详细文档的链接。每个集成需要一个第三方服务的方形 SVG Logo存放于static/images/integrations/logos目录Hello World 的 Logo 在 static/images/integrations/logos/helloworld.svg。胶囊在集成加入INCOMING_WEBHOOK_INTEGRATIONS后自动生成并可通过IncomingWebhookIntegration类的选项定制。详细文档内容位于集成目录中的doc.md文件Hello World 文档见 zerver/webhooks/helloworld/doc.md。Zulip 有一套基于宏的 Markdown/Jinja2 框架包含 Webhook/集成文档的常用指令宏如{!create-an-incoming-webhook.md!}、{!congrats.md!}。文档页示例截图可参考集成文档编写指南轻松生成。Step 7准备 Pull Request集成完成后推送代码前检查运行测试与 linter处理其报告的问题见测试文档与linter 文档通读代码风格与约定并复查代码检查 Git 历史确保提交清晰、逻辑合理见提交纪律多数入站 Webhook 集成只需一个提交信息清晰、良好的单一提交。想随时获得反馈可在 Zulip 开发社区发消息也可以在开发过程中先创建 draft pull request见Git 指南。五、常用辅助工具Common Helpers以下辅助函数集中在 zerver/lib/webhooks/common.py可大幅简化集成开发get_setup_webhook_message当集成会收到第三方服务的测试 payload 时用它生成标准的测试消息。导入自zerver/lib/webhooks/common.py生成形如 GitHub webhook is successfully configured! 的消息源码中SETUP_MESSAGE_TEMPLATE {integration} webhook has been successfully configured可附带用户名。guess_zulip_user_from_external_account若集成引用了外部账号用户名如 GitHub 用户名可用它自动把外部账号与在个人资料自定义字段中关联了这些账号的 Zulip 用户匹配从而把外部用户名转换为静默提及silent mentions通知相关 Zulip 用户。六、一般性建议善用消息格式用 Zulip 的 Markdown 消息格式emoji、Markdown 强调、提及让集成输出更具可读性或实用性。有效使用主题确保同一事物的连续消息能串联到同一主题。例如 bug 跟踪集成把所有消息的 bug 编号放进主题Nagios 类集成把服务名放进主题。提供可配置性不符合团队工作流的集成往往是无效的垃圾信息。应认真考虑提供仅对某些事件类型、某些项目触发消息把不同消息发送到不同频道/主题等选项让团队能按自身工作流配置。命名一致文档与实现中对集成名及服务名的拼写、大小写应与第三方厂商保持一致实现中可以全部小写。主动联系第三方若第三方看起来没有可用的 API 或出站 Webhook联系其官方有时会有收获——你要找的 API 可能只是文档不完善。七、进阶主题自定义 HTTP 头部分第三方出站 Webhook API如 GitHub并不把所有事件信息编码进请求体而是把事件类型等关键细节放在单独的 HTTP 头中。这通常在第三方 API 文档中会明确说明。从 payload 中提取事件类型 HTTP 头在view.py中使用zerver/lib/webhooks/common.py的get_event_header函数event get_event_header(request, header, integration_name)request传给主 Webhook 函数的HttpRequest对象header要提取的自定义头名如X-Event-Keyintegration_name第三方服务名如GitHub。由于这类头是部分集成标识出站 payload 事件类型的方式缺少该头通常意味着配置问题如输入了其他集成的 URL或运行了不设置该头的旧版集成。当头缺失时该函数会向 Webhook 机器人所有者发送限流私信通知并抛出MissingHTTPEventHeaderError其错误消息为 Missing the HTTP event header {header}定义于同一文件的MissingHTTPEventHeaderError异常类。在 fixtures 中记录事件类型 HTTP 头测试 Zulip 对该数据的处理时需要记录每个捕获 fixture 使用的 HTTP 头。Zulip 支持一种简单格式把 HTTP 头的值编码在 fixture 文件名的首段例如pull_request__opened.jsonpull_request是头X-Github-Event的值opened是事件类型的子类型。二者用双下划线分隔以允许单段内使用单下划线。从 fixtures 中提取事件类型 HTTP 头在view.py中定义fixture_to_headers函数使用default_fixture_to_headersfixture_to_headers default_fixture_to_headers(HTTP_X_GITHUB_EVENT)HTTP_X_GITHUB_EVENT是要提取的自定义头名。默认实现取 fixture 文件名的双下划线首段作为头值default_fixture_to_headers源码逻辑文件名含__则取split(__, 1)[0]否则整个文件名即头值。需要不同的编码方法时可在view.py中自定义fixture_to_headers函数。测试框架通过call_fixture_to_headers同文件按约定从集成模块导入该函数来解析头。八、进阶主题自定义 URL 查询参数注册需要自定义配置的 Webhook若集成支持可选 URL 参数可使用url_options功能。它是IncomingWebhookIntegration类的字段用于在 Web/桌面应用生成集成 URL时为机器人编码用户输入。声明方式IncomingWebhookIntegration( helloworld, ... url_options[ WebhookUrlOption( nameignore_private_repositories, labelExclude notifications from private repositories, input_typecheckbox, ), ], )url_options是一个列表描述 Web 应用 UI 在生成集成 URL 时应提供的参数WebhookUrlOption是定义于zerver/lib/webhooks/common.py的 dataclass字段为name/label/input_typename参数名用于把用户输入编码进集成的 Webhook URLlabelWeb 应用 UI 中该 URL 参数的简短描述性标签input_typeUI 中该选项对应的输入字段类型目前支持checkbox存在性取值的复选框true 或缺失默认关闭checkbox_enabled布尔参数的复选框默认开启text字符串值的文本输入框。要支持更多输入类型可更新web/src/integration_url_modal.ts。极少数情况下入站 Webhook 需要 POST URL 之外的用户配置——典型场景是 API 要求客户端回调以获取不透明对象 ID 之外的更多详情用于放进 Zulip 通知消息。IncomingWebhookIntegration类的config_options字段为这种场景保留对应WebhookConfigOptionname、label与校验函数validator。WebhookUrlOption 预设Presetsbuild_preset_config方法创建预配置字段的WebhookUrlOption对象源码实现见zerver/lib/webhooks/common.py的PresetUrlOption枚举与build_preset_config类方法。预设主要用于两件事构造多个集成共用的WebhookUrlOption对象构造 Web 应用中生成入站 Webhook URL的特殊 UI。其他用途可直接使用WebhookUrlOption类。使用预设的示例GitHub 集成的实际写法见 zerver/lib/integrations.py# zerver/lib/integrations.py from zerver.lib.webhooks.common import PresetUrlOption, WebhookUrlOption # -- snip -- IncomingWebhookIntegration( github, # -- snip -- url_options[ WebhookUrlOption.build_preset_config(PresetUrlOption.BRANCHES), ], ),当前配置的预设选项BRANCHES面向版本控制集成。为用户提供配置仓库的哪些分支触发 Zulip 通知的 UI。用户指定分支后branches参数会加入生成的集成 URL——例如输入main和devURL 会追加branchesmain%2Cdev。IGNORE_PRIVATE_REPOSITORIES面向版本控制集成。为用户提供排除私有仓库触发通知的 UI。选中后ignore_private_repositories布尔参数会加入生成的集成 URL。CHANNEL_MAPPING面向聊天应用类集成如 Slack。在 Web 应用 UI 中添加特殊选项Matching Zulip channel用于选择通知发送到哪个 Zulip 频道——按消息在第三方服务中的原始频道名匹配 Zulip 频道。选中后需要为通知消息设置单一主题并会在生成的集成 URL 中追加mappingchannels。仓库中实际使用示例GitHub、Azure DevOps、Beanstalk、Bitbucket 等多个集成都使用了BRANCHES预设见zerver/lib/integrations.py中各IncomingWebhookIntegration条目Slack 集成则使用了CHANNEL_MAPPING。为自定义 URL 查询参数编写测试URL 查询参数中的自定义参数在 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:实际使用时第三方服务可能配置为调用这样的 URLhttp://myhost/api/v1/external/querytest?api_keyabcdefghstreamalertstopicqueries它提供了stream与topic的值集成无需特殊处理即可通过typed_endpoint获取。测试时为了构造带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)若测试数据需要非常规构造还可覆写get_body或get_payload。更多细节参见基类WebhookTestCasezerver/lib/test_classes.py或在仓库中搜索相关示例。九、进阶主题负面测试Negative Tests负面测试是指预期产生错误的测试例如第三方 payload 或头数据不正确。要正确测试这些场景必须显式编写测试执行逻辑必要时使用其他测试辅助函数而不是调用常规的check_webhook辅助函数。以下是 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)原理说明正常测试中check_webhook会完成所有设置并检查入站 Webhook 的响应是否符合预期成功结果若 Webhook 返回错误测试失败。负面测试则改为显式完成check_webhook会做的设置并自行检查错误结果subscribe测试辅助函数使用test_user和channel_name基类属性注册用户以接收指定频道消息频道不存在时自动创建client_post辅助函数执行调用入站 Webhook 的 HTTP POST只要self.url正确就无需自行构造 Webhook URLassert_json_error检查结果是否与预期错误匹配。若使用check_webhook它内部会调用send_webhook_payload用assert_json_success检查结果——那会与负面测试的目标相悖。十、进阶主题处理意外事件类型许多第三方服务有几十种事件类型。有些事件我们可能选择显式忽略有些则是新增或未知事件。此时推荐抛出UnsupportedWebhookEventTypeError位于zerver/lib/exceptions.py并传入描述不支持事件类型的字符串raise UnsupportedWebhookEventTypeError(event_type)这样既能明确记录未支持的事件也让未来的维护者清楚该事件需要单独评估与实现。结语Zulip 的入站 Webhook 体系是一套设计完整、工程化程度很高的集成框架URL 注册由 zerver/lib/integrations.py 的注册表驱动并自动生成路由消息发送、事件过滤、头提取等通用逻辑集中在 zerver/lib/webhooks/common.py测试基础设施由WebhookTestCasezerver/lib/test_classes.py提供。参照本文七步流程与进阶指引结合zerver/webhooks/目录中数百个现成集成示例即可在数小时内完成一个包含真实 fixtures、自动化测试与终端用户文档的官方级集成。【免费下载链接】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),仅供参考