From 5badde549a65472c06fd725792cbb958a43fa28a Mon Sep 17 00:00:00 2001 From: uy_sun Date: Thu, 12 Dec 2024 22:56:01 +0800 Subject: [PATCH 1/5] =?UTF-8?q?test:=20=E6=B5=8B=E8=AF=95=20git=20handler?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../github/handlers/test_git_handler.py | 104 ++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 tests/plugins/github/handlers/test_git_handler.py diff --git a/tests/plugins/github/handlers/test_git_handler.py b/tests/plugins/github/handlers/test_git_handler.py new file mode 100644 index 00000000..f0e6495e --- /dev/null +++ b/tests/plugins/github/handlers/test_git_handler.py @@ -0,0 +1,104 @@ +from unittest.mock import _Call, call + +from pytest_mock import MockerFixture + + +async def test_checkout_branch(mocker: MockerFixture): + from src.plugins.github.handlers.git import GitHandler + + mock_run_shell_command = mocker.patch( + "src.plugins.github.handlers.git.run_shell_command" + ) + + git_handler = GitHandler() + git_handler.checkout_branch("main") + + mock_run_shell_command.assert_has_calls( + [ + call(["git", "checkout", "main"]), + ] + ) + + +async def test_checkout_remote_branch(mocker: MockerFixture): + from src.plugins.github.handlers.git import GitHandler + + mock_run_shell_command = mocker.patch( + "src.plugins.github.handlers.git.run_shell_command" + ) + + git_handler = GitHandler() + git_handler.checkout_remote_branch("main") + + mock_run_shell_command.assert_has_calls( + [ + call(["git", "fetch", "origin", "main"]), + call(["git", "checkout", "main"]), + ] + ) + + +async def test_commit_and_push(mocker: MockerFixture): + from src.plugins.github.handlers.git import GitHandler + + mock_run_shell_command = mocker.patch( + "src.plugins.github.handlers.git.run_shell_command" + ) + + git_handler = GitHandler() + git_handler.commit_and_push("commit message", "main", "author") + + mock_run_shell_command.assert_has_calls( + [ + call(["git", "config", "--global", "user.name", "author"]), + call( + [ + "git", + "config", + "--global", + "user.email", + "author@users.noreply.github.com", + ] + ), + call(["git", "add", "-A"]), + call(["git", "commit", "-m", "commit message"]), + call(["git", "fetch", "origin"]), + call(["git", "diff", "origin/main", "main"]), + _Call(("().stdout.__bool__", (), {})), + call(["git", "push", "origin", "main", "-f"]), + ], + ) + + +async def test_delete_origin_branch(mocker: MockerFixture): + from src.plugins.github.handlers.git import GitHandler + + mock_run_shell_command = mocker.patch( + "src.plugins.github.handlers.git.run_shell_command" + ) + + git_handler = GitHandler() + git_handler.delete_origin_branch("main") + + mock_run_shell_command.assert_has_calls( + [ + call(["git", "push", "origin", "--delete", "main"]), + ] + ) + + +async def test_switch_branch(mocker: MockerFixture): + from src.plugins.github.handlers.git import GitHandler + + mock_run_shell_command = mocker.patch( + "src.plugins.github.handlers.git.run_shell_command" + ) + + git_handler = GitHandler() + git_handler.switch_branch("main") + + mock_run_shell_command.assert_has_calls( + [ + call(["git", "switch", "-C", "main"]), + ] + ) From 470dd1bf4f5e7e2ac76d8938b0d91ace809c1a24 Mon Sep 17 00:00:00 2001 From: uy/sun Date: Wed, 18 Dec 2024 13:05:58 +0800 Subject: [PATCH 2/5] =?UTF-8?q?test:=20=E6=B7=BB=E5=8A=A0=20git=20handler?= =?UTF-8?q?=20=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/github/handlers/git.py | 8 +-- .../github/handlers/test_git_handler.py | 62 ++++++++++++------- 2 files changed, 40 insertions(+), 30 deletions(-) diff --git a/src/plugins/github/handlers/git.py b/src/plugins/github/handlers/git.py index dd2815bb..f74d732f 100644 --- a/src/plugins/github/handlers/git.py +++ b/src/plugins/github/handlers/git.py @@ -25,13 +25,7 @@ def commit_and_push(self, message: str, branch_name: str, author: str): user_email = f"{author}@users.noreply.github.com" run_shell_command(["git", "config", "--global", "user.email", user_email]) run_shell_command(["git", "add", "-A"]) - try: - run_shell_command(["git", "commit", "-m", message]) - except Exception: - # 如果提交失败,因为是 pre-commit hooks 格式化代码导致的,所以需要再次提交 - run_shell_command(["git", "add", "-A"]) - run_shell_command(["git", "commit", "-m", message]) - + run_shell_command(["git", "commit", "-m", message]) try: run_shell_command(["git", "fetch", "origin"]) r = run_shell_command(["git", "diff", f"origin/{branch_name}", branch_name]) diff --git a/tests/plugins/github/handlers/test_git_handler.py b/tests/plugins/github/handlers/test_git_handler.py index f0e6495e..f58c1e59 100644 --- a/tests/plugins/github/handlers/test_git_handler.py +++ b/tests/plugins/github/handlers/test_git_handler.py @@ -1,14 +1,16 @@ from unittest.mock import _Call, call +import pytest from pytest_mock import MockerFixture -async def test_checkout_branch(mocker: MockerFixture): - from src.plugins.github.handlers.git import GitHandler +@pytest.fixture +def mock_run_shell_command(mocker: MockerFixture): + return mocker.patch("src.plugins.github.handlers.git.run_shell_command") - mock_run_shell_command = mocker.patch( - "src.plugins.github.handlers.git.run_shell_command" - ) + +async def test_checkout_branch(mock_run_shell_command): + from src.plugins.github.handlers.git import GitHandler git_handler = GitHandler() git_handler.checkout_branch("main") @@ -20,13 +22,9 @@ async def test_checkout_branch(mocker: MockerFixture): ) -async def test_checkout_remote_branch(mocker: MockerFixture): +async def test_checkout_remote_branch(mock_run_shell_command): from src.plugins.github.handlers.git import GitHandler - mock_run_shell_command = mocker.patch( - "src.plugins.github.handlers.git.run_shell_command" - ) - git_handler = GitHandler() git_handler.checkout_remote_branch("main") @@ -38,13 +36,9 @@ async def test_checkout_remote_branch(mocker: MockerFixture): ) -async def test_commit_and_push(mocker: MockerFixture): +async def test_commit_and_push(mock_run_shell_command): from src.plugins.github.handlers.git import GitHandler - mock_run_shell_command = mocker.patch( - "src.plugins.github.handlers.git.run_shell_command" - ) - git_handler = GitHandler() git_handler.commit_and_push("commit message", "main", "author") @@ -70,13 +64,39 @@ async def test_commit_and_push(mocker: MockerFixture): ) -async def test_delete_origin_branch(mocker: MockerFixture): +async def test_commit_and_push_diff_no_change(mock_run_shell_command): + """本地分支与远程分支一致,跳过推送的情况""" from src.plugins.github.handlers.git import GitHandler - mock_run_shell_command = mocker.patch( - "src.plugins.github.handlers.git.run_shell_command" + # 本地分支与远程分支一致时 git diff 应该返回空字符串 + mock_run_shell_command.return_value.stdout = "" + + git_handler = GitHandler() + git_handler.commit_and_push("commit message", "main", "author") + + mock_run_shell_command.assert_has_calls( + [ + call(["git", "config", "--global", "user.name", "author"]), + call( + [ + "git", + "config", + "--global", + "user.email", + "author@users.noreply.github.com", + ] + ), + call(["git", "add", "-A"]), + call(["git", "commit", "-m", "commit message"]), + call(["git", "fetch", "origin"]), + call(["git", "diff", "origin/main", "main"]), + ], ) + +async def test_delete_origin_branch(mock_run_shell_command): + from src.plugins.github.handlers.git import GitHandler + git_handler = GitHandler() git_handler.delete_origin_branch("main") @@ -87,13 +107,9 @@ async def test_delete_origin_branch(mocker: MockerFixture): ) -async def test_switch_branch(mocker: MockerFixture): +async def test_switch_branch(mock_run_shell_command): from src.plugins.github.handlers.git import GitHandler - mock_run_shell_command = mocker.patch( - "src.plugins.github.handlers.git.run_shell_command" - ) - git_handler = GitHandler() git_handler.switch_branch("main") From a2b3d66fe1d8e4186e1889f896fef90c3248235a Mon Sep 17 00:00:00 2001 From: uy/sun Date: Wed, 18 Dec 2024 15:33:00 +0800 Subject: [PATCH 3/5] =?UTF-8?q?test:=20=E6=B7=BB=E5=8A=A0=20github=20handl?= =?UTF-8?q?er=20=E6=B5=8B=E8=AF=95=EF=BC=88=E9=83=A8=E5=88=86=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../github/handlers/test_github_handler.py | 358 ++++++++++++++++++ tests/plugins/github/utils.py | 3 +- 2 files changed, 360 insertions(+), 1 deletion(-) create mode 100644 tests/plugins/github/handlers/test_github_handler.py diff --git a/tests/plugins/github/handlers/test_github_handler.py b/tests/plugins/github/handlers/test_github_handler.py new file mode 100644 index 00000000..27d9a462 --- /dev/null +++ b/tests/plugins/github/handlers/test_github_handler.py @@ -0,0 +1,358 @@ +from inline_snapshot import snapshot +from nonebug import App +from pytest_mock import MockerFixture + +from tests.plugins.github.utils import GitHubApi, get_github_bot, should_call_apis + + +async def test_update_issue_title(app: App) -> None: + """测试修改议题标题""" + from src.plugins.github.handlers.github import GithubHandler + from src.plugins.github.models import RepoInfo + + async with app.test_api() as ctx: + _, bot = get_github_bot(ctx) + + github_handler = GithubHandler( + bot=bot, + repo_info=RepoInfo(owner="owner", repo="repo"), + ) + + should_call_apis( + ctx, + [ + GitHubApi(api="rest.issues.async_update", result=True), + ], + snapshot( + { + 0: { + "owner": "owner", + "repo": "repo", + "issue_number": 76, + "title": "new title", + }, + } + ), + ) + await github_handler.update_issue_title("new title", 76) + + +async def test_update_issue_body(app: App) -> None: + """测试更新议题内容""" + from src.plugins.github.handlers.github import GithubHandler + from src.plugins.github.models import RepoInfo + + async with app.test_api() as ctx: + _, bot = get_github_bot(ctx) + + github_handler = GithubHandler( + bot=bot, + repo_info=RepoInfo(owner="owner", repo="repo"), + ) + + should_call_apis( + ctx, + [ + GitHubApi(api="rest.issues.async_update", result=True), + ], + snapshot( + { + 0: { + "owner": "owner", + "repo": "repo", + "issue_number": 76, + "body": "new body", + }, + } + ), + ) + await github_handler.update_issue_body("new body", 76) + + +async def test_create_dispatch_event(app: App) -> None: + """测试创建触发事件""" + from src.plugins.github.handlers.github import GithubHandler + from src.plugins.github.models import RepoInfo + + async with app.test_api() as ctx: + _, bot = get_github_bot(ctx) + + github_handler = GithubHandler( + bot=bot, + repo_info=RepoInfo(owner="owner", repo="repo"), + ) + + should_call_apis( + ctx, + [ + GitHubApi(api="rest.repos.async_create_dispatch_event", result=True), + ], + snapshot( + { + 0: { + "owner": "owner", + "repo": "repo", + "event_type": "event", + "client_payload": {"key": "value"}, + }, + } + ), + ) + await github_handler.create_dispatch_event("event", {"key": "value"}) + + +async def test_list_comments(app: App, mocker: MockerFixture) -> None: + """测试拉取所有评论""" + from src.plugins.github.handlers.github import GithubHandler + from src.plugins.github.models import RepoInfo + + mock_comments_resp = mocker.MagicMock() + mock_comments_resp.parsed_data = [] + + async with app.test_api() as ctx: + _, bot = get_github_bot(ctx) + + github_handler = GithubHandler( + bot=bot, + repo_info=RepoInfo(owner="owner", repo="repo"), + ) + + should_call_apis( + ctx, + [ + GitHubApi( + api="rest.issues.async_list_comments", result=mock_comments_resp + ), + ], + snapshot( + { + 0: { + "owner": "owner", + "repo": "repo", + "issue_number": 76, + }, + } + ), + ) + await github_handler.list_comments(76) + + +async def test_create_comment(app: App) -> None: + """测试发布评论""" + from src.plugins.github.handlers.github import GithubHandler + from src.plugins.github.models import RepoInfo + + async with app.test_api() as ctx: + _, bot = get_github_bot(ctx) + + github_handler = GithubHandler( + bot=bot, + repo_info=RepoInfo(owner="owner", repo="repo"), + ) + + should_call_apis( + ctx, + [ + GitHubApi(api="rest.issues.async_create_comment", result=True), + ], + snapshot( + { + 0: { + "owner": "owner", + "repo": "repo", + "issue_number": 76, + "body": "new comment", + }, + } + ), + ) + await github_handler.create_comment("new comment", 76) + + +async def test_update_comment(app: App) -> None: + """测试修改评论""" + from src.plugins.github.handlers.github import GithubHandler + from src.plugins.github.models import RepoInfo + + async with app.test_api() as ctx: + _, bot = get_github_bot(ctx) + + github_handler = GithubHandler( + bot=bot, + repo_info=RepoInfo(owner="owner", repo="repo"), + ) + + should_call_apis( + ctx, + [ + GitHubApi(api="rest.issues.async_update_comment", result=True), + ], + snapshot( + { + 0: { + "owner": "owner", + "repo": "repo", + "comment_id": 123, + "body": "updated comment", + }, + } + ), + ) + await github_handler.update_comment(123, "updated comment") + + +async def test_comment_issue(app: App, mocker: MockerFixture) -> None: + """测试发布评论""" + from src.plugins.github.handlers.github import GithubHandler + from src.plugins.github.models import RepoInfo + + mock_comments_resp = mocker.MagicMock() + mock_comments_resp.parsed_data = [] + + async with app.test_api() as ctx: + _, bot = get_github_bot(ctx) + + github_handler = GithubHandler( + bot=bot, + repo_info=RepoInfo(owner="owner", repo="repo"), + ) + + should_call_apis( + ctx, + [ + GitHubApi( + api="rest.issues.async_list_comments", result=mock_comments_resp + ), + GitHubApi(api="rest.issues.async_create_comment", result=True), + ], + snapshot( + { + 0: {"owner": "owner", "repo": "repo", "issue_number": 76}, + 1: { + "owner": "owner", + "repo": "repo", + "issue_number": 76, + "body": "new comment", + }, + } + ), + ) + await github_handler.comment_issue("new comment", 76) + + +async def test_comment_issue_reuse(app: App, mocker: MockerFixture) -> None: + """测试发布评论,复用的情况""" + from src.plugins.github.handlers.github import GithubHandler + from src.plugins.github.models import RepoInfo + + mock_comment = mocker.MagicMock() + mock_comment.body = "old comment\n" + mock_comment.id = 123 + mock_comments_resp = mocker.MagicMock() + mock_comments_resp.parsed_data = [mock_comment] + + async with app.test_api() as ctx: + _, bot = get_github_bot(ctx) + + github_handler = GithubHandler( + bot=bot, + repo_info=RepoInfo(owner="owner", repo="repo"), + ) + + should_call_apis( + ctx, + [ + GitHubApi( + api="rest.issues.async_list_comments", result=mock_comments_resp + ), + GitHubApi(api="rest.issues.async_update_comment", result=True), + ], + snapshot( + { + 0: {"owner": "owner", "repo": "repo", "issue_number": 76}, + 1: { + "owner": "owner", + "repo": "repo", + "comment_id": 123, + "body": "new comment", + }, + } + ), + ) + await github_handler.comment_issue("new comment", 76) + + +async def test_comment_issue_reuse_no_change(app: App, mocker: MockerFixture) -> None: + """测试发布评论,复用且无变化的情况""" + from src.plugins.github.handlers.github import GithubHandler + from src.plugins.github.models import RepoInfo + + mock_comment = mocker.MagicMock() + mock_comment.body = "comment\n" + mock_comment.id = 123 + mock_comments_resp = mocker.MagicMock() + mock_comments_resp.parsed_data = [mock_comment] + + async with app.test_api() as ctx: + _, bot = get_github_bot(ctx) + + github_handler = GithubHandler( + bot=bot, + repo_info=RepoInfo(owner="owner", repo="repo"), + ) + + should_call_apis( + ctx, + [ + GitHubApi( + api="rest.issues.async_list_comments", result=mock_comments_resp + ), + ], + snapshot( + { + 0: {"owner": "owner", "repo": "repo", "issue_number": 76}, + } + ), + ) + await github_handler.comment_issue("comment\n", 76) + + +async def test_get_pull_requests_by_label(app: App, mocker: MockerFixture) -> None: + """测试获取指定标签下的所有 PR""" + from src.plugins.github.handlers.github import GithubHandler + from src.plugins.github.models import RepoInfo + + mock_label_plugin = mocker.MagicMock() + mock_label_plugin.name = "Plugin" + mock_label_bot = mocker.MagicMock() + mock_label_bot.name = "Bot" + + mock_pull_bot = mocker.MagicMock() + mock_pull_bot.labels = [mock_label_bot] + mock_pull_plugin = mocker.MagicMock() + mock_pull_plugin.labels = [mock_label_plugin] + + mock_pulls_resp = mocker.MagicMock() + mock_pulls_resp.parsed_data = [mock_pull_bot, mock_pull_plugin] + + async with app.test_api() as ctx: + _, bot = get_github_bot(ctx) + + github_handler = GithubHandler( + bot=bot, + repo_info=RepoInfo(owner="owner", repo="repo"), + ) + + should_call_apis( + ctx, + [ + GitHubApi(api="rest.pulls.async_list", result=mock_pulls_resp), + ], + snapshot( + { + 0: {"owner": "owner", "repo": "repo", "state": "open"}, + } + ), + ) + pulls = await github_handler.get_pull_requests_by_label("Plugin") + assert pulls == [mock_pull_plugin] diff --git a/tests/plugins/github/utils.py b/tests/plugins/github/utils.py index ee043b44..475a9d99 100644 --- a/tests/plugins/github/utils.py +++ b/tests/plugins/github/utils.py @@ -6,6 +6,7 @@ import pyjson5 from githubkit.rest import Issue +from nonebug.mixin.call_api import ApiContext from nonebug.mixin.process import MatcherContext from pytest_mock import MockFixture, MockType @@ -17,7 +18,7 @@ class GitHubApi(TypedDict): def should_call_apis( - ctx: MatcherContext, apis: list[GitHubApi], data: list[Any] + ctx: MatcherContext | ApiContext, apis: list[GitHubApi], data: list[Any] ) -> None: for n, api in enumerate(apis): ctx.should_call_api(**api, data=data[n]) From e8fc10d0be1e77135312e488027f8713a4f965ef Mon Sep 17 00:00:00 2001 From: uy/sun Date: Thu, 19 Dec 2024 12:39:58 +0800 Subject: [PATCH 4/5] =?UTF-8?q?test:=20=E6=B7=BB=E5=8A=A0=20github=20handl?= =?UTF-8?q?er=20=E6=B5=8B=E8=AF=95=EF=BC=88=E5=AE=8C=E6=95=B4=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../github/handlers/test_github_handler.py | 617 ++++++++++++++++++ 1 file changed, 617 insertions(+) diff --git a/tests/plugins/github/handlers/test_github_handler.py b/tests/plugins/github/handlers/test_github_handler.py index 27d9a462..03bfddf4 100644 --- a/tests/plugins/github/handlers/test_github_handler.py +++ b/tests/plugins/github/handlers/test_github_handler.py @@ -1,3 +1,5 @@ +import pytest +from githubkit.rest import Issue from inline_snapshot import snapshot from nonebug import App from pytest_mock import MockerFixture @@ -356,3 +358,618 @@ async def test_get_pull_requests_by_label(app: App, mocker: MockerFixture) -> No ) pulls = await github_handler.get_pull_requests_by_label("Plugin") assert pulls == [mock_pull_plugin] + + +async def test_get_pull_request_by_branch(app: App, mocker: MockerFixture) -> None: + """测试根据分支名称获取拉取请求""" + from src.plugins.github.handlers.github import GithubHandler + from src.plugins.github.models import RepoInfo + + mock_pull = mocker.MagicMock() + mock_pulls_resp = mocker.MagicMock() + mock_pulls_resp.parsed_data = [mock_pull] + + async with app.test_api() as ctx: + _, bot = get_github_bot(ctx) + + github_handler = GithubHandler( + bot=bot, + repo_info=RepoInfo(owner="owner", repo="repo"), + ) + + should_call_apis( + ctx, + [ + GitHubApi(api="rest.pulls.async_list", result=mock_pulls_resp), + ], + snapshot( + { + 0: {"owner": "owner", "repo": "repo", "head": "owner:branch"}, + } + ), + ) + pull = await github_handler.get_pull_request_by_branch("branch") + assert pull == mock_pull + + +async def test_get_pull_request_by_branch_empty( + app: App, mocker: MockerFixture +) -> None: + """测试根据分支名称获取拉取请求,为空的情况""" + from src.plugins.github.handlers.github import GithubHandler + from src.plugins.github.models import RepoInfo + + mock_pulls_resp = mocker.MagicMock() + mock_pulls_resp.parsed_data = [] + + async with app.test_api() as ctx: + _, bot = get_github_bot(ctx) + + github_handler = GithubHandler( + bot=bot, + repo_info=RepoInfo(owner="owner", repo="repo"), + ) + + should_call_apis( + ctx, + [ + GitHubApi(api="rest.pulls.async_list", result=mock_pulls_resp), + ], + snapshot( + { + 0: {"owner": "owner", "repo": "repo", "head": "owner:branch"}, + } + ), + ) + with pytest.raises(ValueError, match="找不到分支 branch 对应的拉取请求"): + await github_handler.get_pull_request_by_branch("branch") + + +async def test_get_pull_request(app: App, mocker: MockerFixture) -> None: + """测试获取拉取请求""" + from src.plugins.github.handlers.github import GithubHandler + from src.plugins.github.models import RepoInfo + + mock_pull = mocker.MagicMock() + mock_pull_resp = mocker.MagicMock() + mock_pull_resp.parsed_data = mock_pull + + async with app.test_api() as ctx: + _, bot = get_github_bot(ctx) + + github_handler = GithubHandler( + bot=bot, + repo_info=RepoInfo(owner="owner", repo="repo"), + ) + + should_call_apis( + ctx, + [ + GitHubApi( + api="rest.pulls.async_get", + result=mock_pull_resp, + ), + ], + snapshot( + { + 0: {"owner": "owner", "repo": "repo", "pull_number": 123}, + } + ), + ) + pull = await github_handler.get_pull_request(123) + assert pull == mock_pull + + +async def test_draft_pull_request(app: App, mocker: MockerFixture) -> None: + """测试将拉取请求标记为草稿""" + from src.plugins.github.handlers.github import GithubHandler + from src.plugins.github.models import RepoInfo + + mock_pull = mocker.MagicMock() + mock_pull.draft = False + mock_pull.node_id = 123 + mock_pulls_resp = mocker.MagicMock() + mock_pulls_resp.parsed_data = [mock_pull] + + async with app.test_api() as ctx: + _, bot = get_github_bot(ctx) + + github_handler = GithubHandler( + bot=bot, + repo_info=RepoInfo(owner="owner", repo="repo"), + ) + + should_call_apis( + ctx, + [ + GitHubApi( + api="rest.pulls.async_list", + result=mock_pulls_resp, + ), + GitHubApi(api="async_graphql", result=None), + ], + snapshot( + { + 0: {"owner": "owner", "repo": "repo", "head": "owner:branch"}, + 1: { + "query": """\ +mutation convertPullRequestToDraft($pullRequestId: ID!) { + convertPullRequestToDraft(input: {pullRequestId: $pullRequestId}) { + clientMutationId + } + }\ +""", + "variables": { + "pullRequestId": 123, + }, + }, + } + ), + ) + await github_handler.draft_pull_request("branch") + + +async def test_draft_pull_request_no_pr(app: App, mocker: MockerFixture) -> None: + """测试将拉取请求标记为草稿,但是没有对应的拉取请求""" + from src.plugins.github.handlers.github import GithubHandler + from src.plugins.github.models import RepoInfo + + mock_pulls_resp = mocker.MagicMock() + mock_pulls_resp.parsed_data = [] + + async with app.test_api() as ctx: + _, bot = get_github_bot(ctx) + + github_handler = GithubHandler( + bot=bot, + repo_info=RepoInfo(owner="owner", repo="repo"), + ) + + should_call_apis( + ctx, + [ + GitHubApi( + api="rest.pulls.async_list", + result=mock_pulls_resp, + ), + ], + snapshot( + { + 0: {"owner": "owner", "repo": "repo", "head": "owner:branch"}, + } + ), + ) + await github_handler.draft_pull_request("branch") + + +async def test_draft_pull_request_drafted(app: App, mocker: MockerFixture) -> None: + """测试将拉取请求标记为草稿,但已经是草稿的情况""" + from src.plugins.github.handlers.github import GithubHandler + from src.plugins.github.models import RepoInfo + + mock_pull = mocker.MagicMock() + mock_pull.draft = True + mock_pulls_resp = mocker.MagicMock() + mock_pulls_resp.parsed_data = [mock_pull] + + async with app.test_api() as ctx: + _, bot = get_github_bot(ctx) + + github_handler = GithubHandler( + bot=bot, + repo_info=RepoInfo(owner="owner", repo="repo"), + ) + + should_call_apis( + ctx, + [ + GitHubApi( + api="rest.pulls.async_list", + result=mock_pulls_resp, + ), + ], + snapshot( + { + 0: {"owner": "owner", "repo": "repo", "head": "owner:branch"}, + } + ), + ) + await github_handler.draft_pull_request("branch") + + +async def test_merge_pull_request(app: App, mocker: MockerFixture) -> None: + """测试合并拉取请求""" + from src.plugins.github.handlers.github import GithubHandler + from src.plugins.github.models import RepoInfo + + mock_pull = mocker.MagicMock() + mock_pull_resp = mocker.MagicMock() + mock_pull_resp.parsed_data = mock_pull + + async with app.test_api() as ctx: + _, bot = get_github_bot(ctx) + + github_handler = GithubHandler( + bot=bot, + repo_info=RepoInfo(owner="owner", repo="repo"), + ) + + should_call_apis( + ctx, + [ + GitHubApi( + api="rest.pulls.async_merge", + result=mock_pull, + ), + ], + snapshot( + { + 0: { + "owner": "owner", + "repo": "repo", + "pull_number": 123, + "merge_method": "rebase", + }, + } + ), + ) + await github_handler.merge_pull_request(123, "rebase") + + +async def test_update_pull_request_status(app: App, mocker: MockerFixture) -> None: + """测试更新拉取请求状态""" + from src.plugins.github.handlers.github import GithubHandler + from src.plugins.github.models import RepoInfo + + mock_pull = mocker.MagicMock() + mock_pull.title = "old title" + mock_pull.draft = True + mock_pull.number = 111 + mock_pull.node_id = 222 + mock_pulls_resp = mocker.MagicMock() + mock_pulls_resp.parsed_data = [mock_pull] + + async with app.test_api() as ctx: + _, bot = get_github_bot(ctx) + + github_handler = GithubHandler( + bot=bot, + repo_info=RepoInfo(owner="owner", repo="repo"), + ) + + should_call_apis( + ctx, + [ + GitHubApi(api="rest.pulls.async_list", result=mock_pulls_resp), + GitHubApi(api="rest.pulls.async_update", result=None), + GitHubApi(api="async_graphql", result=None), + ], + snapshot( + { + 0: {"owner": "owner", "repo": "repo", "head": "owner:branch"}, + 1: { + "owner": "owner", + "repo": "repo", + "pull_number": 111, + "title": "new title", + }, + 2: { + "query": """\ +mutation markPullRequestReadyForReview($pullRequestId: ID!) { + markPullRequestReadyForReview(input: {pullRequestId: $pullRequestId}) { + clientMutationId + } + }\ +""", + "variables": {"pullRequestId": 222}, + }, + } + ), + ) + await github_handler.update_pull_request_status("new title", "branch") + + +async def test_create_pull_request(app: App, mocker: MockerFixture) -> None: + """测试创建拉取请求""" + from src.plugins.github.handlers.github import GithubHandler + from src.plugins.github.models import RepoInfo + + mock_pull = mocker.MagicMock() + mock_pull.number = 123 + mock_pull_resp = mocker.MagicMock() + mock_pull_resp.parsed_data = mock_pull + + async with app.test_api() as ctx: + _, bot = get_github_bot(ctx) + + github_handler = GithubHandler( + bot=bot, + repo_info=RepoInfo(owner="owner", repo="repo"), + ) + should_call_apis( + ctx, + [ + GitHubApi(api="rest.pulls.async_create", result=mock_pull_resp), + ], + snapshot( + { + 0: { + "owner": "owner", + "repo": "repo", + "title": "new title", + "body": "new body", + "base": "main", + "head": "branch", + }, + } + ), + ) + number = await github_handler.create_pull_request( + "main", "new title", "branch", "new body" + ) + assert number == 123 + + +async def test_add_labels(app: App) -> None: + """测试添加标签""" + from src.plugins.github.handlers.github import GithubHandler + from src.plugins.github.models import RepoInfo + + async with app.test_api() as ctx: + _, bot = get_github_bot(ctx) + + github_handler = GithubHandler( + bot=bot, + repo_info=RepoInfo(owner="owner", repo="repo"), + ) + + should_call_apis( + ctx, + [ + GitHubApi(api="rest.issues.async_add_labels", result=True), + ], + snapshot( + { + 0: { + "owner": "owner", + "repo": "repo", + "issue_number": 76, + "labels": ["Publish", "Plugin"], + }, + } + ), + ) + await github_handler.add_labels(76, ["Publish", "Plugin"]) + + +async def test_ready_pull_request(app: App) -> None: + """测试标记拉取请求为可评审""" + from src.plugins.github.handlers.github import GithubHandler + from src.plugins.github.models import RepoInfo + + async with app.test_api() as ctx: + _, bot = get_github_bot(ctx) + + github_handler = GithubHandler( + bot=bot, + repo_info=RepoInfo(owner="owner", repo="repo"), + ) + + should_call_apis( + ctx, + [ + GitHubApi(api="async_graphql", result=None), + ], + snapshot( + { + 0: { + "query": """\ +mutation markPullRequestReadyForReview($pullRequestId: ID!) { + markPullRequestReadyForReview(input: {pullRequestId: $pullRequestId}) { + clientMutationId + } + }\ +""", + "variables": {"pullRequestId": "node_id"}, + }, + } + ), + ) + await github_handler.ready_pull_request("node_id") + + +async def test_update_pull_request_title(app: App) -> None: + """测试修改拉取请求标题""" + from src.plugins.github.handlers.github import GithubHandler + from src.plugins.github.models import RepoInfo + + async with app.test_api() as ctx: + _, bot = get_github_bot(ctx) + + github_handler = GithubHandler( + bot=bot, + repo_info=RepoInfo(owner="owner", repo="repo"), + ) + + should_call_apis( + ctx, + [ + GitHubApi(api="rest.pulls.async_update", result=True), + ], + snapshot( + { + 0: { + "owner": "owner", + "repo": "repo", + "pull_number": 123, + "title": "new title", + }, + } + ), + ) + await github_handler.update_pull_request_title("new title", 123) + + +async def test_get_user_name(app: App, mocker: MockerFixture) -> None: + """测试获取用户名""" + from src.plugins.github.handlers.github import GithubHandler + from src.plugins.github.models import RepoInfo + + mock_user = mocker.MagicMock() + mock_user.login = "name" + mock_user_resp = mocker.MagicMock() + mock_user_resp.parsed_data = mock_user + + async with app.test_api() as ctx: + _, bot = get_github_bot(ctx) + + github_handler = GithubHandler( + bot=bot, + repo_info=RepoInfo(owner="owner", repo="repo"), + ) + + should_call_apis( + ctx, + [ + GitHubApi(api="rest.users.async_get_by_id", result=mock_user_resp), + ], + snapshot( + { + 0: {"account_id": 1}, + } + ), + ) + await github_handler.get_user_name(1) + + +async def test_get_user_id(app: App, mocker: MockerFixture) -> None: + """测试获取用户 ID""" + from src.plugins.github.handlers.github import GithubHandler + from src.plugins.github.models import RepoInfo + + mock_user = mocker.MagicMock() + mock_user.id = "1" + mock_user_resp = mocker.MagicMock() + mock_user_resp.parsed_data = mock_user + + async with app.test_api() as ctx: + _, bot = get_github_bot(ctx) + + github_handler = GithubHandler( + bot=bot, + repo_info=RepoInfo(owner="owner", repo="repo"), + ) + + should_call_apis( + ctx, + [ + GitHubApi( + api="rest.users.async_get_by_username", result=mock_user_resp + ), + ], + snapshot( + { + 0: {"username": "name"}, + } + ), + ) + await github_handler.get_user_id("name") + + +async def test_get_issue(app: App, mocker: MockerFixture) -> None: + """测试获取议题""" + from src.plugins.github.handlers.github import GithubHandler + from src.plugins.github.models import RepoInfo + + mock_issue = mocker.MagicMock() + mock_issue_resp = mocker.MagicMock() + mock_issue_resp.parsed_data = mock_issue + + async with app.test_api() as ctx: + _, bot = get_github_bot(ctx) + + github_handler = GithubHandler( + bot=bot, + repo_info=RepoInfo(owner="owner", repo="repo"), + ) + + should_call_apis( + ctx, + [ + GitHubApi(api="rest.issues.async_get", result=mock_issue_resp), + ], + snapshot( + { + 0: {"owner": "owner", "repo": "repo", "issue_number": 123}, + } + ), + ) + issue = await github_handler.get_issue(123) + assert issue == mock_issue + + +async def test_close_issue(app: App) -> None: + """测试关闭议题""" + from src.plugins.github.handlers.github import GithubHandler + from src.plugins.github.models import RepoInfo + + async with app.test_api() as ctx: + _, bot = get_github_bot(ctx) + + github_handler = GithubHandler( + bot=bot, + repo_info=RepoInfo(owner="owner", repo="repo"), + ) + + should_call_apis( + ctx, + [ + GitHubApi(api="rest.issues.async_update", result=True), + ], + snapshot( + { + 0: { + "owner": "owner", + "repo": "repo", + "issue_number": 123, + "state": "closed", + "state_reason": "completed", + }, + } + ), + ) + await github_handler.close_issue("completed", 123) + + +async def test_to_issue_handler(app: App, mocker: MockerFixture) -> None: + """测试获取议题处理器""" + from src.plugins.github.handlers.github import GithubHandler + from src.plugins.github.handlers.issue import IssueHandler + from src.plugins.github.models import RepoInfo + + mock_issue = mocker.MagicMock(spec=Issue) + mock_issue_resp = mocker.MagicMock() + mock_issue_resp.parsed_data = mock_issue + + async with app.test_api() as ctx: + _, bot = get_github_bot(ctx) + + github_handler = GithubHandler( + bot=bot, + repo_info=RepoInfo(owner="owner", repo="repo"), + ) + + should_call_apis( + ctx, + [ + GitHubApi(api="rest.issues.async_get", result=mock_issue_resp), + ], + snapshot( + { + 0: {"owner": "owner", "repo": "repo", "issue_number": 123}, + } + ), + ) + issue_handler = await github_handler.to_issue_handler(123) + assert isinstance(issue_handler, IssueHandler) + assert issue_handler.issue == mock_issue From 803828af524c8045df78de35b31d676c044fe304 Mon Sep 17 00:00:00 2001 From: uy/sun Date: Thu, 19 Dec 2024 13:35:04 +0800 Subject: [PATCH 5/5] =?UTF-8?q?test:=20=E6=B7=BB=E5=8A=A0=20issue=20handle?= =?UTF-8?q?r=20=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../github/handlers/test_issue_handler.py | 445 ++++++++++++++++++ 1 file changed, 445 insertions(+) create mode 100644 tests/plugins/github/handlers/test_issue_handler.py diff --git a/tests/plugins/github/handlers/test_issue_handler.py b/tests/plugins/github/handlers/test_issue_handler.py new file mode 100644 index 00000000..41d40413 --- /dev/null +++ b/tests/plugins/github/handlers/test_issue_handler.py @@ -0,0 +1,445 @@ +from unittest.mock import _Call, call + +from githubkit.rest import Issue +from inline_snapshot import snapshot +from nonebug import App +from pytest_mock import MockerFixture + +from tests.plugins.github.utils import GitHubApi, get_github_bot, should_call_apis + + +async def test_issue_property(app: App, mocker: MockerFixture) -> None: + """测试获取 IssueHandler 的属性""" + from src.plugins.github.handlers.issue import IssueHandler + from src.plugins.github.models import AuthorInfo, RepoInfo + + mock_issue = mocker.MagicMock(spec=Issue) + mock_issue.number = 111 + mock_issue.user = mocker.MagicMock() + mock_issue.user.login = "he0119" + mock_issue.user.id = 1 + + mock_pull = mocker.MagicMock() + mock_pulls_resp = mocker.MagicMock() + mock_pulls_resp.parsed_data = [mock_pull] + + async with app.test_api() as ctx: + _, bot = get_github_bot(ctx) + + issue_handler = IssueHandler( + bot=bot, + repo_info=RepoInfo(owner="owner", repo="repo"), + issue=mock_issue, + ) + + assert issue_handler.issue_number == 111 + assert issue_handler.author_info == AuthorInfo(author="he0119", author_id=1) + assert issue_handler.author == "he0119" + assert issue_handler.author_id == 1 + + +async def test_update_issue_title(app: App, mocker: MockerFixture) -> None: + """测试修改议题标题""" + from src.plugins.github.handlers.issue import IssueHandler + from src.plugins.github.models import RepoInfo + + mock_issue = mocker.MagicMock(spec=Issue) + mock_issue.number = 76 + mock_issue.title = "old title" + + async with app.test_api() as ctx: + _, bot = get_github_bot(ctx) + + issue_handler = IssueHandler( + bot=bot, + repo_info=RepoInfo(owner="owner", repo="repo"), + issue=mock_issue, + ) + + should_call_apis( + ctx, + [ + GitHubApi(api="rest.issues.async_update", result=True), + ], + snapshot( + { + 0: { + "owner": "owner", + "repo": "repo", + "issue_number": 76, + "title": "new title", + }, + } + ), + ) + await issue_handler.update_issue_title("new title") + assert mock_issue.title == "new title" + # 再次修改,但标题一致,不会调用 API + await issue_handler.update_issue_title("new title") + + +async def test_update_issue_body(app: App, mocker: MockerFixture) -> None: + """测试更新议题内容""" + from src.plugins.github.handlers.issue import IssueHandler + from src.plugins.github.models import RepoInfo + + mock_issue = mocker.MagicMock(spec=Issue) + mock_issue.number = 76 + mock_issue.body = "old body" + + async with app.test_api() as ctx: + _, bot = get_github_bot(ctx) + + issue_handler = IssueHandler( + bot=bot, + repo_info=RepoInfo(owner="owner", repo="repo"), + issue=mock_issue, + ) + + should_call_apis( + ctx, + [ + GitHubApi(api="rest.issues.async_update", result=True), + ], + snapshot( + { + 0: { + "owner": "owner", + "repo": "repo", + "issue_number": 76, + "body": "new body", + }, + } + ), + ) + await issue_handler.update_issue_body("new body") + assert mock_issue.body == "new body" + # 再次修改,但内容一致,不会调用 API + await issue_handler.update_issue_body("new body") + + +async def test_close_issue(app: App, mocker: MockerFixture) -> None: + """测试关闭议题""" + from src.plugins.github.handlers.issue import IssueHandler + from src.plugins.github.models import RepoInfo + + mock_issue = mocker.MagicMock(spec=Issue) + mock_issue.number = 123 + mock_issue.state = "open" + + async with app.test_api() as ctx: + _, bot = get_github_bot(ctx) + + issue_handler = IssueHandler( + bot=bot, + repo_info=RepoInfo(owner="owner", repo="repo"), + issue=mock_issue, + ) + + should_call_apis( + ctx, + [ + GitHubApi(api="rest.issues.async_update", result=True), + ], + snapshot( + { + 0: { + "owner": "owner", + "repo": "repo", + "issue_number": 123, + "state": "closed", + "state_reason": "completed", + }, + } + ), + ) + await issue_handler.close_issue("completed") + + +async def test_create_pull_request(app: App, mocker: MockerFixture) -> None: + """测试创建拉取请求""" + from src.plugins.github.handlers.issue import IssueHandler + from src.plugins.github.models import RepoInfo + + mock_pull = mocker.MagicMock() + mock_pull.number = 123 + mock_pull_resp = mocker.MagicMock() + mock_pull_resp.parsed_data = mock_pull + + mock_issue = mocker.MagicMock(spec=Issue) + mock_issue.number = 76 + + async with app.test_api() as ctx: + _, bot = get_github_bot(ctx) + + issue_handler = IssueHandler( + bot=bot, + repo_info=RepoInfo(owner="owner", repo="repo"), + issue=mock_issue, + ) + should_call_apis( + ctx, + [ + GitHubApi(api="rest.pulls.async_create", result=mock_pull_resp), + ], + snapshot( + { + 0: { + "owner": "owner", + "repo": "repo", + "title": "new title", + "body": "resolve #76", + "base": "main", + "head": "branch", + }, + } + ), + ) + number = await issue_handler.create_pull_request("main", "new title", "branch") + assert number == 123 + + +async def test_list_comments(app: App, mocker: MockerFixture) -> None: + """测试拉取所有评论""" + from src.plugins.github.handlers.issue import IssueHandler + from src.plugins.github.models import RepoInfo + + mock_comments_resp = mocker.MagicMock() + mock_comments_resp.parsed_data = [] + + mock_issue = mocker.MagicMock(spec=Issue) + mock_issue.number = 76 + + async with app.test_api() as ctx: + _, bot = get_github_bot(ctx) + + issue_handler = IssueHandler( + bot=bot, + repo_info=RepoInfo(owner="owner", repo="repo"), + issue=mock_issue, + ) + + should_call_apis( + ctx, + [ + GitHubApi( + api="rest.issues.async_list_comments", result=mock_comments_resp + ), + ], + snapshot( + { + 0: { + "owner": "owner", + "repo": "repo", + "issue_number": 76, + }, + } + ), + ) + await issue_handler.list_comments() + + +async def test_comment_issue(app: App, mocker: MockerFixture) -> None: + """测试发布评论""" + from src.plugins.github.handlers.issue import IssueHandler + from src.plugins.github.models import RepoInfo + + mock_comments_resp = mocker.MagicMock() + mock_comments_resp.parsed_data = [] + + mock_issue = mocker.MagicMock(spec=Issue) + mock_issue.number = 76 + + async with app.test_api() as ctx: + _, bot = get_github_bot(ctx) + + issue_handler = IssueHandler( + bot=bot, + repo_info=RepoInfo(owner="owner", repo="repo"), + issue=mock_issue, + ) + + should_call_apis( + ctx, + [ + GitHubApi( + api="rest.issues.async_list_comments", result=mock_comments_resp + ), + GitHubApi(api="rest.issues.async_create_comment", result=True), + ], + snapshot( + { + 0: {"owner": "owner", "repo": "repo", "issue_number": 76}, + 1: { + "owner": "owner", + "repo": "repo", + "issue_number": 76, + "body": "new comment", + }, + } + ), + ) + await issue_handler.comment_issue("new comment") + + +async def test_should_skip_test(app: App, mocker: MockerFixture) -> None: + """测试是否应该跳过测试""" + from src.plugins.github.handlers.issue import IssueHandler + from src.plugins.github.models import RepoInfo + + mock_issue = mocker.MagicMock(spec=Issue) + mock_issue.number = 76 + + mock_comments_resp = mocker.MagicMock() + mock_comments_resp.parsed_data = [] + + async with app.test_api() as ctx: + _, bot = get_github_bot(ctx) + + issue_handler = IssueHandler( + bot=bot, + repo_info=RepoInfo(owner="owner", repo="repo"), + issue=mock_issue, + ) + + should_call_apis( + ctx, + [ + GitHubApi( + api="rest.issues.async_list_comments", result=mock_comments_resp + ), + ], + snapshot( + { + 0: {"owner": "owner", "repo": "repo", "issue_number": 76}, + } + ), + ) + assert await issue_handler.should_skip_test() is False + + +async def test_should_skip_test_true(app: App, mocker: MockerFixture) -> None: + """测试是否应该跳过测试,应该跳过""" + from src.plugins.github.handlers.issue import IssueHandler + from src.plugins.github.models import RepoInfo + + mock_issue = mocker.MagicMock(spec=Issue) + mock_issue.number = 76 + + mock_comment = mocker.MagicMock() + mock_comment.author_association = "OWNER" + mock_comment.body = "/skip" + + mock_comments_resp = mocker.MagicMock() + mock_comments_resp.parsed_data = [mock_comment] + + async with app.test_api() as ctx: + _, bot = get_github_bot(ctx) + + issue_handler = IssueHandler( + bot=bot, + repo_info=RepoInfo(owner="owner", repo="repo"), + issue=mock_issue, + ) + + should_call_apis( + ctx, + [ + GitHubApi( + api="rest.issues.async_list_comments", result=mock_comments_resp + ), + ], + snapshot( + { + 0: {"owner": "owner", "repo": "repo", "issue_number": 76}, + } + ), + ) + assert await issue_handler.should_skip_test() is True + + +async def test_should_skip_test_not_admin(app: App, mocker: MockerFixture) -> None: + """测试是否应该跳过测试,只是贡献者""" + from src.plugins.github.handlers.issue import IssueHandler + from src.plugins.github.models import RepoInfo + + mock_issue = mocker.MagicMock(spec=Issue) + mock_issue.number = 76 + + mock_comment = mocker.MagicMock() + mock_comment.author_association = "CONTRIBUTOR" + mock_comment.body = "/skip" + + mock_comments_resp = mocker.MagicMock() + mock_comments_resp.parsed_data = [mock_comment] + + async with app.test_api() as ctx: + _, bot = get_github_bot(ctx) + + issue_handler = IssueHandler( + bot=bot, + repo_info=RepoInfo(owner="owner", repo="repo"), + issue=mock_issue, + ) + + should_call_apis( + ctx, + [ + GitHubApi( + api="rest.issues.async_list_comments", result=mock_comments_resp + ), + ], + snapshot( + { + 0: {"owner": "owner", "repo": "repo", "issue_number": 76}, + } + ), + ) + assert await issue_handler.should_skip_test() is False + + +async def test_commit_and_push(app: App, mocker: MockerFixture) -> None: + """测试提交并推送""" + from src.plugins.github.handlers.issue import IssueHandler + from src.plugins.github.models import RepoInfo + + mock_run_shell_command = mocker.patch( + "src.plugins.github.handlers.git.run_shell_command" + ) + + mock_issue = mocker.MagicMock(spec=Issue) + mock_issue.user = mocker.MagicMock() + mock_issue.user.login = "he0119" + mock_issue.user.id = 1 + + async with app.test_api() as ctx: + _, bot = get_github_bot(ctx) + + issue_handler = IssueHandler( + bot=bot, + repo_info=RepoInfo(owner="owner", repo="repo"), + issue=mock_issue, + ) + + issue_handler.commit_and_push("message", "main") + + mock_run_shell_command.assert_has_calls( + [ + call(["git", "config", "--global", "user.name", "he0119"]), + call( + [ + "git", + "config", + "--global", + "user.email", + "he0119@users.noreply.github.com", + ] + ), + call(["git", "add", "-A"]), + call(["git", "commit", "-m", "message"]), + call(["git", "fetch", "origin"]), + call(["git", "diff", "origin/main", "main"]), + _Call(("().stdout.__bool__", (), {})), + call(["git", "push", "origin", "main", "-f"]), + ], + )