Skip to content

Commit 1ee13f0

Browse files
authored
feat: 限制名称的长度不超过 50 个字符 (#138)
1 parent e6367e5 commit 1ee13f0

File tree

7 files changed

+201
-17
lines changed

7 files changed

+201
-17
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/lang/zh-CN/
77

88
## [Unreleased]
99

10+
### Added
11+
12+
- 限制名称的长度不超过 50 个字符
13+
1014
### Changed
1115

1216
- 通过议题标签判断商店发布类型

src/plugins/publish/__init__.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from nonebot.params import Depends
1212

1313
from .config import plugin_config
14-
from .constants import BOT_MARKER, BRANCH_NAME_PREFIX
14+
from .constants import BOT_MARKER, BRANCH_NAME_PREFIX, MAX_NAME_LENGTH
1515
from .depends import (
1616
get_installation_id,
1717
get_issue_number,
@@ -167,9 +167,16 @@ async def handle_publish_check(
167167
# 检查是否满足发布要求
168168
# 仅在通过检查的情况下创建拉取请求
169169
info = extract_publish_info_from_issue(issue, publish_type)
170+
171+
# 设置拉取请求与议题的标题
172+
if isinstance(info, PublishInfo):
173+
name = info.name
174+
else:
175+
name = info.raw_data.get("name") or ""
176+
# 限制标题长度,过长的标题不好看
177+
title = f"{publish_type.value}: {name[:MAX_NAME_LENGTH]}"
178+
170179
if isinstance(info, PublishInfo):
171-
# 拉取请求与议题的标题
172-
title = f"{info.get_type().value}: {info.name}"
173180
# 创建新分支
174181
# 命名示例 publish/issue123
175182
branch_name = f"{BRANCH_NAME_PREFIX}{issue_number}"
@@ -183,7 +190,6 @@ async def handle_publish_check(
183190
)
184191
message = info.validation_message
185192
else:
186-
title = f"{publish_type.value}: {info.raw_data.get('name') or ''}"
187193
message = info.message
188194
logger.info("发布没通过检查,暂不创建拉取请求")
189195

src/plugins/publish/constants.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@
5656
re.IGNORECASE,
5757
)
5858

59+
MAX_NAME_LENGTH = 50
60+
"""名称最大长度"""
61+
5962
# 匹配信息的正则表达式
6063
# 格式:### {标题}\n\n{内容}
6164
ISSUE_PATTERN = r"### {}\s+([^\s#].*?)(?=(?:\s+###|$))"

src/plugins/publish/validation.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
BOT_HOMEPAGE_PATTERN,
2121
BOT_NAME_PATTERN,
2222
DETAIL_MESSAGE_TEMPLATE,
23+
MAX_NAME_LENGTH,
2324
PLUGIN_DESC_PATTERN,
2425
PLUGIN_HOMEPAGE_PATTERN,
2526
PLUGIN_MODULE_NAME_PATTERN,
@@ -94,6 +95,12 @@ class PublishInfo(abc.ABC, BaseModel):
9495
tags: list[Tag]
9596
is_official: bool = False
9697

98+
@validator("name", pre=True)
99+
def name_validator(cls, v: str) -> str:
100+
if len(v) > MAX_NAME_LENGTH:
101+
raise ValueError(f"⚠️ 名称过长。<dt>请确保名称不超过 {MAX_NAME_LENGTH} 个字符。</dt>")
102+
return v
103+
97104
@validator("homepage", pre=True)
98105
def homepage_validator(cls, v: str) -> str:
99106
if v:

tests/publish/models/test_adapter.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,11 +152,16 @@ async def test_adapter_info_validation_partial_failed(
152152
async def test_adapter_info_name_validation_failed(
153153
mocker: MockerFixture, mocked_api: MockRouter
154154
) -> None:
155-
"""测试名称重复检测失败的情况"""
155+
"""测试名称不符合规范的情况
156+
157+
名称过长
158+
重复的项目名与报名
159+
"""
156160
from src.plugins.publish.validation import AdapterPublishInfo, MyValidationError
157161

158162
mock_issue = mocker.MagicMock()
159163
mock_issue.body = generate_issue_body_adapter(
164+
name="looooooooooooooooooooooooooooooooooooooooooooooooooooooooong",
160165
module_name="module_name1",
161166
project_link="project_link1",
162167
)
@@ -167,7 +172,7 @@ async def test_adapter_info_name_validation_failed(
167172

168173
assert (
169174
e.value.message
170-
== """> 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>"""
175+
== """> 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>"""
171176
)
172177

173178
assert mocked_api["project_link1"].called

tests/publish/models/test_name.py

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,7 @@ async def test_pypi_project_name_invalid(mocked_api: MockRouter) -> None:
2626
assert mocked_api["homepage"].called
2727

2828

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

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

5149

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

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

7270
assert mocked_api["project_link1"].called
7371
assert mocked_api["homepage"].called
72+
73+
74+
async def test_name_too_long(mocked_api: MockRouter) -> None:
75+
"""测试名称过长的情况"""
76+
from src.plugins.publish.validation import AdapterPublishInfo
77+
78+
with pytest.raises(ValidationError) as e:
79+
AdapterPublishInfo(
80+
module_name="module_name",
81+
project_link="project_link",
82+
name="looooooooooooooooooooooooooooooooooooooooooooooooooooooooong",
83+
desc="desc",
84+
author="author",
85+
homepage="https://nonebot.dev",
86+
tags=json.dumps([]), # type: ignore
87+
is_official=False,
88+
)
89+
assert "⚠️ 名称过长。<dt>请确保名称不超过 50 个字符。</dt>" in str(e.value)
90+
91+
assert mocked_api["project_link"].called
92+
assert mocked_api["homepage"].called

tests/publish/process/test_publish_check.py

Lines changed: 147 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
from pathlib import Path
33
from typing import Any, cast
44

5+
import httpx
6+
from githubkit import Response
7+
from githubkit.exception import RequestFailed
58
from nonebot import get_adapter
69
from nonebot.adapters.github import (
710
Adapter,
@@ -206,7 +209,7 @@ async def test_edit_title(
206209
) -> None:
207210
"""测试编辑标题
208211
209-
插件名被修改后,标题也应该被修改
212+
名称被修改后,标题也应该被修改
210213
"""
211214
from src.plugins.publish import publish_check_matcher
212215
from src.plugins.publish.config import plugin_config
@@ -242,7 +245,7 @@ async def test_edit_title(
242245
mock_pull = mocker.MagicMock()
243246
mock_pull.number = 2
244247
mock_pulls_resp = mocker.MagicMock()
245-
mock_pulls_resp.parsed_data = mock_pull
248+
mock_pulls_resp.parsed_data = [mock_pull]
246249

247250
with open(tmp_path / "bots.json", "w") as f:
248251
json.dump([], f)
@@ -276,7 +279,6 @@ async def test_edit_title(
276279
{"owner": "he0119", "repo": "action-test", "issue_number": 80},
277280
mock_list_comments_resp,
278281
)
279-
# TODO: 抛出一个异常,然后执行修改拉取请求标题的逻辑
280282
ctx.should_call_api(
281283
"rest.pulls.async_create",
282284
{
@@ -287,19 +289,32 @@ async def test_edit_title(
287289
"base": "master",
288290
"head": "publish/issue80",
289291
},
292+
exception=RequestFailed(
293+
Response(
294+
httpx.Response(422, request=httpx.Request("test", "test")), None
295+
)
296+
),
297+
)
298+
ctx.should_call_api(
299+
"rest.pulls.async_list",
300+
{
301+
"owner": "he0119",
302+
"repo": "action-test",
303+
"head": "he0119:publish/issue80",
304+
},
290305
mock_pulls_resp,
291306
)
307+
# 修改标题
292308
ctx.should_call_api(
293-
"rest.issues.async_add_labels",
309+
"rest.pulls.async_update",
294310
{
295311
"owner": "he0119",
296312
"repo": "action-test",
297-
"issue_number": 2,
298-
"labels": ["Bot"],
313+
"pull_number": 2,
314+
"title": "Bot: test1",
299315
},
300316
True,
301317
)
302-
# 修改标题
303318
ctx.should_call_api(
304319
"rest.issues.async_update",
305320
{
@@ -396,6 +411,130 @@ async def test_edit_title(
396411
assert mocked_api["homepage"].called
397412

398413

414+
async def test_edit_title_too_long(
415+
app: App, mocker: MockerFixture, mocked_api: MockRouter, tmp_path: Path
416+
) -> None:
417+
"""测试编辑标题
418+
419+
标题过长的情况
420+
"""
421+
from src.plugins.publish import publish_check_matcher
422+
from src.plugins.publish.config import plugin_config
423+
424+
mock_subprocess_run = mocker.patch(
425+
"subprocess.run", side_effect=lambda *args, **kwargs: mocker.MagicMock()
426+
)
427+
428+
mock_installation = mocker.MagicMock()
429+
mock_installation.id = 123
430+
mock_installation_resp = mocker.MagicMock()
431+
mock_installation_resp.parsed_data = mock_installation
432+
433+
mock_issue = mocker.MagicMock()
434+
mock_issue.pull_request = None
435+
mock_issue.title = "Bot: test"
436+
mock_issue.number = 80
437+
mock_issue.state = "open"
438+
mock_issue.body = generate_issue_body_bot(
439+
name="looooooooooooooooooooooooooooooooooooooooooooooooooooooong"
440+
)
441+
mock_issue.user.login = "test"
442+
443+
mock_event = mocker.MagicMock()
444+
mock_event.issue = mock_issue
445+
446+
mock_issues_resp = mocker.MagicMock()
447+
mock_issues_resp.parsed_data = mock_issue
448+
449+
mock_comment = mocker.MagicMock()
450+
mock_comment.body = "Bot: test"
451+
mock_list_comments_resp = mocker.MagicMock()
452+
mock_list_comments_resp.parsed_data = [mock_comment]
453+
454+
mock_pull = mocker.MagicMock()
455+
mock_pull.number = 2
456+
mock_pulls_resp = mocker.MagicMock()
457+
mock_pulls_resp.parsed_data = [mock_pull]
458+
459+
with open(tmp_path / "bots.json", "w") as f:
460+
json.dump([], f)
461+
462+
check_json_data(plugin_config.input_config.bot_path, [])
463+
464+
async with app.test_matcher(publish_check_matcher) as ctx:
465+
adapter = get_adapter(Adapter)
466+
bot = ctx.create_bot(
467+
base=GitHubBot,
468+
adapter=adapter,
469+
self_id=GitHubApp(app_id="1", private_key="1"), # type: ignore
470+
)
471+
bot = cast(GitHubBot, bot)
472+
event_path = Path(__file__).parent.parent / "events" / "issue-open.json"
473+
event = Adapter.payload_to_event("1", "issues", event_path.read_bytes())
474+
assert isinstance(event, IssuesOpened)
475+
476+
ctx.should_call_api(
477+
"rest.apps.async_get_repo_installation",
478+
{"owner": "he0119", "repo": "action-test"},
479+
mock_installation_resp,
480+
)
481+
ctx.should_call_api(
482+
"rest.issues.async_get",
483+
{"owner": "he0119", "repo": "action-test", "issue_number": 80},
484+
mock_issues_resp,
485+
)
486+
ctx.should_call_api(
487+
"rest.issues.async_list_comments",
488+
{"owner": "he0119", "repo": "action-test", "issue_number": 80},
489+
mock_list_comments_resp,
490+
)
491+
# 修改标题,应该被阶段,且不会更新拉取请求的标题
492+
ctx.should_call_api(
493+
"rest.issues.async_update",
494+
{
495+
"owner": "he0119",
496+
"repo": "action-test",
497+
"issue_number": 80,
498+
"title": "Bot: looooooooooooooooooooooooooooooooooooooooooooooooo",
499+
},
500+
True,
501+
)
502+
503+
ctx.should_call_api(
504+
"rest.issues.async_list_comments",
505+
{"owner": "he0119", "repo": "action-test", "issue_number": 80},
506+
mock_list_comments_resp,
507+
)
508+
ctx.should_call_api(
509+
"rest.issues.async_create_comment",
510+
{
511+
"owner": "he0119",
512+
"repo": "action-test",
513+
"issue_number": 80,
514+
"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""",
515+
},
516+
True,
517+
)
518+
519+
ctx.receive_event(bot, event)
520+
521+
# 测试 git 命令
522+
mock_subprocess_run.assert_has_calls(
523+
[
524+
mocker.call(
525+
["git", "config", "--global", "safe.directory", "*"],
526+
check=True,
527+
capture_output=True,
528+
) # type: ignore
529+
]
530+
)
531+
532+
# 检查文件是否正确
533+
check_json_data(plugin_config.input_config.bot_path, [])
534+
535+
assert mocked_api["homepage"].called
536+
537+
399538
async def test_process_publish_check_not_pass(
400539
app: App, mocker: MockerFixture, mocked_api: MockRouter, tmp_path: Path
401540
) -> None:
@@ -767,3 +906,4 @@ async def test_skip_plugin_check(
767906
check_json_data(plugin_config.input_config.plugin_path, [])
768907

769908
assert mocked_api["project_link"].called
909+
assert mocked_api["project_link"].called

0 commit comments

Comments
 (0)