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 = """[]({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://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:
"""插件验证失败,但提供了之前插件信息的情况