Skip to content
Merged
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/lang/zh-CN/

## [Unreleased]

### Fixed

- 修复议题已经被关闭时无法正确设置为 not_planned 的问题

## [4.2.6] - 2025-01-22

### Fixed
Expand Down
4 changes: 3 additions & 1 deletion src/plugins/github/handlers/issue.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,9 @@ async def close_issue(
if issue_number is None:
issue_number = self.issue_number

if self.issue and self.issue.state == "open":
if (
self.issue and self.issue.state == "open"
) or self.issue.state_reason != reason:
await super().close_issue(reason, issue_number)

async def create_pull_request(
Expand Down
4 changes: 2 additions & 2 deletions src/plugins/github/plugins/resolve/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@ async def handle_pr_close(
handler: IssueHandler = Depends(get_related_issue_handler),
) -> None:
async with bot.as_installation(installation_id):
if handler.issue.state == "open":
reason = "completed" if event.payload.pull_request.merged else "not_planned"
reason = "completed" if event.payload.pull_request.merged else "not_planned"
if handler.issue.state == "open" or handler.issue.state_reason != reason:
await handler.close_issue(reason)
logger.info(f"议题 #{handler.issue.number} 已关闭")

Expand Down
171 changes: 171 additions & 0 deletions tests/plugins/github/resolve/test_resolve_close_issue.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
from unittest.mock import MagicMock

from inline_snapshot import snapshot
from nonebot.adapters.github import PullRequestClosed
from nonebug import App
from pytest_mock import MockerFixture
from respx import MockRouter

from tests.plugins.github.event import get_mock_event
from tests.plugins.github.resolve.utils import get_pr_labels
from tests.plugins.github.utils import (
GitHubApi,
MockIssue,
assert_subprocess_run_calls,
generate_issue_body_remove,
get_github_bot,
mock_subprocess_run_with_side_effect,
should_call_apis,
)


async def test_resolve_close_issue(
app: App,
mocker: MockerFixture,
mock_installation: MagicMock,
mocked_api: MockRouter,
) -> None:
"""测试能正确关闭议题"""
from src.plugins.github.plugins.resolve import pr_close_matcher

mock_subprocess_run = mock_subprocess_run_with_side_effect(mocker)
mock_resolve_conflict_pull_requests = mocker.patch(
"src.plugins.github.plugins.resolve.resolve_conflict_pull_requests"
)

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

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(["Publish", "Bot"])
event.payload.pull_request.merged = False

should_call_apis(
ctx,
[
GitHubApi(
api="rest.apps.async_get_repo_installation",
result=mock_installation,
),
GitHubApi(
api="rest.issues.async_get",
result=mock_issues_resp,
),
GitHubApi(
api="rest.issues.async_update",
result=mock_issues_resp,
),
],
snapshot(
{
0: {"owner": "he0119", "repo": "action-test"},
1: {"owner": "he0119", "repo": "action-test", "issue_number": 76},
2: {
"owner": "he0119",
"repo": "action-test",
"issue_number": 76,
"state": "closed",
"state_reason": "not_planned",
},
}
),
)
ctx.receive_event(bot, event)
ctx.should_pass_rule(pr_close_matcher)

# 测试 git 命令
assert_subprocess_run_calls(
mock_subprocess_run,
[
["git", "config", "--global", "safe.directory", "*"],
["git", "push", "origin", "--delete", "publish/issue76"],
],
)

assert not mocked_api["homepage"].called
mock_resolve_conflict_pull_requests.assert_not_awaited()


async def test_resolve_close_issue_already_closed(
app: App,
mocker: MockerFixture,
mock_installation: MagicMock,
mocked_api: MockRouter,
) -> None:
"""测试议题已经关闭的情况

需要再关闭一次议题,设置为 not_planned
"""
from src.plugins.github.plugins.resolve import pr_close_matcher

mock_subprocess_run = mock_subprocess_run_with_side_effect(mocker)
mock_resolve_conflict_pull_requests = mocker.patch(
"src.plugins.github.plugins.resolve.resolve_conflict_pull_requests"
)

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

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(["Publish", "Bot"])
event.payload.pull_request.merged = False

should_call_apis(
ctx,
[
GitHubApi(
api="rest.apps.async_get_repo_installation",
result=mock_installation,
),
GitHubApi(
api="rest.issues.async_get",
result=mock_issues_resp,
),
GitHubApi(
api="rest.issues.async_update",
result=mock_issues_resp,
),
],
snapshot(
{
0: {"owner": "he0119", "repo": "action-test"},
1: {"owner": "he0119", "repo": "action-test", "issue_number": 76},
2: {
"owner": "he0119",
"repo": "action-test",
"issue_number": 76,
"state": "closed",
"state_reason": "not_planned",
},
}
),
)
ctx.receive_event(bot, event)
ctx.should_pass_rule(pr_close_matcher)

# 测试 git 命令
assert_subprocess_run_calls(
mock_subprocess_run,
[
["git", "config", "--global", "safe.directory", "*"],
["git", "push", "origin", "--delete", "publish/issue76"],
],
)

assert not mocked_api["homepage"].called
mock_resolve_conflict_pull_requests.assert_not_awaited()
3 changes: 2 additions & 1 deletion tests/plugins/github/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ def generate_issue_body_plugin_skip_test(
def generate_issue_body_plugin_test_button(body: str, selected: bool):
from src.plugins.github.plugins.publish.constants import PLUGIN_TEST_BUTTON_TIPS

return f"""{body}\n\n### 插件测试\n\n- [{'x' if selected else ' '}] {PLUGIN_TEST_BUTTON_TIPS}"""
return f"""{body}\n\n### 插件测试\n\n- [{"x" if selected else " "}] {PLUGIN_TEST_BUTTON_TIPS}"""


def generate_issue_body_remove(
Expand Down Expand Up @@ -200,6 +200,7 @@ class MockIssue:
number: int = 80
title: str = "Bot: test"
state: Literal["open", "closed"] = "open"
state_reason: None | Literal["completed", "reopened", "not_planned"] = None
body: str = field(default_factory=lambda: MockBody("bot").generate())
pull_request: Any = None
user: MockUser = field(default_factory=MockUser)
Expand Down