Skip to content

Commit 644656c

Browse files
fix(docker_test): 修复插件元数据异常时导致插件测试验证报错的问题 (#283)
* fix(docker_test): 修复插件元数据异常时导致插件测试验证报错的问题 * style: auto fix by pre-commit hooks --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 53c3e63 commit 644656c

File tree

12 files changed

+215
-86
lines changed

12 files changed

+215
-86
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+
### Fixed
11+
12+
- 修复插件元数据异常时导致插件测试验证报错的问题
13+
1014
## [4.0.7] - 2024-11-20
1115

1216
### Fixed

src/plugins/github/plugins/publish/render.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ async def render_summary(test_result: DockerTestResult, output: str, project_lin
102102
version=test_result.version,
103103
load=test_result.load,
104104
run=test_result.run,
105-
metadata=dumps_json(test_result.metadata.model_dump(), False)
105+
metadata=dumps_json(test_result.metadata, False)
106106
if test_result.metadata
107107
else {},
108108
output=output,

src/plugins/github/plugins/publish/validation.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ async def validate_plugin_info_from_issue(
108108
metadata = test_result.metadata
109109
if metadata:
110110
# 从插件测试结果中获得元数据
111-
raw_data.update(metadata.model_dump())
111+
raw_data.update(metadata)
112112

113113
raw_data["load"] = test_result.load
114114
raw_data["test_output"] = test_output
@@ -130,7 +130,7 @@ async def validate_plugin_info_from_issue(
130130
if not result.valid_data.get("metadata") and not skip_test:
131131
# 如果没有跳过测试且缺少插件元数据,则跳过元数据相关的错误
132132
# 因为这个时候这些项都会报错,错误在此时没有意义
133-
metadata_keys = Metadata.model_fields.keys()
133+
metadata_keys = Metadata.__annotations__.keys()
134134
# 如果是重复报错,error["loc"] 是 ()
135135
result.errors = [
136136
error

src/providers/docker_test/__init__.py

Lines changed: 7 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,20 @@
11
import json
2-
from typing import Any
2+
from typing import TypedDict
33

44
import docker
5-
from pydantic import BaseModel, Field, field_validator, model_validator
6-
from pydantic_core import PydanticCustomError
7-
from pyjson5 import Json5DecoderException
5+
from pydantic import BaseModel, Field, SkipValidation, field_validator
86

97
from src.providers.constants import DOCKER_IMAGES, REGISTRY_PLUGINS_URL
10-
from src.providers.utils import load_json
118

129

13-
class Metadata(BaseModel):
10+
class Metadata(TypedDict):
1411
"""插件元数据"""
1512

1613
name: str
1714
desc: str
18-
homepage: str | None = None
19-
type: str | None = None
20-
supported_adapters: list[str] | None = None
21-
22-
@model_validator(mode="before")
23-
@classmethod
24-
def model_validator(cls, data: dict[str, Any]):
25-
if data.get("desc") is None:
26-
data["desc"] = data.get("description")
27-
return data
28-
29-
@field_validator("supported_adapters", mode="before")
30-
@classmethod
31-
def supported_adapters_validator(cls, v: list[str] | str | None):
32-
if isinstance(v, str):
33-
try:
34-
v = load_json(v)
35-
except Json5DecoderException:
36-
raise PydanticCustomError("json_type", "JSON 格式不合法")
37-
38-
return v
15+
homepage: str | None
16+
type: str | None
17+
supported_adapters: list[str] | None
3918

4019

4120
class DockerTestResult(BaseModel):
@@ -47,16 +26,9 @@ class DockerTestResult(BaseModel):
4726
config: str = ""
4827
# 测试环境 python==3.10 pytest==6.2.5 nonebot2==2.0.0a1 ...
4928
test_env: str = Field(default="unknown")
50-
metadata: Metadata | None
29+
metadata: SkipValidation[Metadata] | None
5130
outputs: list[str]
5231

53-
@field_validator("metadata", mode="before")
54-
@classmethod
55-
def metadata_validator(cls, v: Any):
56-
if v:
57-
return v
58-
return None
59-
6032
@field_validator("config", mode="before")
6133
@classmethod
6234
def config_validator(cls, v: str | None):

src/providers/docker_test/plugin_test.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -100,17 +100,16 @@ def get_session(
100100
"""
101101

102102
RUNNER_SCRIPT = """import json
103-
import os
104103
105104
from nonebot import init, load_plugin, logger, require
106105
from pydantic import BaseModel
107106
108107
109108
class SetEncoder(json.JSONEncoder):
110-
def default(self, obj):
111-
if isinstance(obj, set):
112-
return list(obj)
113-
return json.JSONEncoder.default(self, obj)
109+
def default(self, o):
110+
if isinstance(o, set):
111+
return list(o)
112+
return json.JSONEncoder.default(self, o)
114113
115114
116115
init()
@@ -122,7 +121,7 @@ def default(self, obj):
122121
if plugin.metadata:
123122
metadata = {{
124123
"name": plugin.metadata.name,
125-
"description": plugin.metadata.description,
124+
"desc": plugin.metadata.description,
126125
"usage": plugin.metadata.usage,
127126
"type": plugin.metadata.type,
128127
"homepage": plugin.metadata.homepage,
@@ -264,7 +263,7 @@ async def run(self):
264263
)
265264
await self.run_poetry_project()
266265

267-
metadata = {}
266+
metadata = None
268267
metadata_path = self.path / "metadata.json"
269268
if metadata_path.exists():
270269
with open(self.path / "metadata.json", encoding="utf-8") as f:
@@ -373,7 +372,7 @@ async def run_poetry_project(self) -> None:
373372
f.write(
374373
RUNNER_SCRIPT.format(
375374
self.module_name,
376-
"\n".join([f"require('{i}')" for i in self._deps]),
375+
"\n".join([f'require("{i}")' for i in self._deps]),
377376
)
378377
)
379378

src/providers/models.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,7 @@ def from_info(cls, info: PluginPublishInfo) -> Self:
353353
homepage=info.homepage,
354354
type=info.type,
355355
supported_adapters=info.supported_adapters,
356-
).model_dump(),
356+
),
357357
},
358358
)
359359

src/providers/store_test/validation.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ async def validate_plugin(
8282
# 使用最新的插件元数据更新插件信息
8383
raw_data["metadata"] = bool(plugin_metadata)
8484
if plugin_metadata:
85-
raw_data.update(plugin_metadata.model_dump())
85+
raw_data.update(plugin_metadata)
8686

8787
# 通过 Github API 获取插件作者名称
8888
try:

tests/utils/docker_test/test_docker_plugin_test.py

Lines changed: 70 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ async def test_docker_plugin_test(mocked_api: MockRouter, mocker: MockerFixture)
1111
mocked_run = mocker.Mock()
1212
mocked_run.return_value = json.dumps(
1313
{
14-
"metadata": {},
14+
"metadata": None,
1515
"outputs": ["test"],
1616
"load": True,
1717
"run": True,
@@ -58,7 +58,7 @@ async def test_docker_plugin_test_metadata_some_fields_empty(
5858
mocked_api: MockRouter, mocker: MockerFixture
5959
):
6060
"""测试 metadata 的部分字段为空"""
61-
from src.providers.docker_test import DockerPluginTest, DockerTestResult, Metadata
61+
from src.providers.docker_test import DockerPluginTest, DockerTestResult
6262

6363
mocked_run = mocker.Mock()
6464
mocked_run.return_value = json.dumps(
@@ -85,16 +85,80 @@ async def test_docker_plugin_test_metadata_some_fields_empty(
8585
test = DockerPluginTest("project_link", "module_name")
8686
result = await test.run("3.12")
8787

88+
assert result == snapshot(
89+
DockerTestResult(
90+
config="",
91+
load=True,
92+
metadata={
93+
"name": "name",
94+
"desc": "desc",
95+
"homepage": None,
96+
"type": None,
97+
"supported_adapters": None,
98+
},
99+
outputs=["test"],
100+
run=True,
101+
test_env="python==3.12",
102+
version="0.0.1",
103+
)
104+
)
105+
106+
assert not mocked_api["store_plugins"].called
107+
mocked_run.assert_called_once_with(
108+
"ghcr.io/nonebot/nonetest:3.12-latest",
109+
environment=snapshot(
110+
{
111+
"PLUGIN_INFO": "project_link:module_name",
112+
"PLUGIN_CONFIG": "",
113+
"PLUGINS_URL": "https://raw.githubusercontent.com/nonebot/registry/results/plugins.json",
114+
}
115+
),
116+
detach=False,
117+
remove=True,
118+
)
119+
120+
121+
async def test_docker_plugin_test_metadata_some_fields_invalid(
122+
mocked_api: MockRouter, mocker: MockerFixture
123+
):
124+
"""测试 metadata 的部分字段不符合规范"""
125+
from src.providers.docker_test import DockerPluginTest, DockerTestResult, Metadata
126+
127+
mocked_run = mocker.Mock()
128+
mocked_run.return_value = json.dumps(
129+
{
130+
"metadata": {
131+
"name": "name",
132+
"desc": "desc",
133+
"homepage": 12,
134+
"type": True,
135+
"supported_adapters": {},
136+
},
137+
"outputs": ["test"],
138+
"load": True,
139+
"run": True,
140+
"version": "0.0.1",
141+
"config": "",
142+
}
143+
).encode()
144+
mocked_client = mocker.Mock()
145+
mocked_client.containers.run = mocked_run
146+
mocked_docker = mocker.patch("docker.DockerClient")
147+
mocked_docker.return_value = mocked_client
148+
149+
test = DockerPluginTest("project_link", "module_name")
150+
result = await test.run("3.12")
151+
88152
assert result == snapshot(
89153
DockerTestResult(
90154
config="",
91155
load=True,
92156
metadata=Metadata(
93-
desc="desc",
94-
homepage=None,
95157
name="name",
96-
supported_adapters=None,
97-
type=None,
158+
desc="desc",
159+
homepage=12, # type: ignore
160+
type=True, # type: ignore
161+
supported_adapters={}, # type: ignore
98162
),
99163
outputs=["test"],
100164
run=True,

tests/utils/store_test/output.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"metadata": {
33
"name": "TREEHELP",
4-
"description": "\u8ba2\u9605\u725b\u5ba2/CF/AT\u5e73\u53f0\u7684\u6bd4\u8d5b\u4fe1\u606f",
4+
"desc": "\u8ba2\u9605\u725b\u5ba2/CF/AT\u5e73\u53f0\u7684\u6bd4\u8d5b\u4fe1\u606f",
55
"usage": "/contest.list \u83b7\u53d6\u6240\u6709/CF/\u725b\u5ba2/AT\u5e73\u53f0\u7684\u6bd4\u8d5b\u4fe1\u606f\n/contest.subscribe \u8ba2\u9605CF/\u725b\u5ba2/AT\u5e73\u53f0\u7684\u6bd4\u8d5b\u4fe1\u606f\n/contest.update \u624b\u52a8\u66f4\u65b0\u6bd4\u8d5b\u4fe1\u606f\n",
66
"type": "application",
77
"homepage": "https://nonebot.dev/",

tests/utils/store_test/output_failed.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"metadata": {},
2+
"metadata": null,
33
"outputs": [
44
"\u521b\u5efa\u6d4b\u8bd5\u76ee\u5f55 plugin_test",
55
" For further information visit https://errors.pydantic.dev/2.9/v/model_type\u001b[0m"

0 commit comments

Comments
 (0)