Skip to content

Commit

Permalink
feat: 限制名称的长度不超过 50 个字符 (#138)
Browse files Browse the repository at this point in the history
  • Loading branch information
he0119 authored Jun 12, 2023
1 parent e6367e5 commit 1ee13f0
Show file tree
Hide file tree
Showing 7 changed files with 201 additions and 17 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/lang/zh-CN/

## [Unreleased]

### Added

- 限制名称的长度不超过 50 个字符

### Changed

- 通过议题标签判断商店发布类型
Expand Down
14 changes: 10 additions & 4 deletions src/plugins/publish/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from nonebot.params import Depends

from .config import plugin_config
from .constants import BOT_MARKER, BRANCH_NAME_PREFIX
from .constants import BOT_MARKER, BRANCH_NAME_PREFIX, MAX_NAME_LENGTH
from .depends import (
get_installation_id,
get_issue_number,
Expand Down Expand Up @@ -167,9 +167,16 @@ async def handle_publish_check(
# 检查是否满足发布要求
# 仅在通过检查的情况下创建拉取请求
info = extract_publish_info_from_issue(issue, publish_type)

# 设置拉取请求与议题的标题
if isinstance(info, PublishInfo):
name = info.name
else:
name = info.raw_data.get("name") or ""
# 限制标题长度,过长的标题不好看
title = f"{publish_type.value}: {name[:MAX_NAME_LENGTH]}"

if isinstance(info, PublishInfo):
# 拉取请求与议题的标题
title = f"{info.get_type().value}: {info.name}"
# 创建新分支
# 命名示例 publish/issue123
branch_name = f"{BRANCH_NAME_PREFIX}{issue_number}"
Expand All @@ -183,7 +190,6 @@ async def handle_publish_check(
)
message = info.validation_message
else:
title = f"{publish_type.value}: {info.raw_data.get('name') or ''}"
message = info.message
logger.info("发布没通过检查,暂不创建拉取请求")

Expand Down
3 changes: 3 additions & 0 deletions src/plugins/publish/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@
re.IGNORECASE,
)

MAX_NAME_LENGTH = 50
"""名称最大长度"""

# 匹配信息的正则表达式
# 格式:### {标题}\n\n{内容}
ISSUE_PATTERN = r"### {}\s+([^\s#].*?)(?=(?:\s+###|$))"
Expand Down
7 changes: 7 additions & 0 deletions src/plugins/publish/validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
BOT_HOMEPAGE_PATTERN,
BOT_NAME_PATTERN,
DETAIL_MESSAGE_TEMPLATE,
MAX_NAME_LENGTH,
PLUGIN_DESC_PATTERN,
PLUGIN_HOMEPAGE_PATTERN,
PLUGIN_MODULE_NAME_PATTERN,
Expand Down Expand Up @@ -94,6 +95,12 @@ class PublishInfo(abc.ABC, BaseModel):
tags: list[Tag]
is_official: bool = False

@validator("name", pre=True)
def name_validator(cls, v: str) -> str:
if len(v) > MAX_NAME_LENGTH:
raise ValueError(f"⚠️ 名称过长。<dt>请确保名称不超过 {MAX_NAME_LENGTH} 个字符。</dt>")
return v

@validator("homepage", pre=True)
def homepage_validator(cls, v: str) -> str:
if v:
Expand Down
9 changes: 7 additions & 2 deletions tests/publish/models/test_adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,11 +152,16 @@ async def test_adapter_info_validation_partial_failed(
async def test_adapter_info_name_validation_failed(
mocker: MockerFixture, mocked_api: MockRouter
) -> None:
"""测试名称重复检测失败的情况"""
"""测试名称不符合规范的情况
名称过长
重复的项目名与报名
"""
from src.plugins.publish.validation import AdapterPublishInfo, MyValidationError

mock_issue = mocker.MagicMock()
mock_issue.body = generate_issue_body_adapter(
name="looooooooooooooooooooooooooooooooooooooooooooooooooooooooong",
module_name="module_name1",
project_link="project_link1",
)
Expand All @@ -167,7 +172,7 @@ async def test_adapter_info_name_validation_failed(

assert (
e.value.message
== """> Adapter: name\n\n**⚠️ 在发布检查过程中,我们发现以下问题:**\n<pre><code><li>⚠️ PyPI 项目名 project_link1 加包名 module_name1 的值与商店重复。<dt>请确保没有重复发布。</dt></li></code></pre>\n<details><summary>详情</summary><pre><code><li>✅ 标签: test-#ffffff。</li><li>✅ 项目 <a href="https://nonebot.dev">主页</a> 返回状态码 200。</li><li>✅ 包 <a href="https://pypi.org/project/project_link1/">project_link1</a> 已发布至 PyPI。</li></code></pre></details>"""
== """> Adapter: looooooooooooooooooooooooooooooooooooooooooooooooooooooooong\n\n**⚠️ 在发布检查过程中,我们发现以下问题:**\n<pre><code><li>⚠️ 名称过长。<dt>请确保名称不超过 50 个字符。</dt></li><li>⚠️ PyPI 项目名 project_link1 加包名 module_name1 的值与商店重复。<dt>请确保没有重复发布。</dt></li></code></pre>\n<details><summary>详情</summary><pre><code><li>✅ 标签: test-#ffffff。</li><li>✅ 项目 <a href="https://nonebot.dev">主页</a> 返回状态码 200。</li><li>✅ 包 <a href="https://pypi.org/project/project_link1/">project_link1</a> 已发布至 PyPI。</li></code></pre></details>"""
)

assert mocked_api["project_link1"].called
Expand Down
27 changes: 23 additions & 4 deletions tests/publish/models/test_name.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,7 @@ async def test_pypi_project_name_invalid(mocked_api: MockRouter) -> None:
assert mocked_api["homepage"].called


async def test_module_name_invalid(
mocker: MockerFixture, mocked_api: MockRouter
) -> None:
async def test_module_name_invalid(mocked_api: MockRouter) -> None:
"""测试模块名称不正确的情况"""
from src.plugins.publish.validation import AdapterPublishInfo

Expand All @@ -49,7 +47,7 @@ async def test_module_name_invalid(
assert mocked_api["homepage"].called


async def test_name_duplication(mocker: MockerFixture, mocked_api: MockRouter) -> None:
async def test_name_duplication(mocked_api: MockRouter) -> None:
"""测试名称重复的情况"""
from src.plugins.publish.validation import AdapterPublishInfo

Expand All @@ -71,3 +69,24 @@ async def test_name_duplication(mocker: MockerFixture, mocked_api: MockRouter) -

assert mocked_api["project_link1"].called
assert mocked_api["homepage"].called


async def test_name_too_long(mocked_api: MockRouter) -> None:
"""测试名称过长的情况"""
from src.plugins.publish.validation import AdapterPublishInfo

with pytest.raises(ValidationError) as e:
AdapterPublishInfo(
module_name="module_name",
project_link="project_link",
name="looooooooooooooooooooooooooooooooooooooooooooooooooooooooong",
desc="desc",
author="author",
homepage="https://nonebot.dev",
tags=json.dumps([]), # type: ignore
is_official=False,
)
assert "⚠️ 名称过长。<dt>请确保名称不超过 50 个字符。</dt>" in str(e.value)

assert mocked_api["project_link"].called
assert mocked_api["homepage"].called
154 changes: 147 additions & 7 deletions tests/publish/process/test_publish_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
from pathlib import Path
from typing import Any, cast

import httpx
from githubkit import Response
from githubkit.exception import RequestFailed
from nonebot import get_adapter
from nonebot.adapters.github import (
Adapter,
Expand Down Expand Up @@ -206,7 +209,7 @@ async def test_edit_title(
) -> None:
"""测试编辑标题
插件名被修改后,标题也应该被修改
名称被修改后,标题也应该被修改
"""
from src.plugins.publish import publish_check_matcher
from src.plugins.publish.config import plugin_config
Expand Down Expand Up @@ -242,7 +245,7 @@ async def test_edit_title(
mock_pull = mocker.MagicMock()
mock_pull.number = 2
mock_pulls_resp = mocker.MagicMock()
mock_pulls_resp.parsed_data = mock_pull
mock_pulls_resp.parsed_data = [mock_pull]

with open(tmp_path / "bots.json", "w") as f:
json.dump([], f)
Expand Down Expand Up @@ -276,7 +279,6 @@ async def test_edit_title(
{"owner": "he0119", "repo": "action-test", "issue_number": 80},
mock_list_comments_resp,
)
# TODO: 抛出一个异常,然后执行修改拉取请求标题的逻辑
ctx.should_call_api(
"rest.pulls.async_create",
{
Expand All @@ -287,19 +289,32 @@ async def test_edit_title(
"base": "master",
"head": "publish/issue80",
},
exception=RequestFailed(
Response(
httpx.Response(422, request=httpx.Request("test", "test")), None
)
),
)
ctx.should_call_api(
"rest.pulls.async_list",
{
"owner": "he0119",
"repo": "action-test",
"head": "he0119:publish/issue80",
},
mock_pulls_resp,
)
# 修改标题
ctx.should_call_api(
"rest.issues.async_add_labels",
"rest.pulls.async_update",
{
"owner": "he0119",
"repo": "action-test",
"issue_number": 2,
"labels": ["Bot"],
"pull_number": 2,
"title": "Bot: test1",
},
True,
)
# 修改标题
ctx.should_call_api(
"rest.issues.async_update",
{
Expand Down Expand Up @@ -396,6 +411,130 @@ async def test_edit_title(
assert mocked_api["homepage"].called


async def test_edit_title_too_long(
app: App, mocker: MockerFixture, mocked_api: MockRouter, tmp_path: Path
) -> None:
"""测试编辑标题
标题过长的情况
"""
from src.plugins.publish import publish_check_matcher
from src.plugins.publish.config import plugin_config

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 = generate_issue_body_bot(
name="looooooooooooooooooooooooooooooooooooooooooooooooooooooong"
)
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(publish_check_matcher) 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 / "events" / "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_list_comments",
{"owner": "he0119", "repo": "action-test", "issue_number": 80},
mock_list_comments_resp,
)
# 修改标题,应该被阶段,且不会更新拉取请求的标题
ctx.should_call_api(
"rest.issues.async_update",
{
"owner": "he0119",
"repo": "action-test",
"issue_number": 80,
"title": "Bot: looooooooooooooooooooooooooooooooooooooooooooooooo",
},
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: looooooooooooooooooooooooooooooooooooooooooooooooooooooong\n\n**⚠️ 在发布检查过程中,我们发现以下问题:**\n<pre><code><li>⚠️ 名称过长。<dt>请确保名称不超过 50 个字符。</dt></li></code></pre>\n<details><summary>详情</summary><pre><code><li>✅ 标签: test-#ffffff。</li><li>✅ 项目 <a href="https://nonebot.dev">主页</a> 返回状态码 200。</li></code></pre></details>\n\n---\n\n💡 如需修改信息,请直接修改 issue,机器人会自动更新检查结果。\n💡 当插件加载测试失败时,请发布新版本后在当前页面下评论任意内容以触发测试。\n\n💪 Powered by [NoneFlow](https://github.com/nonebot/noneflow)\n<!-- NONEFLOW -->\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,
) # type: ignore
]
)

# 检查文件是否正确
check_json_data(plugin_config.input_config.bot_path, [])

assert mocked_api["homepage"].called


async def test_process_publish_check_not_pass(
app: App, mocker: MockerFixture, mocked_api: MockRouter, tmp_path: Path
) -> None:
Expand Down Expand Up @@ -767,3 +906,4 @@ async def test_skip_plugin_check(
check_json_data(plugin_config.input_config.plugin_path, [])

assert mocked_api["project_link"].called
assert mocked_api["project_link"].called

0 comments on commit 1ee13f0

Please sign in to comment.