Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion src/plugins/github/plugins/publish/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from githubkit.exception import RequestFailed
from nonebot import logger
from nonebot.adapters.github import ActionFailed

from src.plugins.github import plugin_config
from src.plugins.github.constants import (
Expand Down Expand Up @@ -132,7 +133,11 @@ async def resolve_conflict_pull_requests(
logger.info("拉取请求为草稿,跳过处理")
continue

issue_handler = await handler.to_issue_handler(issue_number)
try:
issue_handler = await handler.to_issue_handler(issue_number)
except (ActionFailed, RequestFailed):
logger.error(f"议题 #{issue_number} 不存在,跳过处理 {pull.title}")
continue

try:
artifact = await get_noneflow_artifact(issue_handler)
Expand Down
7 changes: 6 additions & 1 deletion src/plugins/github/plugins/remove/utils.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from githubkit.exception import RequestFailed
from nonebot import logger
from nonebot.adapters.github import ActionFailed
from pydantic_core import PydanticCustomError

from src.plugins.github import plugin_config
Expand Down Expand Up @@ -97,7 +98,11 @@ async def resolve_conflict_pull_requests(

# 根据标签获取发布类型
publish_type = get_type_by_labels(pull.labels)
issue_handler = await handler.to_issue_handler(issue_number)
try:
issue_handler = await handler.to_issue_handler(issue_number)
except (ActionFailed, RequestFailed):
logger.error(f"议题 #{issue_number} 不存在,跳过处理 {pull.title}")
continue

if publish_type:
# 验证作者信息
Expand Down
222 changes: 222 additions & 0 deletions tests/plugins/github/resolve/test_resolve_pull_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
from pathlib import Path
from unittest.mock import MagicMock

import httpx
from githubkit import Response
from githubkit.exception import RequestFailed
from inline_snapshot import snapshot
from nonebot.adapters.github import PullRequestClosed
from nonebug import App
Expand Down Expand Up @@ -257,3 +260,222 @@ async def test_resolve_pull_request(
)

assert not mocked_api["homepage"].called


async def test_resolve_pull_request_issue_not_found(
app: App,
mocker: MockerFixture,
mock_installation: MagicMock,
mock_installation_token: MagicMock,
mocked_api: MockRouter,
tmp_path: Path,
) -> None:
"""测试当 PR 对应的议题不存在时,跳过该 PR 继续处理后续 PR"""
from src.plugins.github.plugins.resolve import pr_close_matcher
from src.providers.models import (
REGISTRY_DATA_NAME,
BotPublishInfo,
Color,
RegistryArtifactData,
Tag,
)

mock_subprocess_run = mock_subprocess_run_with_side_effect(mocker)

mock_issue = MockIssue(
body=generate_issue_body_remove(type="Bot"), number=76
).as_mock(mocker)
mock_issues_resp = mocker.MagicMock()
mock_issues_resp.parsed_data = mock_issue

# 议题不存在的发布 PR
mock_missing_pull = mocker.MagicMock()
mock_missing_pull.title = "Bot: missing"
mock_missing_pull.draft = False
mock_missing_pull.head.ref = "publish/issue999"
mock_missing_pull.labels = get_pr_labels(["Publish", "Bot"])

# 正常存在的发布 PR
mock_publish_issue = MockIssue(body=generate_issue_body_bot(), number=100).as_mock(
mocker
)
mock_publish_issue_resp = mocker.MagicMock()
mock_publish_issue_resp.parsed_data = mock_publish_issue
mock_publish_pull = mocker.MagicMock()
mock_publish_pull.title = "Bot: test"
mock_publish_pull.draft = False
mock_publish_pull.head.ref = "publish/issue100"
mock_publish_pull.labels = get_pr_labels(["Publish", "Bot"])

mock_publish_issue_comment = mocker.MagicMock()
mock_publish_issue_comment.body = """
<details>
<summary>历史测试</summary>
<pre><code>
<li>⚠️ <a href="https://github.com/owner/repo/actions/runs/14156878699">2025-03-28 02:21:18 CST</a></li><li>✅ <a href="https://github.com/nonebot/nonebot2/actions/runs/14156878699">2025-03-28 02:21:18 CST</a></li><li>✅ <a href="https://github.com/nonebot/nonebot2/actions/runs/14156878699">2025-03-28 02:22:18 CST</a>。</li><li>⚠️ <a href="https://github.com/nonebot/nonebot2/actions/runs/14156878699">2025-03-28 02:22:18 CST</a></li>
</code></pre>
</details>
<!-- NONEFLOW -->
"""
mock_publish_list_comments_resp = mocker.MagicMock()
mock_publish_list_comments_resp.parsed_data = [mock_publish_issue_comment]

mock_publish_artifact = mocker.MagicMock()
mock_publish_artifact.name = "noneflow"
mock_publish_artifact.id = 123456789
mock_publish_artifacts = mocker.MagicMock()
mock_publish_artifacts.artifacts = [mock_publish_artifact]
mock_publish_artifact_resp = mocker.MagicMock()
mock_publish_artifact_resp.parsed_data = mock_publish_artifacts

raw_data = {
"module_name": "module_name",
"project_link": "project_link",
"time": "2025-03-28T02:21:18Z",
"version": "1.0.0",
"name": "name",
"desc": "desc",
"author": "he0119",
"author_id": 1,
"homepage": "https://nonebot.dev",
"tags": [Tag(label="test", color=Color("#ffffff"))],
"is_official": False,
}
info = BotPublishInfo.model_construct(**raw_data)
registry_data = RegistryArtifactData.from_info(info)

zip_buffer = BytesIO()
with zipfile.ZipFile(zip_buffer, "w", zipfile.ZIP_DEFLATED) as zip_file:
json_content = registry_data.model_dump_json(indent=2)
zip_file.writestr(REGISTRY_DATA_NAME, json_content)

publish_zip_content = zip_buffer.getvalue()

mock_publish_download_artifact_resp = mocker.MagicMock()
mock_publish_download_artifact_resp.content = publish_zip_content

mock_pulls_resp = mocker.MagicMock()
mock_pulls_resp.parsed_data = [mock_missing_pull, mock_publish_pull]

async with app.test_matcher() as ctx:
_adapter, bot = get_github_bot(ctx)

event = get_mock_event(PullRequestClosed)
event.payload.pull_request.labels = get_pr_labels(["Remove", "Bot"])
event.payload.pull_request.merged = True

ctx.should_call_api(
"rest.apps.async_get_repo_installation",
snapshot({"owner": "he0119", "repo": "action-test"}),
mock_installation,
)
ctx.should_call_api(
"rest.apps.async_create_installation_access_token",
snapshot({"installation_id": mock_installation.parsed_data.id}),
mock_installation_token,
)
ctx.should_call_api(
"rest.issues.async_get",
snapshot({"owner": "he0119", "repo": "action-test", "issue_number": 76}),
mock_issues_resp,
)
ctx.should_call_api(
"rest.issues.async_update",
snapshot(
{
"owner": "he0119",
"repo": "action-test",
"issue_number": 76,
"state": "closed",
"state_reason": "completed",
}
),
True,
)
ctx.should_call_api(
"rest.pulls.async_list",
snapshot({"owner": "he0119", "repo": "action-test", "state": "open"}),
mock_pulls_resp,
)
# 议题不存在,应该返回 404
ctx.should_call_api(
"rest.issues.async_get",
snapshot({"owner": "he0119", "repo": "action-test", "issue_number": 999}),
exception=RequestFailed(
Response(
httpx.Response(404, request=httpx.Request("GET", "test")),
None, # type: ignore
)
),
)
# 跳过不存在的议题,继续处理下一个 PR
ctx.should_call_api(
"rest.issues.async_get",
snapshot({"owner": "he0119", "repo": "action-test", "issue_number": 100}),
mock_publish_issue_resp,
)
ctx.should_call_api(
"rest.issues.async_list_comments",
snapshot({"owner": "he0119", "repo": "action-test", "issue_number": 100}),
mock_publish_list_comments_resp,
)
ctx.should_call_api(
"rest.actions.async_list_workflow_run_artifacts",
snapshot(
{
"owner": "he0119",
"repo": "action-test",
"run_id": 14156878699,
}
),
mock_publish_artifact_resp,
)
ctx.should_call_api(
"rest.actions.async_download_artifact",
snapshot(
{
"owner": "he0119",
"repo": "action-test",
"artifact_id": 123456789,
"archive_format": "zip",
}
),
mock_publish_download_artifact_resp,
)
ctx.receive_event(bot, event)
ctx.should_pass_rule(pr_close_matcher)

# 测试 git 命令 - 应该只处理了 issue100 的 PR,跳过了 issue999
assert_subprocess_run_calls(
mock_subprocess_run,
[
["git", "config", "--global", "safe.directory", "*"],
[
"git",
"config",
"--global",
"url.https://x-access-token:test-token@github.com/.insteadOf",
"https://github.com/",
],
["git", "push", "origin", "--delete", "publish/issue76"],
# 处理发布(issue999 被跳过,只处理 issue100)
["git", "checkout", "master"],
["git", "pull"],
["git", "switch", "-C", "publish/issue100"],
["git", "add", str(tmp_path / "bots.json5")],
["git", "config", "--global", "user.name", "test"],
[
"git",
"config",
"--global",
"user.email",
"test@users.noreply.github.com",
],
["git", "commit", "-m", ":beers: publish bot name (#100)"],
["git", "fetch", "origin"],
["git", "diff", "origin/publish/issue100", "publish/issue100"],
["git", "push", "origin", "publish/issue100", "-f"],
],
)

assert not mocked_api["homepage"].called
Loading