diff --git a/tests/conftest.py b/tests/conftest.py index 08eaa370..66b0afd2 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,6 +4,7 @@ import nonebot import pytest +from nonebot.adapters.github import Adapter from nonebug import NONEBOT_INIT_KWARGS from nonebug.app import App from pytest_mock import MockerFixture @@ -26,11 +27,13 @@ def pytest_configure(config: pytest.Config) -> None: "github_event_path": "event_path", "plugin_test_output": "test_output", "plugin_test_result": False, + "github_apps": [], } @pytest.fixture(scope="session", autouse=True) def load_plugin(nonebug_init: None) -> set["Plugin"]: + nonebot.get_driver().register_adapter(Adapter) return nonebot.load_plugins(str(Path(__file__).parent.parent / "src" / "plugins")) diff --git a/tests/process/test_publish_check.py b/tests/process/test_publish_check.py index 85c491a2..9e8f9fcf 100644 --- a/tests/process/test_publish_check.py +++ b/tests/process/test_publish_check.py @@ -1,469 +1,671 @@ -# import json -# from pathlib import Path -# from typing import Any - -# from pytest_mock import MockerFixture - - -# def mocked_httpx_get(url: str): -# class MockResponse: -# def __init__(self, status_code: int): -# self.status_code = status_code - -# if url == "https://v2.nonebot.dev": -# return MockResponse(200) - -# return MockResponse(404) - - -# def check_json_data(file: Path, data: Any) -> None: -# with open(file) as f: -# assert json.load(f) == data - - -# def test_process_publish_check(mocker: MockerFixture, tmp_path: Path) -> None: -# """测试一个正常的发布流程""" -# import src.globals as g -# from src import Bot - -# bot = Bot() -# bot.github = mocker.MagicMock() - -# mocker.patch("httpx.get", side_effect=mocked_httpx_get) -# mock_subprocess_run = mocker.patch( -# "subprocess.run", side_effect=lambda *args, **kwargs: mocker.MagicMock() -# ) - -# mock_issue = mocker.MagicMock() -# mock_issue.pull_request = None -# mock_issue.title = "Bot: test" -# mock_issue.number = 1 -# mock_issue.state = "open" -# mock_issue.body = """**机器人名称:**\n\ntest\n\n**机器人功能:**\n\ndesc\n\n**机器人项目仓库/主页链接:**\n\nhttps://v2.nonebot.dev\n\n**标签:**\n\n[{"label": "test", "color": "#ffffff"}]""" -# mock_issue.user.login = "test" - -# mock_event = mocker.MagicMock() -# mock_event.issue = mock_issue - -# mock_issues_resp = mocker.MagicMock() -# mock_issues_resp.parsed_data = mock_issue -# bot.github.rest.issues.get.return_value = mock_issues_resp - -# mock_comment = mocker.MagicMock() -# mock_comment.body = "Bot: test" -# mock_list_comments_resp = mocker.MagicMock() -# mock_list_comments_resp.parsed_data = [mock_comment] -# bot.github.rest.issues.list_comments.return_value = mock_list_comments_resp - -# mock_pull = mocker.MagicMock() -# mock_pull.number = 2 -# mock_pulls_resp = mocker.MagicMock() -# mock_pulls_resp.parsed_data = mock_pull -# bot.github.rest.pulls.create.return_value = mock_pulls_resp - -# with open(tmp_path / "bots.json", "w") as f: -# json.dump([], f) - -# check_json_data(g.settings.input_config.bot_path, []) - -# bot.process_publish_check(mock_event) - -# # 获取最新的议题信息 -# bot.github.rest.issues.get.assert_called_with("owner", "repo", 1) - -# # 测试 git 命令 -# mock_subprocess_run.assert_has_calls( -# [ -# mocker.call( -# ["git", "switch", "-C", "publish/issue1"], -# check=True, -# capture_output=True, -# ), -# mocker.call( -# ["git", "config", "--global", "user.name", "test"], -# check=True, -# capture_output=True, -# ), -# mocker.call( -# [ -# "git", -# "config", -# "--global", -# "user.email", -# "test@users.noreply.github.com", -# ], -# check=True, -# capture_output=True, -# ), -# mocker.call(["git", "add", "-A"], check=True, capture_output=True), -# mocker.call( -# ["git", "commit", "-m", ":beers: publish bot test (#1)"], -# check=True, -# capture_output=True, -# ), -# mocker.call(["git", "fetch", "origin"], check=True, capture_output=True), -# mocker.call( -# ["git", "diff", "origin/publish/issue1", "publish/issue1"], -# check=True, -# capture_output=True, -# ), -# mocker.call( -# ["git", "push", "origin", "publish/issue1", "-f"], -# check=True, -# capture_output=True, -# ), -# ] # type: ignore -# ) - -# # 检查文件是否正确 -# check_json_data( -# g.settings.input_config.bot_path, -# [ -# { -# "name": "test", -# "desc": "desc", -# "author": "test", -# "homepage": "https://v2.nonebot.dev", -# "tags": [{"label": "test", "color": "#ffffff"}], -# "is_official": False, -# } -# ], -# ) - -# # 检查是否创建了拉取请求 -# bot.github.rest.pulls.create.assert_called_with( -# "owner", -# "repo", -# title="Bot: test", -# body="resolve #1", -# base="master", -# head="publish/issue1", -# ) - -# # 测试自动添加标签 -# bot.github.rest.issues.add_labels.assert_has_calls( -# [ -# mocker.call("owner", "repo", 1, labels=["Bot"]), # 给议题添加标签 -# mocker.call("owner", "repo", 2, labels=["Bot"]), # 给拉取请求添加标签 -# ] -# ) - -# # 检查是否创建了评论 -# bot.github.rest.issues.create_comment.assert_called_with( -# "owner", -# "repo", -# 1, -# body="""# 📃 商店发布检查结果\n\n> Bot: test\n\n**✅ 所有测试通过,一切准备就绪!**\n\n
详情
  • ✅ 标签: test-#ffffff。
  • ✅ 项目 主页 返回状态码 200。
  • \n\n---\n\n💡 如需修改信息,请直接修改 issue,机器人会自动更新检查结果。\n💡 当插件加载测试失败时,请发布新版本后在当前页面下评论任意内容以触发测试。\n\n💪 Powered by [NoneBot2 Publish Bot](https://github.com/nonebot/noneflow)\n\n""", -# ) - - -# def test_edit_title(mocker: MockerFixture, tmp_path: Path) -> None: -# """测试编辑标题 - -# 插件名被修改后,标题也应该被修改 -# """ -# from githubkit.exception import RequestFailed - -# import src.globals as g -# from src import Bot - -# bot = Bot() -# bot.github = mocker.MagicMock() - -# mocker.patch("httpx.get", side_effect=mocked_httpx_get) -# mock_subprocess_run = mocker.patch( -# "subprocess.run", side_effect=lambda *args, **kwargs: mocker.MagicMock() -# ) - -# mock_issue = mocker.MagicMock() -# mock_issue.pull_request = None -# mock_issue.title = "Bot: test" -# mock_issue.number = 1 -# mock_issue.state = "open" -# mock_issue.body = """**机器人名称:**\n\ntest1\n\n**机器人功能:**\n\ndesc\n\n**机器人项目仓库/主页链接:**\n\nhttps://v2.nonebot.dev\n\n**标签:**\n\n[{"label": "test", "color": "#ffffff"}]""" -# mock_issue.user.login = "test" - -# mock_event = mocker.MagicMock() -# mock_event.issue = mock_issue - -# mock_issues_resp = mocker.MagicMock() -# mock_issues_resp.parsed_data = mock_issue -# bot.github.rest.issues.get.return_value = mock_issues_resp - -# mock_comment = mocker.MagicMock() -# mock_comment.body = "Bot: test" -# mock_list_comments_resp = mocker.MagicMock() -# mock_list_comments_resp.parsed_data = [mock_comment] -# bot.github.rest.issues.list_comments.return_value = mock_list_comments_resp - -# bot.github.rest.pulls.create.side_effect = RequestFailed(mocker.MagicMock()) - -# mock_pull = mocker.MagicMock() -# mock_pull.number = 2 -# mock_pull.title = "Bot: test" -# mock_pulls_resp = mocker.MagicMock() -# mock_pulls_resp.parsed_data = [mock_pull] -# bot.github.rest.pulls.list.return_value = mock_pulls_resp - -# with open(tmp_path / "bots.json", "w") as f: -# json.dump([], f) - -# check_json_data(g.settings.input_config.bot_path, []) - -# bot.process_publish_check(mock_event) - -# # 获取最新的议题信息 -# bot.github.rest.issues.get.assert_called_with("owner", "repo", 1) - -# # 测试 git 命令 -# mock_subprocess_run.assert_has_calls( -# [ -# mocker.call( -# ["git", "switch", "-C", "publish/issue1"], -# check=True, -# capture_output=True, -# ), -# mocker.call( -# ["git", "config", "--global", "user.name", "test"], -# check=True, -# capture_output=True, -# ), -# mocker.call( -# [ -# "git", -# "config", -# "--global", -# "user.email", -# "test@users.noreply.github.com", -# ], -# check=True, -# capture_output=True, -# ), -# mocker.call(["git", "add", "-A"], check=True, capture_output=True), -# mocker.call( -# ["git", "commit", "-m", ":beers: publish bot test1 (#1)"], -# check=True, -# capture_output=True, -# ), -# mocker.call(["git", "fetch", "origin"], check=True, capture_output=True), -# mocker.call( -# ["git", "diff", "origin/publish/issue1", "publish/issue1"], -# check=True, -# capture_output=True, -# ), -# mocker.call( -# ["git", "push", "origin", "publish/issue1", "-f"], -# check=True, -# capture_output=True, -# ), -# ] # type: ignore -# ) - -# # 检查文件是否正确 -# check_json_data( -# g.settings.input_config.bot_path, -# [ -# { -# "name": "test1", -# "desc": "desc", -# "author": "test", -# "homepage": "https://v2.nonebot.dev", -# "tags": [{"label": "test", "color": "#ffffff"}], -# "is_official": False, -# } -# ], -# ) - -# # 检查是否创建了拉取请求 -# bot.github.rest.pulls.create.assert_called_with( -# "owner", -# "repo", -# title="Bot: test1", -# body="resolve #1", -# base="master", -# head="publish/issue1", -# ) - -# # 测试自动添加标签 -# bot.github.rest.issues.add_labels.assert_has_calls( -# [ -# mocker.call("owner", "repo", 1, labels=["Bot"]), # 给议题添加标签 -# ] -# ) - -# # # 检查是否修改了标题 -# bot.github.rest.issues.update.assert_called_with( -# "owner", "repo", 1, title="Bot: test1" -# ) -# bot.github.rest.pulls.list.assert_called_with( -# "owner", "repo", head="owner:publish/issue1" -# ) -# bot.github.rest.pulls.update.assert_called_with( -# "owner", "repo", 2, title="Bot: test1" -# ) - -# # 检查是否创建了评论 -# bot.github.rest.issues.create_comment.assert_called_with( -# "owner", -# "repo", -# 1, -# body="""# 📃 商店发布检查结果\n\n> Bot: test1\n\n**✅ 所有测试通过,一切准备就绪!**\n\n
    详情
  • ✅ 标签: test-#ffffff。
  • ✅ 项目 主页 返回状态码 200。
  • \n\n---\n\n💡 如需修改信息,请直接修改 issue,机器人会自动更新检查结果。\n💡 当插件加载测试失败时,请发布新版本后在当前页面下评论任意内容以触发测试。\n\n💪 Powered by [NoneBot2 Publish Bot](https://github.com/nonebot/noneflow)\n\n""", -# ) - - -# def test_process_publish_check_not_pass(mocker: MockerFixture, tmp_path: Path) -> None: -# """测试发布检查不通过""" -# import src.globals as g -# from src import Bot - -# bot = Bot() -# bot.github = mocker.MagicMock() - -# mocker.patch("httpx.get", side_effect=mocked_httpx_get) -# mock_subprocess_run = mocker.patch( -# "subprocess.run", side_effect=lambda *args, **kwargs: mocker.MagicMock() -# ) - -# mock_issue = mocker.MagicMock() -# mock_issue.pull_request = None -# mock_issue.title = "Bot: test" -# mock_issue.number = 1 -# mock_issue.state = "open" -# mock_issue.body = """**机器人名称:**\n\ntest\n\n**机器人功能:**\n\ndesc\n\n**机器人项目仓库/主页链接:**\n\nhttps://test\n\n**标签:**\n\n[{"label": "test", "color": "#ffffff"}]""" -# mock_issue.user.login = "test" - -# mock_event = mocker.MagicMock() -# mock_event.issue = mock_issue - -# mock_issues_resp = mocker.MagicMock() -# mock_issues_resp.parsed_data = mock_issue -# bot.github.rest.issues.get.return_value = mock_issues_resp - -# mock_comment = mocker.MagicMock() -# mock_comment.body = "Bot: test" -# mock_list_comments_resp = mocker.MagicMock() -# mock_list_comments_resp.parsed_data = [mock_comment] -# bot.github.rest.issues.list_comments.return_value = mock_list_comments_resp - -# with open(tmp_path / "bots.json", "w") as f: -# json.dump([], f) - -# check_json_data(g.settings.input_config.bot_path, []) - -# bot.process_publish_check(mock_event) - -# # 获取最新的议题信息 -# bot.github.rest.issues.get.assert_called_with("owner", "repo", 1) - -# # 测试 git 命令 -# mock_subprocess_run.assert_not_called() - -# # 检查文件是否正确 -# check_json_data(g.settings.input_config.bot_path, []) - -# # 检查是否创建了拉取请求 -# bot.github.rest.pulls.create.assert_not_called() - -# # 测试自动添加标签 -# bot.github.rest.issues.add_labels.assert_has_calls( -# [ -# mocker.call("owner", "repo", 1, labels=["Bot"]), # 给议题添加标签 -# ] -# ) - -# # 检查是否创建了评论 -# bot.github.rest.issues.create_comment.assert_called_with( -# "owner", -# "repo", -# 1, -# body="""# 📃 商店发布检查结果\n\n> Bot: test\n\n**⚠️ 在发布检查过程中,我们发现以下问题:**\n
  • ⚠️ 项目 主页 返回状态码 404。
    请确保您的项目主页可访问。
  • \n
    详情
  • ✅ 标签: test-#ffffff。
  • \n\n---\n\n💡 如需修改信息,请直接修改 issue,机器人会自动更新检查结果。\n💡 当插件加载测试失败时,请发布新版本后在当前页面下评论任意内容以触发测试。\n\n💪 Powered by [NoneBot2 Publish Bot](https://github.com/nonebot/noneflow)\n\n""", -# ) - - -# def test_comment_at_pull_request(mocker: MockerFixture, tmp_path: Path) -> None: -# """测试在拉取请求下评论 - -# event.issue.pull_request 不为空 -# """ -# import src.globals as g -# from src import Bot - -# bot = Bot() -# bot.github = mocker.MagicMock() - -# mock_httpx = mocker.patch("httpx.get", side_effect=mocked_httpx_get) -# mock_subprocess_run = mocker.patch( -# "subprocess.run", side_effect=lambda *args, **kwargs: mocker.MagicMock() -# ) - -# mock_issue = mocker.MagicMock() - -# mock_event = mocker.MagicMock() -# mock_event.issue = mock_issue - -# bot.process_publish_check(mock_event) - -# mock_httpx.assert_not_called() -# mock_subprocess_run.assert_not_called() -# bot.github.rest.issues.add_labels.assert_not_called() - - -# def test_issue_state_closed(mocker: MockerFixture, tmp_path: Path) -> None: -# """测试议题已关闭 - -# event.issue.state = "closed" -# """ -# import src.globals as g -# from src import Bot - -# bot = Bot() -# bot.github = mocker.MagicMock() - -# mock_httpx = mocker.patch("httpx.get", side_effect=mocked_httpx_get) -# mock_subprocess_run = mocker.patch( -# "subprocess.run", side_effect=lambda *args, **kwargs: mocker.MagicMock() -# ) - -# mock_issue = mocker.MagicMock() -# mock_issue.pull_request = None -# mock_issue.state = "closed" - -# mock_event = mocker.MagicMock() -# mock_event.issue = mock_issue - -# bot.process_publish_check(mock_event) - -# mock_httpx.assert_not_called() -# mock_subprocess_run.assert_not_called() -# bot.github.rest.issues.add_labels.assert_not_called() - - -# def test_not_publish_issue(mocker: MockerFixture, tmp_path: Path) -> None: -# """测试议题与发布无关 - -# 议题的标题不以 "Bot/Adapter/Plugin" 开头 -# """ -# import src.globals as g -# from src import Bot - -# bot = Bot() -# bot.github = mocker.MagicMock() - -# mock_httpx = mocker.patch("httpx.get", side_effect=mocked_httpx_get) -# mock_subprocess_run = mocker.patch( -# "subprocess.run", side_effect=lambda *args, **kwargs: mocker.MagicMock() -# ) - -# mock_issue = mocker.MagicMock() -# mock_issue.pull_request = None -# mock_issue.state = "open" -# mock_issue.title = "test" - -# mock_event = mocker.MagicMock() -# mock_event.issue = mock_issue - -# bot.process_publish_check(mock_event) - -# mock_httpx.assert_not_called() -# mock_subprocess_run.assert_not_called() -# bot.github.rest.issues.add_labels.assert_not_called() +import json +from pathlib import Path +from typing import Any, cast + +from nonebot import get_adapter +from nonebot.adapters.github import ( + Adapter, + GitHubBot, + IssueCommentCreated, + IssuesOpened, +) +from nonebot.adapters.github.config import GitHubApp +from nonebug import App +from pytest_mock import MockerFixture + + +def mocked_httpx_get(url: str): + class MockResponse: + def __init__(self, status_code: int): + self.status_code = status_code + + if url == "https://v2.nonebot.dev": + return MockResponse(200) + + return MockResponse(404) + + +def check_json_data(file: Path, data: Any) -> None: + with open(file) as f: + assert json.load(f) == data + + +async def test_process_publish_check( + app: App, mocker: MockerFixture, tmp_path: Path +) -> None: + """测试一个正常的发布流程""" + from src.plugins.publish import check + from src.plugins.publish.config import plugin_config + + mocker.patch("httpx.get", side_effect=mocked_httpx_get) + mock_subprocess_run = mocker.patch( + "subprocess.run", side_effect=lambda *args, **kwargs: mocker.MagicMock() + ) + + mock_installation = mocker.MagicMock() + mock_installation.id = 123 + + mock_installation_resp = mocker.MagicMock() + mock_installation_resp.parsed_data = mock_installation + + mock_issue = mocker.MagicMock() + mock_issue.pull_request = None + mock_issue.title = "Bot: test" + mock_issue.number = 80 + mock_issue.state = "open" + mock_issue.body = """**机器人名称:**\n\ntest\n\n**机器人功能:**\n\ndesc\n\n**机器人项目仓库/主页链接:**\n\nhttps://v2.nonebot.dev\n\n**标签:**\n\n[{"label": "test", "color": "#ffffff"}]""" + mock_issue.user.login = "test" + + mock_event = mocker.MagicMock() + mock_event.issue = mock_issue + + mock_issues_resp = mocker.MagicMock() + mock_issues_resp.parsed_data = mock_issue + + mock_comment = mocker.MagicMock() + mock_comment.body = "Bot: test" + mock_list_comments_resp = mocker.MagicMock() + mock_list_comments_resp.parsed_data = [mock_comment] + + mock_pull = mocker.MagicMock() + mock_pull.number = 2 + mock_pulls_resp = mocker.MagicMock() + mock_pulls_resp.parsed_data = mock_pull + + with open(tmp_path / "bots.json", "w") as f: + json.dump([], f) + + check_json_data(plugin_config.input_config.bot_path, []) + + async with app.test_matcher(check) as ctx: + adapter = get_adapter(Adapter) + bot = ctx.create_bot( + base=GitHubBot, + adapter=adapter, + self_id=GitHubApp(app_id="1", private_key="1"), # type: ignore + ) + bot = cast(GitHubBot, bot) + event_path = Path(__file__).parent.parent / "plugin-test" / "issue-open.json" + event = Adapter.payload_to_event("1", "issues", event_path.read_bytes()) + assert isinstance(event, IssuesOpened) + + ctx.should_call_api( + "rest.apps.async_get_repo_installation", + {"owner": "he0119", "repo": "action-test"}, + mock_installation_resp, + ) + ctx.should_call_api( + "rest.issues.async_get", + {"owner": "he0119", "repo": "action-test", "issue_number": 80}, + mock_issues_resp, + ) + ctx.should_call_api( + "rest.issues.async_add_labels", + { + "owner": "he0119", + "repo": "action-test", + "issue_number": 80, + "labels": ["Bot"], + }, + True, + ) + ctx.should_call_api( + "rest.issues.async_list_comments", + {"owner": "he0119", "repo": "action-test", "issue_number": 80}, + mock_list_comments_resp, + ) + ctx.should_call_api( + "rest.pulls.async_create", + { + "owner": "he0119", + "repo": "action-test", + "title": "Bot: test", + "body": "resolve #80", + "base": "master", + "head": "publish/issue80", + }, + mock_pulls_resp, + ) + ctx.should_call_api( + "rest.issues.async_add_labels", + { + "owner": "he0119", + "repo": "action-test", + "issue_number": 2, + "labels": ["Bot"], + }, + True, + ) + ctx.should_call_api( + "rest.issues.async_list_comments", + {"owner": "he0119", "repo": "action-test", "issue_number": 80}, + mock_list_comments_resp, + ) + ctx.should_call_api( + "rest.issues.async_create_comment", + { + "owner": "he0119", + "repo": "action-test", + "issue_number": 80, + "body": """# 📃 商店发布检查结果\n\n> Bot: test\n\n**✅ 所有测试通过,一切准备就绪!**\n\n
    详情
  • ✅ 标签: test-#ffffff。
  • ✅ 项目 主页 返回状态码 200。
  • \n\n---\n\n💡 如需修改信息,请直接修改 issue,机器人会自动更新检查结果。\n💡 当插件加载测试失败时,请发布新版本后在当前页面下评论任意内容以触发测试。\n\n💪 Powered by [NoneFlow](https://github.com/nonebot/noneflow)\n\n""", + }, + True, + ) + + ctx.receive_event(bot, event) + + # 测试 git 命令 + mock_subprocess_run.assert_has_calls( + [ + mocker.call( + ["git", "config", "--global", "safe.directory", "*"], + check=True, + capture_output=True, + ), + mocker.call( + ["git", "switch", "-C", "publish/issue80"], + check=True, + capture_output=True, + ), + mocker.call( + ["git", "config", "--global", "user.name", "test"], + check=True, + capture_output=True, + ), + mocker.call( + [ + "git", + "config", + "--global", + "user.email", + "test@users.noreply.github.com", + ], + check=True, + capture_output=True, + ), + mocker.call(["git", "add", "-A"], check=True, capture_output=True), + mocker.call( + ["git", "commit", "-m", ":beers: publish bot test (#80)"], + check=True, + capture_output=True, + ), + mocker.call(["git", "fetch", "origin"], check=True, capture_output=True), + mocker.call( + ["git", "diff", "origin/publish/issue80", "publish/issue80"], + check=True, + capture_output=True, + ), + mocker.call( + ["git", "push", "origin", "publish/issue80", "-f"], + check=True, + capture_output=True, + ), + ] # type: ignore + ) + + # 检查文件是否正确 + check_json_data( + plugin_config.input_config.bot_path, + [ + { + "name": "test", + "desc": "desc", + "author": "test", + "homepage": "https://v2.nonebot.dev", + "tags": [{"label": "test", "color": "#ffffff"}], + "is_official": False, + } + ], + ) + + +async def test_edit_title(app: App, mocker: MockerFixture, tmp_path: Path) -> None: + """测试编辑标题 + + 插件名被修改后,标题也应该被修改 + """ + from src.plugins.publish import check + from src.plugins.publish.config import plugin_config + + mocker.patch("httpx.get", side_effect=mocked_httpx_get) + mock_subprocess_run = mocker.patch( + "subprocess.run", side_effect=lambda *args, **kwargs: mocker.MagicMock() + ) + + mock_installation = mocker.MagicMock() + mock_installation.id = 123 + mock_installation_resp = mocker.MagicMock() + mock_installation_resp.parsed_data = mock_installation + + mock_issue = mocker.MagicMock() + mock_issue.pull_request = None + mock_issue.title = "Bot: test" + mock_issue.number = 80 + mock_issue.state = "open" + mock_issue.body = """**机器人名称:**\n\ntest1\n\n**机器人功能:**\n\ndesc\n\n**机器人项目仓库/主页链接:**\n\nhttps://v2.nonebot.dev\n\n**标签:**\n\n[{"label": "test", "color": "#ffffff"}]""" + mock_issue.user.login = "test" + + mock_event = mocker.MagicMock() + mock_event.issue = mock_issue + + mock_issues_resp = mocker.MagicMock() + mock_issues_resp.parsed_data = mock_issue + + mock_comment = mocker.MagicMock() + mock_comment.body = "Bot: test" + mock_list_comments_resp = mocker.MagicMock() + mock_list_comments_resp.parsed_data = [mock_comment] + + mock_pull = mocker.MagicMock() + mock_pull.number = 2 + mock_pulls_resp = mocker.MagicMock() + mock_pulls_resp.parsed_data = mock_pull + + with open(tmp_path / "bots.json", "w") as f: + json.dump([], f) + + check_json_data(plugin_config.input_config.bot_path, []) + + async with app.test_matcher(check) as ctx: + adapter = get_adapter(Adapter) + bot = ctx.create_bot( + base=GitHubBot, + adapter=adapter, + self_id=GitHubApp(app_id="1", private_key="1"), # type: ignore + ) + bot = cast(GitHubBot, bot) + event_path = Path(__file__).parent.parent / "plugin-test" / "issue-open.json" + event = Adapter.payload_to_event("1", "issues", event_path.read_bytes()) + assert isinstance(event, IssuesOpened) + + ctx.should_call_api( + "rest.apps.async_get_repo_installation", + {"owner": "he0119", "repo": "action-test"}, + mock_installation_resp, + ) + ctx.should_call_api( + "rest.issues.async_get", + {"owner": "he0119", "repo": "action-test", "issue_number": 80}, + mock_issues_resp, + ) + ctx.should_call_api( + "rest.issues.async_add_labels", + { + "owner": "he0119", + "repo": "action-test", + "issue_number": 80, + "labels": ["Bot"], + }, + True, + ) + ctx.should_call_api( + "rest.issues.async_list_comments", + {"owner": "he0119", "repo": "action-test", "issue_number": 80}, + mock_list_comments_resp, + ) + # TODO: 抛出一个异常,然后执行修改拉取请求标题的逻辑 + ctx.should_call_api( + "rest.pulls.async_create", + { + "owner": "he0119", + "repo": "action-test", + "title": "Bot: test1", + "body": "resolve #80", + "base": "master", + "head": "publish/issue80", + }, + mock_pulls_resp, + ) + ctx.should_call_api( + "rest.issues.async_add_labels", + { + "owner": "he0119", + "repo": "action-test", + "issue_number": 2, + "labels": ["Bot"], + }, + True, + ) + # 修改标题 + ctx.should_call_api( + "rest.issues.async_update", + { + "owner": "he0119", + "repo": "action-test", + "issue_number": 80, + "title": "Bot: test1", + }, + True, + ) + + ctx.should_call_api( + "rest.issues.async_list_comments", + {"owner": "he0119", "repo": "action-test", "issue_number": 80}, + mock_list_comments_resp, + ) + ctx.should_call_api( + "rest.issues.async_create_comment", + { + "owner": "he0119", + "repo": "action-test", + "issue_number": 80, + "body": """# 📃 商店发布检查结果\n\n> Bot: test1\n\n**✅ 所有测试通过,一切准备就绪!**\n\n
    详情
  • ✅ 标签: test-#ffffff。
  • ✅ 项目 主页 返回状态码 200。
  • \n\n---\n\n💡 如需修改信息,请直接修改 issue,机器人会自动更新检查结果。\n💡 当插件加载测试失败时,请发布新版本后在当前页面下评论任意内容以触发测试。\n\n💪 Powered by [NoneFlow](https://github.com/nonebot/noneflow)\n\n""", + }, + True, + ) + + ctx.receive_event(bot, event) + + # 测试 git 命令 + mock_subprocess_run.assert_has_calls( + [ + mocker.call( + ["git", "config", "--global", "safe.directory", "*"], + check=True, + capture_output=True, + ), + mocker.call( + ["git", "switch", "-C", "publish/issue80"], + check=True, + capture_output=True, + ), + mocker.call( + ["git", "config", "--global", "user.name", "test"], + check=True, + capture_output=True, + ), + mocker.call( + [ + "git", + "config", + "--global", + "user.email", + "test@users.noreply.github.com", + ], + check=True, + capture_output=True, + ), + mocker.call(["git", "add", "-A"], check=True, capture_output=True), + mocker.call( + ["git", "commit", "-m", ":beers: publish bot test1 (#80)"], + check=True, + capture_output=True, + ), + mocker.call(["git", "fetch", "origin"], check=True, capture_output=True), + mocker.call( + ["git", "diff", "origin/publish/issue80", "publish/issue80"], + check=True, + capture_output=True, + ), + mocker.call( + ["git", "push", "origin", "publish/issue80", "-f"], + check=True, + capture_output=True, + ), + ] # type: ignore + ) + + # 检查文件是否正确 + check_json_data( + plugin_config.input_config.bot_path, + [ + { + "name": "test1", + "desc": "desc", + "author": "test", + "homepage": "https://v2.nonebot.dev", + "tags": [{"label": "test", "color": "#ffffff"}], + "is_official": False, + } + ], + ) + + +async def test_process_publish_check_not_pass( + app: App, mocker: MockerFixture, tmp_path: Path +) -> None: + """测试发布检查不通过""" + from src.plugins.publish import check + from src.plugins.publish.config import plugin_config + + mocker.patch("httpx.get", side_effect=mocked_httpx_get) + mock_subprocess_run = mocker.patch( + "subprocess.run", side_effect=lambda *args, **kwargs: mocker.MagicMock() + ) + + mock_installation = mocker.MagicMock() + mock_installation.id = 123 + mock_installation_resp = mocker.MagicMock() + mock_installation_resp.parsed_data = mock_installation + + mock_issue = mocker.MagicMock() + mock_issue.pull_request = None + mock_issue.title = "Bot: test" + mock_issue.number = 1 + mock_issue.state = "open" + mock_issue.body = """**机器人名称:**\n\ntest\n\n**机器人功能:**\n\ndesc\n\n**机器人项目仓库/主页链接:**\n\nhttps://test\n\n**标签:**\n\n[{"label": "test", "color": "#ffffff"}]""" + mock_issue.user.login = "test" + + mock_issues_resp = mocker.MagicMock() + mock_issues_resp.parsed_data = mock_issue + + mock_comment = mocker.MagicMock() + mock_comment.body = "Bot: test" + mock_list_comments_resp = mocker.MagicMock() + mock_list_comments_resp.parsed_data = [mock_comment] + + with open(tmp_path / "bots.json", "w") as f: + json.dump([], f) + + check_json_data(plugin_config.input_config.bot_path, []) + + async with app.test_matcher(check) as ctx: + adapter = get_adapter(Adapter) + bot = ctx.create_bot( + base=GitHubBot, + adapter=adapter, + self_id=GitHubApp(app_id="1", private_key="1"), # type: ignore + ) + bot = cast(GitHubBot, bot) + event_path = Path(__file__).parent.parent / "plugin-test" / "issue-open.json" + event = Adapter.payload_to_event("1", "issues", event_path.read_bytes()) + assert isinstance(event, IssuesOpened) + + ctx.should_call_api( + "rest.apps.async_get_repo_installation", + {"owner": "he0119", "repo": "action-test"}, + mock_installation_resp, + ) + ctx.should_call_api( + "rest.issues.async_get", + {"owner": "he0119", "repo": "action-test", "issue_number": 80}, + mock_issues_resp, + ) + ctx.should_call_api( + "rest.issues.async_add_labels", + { + "owner": "he0119", + "repo": "action-test", + "issue_number": 80, + "labels": ["Bot"], + }, + True, + ) + # 检查是否需要跳过插件测试 + ctx.should_call_api( + "rest.issues.async_list_comments", + {"owner": "he0119", "repo": "action-test", "issue_number": 80}, + mock_list_comments_resp, + ) + # 检查是否可以复用评论 + ctx.should_call_api( + "rest.issues.async_list_comments", + {"owner": "he0119", "repo": "action-test", "issue_number": 80}, + mock_list_comments_resp, + ) + + ctx.should_call_api( + "rest.issues.async_create_comment", + { + "owner": "he0119", + "repo": "action-test", + "issue_number": 80, + "body": """# 📃 商店发布检查结果\n\n> Bot: test\n\n**⚠️ 在发布检查过程中,我们发现以下问题:**\n
  • ⚠️ 项目 主页 返回状态码 404。
    请确保您的项目主页可访问。
  • \n
    详情
  • ✅ 标签: test-#ffffff。
  • \n\n---\n\n💡 如需修改信息,请直接修改 issue,机器人会自动更新检查结果。\n💡 当插件加载测试失败时,请发布新版本后在当前页面下评论任意内容以触发测试。\n\n💪 Powered by [NoneFlow](https://github.com/nonebot/noneflow)\n\n""", + }, + True, + ) + + ctx.receive_event(bot, event) + + # 测试 git 命令 + mock_subprocess_run.assert_called_once_with( + ["git", "config", "--global", "safe.directory", "*"], + check=True, + capture_output=True, + ) + + # 检查文件是否正确 + check_json_data(plugin_config.input_config.bot_path, []) + + +async def test_comment_at_pull_request(app: App, mocker: MockerFixture) -> None: + """测试在拉取请求下评论 + + event.issue.pull_request 不为空 + """ + from src.plugins.publish import check + + mock_httpx = mocker.patch("httpx.get", side_effect=mocked_httpx_get) + mock_subprocess_run = mocker.patch( + "subprocess.run", side_effect=lambda *args, **kwargs: mocker.MagicMock() + ) + + async with app.test_matcher(check) as ctx: + adapter = get_adapter(Adapter) + bot = ctx.create_bot( + base=GitHubBot, + adapter=adapter, + self_id=GitHubApp(app_id="1", private_key="1"), # type: ignore + ) + bot = cast(GitHubBot, bot) + event_path = Path(__file__).parent.parent / "plugin-test" / "pr-comment.json" + event = Adapter.payload_to_event("1", "issue_comment", event_path.read_bytes()) + assert isinstance(event, IssueCommentCreated) + + ctx.receive_event(bot, event) + + mock_httpx.assert_not_called() + mock_subprocess_run.assert_not_called() + + +async def test_issue_state_closed(app: App, mocker: MockerFixture) -> None: + """测试议题已关闭 + + event.issue.state = "closed" + """ + from src.plugins.publish import check + + mock_httpx = mocker.patch("httpx.get", side_effect=mocked_httpx_get) + mock_subprocess_run = mocker.patch( + "subprocess.run", side_effect=lambda *args, **kwargs: mocker.MagicMock() + ) + + mock_installation = mocker.MagicMock() + mock_installation.id = 123 + mock_installation_resp = mocker.MagicMock() + mock_installation_resp.parsed_data = mock_installation + + mock_issue = mocker.MagicMock() + mock_issue.pull_request = None + mock_issue.state = "closed" + mock_issues_resp = mocker.MagicMock() + mock_issues_resp.parsed_data = mock_issue + + async with app.test_matcher(check) as ctx: + adapter = get_adapter(Adapter) + bot = ctx.create_bot( + base=GitHubBot, + adapter=adapter, + self_id=GitHubApp(app_id="1", private_key="1"), # type: ignore + ) + bot = cast(GitHubBot, bot) + event_path = Path(__file__).parent.parent / "plugin-test" / "issue-open.json" + event = Adapter.payload_to_event("1", "issues", event_path.read_bytes()) + assert isinstance(event, IssuesOpened) + + ctx.should_call_api( + "rest.apps.async_get_repo_installation", + {"owner": "he0119", "repo": "action-test"}, + mock_installation_resp, + ) + ctx.should_call_api( + "rest.issues.async_get", + {"owner": "he0119", "repo": "action-test", "issue_number": 80}, + mock_issues_resp, + ) + + ctx.receive_event(bot, event) + + mock_httpx.assert_not_called() + mock_subprocess_run.assert_called_once_with( + ["git", "config", "--global", "safe.directory", "*"], + check=True, + capture_output=True, + ) + + +async def test_not_publish_issue(app: App, mocker: MockerFixture) -> None: + """测试议题与发布无关 + + 议题的标题不以 "Bot/Adapter/Plugin" 开头 + """ + from src.plugins.publish import check + + mock_httpx = mocker.patch("httpx.get", side_effect=mocked_httpx_get) + mock_subprocess_run = mocker.patch( + "subprocess.run", side_effect=lambda *args, **kwargs: mocker.MagicMock() + ) + + mock_installation = mocker.MagicMock() + mock_installation.id = 123 + mock_installation_resp = mocker.MagicMock() + mock_installation_resp.parsed_data = mock_installation + + mock_issue = mocker.MagicMock() + mock_issue.pull_request = None + mock_issue.state = "open" + mock_issue.title = "test" + mock_issues_resp = mocker.MagicMock() + mock_issues_resp.parsed_data = mock_issue + + async with app.test_matcher(check) as ctx: + adapter = get_adapter(Adapter) + bot = ctx.create_bot( + base=GitHubBot, + adapter=adapter, + self_id=GitHubApp(app_id="1", private_key="1"), # type: ignore + ) + bot = cast(GitHubBot, bot) + event_path = Path(__file__).parent.parent / "plugin-test" / "issue-open.json" + event = Adapter.payload_to_event("1", "issues", event_path.read_bytes()) + assert isinstance(event, IssuesOpened) + + ctx.should_call_api( + "rest.apps.async_get_repo_installation", + {"owner": "he0119", "repo": "action-test"}, + mock_installation_resp, + ) + ctx.should_call_api( + "rest.issues.async_get", + {"owner": "he0119", "repo": "action-test", "issue_number": 80}, + mock_issues_resp, + ) + + ctx.receive_event(bot, event) + + mock_httpx.assert_not_called() + mock_subprocess_run.assert_called_once_with( + ["git", "config", "--global", "safe.directory", "*"], + check=True, + capture_output=True, + ) diff --git a/tests/process/test_pull_request.py b/tests/process/test_pull_request.py index e164bb72..f94c5fbc 100644 --- a/tests/process/test_pull_request.py +++ b/tests/process/test_pull_request.py @@ -1,171 +1,216 @@ -# from pytest_mock import MockerFixture - - -# def test_process_pull_request(mocker: MockerFixture) -> None: -# from src import Bot - -# bot = Bot() -# bot.github = mocker.MagicMock() - -# mock_subprocess_run = mocker.patch("subprocess.run") -# bot.get_pull_requests_by_label = mocker.MagicMock() -# bot.get_pull_requests_by_label.return_value = [] -# bot.resolve_conflict_pull_requests = mocker.MagicMock() - -# mock_label = mocker.MagicMock() -# mock_label.name = "Bot" - -# mock_event = mocker.MagicMock() -# mock_event.pull_request.labels = [mock_label] -# mock_event.pull_request.head.ref = "publish/issue1" -# mock_event.pull_request.merged = True - -# mock_issues_resp = mocker.MagicMock() -# bot.github.rest.issues.get.return_value = mock_issues_resp -# mock_issue = mocker.MagicMock() -# mock_issue.state = "open" -# mock_issues_resp.parsed_data = mock_issue - -# bot.process_pull_request_event(mock_event) - -# bot.github.rest.issues.get.assert_called_once_with("owner", "repo", 1) - -# bot.github.rest.issues.update.assert_called_once_with( -# "owner", "repo", 1, state="closed", state_reason="completed" -# ) - -# # 测试 git 命令 -# mock_subprocess_run.assert_called_once_with( -# ["git", "push", "origin", "--delete", "publish/issue1"], -# check=True, -# capture_output=True, -# ) - -# # 处理冲突的拉取请求 -# bot.get_pull_requests_by_label.assert_called_once_with("Bot") -# bot.resolve_conflict_pull_requests.assert_called_once_with([]) - - -# def test_process_pull_request_not_merged(mocker: MockerFixture) -> None: -# from src import Bot - -# bot = Bot() -# bot.github = mocker.MagicMock() - -# mock_subprocess_run = mocker.patch("subprocess.run") -# bot.get_pull_requests_by_label = mocker.MagicMock() -# bot.get_pull_requests_by_label.return_value = [] -# bot.resolve_conflict_pull_requests = mocker.MagicMock() - -# mock_label = mocker.MagicMock() -# mock_label.name = "Bot" - -# mock_event = mocker.MagicMock() -# mock_event.pull_request.labels = [mock_label] -# mock_event.pull_request.head.ref = "publish/issue1" -# mock_event.pull_request.merged = False - -# mock_issues_resp = mocker.MagicMock() -# bot.github.rest.issues.get.return_value = mock_issues_resp -# mock_issue = mocker.MagicMock() -# mock_issue.state = "open" -# mock_issues_resp.parsed_data = mock_issue - -# bot.process_pull_request_event(mock_event) - -# bot.github.rest.issues.get.assert_called_once_with("owner", "repo", 1) - -# bot.github.rest.issues.update.assert_called_once_with( -# "owner", "repo", 1, state="closed", state_reason="not_planned" -# ) - -# # 测试 git 命令 -# mock_subprocess_run.assert_called_once_with( -# ["git", "push", "origin", "--delete", "publish/issue1"], -# check=True, -# capture_output=True, -# ) - -# # 处理冲突的拉取请求 -# bot.get_pull_requests_by_label.assert_not_called() -# bot.resolve_conflict_pull_requests.assert_not_called() - - -# def test_not_publish(mocker: MockerFixture) -> None: -# """测试与发布无关的拉取请求""" -# from src import Bot - -# bot = Bot() -# bot.github = mocker.MagicMock() - -# mock_subprocess_run = mocker.patch("subprocess.run") -# bot.get_pull_requests_by_label = mocker.MagicMock() -# bot.get_pull_requests_by_label.return_value = [] -# bot.resolve_conflict_pull_requests = mocker.MagicMock() - -# mock_label = mocker.MagicMock() -# mock_label.name = "Something" - -# mock_event = mocker.MagicMock() -# mock_event.pull_request.labels = [mock_label] -# mock_event.pull_request.head.ref = "publish/issue1" -# mock_event.pull_request.merged = True - -# mock_issues_resp = mocker.MagicMock() -# bot.github.rest.issues.get.return_value = mock_issues_resp -# mock_issue = mocker.MagicMock() -# mock_issue.state = "open" -# mock_issues_resp.parsed_data = mock_issue - -# bot.process_pull_request_event(mock_event) - -# bot.github.rest.issues.get.assert_not_called() - -# bot.github.rest.issues.update.assert_not_called() - -# # 测试 git 命令 -# mock_subprocess_run.assert_not_called() - -# # 处理冲突的拉取请求 -# bot.get_pull_requests_by_label.assert_not_called() -# bot.resolve_conflict_pull_requests.assert_not_called() - - -# def test_extract_issue_number_from_ref_failed(mocker: MockerFixture) -> None: -# """测试从分支名中提取议题号失败""" -# from src import Bot - -# bot = Bot() -# bot.github = mocker.MagicMock() - -# mock_subprocess_run = mocker.patch("subprocess.run") -# bot.get_pull_requests_by_label = mocker.MagicMock() -# bot.get_pull_requests_by_label.return_value = [] -# bot.resolve_conflict_pull_requests = mocker.MagicMock() - -# mock_label = mocker.MagicMock() -# mock_label.name = "Bot" - -# mock_event = mocker.MagicMock() -# mock_event.pull_request.labels = [mock_label] -# mock_event.pull_request.head.ref = "1" -# mock_event.pull_request.merged = True - -# mock_issues_resp = mocker.MagicMock() -# bot.github.rest.issues.get.return_value = mock_issues_resp -# mock_issue = mocker.MagicMock() -# mock_issue.state = "open" -# mock_issues_resp.parsed_data = mock_issue - -# bot.process_pull_request_event(mock_event) - -# bot.github.rest.issues.get.assert_not_called() - -# bot.github.rest.issues.update.assert_not_called() - -# # 测试 git 命令 -# mock_subprocess_run.assert_not_called() - -# # 处理冲突的拉取请求 -# bot.get_pull_requests_by_label.assert_not_called() -# bot.resolve_conflict_pull_requests.assert_not_called() +from pathlib import Path +from typing import cast + +from nonebot import get_adapter +from nonebot.adapters.github import Adapter, GitHubBot, PullRequestClosed +from nonebot.adapters.github.config import GitHubApp +from nonebug import App +from pytest_mock import MockerFixture + + +async def test_process_pull_request(app: App, mocker: MockerFixture) -> None: + from src.plugins.publish import pr_close + + event_path = Path(__file__).parent.parent / "plugin-test" / "pr-close.json" + + mock_subprocess_run = mocker.patch("subprocess.run") + + mock_issue = mocker.MagicMock() + mock_issue.state = "open" + + mock_issues_resp = mocker.MagicMock() + mock_issues_resp.parsed_data = mock_issue + + mock_installation = mocker.MagicMock() + mock_installation.id = 123 + + mock_installation_resp = mocker.MagicMock() + mock_installation_resp.parsed_data = mock_installation + + mock_pulls_resp = mocker.MagicMock() + mock_pulls_resp.parsed_data = [] + + async with app.test_matcher(pr_close) as ctx: + adapter = get_adapter(Adapter) + bot = ctx.create_bot( + base=GitHubBot, + adapter=adapter, + self_id=GitHubApp(app_id="1", private_key="1"), # type: ignore + ) + bot = cast(GitHubBot, bot) + event = Adapter.payload_to_event("1", "pull_request", event_path.read_bytes()) + assert isinstance(event, PullRequestClosed) + event.payload.pull_request.merged = True + + ctx.should_call_api( + "rest.apps.async_get_repo_installation", + {"owner": "he0119", "repo": "action-test"}, + mock_installation_resp, + ) + ctx.should_call_api( + "rest.issues.async_get", + {"owner": "he0119", "repo": "action-test", "issue_number": 76}, + mock_issues_resp, + ) + ctx.should_call_api( + "rest.issues.async_update", + { + "owner": "he0119", + "repo": "action-test", + "issue_number": 76, + "state": "closed", + "state_reason": "completed", + }, + True, + ) + ctx.should_call_api( + "rest.pulls.async_list", + {"owner": "he0119", "repo": "action-test", "state": "open"}, + mock_pulls_resp, + ) + + ctx.receive_event(bot, event) + + # 测试 git 命令 + mock_subprocess_run.assert_has_calls( + [ + mocker.call( + ["git", "config", "--global", "safe.directory", "*"], + check=True, + capture_output=True, + ), + mocker.call( + ["git", "push", "origin", "--delete", "publish/issue76"], + check=True, + capture_output=True, + ), + ], # type: ignore + any_order=True, + ) + + +async def test_process_pull_request_not_merged(app: App, mocker: MockerFixture) -> None: + from src.plugins.publish import pr_close + + event_path = Path(__file__).parent.parent / "plugin-test" / "pr-close.json" + + mock_subprocess_run = mocker.patch("subprocess.run") + + mock_issue = mocker.MagicMock() + mock_issue.state = "open" + + mock_issues_resp = mocker.MagicMock() + mock_issues_resp.parsed_data = mock_issue + + mock_installation = mocker.MagicMock() + mock_installation.id = 123 + + mock_installation_resp = mocker.MagicMock() + mock_installation_resp.parsed_data = mock_installation + + async with app.test_matcher(pr_close) as ctx: + adapter = get_adapter(Adapter) + bot = ctx.create_bot( + base=GitHubBot, + adapter=adapter, + self_id=GitHubApp(app_id="1", private_key="1"), # type: ignore + ) + bot = cast(GitHubBot, bot) + event = Adapter.payload_to_event("1", "pull_request", event_path.read_bytes()) + assert isinstance(event, PullRequestClosed) + + ctx.should_call_api( + "rest.apps.async_get_repo_installation", + {"owner": "he0119", "repo": "action-test"}, + mock_installation_resp, + ) + ctx.should_call_api( + "rest.issues.async_get", + {"owner": "he0119", "repo": "action-test", "issue_number": 76}, + mock_issues_resp, + ) + ctx.should_call_api( + "rest.issues.async_update", + { + "owner": "he0119", + "repo": "action-test", + "issue_number": 76, + "state": "closed", + "state_reason": "not_planned", + }, + True, + ) + + ctx.receive_event(bot, event) + + # 测试 git 命令 + mock_subprocess_run.assert_has_calls( + [ + mocker.call( + ["git", "config", "--global", "safe.directory", "*"], + check=True, + capture_output=True, + ), + mocker.call( + ["git", "push", "origin", "--delete", "publish/issue76"], + check=True, + capture_output=True, + ), + ], # type: ignore + any_order=True, + ) + + +async def test_not_publish(app: App, mocker: MockerFixture) -> None: + """测试与发布无关的拉取请求""" + from src.plugins.publish import pr_close + + event_path = Path(__file__).parent.parent / "plugin-test" / "pr-close.json" + + mock_subprocess_run = mocker.patch("subprocess.run") + + async with app.test_matcher(pr_close) as ctx: + adapter = get_adapter(Adapter) + bot = ctx.create_bot( + base=GitHubBot, + adapter=adapter, + self_id=GitHubApp(app_id="1", private_key="1"), # type: ignore + ) + bot = cast(GitHubBot, bot) + event = Adapter.payload_to_event("1", "pull_request", event_path.read_bytes()) + assert isinstance(event, PullRequestClosed) + event.payload.pull_request.labels = [] + + ctx.receive_event(bot, event) + + # 测试 git 命令 + mock_subprocess_run.assert_not_called() + + +async def test_extract_issue_number_from_ref_failed( + app: App, mocker: MockerFixture +) -> None: + """测试从分支名中提取议题号失败""" + from src.plugins.publish import pr_close + + event_path = Path(__file__).parent.parent / "plugin-test" / "pr-close.json" + + mock_subprocess_run = mocker.patch("subprocess.run") + + async with app.test_matcher(pr_close) as ctx: + adapter = get_adapter(Adapter) + bot = ctx.create_bot( + base=GitHubBot, + adapter=adapter, + self_id=GitHubApp(app_id="1", private_key="1"), # type: ignore + ) + bot = cast(GitHubBot, bot) + event = Adapter.payload_to_event("1", "pull_request", event_path.read_bytes()) + assert isinstance(event, PullRequestClosed) + event.payload.pull_request.head.ref = "1" + + ctx.receive_event(bot, event) + + # 测试 git 命令 + mock_subprocess_run.assert_not_called() + mock_subprocess_run.assert_not_called() diff --git a/tests/utils/test_comment_issue.py b/tests/utils/test_comment_issue.py index c05dcbdd..59973ec3 100644 --- a/tests/utils/test_comment_issue.py +++ b/tests/utils/test_comment_issue.py @@ -1,78 +1,116 @@ -# from nonebug import App -# from pytest_mock import MockerFixture - - -# async def test_comment_issue(app: App, mocker: MockerFixture): -# from nonebot.adapters.github import GitHubBot -# from nonebot.adapters.github.config import GitHubApp - -# from src.plugins.publish.models import RepoInfo -# from src.plugins.publish.utils import comment_issue - -# repo_info = RepoInfo(owner="owner", repo="repo") -# github = mocker.MagicMock() - -# async with app.test_api() as ctx: -# bot = ctx.create_bot(base=GitHubBot, app=GitHubApp(app_id="1", private_key="1")) - -# mocker.patch.object(bot, "_github", github) - -# mock_list_comments_resp = mocker.MagicMock() -# github.rest.issues.list_comments.return_value = mock_list_comments_resp - -# mock_comment = mocker.MagicMock() -# mock_comment.body = "Bot: test" -# mock_list_comments_resp.parsed_data = [mock_comment] - -# await comment_issue(bot, repo_info, 1, "test") - -# github.rest.issues.update_comment.assert_not_called() -# github.rest.issues.create_comment.assert_called_once_with( -# "owner", -# "repo", -# 1, -# body="# 📃 商店发布检查结果\n\ntest\n\n---\n\n💡 如需修改信息,请直接修改 issue,机器人会自动更新检查结果。\n💡 当插件加载测试失败时,请发布新版本后在当前页面下评论任意内容以触发测试。\n\n💪 Powered by [NoneBot2 Publish Bot](https://github.com/nonebot/noneflow)\n\n", -# ) - - -# async def test_comment_issue_reuse(mocker: MockerFixture): -# bot.github = mocker.MagicMock() - -# mock_list_comments_resp = mocker.MagicMock() -# bot.github.rest.issues.list_comments.return_value = mock_list_comments_resp - -# mock_comment = mocker.MagicMock() -# mock_comment.body = "任意的东西\n\n" -# mock_comment.id = 123 -# mock_list_comments_resp.parsed_data = [mock_comment] - -# bot.comment_issue(1, "test") - -# bot.github.rest.issues.create_comment.assert_not_called() -# bot.github.rest.issues.update_comment.assert_called_once_with( -# "owner", -# "repo", -# 123, -# body="# 📃 商店发布检查结果\n\ntest\n\n---\n\n💡 如需修改信息,请直接修改 issue,机器人会自动更新检查结果。\n💡 当插件加载测试失败时,请发布新版本后在当前页面下评论任意内容以触发测试。\n\n♻️ 评论已更新至最新检查结果\n\n💪 Powered by [NoneBot2 Publish Bot](https://github.com/nonebot/noneflow)\n\n", -# ) - - -# async def test_comment_issue_reuse_same(mocker: MockerFixture): -# """测试评论内容相同时不会更新评论""" -# from src import Bot - -# bot = Bot() -# bot.github = mocker.MagicMock() - -# mock_list_comments_resp = mocker.MagicMock() -# bot.github.rest.issues.list_comments.return_value = mock_list_comments_resp - -# mock_comment = mocker.MagicMock() -# mock_comment.body = "# 📃 商店发布检查结果\n\ntest\n\n---\n\n💡 如需修改信息,请直接修改 issue,机器人会自动更新检查结果。\n💡 当插件加载测试失败时,请发布新版本后在当前页面下评论任意内容以触发测试。\n\n♻️ 评论已更新至最新检查结果\n\n💪 Powered by [NoneBot2 Publish Bot](https://github.com/nonebot/noneflow)\n\n" -# mock_comment.id = 123 -# mock_list_comments_resp.parsed_data = [mock_comment] - -# bot.comment_issue(1, "test") - -# bot.github.rest.issues.create_comment.assert_not_called() -# bot.github.rest.issues.update_comment.assert_not_called() +from typing import cast + +from nonebot import get_adapter +from nonebot.adapters.github import Adapter, GitHubBot +from nonebot.adapters.github.config import GitHubApp +from nonebug import App +from pytest_mock import MockerFixture + + +async def test_comment_issue(app: App, mocker: MockerFixture): + from src.plugins.publish.models import RepoInfo + from src.plugins.publish.utils import comment_issue + + mock_comment = mocker.MagicMock() + mock_comment.body = "Bot: test" + + mock_list_comments_resp = mocker.MagicMock() + mock_list_comments_resp.parsed_data = [mock_comment] + + async with app.test_api() as ctx: + adapter = get_adapter(Adapter) + bot = ctx.create_bot( + base=GitHubBot, + adapter=adapter, + self_id=GitHubApp(app_id="1", private_key="1"), # type: ignore + ) + bot = cast(GitHubBot, bot) + + ctx.should_call_api( + "rest.issues.async_list_comments", + {"owner": "owner", "repo": "repo", "issue_number": 1}, + mock_list_comments_resp, + ) + + ctx.should_call_api( + "rest.issues.async_create_comment", + { + "owner": "owner", + "repo": "repo", + "issue_number": 1, + "body": "# 📃 商店发布检查结果\n\ntest\n\n---\n\n💡 如需修改信息,请直接修改 issue,机器人会自动更新检查结果。\n💡 当插件加载测试失败时,请发布新版本后在当前页面下评论任意内容以触发测试。\n\n💪 Powered by [NoneFlow](https://github.com/nonebot/noneflow)\n\n", + }, + True, + ) + + await comment_issue(bot, RepoInfo(owner="owner", repo="repo"), 1, "test") + + +async def test_comment_issue_reuse(app: App, mocker: MockerFixture): + from src.plugins.publish.models import RepoInfo + from src.plugins.publish.utils import comment_issue + + mock_comment = mocker.MagicMock() + mock_comment.body = "任意的东西\n\n" + mock_comment.id = 123 + + mock_list_comments_resp = mocker.MagicMock() + mock_list_comments_resp.parsed_data = [mock_comment] + + async with app.test_api() as ctx: + adapter = get_adapter(Adapter) + bot = ctx.create_bot( + base=GitHubBot, + adapter=adapter, + self_id=GitHubApp(app_id="1", private_key="1"), # type: ignore + ) + bot = cast(GitHubBot, bot) + + ctx.should_call_api( + "rest.issues.async_list_comments", + {"owner": "owner", "repo": "repo", "issue_number": 1}, + mock_list_comments_resp, + ) + + ctx.should_call_api( + "rest.issues.async_update_comment", + { + "owner": "owner", + "repo": "repo", + "comment_id": 123, + "body": "# 📃 商店发布检查结果\n\ntest\n\n---\n\n💡 如需修改信息,请直接修改 issue,机器人会自动更新检查结果。\n💡 当插件加载测试失败时,请发布新版本后在当前页面下评论任意内容以触发测试。\n\n♻️ 评论已更新至最新检查结果\n\n💪 Powered by [NoneFlow](https://github.com/nonebot/noneflow)\n\n", + }, + True, + ) + + await comment_issue(bot, RepoInfo(owner="owner", repo="repo"), 1, "test") + + +async def test_comment_issue_reuse_same(app: App, mocker: MockerFixture): + """测试评论内容相同时不会更新评论""" + from src.plugins.publish.models import RepoInfo + from src.plugins.publish.utils import comment_issue + + mock_comment = mocker.MagicMock() + mock_comment.body = "# 📃 商店发布检查结果\n\ntest\n\n---\n\n💡 如需修改信息,请直接修改 issue,机器人会自动更新检查结果。\n💡 当插件加载测试失败时,请发布新版本后在当前页面下评论任意内容以触发测试。\n\n♻️ 评论已更新至最新检查结果\n\n💪 Powered by [NoneFlow](https://github.com/nonebot/noneflow)\n\n" + mock_comment.id = 123 + + mock_list_comments_resp = mocker.MagicMock() + mock_list_comments_resp.parsed_data = [mock_comment] + + async with app.test_api() as ctx: + adapter = get_adapter(Adapter) + bot = ctx.create_bot( + base=GitHubBot, + adapter=adapter, + self_id=GitHubApp(app_id="1", private_key="1"), # type: ignore + ) + bot = cast(GitHubBot, bot) + + ctx.should_call_api( + "rest.issues.async_list_comments", + {"owner": "owner", "repo": "repo", "issue_number": 1}, + mock_list_comments_resp, + ) + + await comment_issue(bot, RepoInfo(owner="owner", repo="repo"), 1, "test") diff --git a/tests/utils/test_get_pull_requests_by_label.py b/tests/utils/test_get_pull_requests_by_label.py index 77d6edd4..628e433a 100644 --- a/tests/utils/test_get_pull_requests_by_label.py +++ b/tests/utils/test_get_pull_requests_by_label.py @@ -1,47 +1,79 @@ -# from pytest_mock import MockerFixture - - -# def test_get_pull_requests_by_label(mocker: MockerFixture) -> None: -# """测试获取指定标签的拉取请求""" -# from src import Bot - -# bot = Bot() -# bot.github = mocker.MagicMock() - -# mock_label = mocker.MagicMock() -# mock_label.name = "Bot" - -# mock_pull = mocker.MagicMock() -# mock_pull.labels = [mock_label] - -# mock_pulls_resp = mocker.MagicMock() -# mock_pulls_resp.parsed_data = [mock_pull] -# bot.github.rest.pulls.list.return_value = mock_pulls_resp - -# pulls = bot.get_pull_requests_by_label("Bot") - -# bot.github.rest.pulls.list.assert_called_with("owner", "repo", state="open") -# assert pulls[0] == mock_pull - - -# def test_get_pull_requests_by_label_not_match(mocker: MockerFixture) -> None: -# """测试获取指定标签的拉取请求,但是没有匹配的""" -# from src import Bot - -# bot = Bot() -# bot.github = mocker.MagicMock() - -# mock_label = mocker.MagicMock() -# mock_label.name = "Some" - -# mock_pull = mocker.MagicMock() -# mock_pull.labels = [mock_label] - -# mock_pulls_resp = mocker.MagicMock() -# mock_pulls_resp.parsed_data = [mock_pull] -# bot.github.rest.pulls.list.return_value = mock_pulls_resp - -# pulls = bot.get_pull_requests_by_label("Bot") - -# bot.github.rest.pulls.list.assert_called_with("owner", "repo", state="open") -# assert pulls == [] +from typing import cast + +from nonebot import get_adapter +from nonebot.adapters.github import Adapter, GitHubBot +from nonebot.adapters.github.config import GitHubApp +from nonebug import App +from pytest_mock import MockerFixture + + +async def test_get_pull_requests_by_label(app: App, mocker: MockerFixture) -> None: + """测试获取指定标签的拉取请求""" + from src.plugins.publish.depends import get_pull_requests_by_label + from src.plugins.publish.models import PublishType, RepoInfo + + mock_label = mocker.MagicMock() + mock_label.name = "Bot" + + mock_pull = mocker.MagicMock() + mock_pull.labels = [mock_label] + + mock_pulls_resp = mocker.MagicMock() + mock_pulls_resp.parsed_data = [mock_pull] + + async with app.test_api() as ctx: + adapter = get_adapter(Adapter) + bot = ctx.create_bot( + base=GitHubBot, + adapter=adapter, + self_id=GitHubApp(app_id="1", private_key="1"), # type: ignore + ) + bot = cast(GitHubBot, bot) + + ctx.should_call_api( + "rest.pulls.async_list", + {"owner": "owner", "repo": "repo", "state": "open"}, + mock_pulls_resp, + ) + + pulls = await get_pull_requests_by_label( + bot, RepoInfo(owner="owner", repo="repo"), PublishType.BOT + ) + assert pulls[0] == mock_pull + + +async def test_get_pull_requests_by_label_not_match( + app: App, mocker: MockerFixture +) -> None: + """测试获取指定标签的拉取请求,但是没有匹配的""" + from src.plugins.publish.depends import get_pull_requests_by_label + from src.plugins.publish.models import PublishType, RepoInfo + + mock_label = mocker.MagicMock() + mock_label.name = "Some" + + mock_pull = mocker.MagicMock() + mock_pull.labels = [mock_label] + + mock_pulls_resp = mocker.MagicMock() + mock_pulls_resp.parsed_data = [mock_pull] + + async with app.test_api() as ctx: + adapter = get_adapter(Adapter) + bot = ctx.create_bot( + base=GitHubBot, + adapter=adapter, + self_id=GitHubApp(app_id="1", private_key="1"), # type: ignore + ) + bot = cast(GitHubBot, bot) + + ctx.should_call_api( + "rest.pulls.async_list", + {"owner": "owner", "repo": "repo", "state": "open"}, + mock_pulls_resp, + ) + + pulls = await get_pull_requests_by_label( + bot, RepoInfo(owner="owner", repo="repo"), PublishType.BOT + ) + assert pulls == [] diff --git a/tests/utils/test_resolve_conflict_pull_requests.py b/tests/utils/test_resolve_conflict_pull_requests.py index 8e7d3dba..f6bd16ce 100644 --- a/tests/utils/test_resolve_conflict_pull_requests.py +++ b/tests/utils/test_resolve_conflict_pull_requests.py @@ -1,122 +1,140 @@ -# import json -# from pathlib import Path -# from typing import Any - -# from pytest_mock import MockerFixture - - -# def mocked_httpx_get(url: str): -# class MockResponse: -# def __init__(self, status_code: int): -# self.status_code = status_code - -# if url == "https://v2.nonebot.dev": -# return MockResponse(200) - -# return MockResponse(404) - - -# def check_json_data(file: Path, data: Any) -> None: -# with open(file) as f: -# assert json.load(f) == data - - -# def test_resolve_conflict_pull_requests(mocker: MockerFixture, tmp_path: Path) -> None: -# import src.globals as g -# from src import Bot - -# bot = Bot() -# bot.github = mocker.MagicMock() - -# mocker.patch("httpx.get", side_effect=mocked_httpx_get) -# mock_subprocess_run = mocker.patch("subprocess.run") -# mock_result = mocker.MagicMock() -# mock_subprocess_run.side_effect = lambda *args, **kwargs: mock_result - -# mock_label = mocker.MagicMock() -# mock_label.name = "Bot" - -# mock_issue = mocker.MagicMock() -# mock_issue.pull_request = None -# mock_issue.title = "Bot: test" -# mock_issue.number = 1 -# mock_issue.state = "open" -# mock_issue.body = """**机器人名称:**\n\ntest\n\n**机器人功能:**\n\ndesc\n\n**机器人项目仓库/主页链接:**\n\nhttps://v2.nonebot.dev\n\n**标签:**\n\n[{"label": "test", "color": "#ffffff"}]""" -# mock_issue.user.login = "test" -# mock_issue.labels = [mock_label] - -# mock_issues_resp = mocker.MagicMock() -# bot.github.rest.issues.get.return_value = mock_issues_resp -# mock_issues_resp.parsed_data = mock_issue - -# mock_pull = mocker.MagicMock() -# mock_pull.head.ref = "publish/issue1" - -# with open(tmp_path / "bots.json", "w") as f: -# json.dump([], f) - -# check_json_data(g.settings.input_config.bot_path, []) - -# bot.resolve_conflict_pull_requests([mock_pull]) - -# bot.github.rest.issues.get.assert_called_with("owner", "repo", 1) - -# # 测试 git 命令 -# mock_subprocess_run.assert_has_calls( -# [ -# mocker.call(["git", "checkout", "master"], check=True, capture_output=True), -# mocker.call( -# ["git", "switch", "-C", "publish/issue1"], -# check=True, -# capture_output=True, -# ), -# mocker.call( -# ["git", "config", "--global", "user.name", "test"], -# check=True, -# capture_output=True, -# ), -# mocker.call( -# [ -# "git", -# "config", -# "--global", -# "user.email", -# "test@users.noreply.github.com", -# ], -# check=True, -# capture_output=True, -# ), -# mocker.call(["git", "add", "-A"], check=True, capture_output=True), -# mocker.call( -# ["git", "commit", "-m", ":beers: publish bot test (#1)"], -# check=True, -# capture_output=True, -# ), -# mocker.call(["git", "fetch", "origin"], check=True, capture_output=True), -# mocker.call( -# ["git", "diff", "origin/publish/issue1", "publish/issue1"], -# check=True, -# capture_output=True, -# ), -# mocker.call( -# ["git", "push", "origin", "publish/issue1", "-f"], -# check=True, -# capture_output=True, -# ), -# ] # type: ignore -# ) - -# # 检查文件是否正确 -# check_json_data( -# g.settings.input_config.bot_path, -# [ -# { -# "name": "test", -# "desc": "desc", -# "author": "test", -# "homepage": "https://v2.nonebot.dev", -# "tags": [{"label": "test", "color": "#ffffff"}], -# "is_official": False, -# } -# ], -# ) +import json +from pathlib import Path +from typing import Any, cast + +from nonebot import get_adapter +from nonebot.adapters.github import Adapter, GitHubBot +from nonebot.adapters.github.config import GitHubApp +from nonebug import App +from pytest_mock import MockerFixture + + +def mocked_httpx_get(url: str): + class MockResponse: + def __init__(self, status_code: int): + self.status_code = status_code + + if url == "https://v2.nonebot.dev": + return MockResponse(200) + + return MockResponse(404) + + +def check_json_data(file: Path, data: Any) -> None: + with open(file) as f: + assert json.load(f) == data + + +async def test_resolve_conflict_pull_requests( + app: App, mocker: MockerFixture, tmp_path: Path +) -> None: + from src.plugins.publish.config import plugin_config + from src.plugins.publish.models import RepoInfo + from src.plugins.publish.utils import resolve_conflict_pull_requests + + mocker.patch("httpx.get", side_effect=mocked_httpx_get) + mock_subprocess_run = mocker.patch("subprocess.run") + mock_result = mocker.MagicMock() + mock_subprocess_run.side_effect = lambda *args, **kwargs: mock_result + + mock_label = mocker.MagicMock() + mock_label.name = "Bot" + + mock_issue = mocker.MagicMock() + mock_issue.pull_request = None + mock_issue.title = "Bot: test" + mock_issue.number = 1 + mock_issue.state = "open" + mock_issue.body = """**机器人名称:**\n\ntest\n\n**机器人功能:**\n\ndesc\n\n**机器人项目仓库/主页链接:**\n\nhttps://v2.nonebot.dev\n\n**标签:**\n\n[{"label": "test", "color": "#ffffff"}]""" + mock_issue.user.login = "test" + mock_issue.labels = [mock_label] + + mock_issues_resp = mocker.MagicMock() + mock_issues_resp.parsed_data = mock_issue + + mock_pull = mocker.MagicMock() + mock_pull.head.ref = "publish/issue1" + + with open(tmp_path / "bots.json", "w") as f: + json.dump([], f) + + check_json_data(plugin_config.input_config.bot_path, []) + + async with app.test_api() as ctx: + adapter = get_adapter(Adapter) + bot = ctx.create_bot( + base=GitHubBot, + adapter=adapter, + self_id=GitHubApp(app_id="1", private_key="1"), # type: ignore + ) + bot = cast(GitHubBot, bot) + + ctx.should_call_api( + "rest.issues.async_get", + {"owner": "owner", "repo": "repo", "issue_number": 1}, + mock_issues_resp, + ) + + await resolve_conflict_pull_requests( + bot, RepoInfo(owner="owner", repo="repo"), [mock_pull] + ) + + # 测试 git 命令 + mock_subprocess_run.assert_has_calls( + [ + mocker.call(["git", "checkout", "master"], check=True, capture_output=True), + mocker.call( + ["git", "switch", "-C", "publish/issue1"], + check=True, + capture_output=True, + ), + mocker.call( + ["git", "config", "--global", "user.name", "test"], + check=True, + capture_output=True, + ), + mocker.call( + [ + "git", + "config", + "--global", + "user.email", + "test@users.noreply.github.com", + ], + check=True, + capture_output=True, + ), + mocker.call(["git", "add", "-A"], check=True, capture_output=True), + mocker.call( + ["git", "commit", "-m", ":beers: publish bot test (#1)"], + check=True, + capture_output=True, + ), + mocker.call(["git", "fetch", "origin"], check=True, capture_output=True), + mocker.call( + ["git", "diff", "origin/publish/issue1", "publish/issue1"], + check=True, + capture_output=True, + ), + mocker.call( + ["git", "push", "origin", "publish/issue1", "-f"], + check=True, + capture_output=True, + ), + ] # type: ignore + ) + + # 检查文件是否正确 + check_json_data( + plugin_config.input_config.bot_path, + [ + { + "name": "test", + "desc": "desc", + "author": "test", + "homepage": "https://v2.nonebot.dev", + "tags": [{"label": "test", "color": "#ffffff"}], + "is_official": False, + } + ], + )