Files
MoviePilot/tests/test_explicit_response_models.py

125 lines
5.1 KiB
Python

"""API 响应中稳定集合与动态 JSON 字段的模型契约测试。"""
from app.schemas.file import FileItem, StorageTransType
from app.schemas.mediaserver import MediaServerLibrary, MediaServerPlayItem, NotExistMediaInfo
from app.schemas.plugin import Plugin, PluginDashboard
from app.schemas.site import SiteStatistic, SiteUserData
from app.schemas.subscribe import Subscribe
from app.schemas.tmdb import TmdbEpisode
from app.schemas.token import Token
from app.schemas.user import User
def _nonnull_branch(schema: dict) -> dict:
"""返回可空字段中非 null 的 JSON Schema 分支。"""
return next(
branch
for branch in schema.get("anyOf", [schema])
if branch.get("type") != "null"
)
def test_stable_collections_serialize_without_changing_payload_shape():
"""稳定集合收窄后应继续输出原有对象、数组及字段名称。"""
child = FileItem(type="file", name="episode.mkv")
file_item = FileItem(type="dir", name="Season 1", children=[child])
episode = TmdbEpisode(
episode_number=1,
crew=[{"id": 11, "name": "Writer", "job": "Writer"}],
guest_stars=[{"id": 12, "name": "Actor", "character": "Guest"}],
)
assert file_item.model_dump(mode="json")["children"][0]["name"] == "episode.mkv"
assert NotExistMediaInfo(episodes=[1, 2]).model_dump()["episodes"] == [1, 2]
assert MediaServerLibrary(path=["/movies", "/tv"]).model_dump()["path"] == [
"/movies",
"/tv",
]
assert MediaServerPlayItem(BackdropImageTags=["backdrop-tag"]).model_dump()[
"BackdropImageTags"
] == ["backdrop-tag"]
assert StorageTransType(transtype={"move": "移动"}).model_dump()["transtype"] == {
"move": "移动"
}
assert episode.model_dump()["crew"][0]["job"] == "Writer"
assert episode.model_dump()["guest_stars"][0]["character"] == "Guest"
def test_site_and_subscribe_legacy_values_remain_compatible():
"""站点消息的三项、四项协议以及订阅集号列表应继续兼容。"""
userdata = SiteUserData(
seeding_info=[[3, 1024]],
message_unread_contents=[
["标题", "日期", "正文"],
["标题", "日期", "正文", "sunnypt-message:1"],
],
)
payload = userdata.model_dump(mode="json")
assert payload["seeding_info"] == [[3, 1024]]
assert payload["message_unread_contents"] == [
["标题", "日期", "正文"],
["标题", "日期", "正文", "sunnypt-message:1"],
]
assert SiteStatistic(note={"2026-08-12 12:00:00": 2}).model_dump()["note"] == {
"2026-08-12 12:00:00": 2
}
assert Subscribe(note=[1, 2]).model_dump()["note"] == [1, 2]
def test_permissions_and_extension_json_keep_values_but_have_explicit_schemas():
"""权限映射应为布尔值,扩展数据应通过递归 JSON Schema 展示合法类型。"""
permissions = {"manage": True, "search": False}
token = Token(
access_token="token",
token_type="bearer",
super_user=False,
user_id=1,
user_name="tester",
permissions=permissions,
)
user = User(
name="tester",
permissions=permissions,
settings={"nickname": "测试", "layout": {"dense": True}},
)
plugin = Plugin(history={"v1.0.0": "首次发布"})
dashboard = PluginDashboard(
attrs={"class": ["pa-2", {"active": True}]},
cols={"md": 6},
elements=[{"component": "VAlert", "text": "状态正常"}],
)
assert token.model_dump()["permissions"] == permissions
assert user.model_dump()["settings"]["layout"] == {"dense": True}
assert plugin.model_dump()["history"] == {"v1.0.0": "首次发布"}
assert dashboard.model_dump()["elements"][0]["component"] == "VAlert"
user_schema = User.model_json_schema()
permission_schema = _nonnull_branch(user_schema["properties"]["permissions"])
settings_schema = _nonnull_branch(user_schema["properties"]["settings"])
assert permission_schema["additionalProperties"] == {"type": "boolean"}
assert settings_schema["additionalProperties"]["$ref"].endswith("/JsonData")
def test_collection_json_schemas_define_items_or_tuple_members():
"""集合字段在 OpenAPI 生成前就应声明元素或元组成员结构。"""
file_schema = FileItem.model_json_schema()
file_item_schema = file_schema["$defs"]["FileItem"]
children_schema = _nonnull_branch(file_item_schema["properties"]["children"])
site_schema = SiteUserData.model_json_schema()
seeding_schema = _nonnull_branch(site_schema["properties"]["seeding_info"])
messages_schema = _nonnull_branch(
site_schema["properties"]["message_unread_contents"]
)
episode_schema = TmdbEpisode.model_json_schema()
crew_schema = _nonnull_branch(episode_schema["properties"]["crew"])
assert children_schema["items"]["$ref"].endswith("/FileItem")
assert len(seeding_schema["items"]["prefixItems"]) == 2
assert all(
branch.get("prefixItems")
for branch in messages_schema["items"]["anyOf"]
)
assert crew_schema["items"]["$ref"].endswith("/TmdbEpisodeCrew")