From 4dfcac8d041c042c4c83cc246dd7d0f083725dd5 Mon Sep 17 00:00:00 2001 From: LIN student <86354789+MiCat-S@users.noreply.github.com> Date: Sat, 31 Oct 2026 16:02:12 +0800 Subject: [PATCH] fix(site): skip search parameters when listing site categories MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 站点分类接口只收集分类配置中的列表分组和字典项;field、delimiter、 param 等搜索请求参数不再被当成分类逐字符返回,避免响应校验失败导致 站点分类请求 500。 - 补充带搜索参数和非法分类项的回归测试,并校验结果符合接口响应模型。 Refs #7250 Co-Authored-By: Claude Opus 5.5 --- app/api/endpoints/site.py | 12 +++++++----- tests/test_api_optional_inputs.py | 24 ++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/app/api/endpoints/site.py b/app/api/endpoints/site.py index e7b621e4c..0cb4bc361 100644 --- a/app/api/endpoints/site.py +++ b/app/api/endpoints/site.py @@ -2,7 +2,7 @@ 提供站点配置、认证、数据查询和状态管理接口。 """ -from typing import Annotated, Any, List, Literal, Optional, cast +from typing import Annotated, Any, List, Literal, Optional from fastapi import Depends, HTTPException, Query, Response @@ -691,11 +691,13 @@ async def site_category( category_value = indexer.get("category") if not isinstance(category_value, dict): return [] - category = cast(dict[str, list[dict[str, Any]]], category_value) - result = [] - for cats in category.values(): + # field、delimiter、param 等是搜索请求参数,只有列表值才是分类项 + result: list[dict[str, Any]] = [] + for cats in category_value.values(): + if not isinstance(cats, list): + continue for cat in cats: - if cat not in result: + if isinstance(cat, dict) and cat not in result: result.append(cat) return result diff --git a/tests/test_api_optional_inputs.py b/tests/test_api_optional_inputs.py index 765e04388..bbec5e84e 100644 --- a/tests/test_api_optional_inputs.py +++ b/tests/test_api_optional_inputs.py @@ -8,12 +8,14 @@ from unittest.mock import AsyncMock, Mock import pytest from fastapi import HTTPException +from pydantic import TypeAdapter from app.api.endpoints import bangumi, download, history, mediaserver, message, search, site, tmdb, transfer from app.domain.context import MediaInfo from app.schemas.context import MediaInfo as MediaInfoInput from app.schemas.context import SubtitleInfo from app.schemas.file import FileItem +from app.schemas.site import SiteCategory from app.schemas.transfer import ManualTransferItem from app.schemas.types import MediaType @@ -124,6 +126,28 @@ def test_site_category_only_projects_category_mappings(category, monkeypatch): assert result == ([{"id": 1}, {"id": 2}] if isinstance(category, dict) else []) +def test_site_category_skips_search_parameter_settings(monkeypatch): + """ + 分类配置中的 field、delimiter、param 等搜索参数不能被当成分类项逐字符返回。 + """ + category = { + "field": "cat[]", + "delimiter": "&", + "param": "cat", + "movie": [{"id": 401, "cat": "Movies", "desc": "电影"}, "invalid"], + "tv": [{"id": 402, "cat": "TV", "desc": "剧集"}], + } + query = SimpleNamespace(get=AsyncMock(return_value=SimpleNamespace(domain="https://example.test"))) + helper = SimpleNamespace(async_get_indexer=AsyncMock(return_value={"category": category})) + monkeypatch.setattr(site, "SitesHelper", Mock(return_value=helper)) + result = asyncio.run(site.site_category(1, query=query, _=None)) + assert result == [{"id": 401, "cat": "Movies", "desc": "电影"}, {"id": 402, "cat": "TV", "desc": "剧集"}] + assert TypeAdapter(list[SiteCategory]).validate_python(result) == [ + SiteCategory(id=401, cat="Movies", desc="电影"), + SiteCategory(id=402, cat="TV", desc="剧集"), + ] + + @pytest.mark.parametrize("batch", [False, True]) @pytest.mark.parametrize("moved", [False, True]) def test_manual_transfer_history_without_file_snapshot_returns_business_error(batch, moved):