diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e9c4790..7ffc66fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/src/plugins/github/handlers/issue.py b/src/plugins/github/handlers/issue.py index e9ecc528..ef1dd03d 100644 --- a/src/plugins/github/handlers/issue.py +++ b/src/plugins/github/handlers/issue.py @@ -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( diff --git a/src/plugins/github/plugins/resolve/__init__.py b/src/plugins/github/plugins/resolve/__init__.py index 183f2dc1..36a2150e 100644 --- a/src/plugins/github/plugins/resolve/__init__.py +++ b/src/plugins/github/plugins/resolve/__init__.py @@ -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} 已关闭") diff --git a/tests/plugins/github/resolve/test_resolve_close_issue.py b/tests/plugins/github/resolve/test_resolve_close_issue.py index 0e17768f..826f9cc1 100644 --- a/tests/plugins/github/resolve/test_resolve_close_issue.py +++ b/tests/plugins/github/resolve/test_resolve_close_issue.py @@ -12,7 +12,6 @@ GitHubApi, MockIssue, assert_subprocess_run_calls, - generate_issue_body_bot, generate_issue_body_remove, get_github_bot, mock_subprocess_run_with_side_effect, @@ -40,11 +39,84 @@ async def test_resolve_close_issue( mock_issues_resp = mocker.MagicMock() mock_issues_resp.parsed_data = mock_issue - mock_publish_issue = MockIssue(body=generate_issue_body_bot(), number=100).as_mock( - mocker + 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_publish_issue_resp = mocker.MagicMock() - mock_publish_issue_resp.parsed_data = mock_publish_issue + + 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) diff --git a/tests/plugins/github/utils.py b/tests/plugins/github/utils.py index f6221d4c..a128d999 100644 --- a/tests/plugins/github/utils.py +++ b/tests/plugins/github/utils.py @@ -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( @@ -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)