diff --git a/CHANGELOG.md b/CHANGELOG.md index c7045a3c..46c7a364 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/lang/zh-CN/ ## [Unreleased] +### Added + +- 在 Noneflow 工作流评论处增设 Noneflow 运行历史 + ## [4.3.1] - 2025-05-10 ### Fixed diff --git a/src/plugins/github/handlers/github.py b/src/plugins/github/handlers/github.py index f0a97be5..bca6ed7c 100644 --- a/src/plugins/github/handlers/github.py +++ b/src/plugins/github/handlers/github.py @@ -3,6 +3,7 @@ from githubkit.rest import PullRequestSimple from githubkit.typing import Missing from githubkit.utils import UNSET +from githubkit.versions.latest.models import IssueComment from nonebot import logger from nonebot.adapters.github import Bot from pydantic import ConfigDict @@ -69,22 +70,34 @@ async def update_comment(self, comment_id: int, comment: str): **self.repo_info.model_dump(), comment_id=comment_id, body=comment ) - async def comment_issue(self, comment: str, issue_number: int): - """发布评论,若之前已评论过,则会进行复用""" - logger.info("开始发布评论") - - # 重复利用评论 - # 如果发现之前评论过,直接修改之前的评论 - comments = await self.list_comments(issue_number=issue_number) - reusable_comment = next( + async def get_self_comment(self, issue_number: int): + """获取自己的评论""" + comments: list[IssueComment] = await self.list_comments( + issue_number=issue_number + ) + return next( filter(lambda x: NONEFLOW_MARKER in (x.body if x.body else ""), comments), None, ) - if reusable_comment: - logger.info(f"发现已有评论 {reusable_comment.id},正在修改") - if reusable_comment.body != comment: - await self.update_comment(reusable_comment.id, comment) + async def resuable_comment_issue( + self, comment: str, issue_number: int + ) -> IssueComment | None: + """复用评论,若之前已评论过,则会进行复用""" + logger.info("查询已有评论") + self_comment = await self.get_self_comment(issue_number=issue_number) + await self.comment_issue(comment, issue_number, self_comment) + + async def comment_issue( + self, comment: str, issue_number: int, self_comment: IssueComment | None = None + ): + """发布评论""" + logger.info("开始发布评论") + + if self_comment: + logger.info(f"发现已有评论 {self_comment.id},正在修改") + if self_comment.body != comment: + await self.update_comment(self_comment.id, comment) logger.info("评论修改完成") else: logger.info("评论内容无变化,跳过修改") diff --git a/src/plugins/github/handlers/issue.py b/src/plugins/github/handlers/issue.py index ef1dd03d..aa224b2a 100644 --- a/src/plugins/github/handlers/issue.py +++ b/src/plugins/github/handlers/issue.py @@ -105,12 +105,31 @@ async def list_comments(self, issue_number: int | None = None): return await super().list_comments(issue_number) - async def comment_issue(self, comment: str, issue_number: int | None = None): + async def get_self_comment(self, issue_number: int | None = None): + """获取自己的评论""" + if issue_number is None: + issue_number = self.issue_number + + return await super().get_self_comment(issue_number) + + async def comment_issue( + self, comment: str, issue_number: int | None = None, self_comment=None + ): + """发布评论""" + if issue_number is None: + issue_number = self.issue_number + + await super().comment_issue(comment, issue_number, self_comment) + + async def resuable_comment_issue( + self, comment: str, issue_number: int | None = None + ): """发布评论,若之前已评论过,则会进行复用""" if issue_number is None: issue_number = self.issue_number - await super().comment_issue(comment, issue_number) + self_comment = await self.get_self_comment(issue_number) + await self.comment_issue(comment, issue_number, self_comment) def commit_and_push( self, diff --git a/src/plugins/github/plugins/config/__init__.py b/src/plugins/github/plugins/config/__init__.py index f71c8297..081d74a6 100644 --- a/src/plugins/github/plugins/config/__init__.py +++ b/src/plugins/github/plugins/config/__init__.py @@ -81,7 +81,7 @@ async def handle_remove_check( comment = await render_comment(result, True) # 对议题评论 - await handler.comment_issue(comment) + await handler.resuable_comment_issue(comment) branch_name = f"{BRANCH_NAME_PREFIX}{handler.issue_number}" diff --git a/src/plugins/github/plugins/publish/__init__.py b/src/plugins/github/plugins/publish/__init__.py index 7702e7a1..f56300ae 100644 --- a/src/plugins/github/plugins/publish/__init__.py +++ b/src/plugins/github/plugins/publish/__init__.py @@ -1,4 +1,4 @@ -from typing import Literal +from typing import TYPE_CHECKING, Literal from nonebot import logger, on_type from nonebot.adapters.github import ( @@ -39,6 +39,7 @@ ensure_issue_content, ensure_issue_plugin_test_button, ensure_issue_plugin_test_button_in_progress, + get_history_workflow_from_comment, process_pull_request, trigger_registry_update, ) @@ -48,6 +49,9 @@ validate_plugin_info_from_issue, ) +if TYPE_CHECKING: + from datetime import datetime + async def check_rule( event: IssuesOpened | IssuesReopened | IssuesEdited | IssueCommentCreated, @@ -153,11 +157,17 @@ async def handle_pull_request_and_update_issue( installation_id: int = Depends(get_installation_id), ) -> None: async with bot.as_installation(installation_id): + self_comment = await handler.get_self_comment(handler.issue_number) + + history: list[tuple[bool, str, datetime]] = [] + if self_comment and self_comment.body: + history = await get_history_workflow_from_comment(self_comment.body) + # 渲染评论信息 - comment = await render_comment(validation, True) + comment = await render_comment(validation, True, history) # 对议题评论 - await handler.comment_issue(comment) + await handler.comment_issue(comment, self_comment=self_comment) # 设置拉取请求与议题的标题 # 限制标题长度,过长的标题不好看 diff --git a/src/plugins/github/plugins/publish/constants.py b/src/plugins/github/plugins/publish/constants.py index 7e00a5f6..c8b57bb1 100644 --- a/src/plugins/github/plugins/publish/constants.py +++ b/src/plugins/github/plugins/publish/constants.py @@ -46,6 +46,13 @@ ADAPTER_MODULE_NAME_PATTERN = re.compile(ISSUE_PATTERN.format("适配器 import 包名")) ADAPTER_HOMEPAGE_PATTERN = re.compile(ISSUE_PATTERN.format("适配器项目仓库/主页链接")) + +WORKFLOW_HISTORY_PATTERN = re.compile( + r'
  • (⚠️|✅)\s*([^<]+?CST)
  • ' +) + +WORKFLOW_HISTORY_TEMPLATE = """
  • {status} {time}
  • """ + # 评论卡片模板 COMMENT_CARD_TEMPLATE = """[![{name}](https://img.shields.io/badge/{head}-{content}-{color}?style=for-the-badge)]({url})""" diff --git a/src/plugins/github/plugins/publish/render.py b/src/plugins/github/plugins/publish/render.py index c16b5310..58fb8638 100644 --- a/src/plugins/github/plugins/publish/render.py +++ b/src/plugins/github/plugins/publish/render.py @@ -49,6 +49,12 @@ def format_time(time: str) -> str: return dt.strftime("%Y-%m-%d %H:%M:%S %Z") +def format_datetime(dt: datetime) -> str: + """格式化 datetime 对象为字符串""" + dt = dt.astimezone(tz=TIME_ZONE) + return dt.strftime("%Y-%m-%d %H:%M:%S %Z") + + env = jinja2.Environment( loader=jinja2.FileSystemLoader(Path(__file__).parent / "templates"), enable_async=True, @@ -63,14 +69,21 @@ def format_time(time: str) -> str: env.filters["loc_to_name"] = loc_to_name env.filters["key_to_name"] = key_to_name env.filters["format_time"] = format_time +env.filters["format_datetime"] = format_datetime -async def render_comment(result: ValidationDict, reuse: bool = False) -> str: +async def render_comment( + result: ValidationDict, + reuse: bool = False, + history: list[tuple[bool, str, datetime]] | None = None, +) -> str: """将验证结果转换为评论内容""" title = f"{result.type}: {result.name}" valid_data = result.valid_data.copy() + history = history or [] + if result.type == PublishType.PLUGIN: # https://github.com/he0119/action-test/actions/runs/4469672520 # 仅在测试通过或跳过测试时显示 @@ -119,6 +132,18 @@ async def render_comment(result: ValidationDict, reuse: bool = False) -> str: url=action_url, ) ) + history.append( + ( + result.valid, + action_url, + datetime.now(tz=TIME_ZONE), + ) + ) + + # 测试历史应该时间倒序排列 + history.sort(key=lambda x: x[2], reverse=True) + # 限制最多显示 10 条历史 + history = history[:10] template = env.get_template("comment.md.jinja") return await template.render_async( @@ -127,6 +152,7 @@ async def render_comment(result: ValidationDict, reuse: bool = False) -> str: title=title, valid=result.valid, data=data, + history=history, errors=result.errors, skip_test=result.skip_test, ) diff --git a/src/plugins/github/plugins/publish/templates/comment.md.jinja b/src/plugins/github/plugins/publish/templates/comment.md.jinja index a020e943..acb68b0e 100644 --- a/src/plugins/github/plugins/publish/templates/comment.md.jinja +++ b/src/plugins/github/plugins/publish/templates/comment.md.jinja @@ -31,6 +31,17 @@ {% endif %} +{%- if history %} +
    +历史测试 +
    
    +{%- for status, action_url, time in history%}
    +
  • {{"✅" if status else "⚠️"}} {{time|format_datetime}}
  • +{%- endfor %} +
    +
    +{% endif %} + --- 💡 如需修改信息,请直接修改 issue,机器人会自动更新检查结果。 diff --git a/src/plugins/github/plugins/publish/utils.py b/src/plugins/github/plugins/publish/utils.py index 9166fdce..37a8adaf 100644 --- a/src/plugins/github/plugins/publish/utils.py +++ b/src/plugins/github/plugins/publish/utils.py @@ -1,4 +1,5 @@ import re +from datetime import datetime from githubkit.exception import RequestFailed from nonebot import logger @@ -13,6 +14,7 @@ from src.plugins.github.handlers import GithubHandler, IssueHandler from src.plugins.github.typing import PullRequestList from src.plugins.github.utils import commit_message as _commit_message +from src.providers.constants import TIME_ZONE from src.providers.models import RegistryUpdatePayload, to_store from src.providers.utils import dump_json5, load_json_from_file from src.providers.validation import PublishType, ValidationDict @@ -25,6 +27,7 @@ PLUGIN_TEST_BUTTON_STRING, PLUGIN_TEST_PATTERN, PLUGIN_TEST_STRING, + WORKFLOW_HISTORY_PATTERN, ) from .validation import ( validate_adapter_info_from_issue, @@ -277,3 +280,17 @@ async def trigger_registry_update(handler: IssueHandler, publish_type: PublishTy repo=plugin_config.input_config.registry_repository, ) logger.info("已触发商店列表更新") + + +async def get_history_workflow_from_comment( + comment: str, +) -> list[tuple[bool, str, datetime]]: + """获取历史工作流""" + return [ + ( + status == "✅", + action_url, + datetime.strptime(time, "%Y-%m-%d %H:%M:%S CST").replace(tzinfo=TIME_ZONE), + ) + for status, action_url, time in WORKFLOW_HISTORY_PATTERN.findall(comment) + ] diff --git a/src/plugins/github/plugins/remove/__init__.py b/src/plugins/github/plugins/remove/__init__.py index d41a162c..5de03881 100644 --- a/src/plugins/github/plugins/remove/__init__.py +++ b/src/plugins/github/plugins/remove/__init__.py @@ -86,7 +86,7 @@ async def handle_remove_check( result = await validate_author_info(handler.issue, publish_type) except PydanticCustomError as err: logger.error(f"信息验证失败: {err}") - await handler.comment_issue(await render_error(err)) + await handler.resuable_comment_issue(await render_error(err)) await remove_check_matcher.finish() title = f"{result.publish_type}: Remove {result.name or 'Unknown'}"[ @@ -113,7 +113,7 @@ async def handle_remove_check( result, f"{plugin_config.input_config.store_repository}#{pull_number}", ) - await handler.comment_issue(comment) + await handler.resuable_comment_issue(comment) async def review_submitted_rule( diff --git a/tests/conftest.py b/tests/conftest.py index f08b68ac..cd187b06 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -230,7 +230,7 @@ def datetime_modules_loc() -> list[str]: return find_datetime_loc(PROJECT_SOURCE_PATH) -@pytest.fixture +@pytest.fixture(autouse=True) def mock_datetime(mocker: MockerFixture, datetime_modules_loc: list[str]): """ 将所有模块中的 datetime.now() 方法返回值固定 diff --git a/tests/plugins/github/config/process/test_config_check.py b/tests/plugins/github/config/process/test_config_check.py index 0bd437a7..f8f37d9c 100644 --- a/tests/plugins/github/config/process/test_config_check.py +++ b/tests/plugins/github/config/process/test_config_check.py @@ -28,7 +28,6 @@ async def test_process_config_check( tmp_path: Path, mock_installation, mock_results: dict[str, Path], - mock_datetime, ) -> None: """测试发布检查不通过""" from src.providers.docker_test import Metadata @@ -146,6 +145,10 @@ async def test_process_config_check( 详情
  • ✅ 项目 主页 返回状态码 200。
  • ✅ 项目 nonebot-plugin-treehelp 已发布至 PyPI。
  • ✅ 标签: test-#ffffff。
  • ✅ 插件类型: application。
  • ✅ 插件支持的适配器: nonebot.adapters.onebot.v11。
  • ✅ 插件 加载测试 通过。
  • ✅ 版本号: 1.0.0。
  • ✅ 发布时间:2024-07-13 12:41:40 CST。
  • +
    +历史测试 +
  • 2023-08-23 09:22:14 CST
  • +
    --- diff --git a/tests/plugins/github/config/utils/test_config_update_file.py b/tests/plugins/github/config/utils/test_config_update_file.py index ce4b56bc..70deb84c 100644 --- a/tests/plugins/github/config/utils/test_config_update_file.py +++ b/tests/plugins/github/config/utils/test_config_update_file.py @@ -9,11 +9,7 @@ async def test_update_file( - app: App, - mocker: MockerFixture, - tmp_path: Path, - mock_results: dict[str, Path], - mock_datetime, + app: App, mocker: MockerFixture, tmp_path: Path, mock_results: dict[str, Path] ) -> None: from src.plugins.github.plugins.config.utils import update_file from src.providers.validation.models import ( diff --git a/tests/plugins/github/handlers/test_github_handler.py b/tests/plugins/github/handlers/test_github_handler.py index 03bfddf4..cea0010e 100644 --- a/tests/plugins/github/handlers/test_github_handler.py +++ b/tests/plugins/github/handlers/test_github_handler.py @@ -239,7 +239,7 @@ async def test_comment_issue(app: App, mocker: MockerFixture) -> None: } ), ) - await github_handler.comment_issue("new comment", 76) + await github_handler.resuable_comment_issue("new comment", 76) async def test_comment_issue_reuse(app: App, mocker: MockerFixture) -> None: @@ -281,7 +281,7 @@ async def test_comment_issue_reuse(app: App, mocker: MockerFixture) -> None: } ), ) - await github_handler.comment_issue("new comment", 76) + await github_handler.resuable_comment_issue("new comment", 76) async def test_comment_issue_reuse_no_change(app: App, mocker: MockerFixture) -> None: @@ -316,7 +316,7 @@ async def test_comment_issue_reuse_no_change(app: App, mocker: MockerFixture) -> } ), ) - await github_handler.comment_issue("comment\n", 76) + await github_handler.resuable_comment_issue("comment\n", 76) async def test_get_pull_requests_by_label(app: App, mocker: MockerFixture) -> None: @@ -973,3 +973,177 @@ async def test_to_issue_handler(app: App, mocker: MockerFixture) -> None: issue_handler = await github_handler.to_issue_handler(123) assert isinstance(issue_handler, IssueHandler) assert issue_handler.issue == mock_issue + + +async def test_get_self_comment(app: App, mocker: MockerFixture) -> None: + """测试获取自己的评论""" + from src.plugins.github.handlers.github import GithubHandler + from src.plugins.github.models import RepoInfo + + mock_comment_bot = mocker.MagicMock() + mock_comment_bot.body = "bot comment\n" + mock_comment_bot.id = 123 + + mock_comment_user = mocker.MagicMock() + mock_comment_user.body = "user comment" + mock_comment_user.id = 456 + + mock_comments_resp = mocker.MagicMock() + mock_comments_resp.parsed_data = [mock_comment_user, mock_comment_bot] + + 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, + }, + } + ), + ) + comment = await github_handler.get_self_comment(76) + assert comment == mock_comment_bot + + +async def test_get_self_comment_not_found(app: App, mocker: MockerFixture) -> None: + """测试获取自己的评论,未找到的情况""" + from src.plugins.github.handlers.github import GithubHandler + from src.plugins.github.models import RepoInfo + + mock_comment_user = mocker.MagicMock() + mock_comment_user.body = "user comment" + mock_comment_user.id = 456 + + mock_comments_resp = mocker.MagicMock() + mock_comments_resp.parsed_data = [mock_comment_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.issues.async_list_comments", result=mock_comments_resp + ), + ], + snapshot( + { + 0: { + "owner": "owner", + "repo": "repo", + "issue_number": 76, + }, + } + ), + ) + comment = await github_handler.get_self_comment(76) + assert comment is None + + +async def test_comment_issue_new(app: App, mocker: MockerFixture) -> 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.comment_issue("new comment", 76) + + +async def test_comment_issue_update(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 + + 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": "new comment", + }, + } + ), + ) + await github_handler.comment_issue("new comment", 76, mock_comment) + + +async def test_comment_issue_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 = "same comment" + mock_comment.id = 123 + + async with app.test_api() as ctx: + _, bot = get_github_bot(ctx) + + github_handler = GithubHandler( + bot=bot, + repo_info=RepoInfo(owner="owner", repo="repo"), + ) + + await github_handler.comment_issue("same comment", 76, mock_comment) diff --git a/tests/plugins/github/handlers/test_issue_handler.py b/tests/plugins/github/handlers/test_issue_handler.py index 41d40413..0183a36c 100644 --- a/tests/plugins/github/handlers/test_issue_handler.py +++ b/tests/plugins/github/handlers/test_issue_handler.py @@ -279,7 +279,7 @@ async def test_comment_issue(app: App, mocker: MockerFixture) -> None: } ), ) - await issue_handler.comment_issue("new comment") + await issue_handler.resuable_comment_issue("new comment") async def test_should_skip_test(app: App, mocker: MockerFixture) -> None: @@ -443,3 +443,164 @@ async def test_commit_and_push(app: App, mocker: MockerFixture) -> None: call(["git", "push", "origin", "main", "-f"]), ], ) + + +async def test_get_self_comment(app: App, mocker: MockerFixture) -> None: + """测试获取自己的评论""" + from src.plugins.github.handlers.issue import IssueHandler + from src.plugins.github.models import RepoInfo + + mock_comment_bot = mocker.MagicMock() + mock_comment_bot.body = "bot comment\n" + mock_comment_bot.id = 123 + + mock_comment_user = mocker.MagicMock() + mock_comment_user.body = "user comment" + mock_comment_user.id = 456 + + mock_comments_resp = mocker.MagicMock() + mock_comments_resp.parsed_data = [mock_comment_user, mock_comment_bot] + + 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}, + } + ), + ) + comment = await issue_handler.get_self_comment() + assert comment == mock_comment_bot + + +async def test_get_self_comment_not_found(app: App, mocker: MockerFixture) -> None: + """测试获取自己的评论,未找到的情况""" + from src.plugins.github.handlers.issue import IssueHandler + from src.plugins.github.models import RepoInfo + + mock_comment_user = mocker.MagicMock() + mock_comment_user.id = 123 + mock_comment_user.body = "bot comment" + mock_comments_resp = mocker.MagicMock() + mock_comments_resp.parsed_data = [mock_comment_user] + + 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}, + } + ), + ) + comment = await issue_handler.get_self_comment() + assert comment is None + + +async def test_comment_issue_new(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 + + 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_create_comment", result=True), + ], + snapshot( + { + 0: { + "owner": "owner", + "repo": "repo", + "issue_number": 76, + "body": "test comment", + }, + } + ), + ) + await issue_handler.comment_issue("test comment") + + +async def test_comment_issue_update_existing(app: App, mocker: MockerFixture) -> None: + """测试更新已存在的评论""" + from src.plugins.github.handlers.issue import IssueHandler + from src.plugins.github.models import RepoInfo + + mock_comment = mocker.MagicMock() + mock_comment.id = 123 + mock_comment.body = "old comment" + + 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_update_comment", result=True), + ], + snapshot( + { + 0: { + "owner": "owner", + "repo": "repo", + "comment_id": 123, + "body": "updated comment", + }, + } + ), + ) + await issue_handler.comment_issue("updated comment", self_comment=mock_comment) diff --git a/tests/plugins/github/publish/process/test_publish_check_plugin.py b/tests/plugins/github/publish/process/test_publish_check_plugin.py index e81f9d4b..14c04041 100644 --- a/tests/plugins/github/publish/process/test_publish_check_plugin.py +++ b/tests/plugins/github/publish/process/test_publish_check_plugin.py @@ -201,6 +201,10 @@ async def test_plugin_process_publish_check( 详情
  • ✅ 项目 主页 返回状态码 200。
  • ✅ 项目 project_link 已发布至 PyPI。
  • ✅ 标签: test-#ffffff。
  • ✅ 插件类型: application。
  • ✅ 插件支持的适配器: 所有。
  • ✅ 插件 加载测试 通过。
  • ✅ 版本号: 1.0.0。
  • ✅ 发布时间:2023-09-01 08:00:00 CST。
  • +
    +历史测试 +
  • 2023-08-23 09:22:14 CST
  • +
    --- @@ -463,6 +467,10 @@ async def test_plugin_process_publish_check_re_run( 详情
  • ✅ 项目 主页 返回状态码 200。
  • ✅ 项目 project_link 已发布至 PyPI。
  • ✅ 标签: test-#ffffff。
  • ✅ 插件类型: application。
  • ✅ 插件支持的适配器: 所有。
  • ✅ 插件 加载测试 通过。
  • ✅ 版本号: 1.0.0。
  • ✅ 发布时间:2023-09-01 08:00:00 CST。
  • +
    +历史测试 +
  • 2023-08-23 09:22:14 CST
  • +
    --- @@ -710,6 +718,10 @@ async def test_plugin_process_publish_check_missing_metadata( 详情
  • ✅ 项目 project_link 已发布至 PyPI。
  • ✅ 标签: test-#ffffff。
  • ✅ 插件 加载测试 通过。
  • ✅ 版本号: 1.0.0。
  • ✅ 发布时间:2023-09-01 08:00:00 CST。
  • +
    +历史测试 +
  • ⚠️ 2023-08-23 09:22:14 CST
  • +
    --- @@ -965,6 +977,10 @@ async def test_skip_plugin_check( 详情
  • ✅ 项目 project_link 已发布至 PyPI。
  • ✅ 标签: test-#ffffff。
  • ✅ 插件 加载测试 已跳过。
  • ✅ 版本号: 0.0.1。
  • ✅ 发布时间:2023-09-01 08:00:00 CST。
  • +
    +历史测试 +
  • ⚠️ 2023-08-23 09:22:14 CST
  • +
    --- diff --git a/tests/plugins/github/publish/render/test_publish_render.py b/tests/plugins/github/publish/render/test_publish_render.py index d2861800..ff82c282 100644 --- a/tests/plugins/github/publish/render/test_publish_render.py +++ b/tests/plugins/github/publish/render/test_publish_render.py @@ -1,7 +1,9 @@ +from datetime import datetime + from inline_snapshot import snapshot from nonebug import App -from tests.providers.validation.utils import generate_bot_data +from tests.providers.validation.utils import generate_bot_data, generate_plugin_data async def test_render_empty(app: App): @@ -71,3 +73,110 @@ async def test_render_reuse(app: App): """ ) + + +async def test_render_history(app: App): + """测试历史记录渲染,包括排序和数量限制""" + + from src.plugins.github.plugins.publish.render import render_comment + from src.providers.constants import TIME_ZONE + from src.providers.validation import ( + PluginPublishInfo, + PublishType, + ValidationDict, + ) + + # 创建多个历史记录用于测试排序和数量限制 + history = [ + ( + False, + "https://github.com/nonebot/nonebot2/actions/runs/14156878700", + datetime(2020, 10, 2, 12, 0, 18, tzinfo=TIME_ZONE), + ), + ( + True, + "https://github.com/nonebot/nonebot2/actions/runs/14156878699", + datetime(2020, 10, 1, 12, 0, 18, tzinfo=TIME_ZONE), + ), + ( + True, + "https://github.com/nonebot/nonebot2/actions/runs/14156878701", + datetime(2020, 10, 3, 12, 0, 18, tzinfo=TIME_ZONE), + ), + ( + True, + "https://github.com/nonebot/nonebot2/actions/runs/14156878702", + datetime(2020, 10, 4, 12, 0, 18, tzinfo=TIME_ZONE), + ), + ( + False, + "https://github.com/nonebot/nonebot2/actions/runs/14156878703", + datetime(2020, 10, 5, 12, 0, 18, tzinfo=TIME_ZONE), + ), + ( + True, + "https://github.com/nonebot/nonebot2/actions/runs/14156878704", + datetime(2020, 10, 6, 12, 0, 18, tzinfo=TIME_ZONE), + ), + ( + False, + "https://github.com/nonebot/nonebot2/actions/runs/14156878705", + datetime(2020, 10, 7, 12, 0, 18, tzinfo=TIME_ZONE), + ), + ( + True, + "https://github.com/nonebot/nonebot2/actions/runs/14156878706", + datetime(2020, 10, 8, 12, 0, 18, tzinfo=TIME_ZONE), + ), + ( + False, + "https://github.com/nonebot/nonebot2/actions/runs/14156878707", + datetime(2020, 10, 9, 12, 0, 18, tzinfo=TIME_ZONE), + ), + ( + True, + "https://github.com/nonebot/nonebot2/actions/runs/14156878708", + datetime(2020, 10, 10, 12, 0, 18, tzinfo=TIME_ZONE), + ), + ] + + result = ValidationDict( + type=PublishType.PLUGIN, + raw_data={ + "name": "name", + "load": True, + }, + info=PluginPublishInfo.model_construct(**generate_plugin_data()), + ) + + comment = await render_comment(result, history=history) + assert comment == snapshot( + """\ +# 📃 商店发布检查结果 + +> Plugin: name + +[![测试结果](https://img.shields.io/badge/RESULT-OK-green?style=for-the-badge)](https://github.com/owner/repo/actions/runs/123456) + +**✅ 所有测试通过,一切准备就绪!** + + +
    +详情 +
  • ✅ 插件 加载测试 通过。
  • +
    +
    +历史测试 +
  • 2023-08-23 09:22:14 CST
  • 2020-10-10 12:00:18 CST
  • ⚠️ 2020-10-09 12:00:18 CST
  • 2020-10-08 12:00:18 CST
  • ⚠️ 2020-10-07 12:00:18 CST
  • 2020-10-06 12:00:18 CST
  • ⚠️ 2020-10-05 12:00:18 CST
  • 2020-10-04 12:00:18 CST
  • 2020-10-03 12:00:18 CST
  • ⚠️ 2020-10-02 12:00:18 CST
  • +
    + +--- + +💡 如需修改信息,请直接修改 issue,机器人会自动更新检查结果。 +💡 当插件加载测试失败时,请发布新版本后勾选插件测试勾选框重新运行插件测试。 + + +💪 Powered by [NoneFlow](https://github.com/nonebot/noneflow) + +""" + ) diff --git a/tests/plugins/github/publish/render/test_publish_render_data.py b/tests/plugins/github/publish/render/test_publish_render_data.py index 97e02ec8..edd981ce 100644 --- a/tests/plugins/github/publish/render/test_publish_render_data.py +++ b/tests/plugins/github/publish/render/test_publish_render_data.py @@ -205,6 +205,10 @@ async def test_render_data_plugin(app: App, mocker: MockFixture): 详情
  • ✅ 项目 主页 返回状态码 200。
  • ✅ 项目 nonebot-plugin-treehelp 已发布至 PyPI。
  • ✅ 标签: render-#ffffff。
  • ✅ 插件类型: application。
  • ✅ 插件支持的适配器: 所有。
  • ✅ 插件 加载测试 通过。
  • ✅ 版本号: 0.5.0。
  • ✅ 发布时间:2024-07-13 12:41:40 CST。
  • +
    +历史测试 +
  • 2023-08-23 09:22:14 CST
  • +
    --- @@ -258,6 +262,10 @@ async def test_render_data_plugin_supported_adapters(app: App, mocker: MockFixtu 详情
  • ✅ 插件支持的适配器: nonebot.adapters.onebot.v11, nonebot.adapters.none。
  • ✅ 插件 加载测试 通过。
  • +
    +历史测试 +
  • 2023-08-23 09:22:14 CST
  • +
    --- diff --git a/tests/plugins/github/publish/render/test_publish_render_error.py b/tests/plugins/github/publish/render/test_publish_render_error.py index 6603238e..90fc6429 100644 --- a/tests/plugins/github/publish/render/test_publish_render_error.py +++ b/tests/plugins/github/publish/render/test_publish_render_error.py @@ -241,6 +241,10 @@ async def test_render_error_plugin(app: App, mocker: MockFixture): 详情
  • ✅ 项目 主页 返回状态码 200。
  • ✅ 插件 加载测试 通过。
  • +
    +历史测试 +
  • ⚠️ 2023-08-23 09:22:14 CST
  • +
    --- @@ -369,6 +373,10 @@ async def test_render_error_plugin_metadata(app: App, mocker: MockFixture): 详情
  • ✅ 插件 加载测试 通过。
  • +
    +历史测试 +
  • ⚠️ 2023-08-23 09:22:14 CST
  • +
    --- @@ -439,6 +447,10 @@ async def test_render_error_tags_invalid(app: App, mocker: MockFixture): 详情
  • ✅ 插件 加载测试 通过。
  • +
    +历史测试 +
  • ⚠️ 2023-08-23 09:22:14 CST
  • +
    --- @@ -510,6 +522,10 @@ async def test_render_type_error(app: App, mocker: MockFixture): 详情
  • ✅ 插件 加载测试 通过。
  • +
    +历史测试 +
  • ⚠️ 2023-08-23 09:22:14 CST
  • +
    --- @@ -568,6 +584,10 @@ async def test_render_unknown_error(app: App, mocker: MockFixture): 详情
  • ✅ 插件 加载测试 通过。
  • +
    +历史测试 +
  • ⚠️ 2023-08-23 09:22:14 CST
  • +
    --- @@ -637,6 +657,10 @@ async def test_render_http_error(app: App, mocker: MockFixture): 详情
  • ✅ 插件 加载测试 通过。
  • +
    +历史测试 +
  • ⚠️ 2023-08-23 09:22:14 CST
  • +
    --- diff --git a/tests/plugins/github/publish/utils/test_comment_issue.py b/tests/plugins/github/publish/utils/test_comment_issue.py index 98c1e34b..24d71a41 100644 --- a/tests/plugins/github/publish/utils/test_comment_issue.py +++ b/tests/plugins/github/publish/utils/test_comment_issue.py @@ -36,7 +36,7 @@ async def test_comment_issue(app: App, mocker: MockerFixture): handler = GithubHandler(bot=bot, repo_info=RepoInfo(owner="owner", repo="repo")) - await handler.comment_issue(render_comment, 1) + await handler.resuable_comment_issue(render_comment, 1) async def test_comment_issue_reuse(app: App, mocker: MockerFixture): @@ -73,7 +73,7 @@ async def test_comment_issue_reuse(app: App, mocker: MockerFixture): handler = GithubHandler(bot=bot, repo_info=RepoInfo(owner="owner", repo="repo")) - await handler.comment_issue(render_comment, 1) + await handler.resuable_comment_issue(render_comment, 1) async def test_comment_issue_reuse_same(app: App, mocker: MockerFixture): @@ -101,4 +101,4 @@ async def test_comment_issue_reuse_same(app: App, mocker: MockerFixture): handler = GithubHandler(bot=bot, repo_info=RepoInfo(owner="owner", repo="repo")) - await handler.comment_issue(render_comment, 1) + await handler.resuable_comment_issue(render_comment, 1) diff --git a/tests/plugins/github/publish/utils/test_history_workflow.py b/tests/plugins/github/publish/utils/test_history_workflow.py new file mode 100644 index 00000000..e6a6c12b --- /dev/null +++ b/tests/plugins/github/publish/utils/test_history_workflow.py @@ -0,0 +1,68 @@ +from inline_snapshot import snapshot +from nonebug import App + + +async def test_history_workflow(app: App): + from src.plugins.github.plugins.publish.utils import ( + get_history_workflow_from_comment, + ) + from src.providers.constants import TIME_ZONE + + CONTENT = """ +# 📃 商店发布检查结果 + +> Plugin: nonebot-plugin-emojilike-automonkey + +**✅ 所有测试通过,一切准备就绪!** + + +
    +详情 +
  • ✅ 项目 主页 返回状态码 200。
  • ✅ 项目 nonebot-plugin-emojilike-automonkey 已发布至 PyPI。
  • ✅ 标签: emoji-#6677ff。
  • ✅ 插件类型: application。
  • ✅ 插件支持的适配器: nonebot.adapters.onebot.v11。
  • ✅ 插件 加载测试 通过。
  • ✅ 版本号: 0.0.12。
  • ✅ 发布时间:2025-03-28 02:03:18 CST。
  • +
    + +
    +历史测试 +
    
    +
  • ⚠️ 2025-03-28 02:21:18 CST
  • 2025-03-28 02:21:18 CST
  • 2025-03-28 02:22:18 CST
  • ⚠️ 2025-03-28 02:22:18 CST
  • +
    +
    + +--- + +💡 如需修改信息,请直接修改 issue,机器人会自动更新检查结果。 +💡 当插件加载测试失败时,请发布新版本后勾选插件测试勾选框重新运行插件测试。 + +♻️ 评论已更新至最新检查结果 + +💪 Powered by [NoneFlow](https://github.com/nonebot/noneflow) + +""" + history = [ + (valid, url, time.astimezone(TIME_ZONE).strftime("%Y-%m-%d %H:%M:%S %Z")) + for valid, url, time in await get_history_workflow_from_comment(CONTENT) + ] + assert history == snapshot( + [ + ( + False, + "https://github.com/nonebot/nonebot2/actions/runs/14156878699", + "2025-03-28 02:21:18 CST", + ), + ( + True, + "https://github.com/nonebot/nonebot2/actions/runs/14156878699", + "2025-03-28 02:21:18 CST", + ), + ( + True, + "https://github.com/nonebot/nonebot2/actions/runs/14156878699", + "2025-03-28 02:22:18 CST", + ), + ( + False, + "https://github.com/nonebot/nonebot2/actions/runs/14156878699", + "2025-03-28 02:22:18 CST", + ), + ] + ) diff --git a/tests/providers/docker_test/test_docker_plugin_test.py b/tests/providers/docker_test/test_docker_plugin_test.py index 3a195d37..f4b2944a 100644 --- a/tests/providers/docker_test/test_docker_plugin_test.py +++ b/tests/providers/docker_test/test_docker_plugin_test.py @@ -31,7 +31,6 @@ async def test_docker_plugin_test(mocked_api: MockRouter, mocker: MockerFixture) assert result == snapshot( DockerTestResult( load=True, - metadata=None, output="test", run=True, test_env="python==3.12", @@ -75,7 +74,6 @@ async def test_docker_plugin_test_exception( DockerTestResult( run=False, load=False, - metadata=None, output="Docker failed", ) ) @@ -130,7 +128,6 @@ async def test_docker_plugin_test_metadata_some_fields_empty( assert result == snapshot( DockerTestResult( - config="", load=True, metadata={ "name": "name", @@ -166,7 +163,7 @@ async def test_docker_plugin_test_metadata_some_fields_invalid( mocked_api: MockRouter, mocker: MockerFixture ): """测试 metadata 的部分字段不符合规范""" - from src.providers.docker_test import DockerPluginTest, DockerTestResult, Metadata + from src.providers.docker_test import DockerPluginTest, DockerTestResult mocked_run = mocker.Mock() mocked_run.return_value = json.dumps( @@ -196,15 +193,14 @@ async def test_docker_plugin_test_metadata_some_fields_invalid( assert result == snapshot( DockerTestResult( - config="", load=True, - metadata=Metadata( - name="name", - desc="desc", - homepage=12, # type: ignore - type=True, # type: ignore - supported_adapters={}, # type: ignore - ), + metadata={ + "name": "name", + "desc": "desc", + "homepage": 12, + "type": True, + "supported_adapters": {}, + }, # type: ignore output="test", run=True, test_env="python==3.12", diff --git a/tests/providers/store_test/test_step_summary.py b/tests/providers/store_test/test_step_summary.py index 1a72c6c5..f4e4dfb5 100644 --- a/tests/providers/store_test/test_step_summary.py +++ b/tests/providers/store_test/test_step_summary.py @@ -6,10 +6,7 @@ async def test_step_summary( - mocked_store_data: dict[str, Path], - mocked_api: MockRouter, - mocker: MockerFixture, - mock_datetime, + mocked_store_data: dict[str, Path], mocked_api: MockRouter, mocker: MockerFixture ) -> None: """验证插件信息""" from src.providers.models import StoreTestResult diff --git a/tests/providers/store_test/test_validate_plugin.py b/tests/providers/store_test/test_validate_plugin.py index 2c40c5d1..1a66a15d 100644 --- a/tests/providers/store_test/test_validate_plugin.py +++ b/tests/providers/store_test/test_validate_plugin.py @@ -22,9 +22,7 @@ def mock_docker_result(path: Path, mocker: MockerFixture): return mock_plugin_test -async def test_validate_plugin( - mocked_api: MockRouter, mocker: MockerFixture, mock_datetime -) -> None: +async def test_validate_plugin(mocked_api: MockRouter, mocker: MockerFixture) -> None: """验证插件信息""" from src.providers.models import RegistryPlugin, StorePlugin, StoreTestResult from src.providers.store_test.validation import validate_plugin @@ -90,7 +88,7 @@ async def test_validate_plugin( async def test_validate_plugin_with_previous( - mocked_api: MockRouter, mocker: MockerFixture, mock_datetime + mocked_api: MockRouter, mocker: MockerFixture ) -> None: """插件验证通过,但提供了之前插件信息的情况 @@ -181,7 +179,7 @@ async def test_validate_plugin_with_previous( async def test_validate_plugin_skip_test( - mocked_api: MockRouter, mocker: MockerFixture, mock_datetime + mocked_api: MockRouter, mocker: MockerFixture ) -> None: """跳过插件测试的情况 @@ -251,7 +249,7 @@ async def test_validate_plugin_skip_test( async def test_validate_plugin_skip_test_plugin_test_failed( - mocked_api: MockRouter, mocker: MockerFixture, mock_datetime + mocked_api: MockRouter, mocker: MockerFixture ) -> None: """跳过插件测试的情况 @@ -360,7 +358,7 @@ async def test_validate_plugin_skip_test_plugin_test_failed( async def test_validate_plugin_failed_with_previous( - mocked_api: MockRouter, mocker: MockerFixture, mock_datetime + mocked_api: MockRouter, mocker: MockerFixture ) -> None: """插件验证失败,但提供了之前插件信息的情况