mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-11-03 13:55:00 +08:00
Merge pull request #7050 from DDSRem-Dev/codex-comment-migration
docs: 统一全项目中文注释和结构化契约说明
This commit is contained in:
@@ -1,3 +1,7 @@
|
||||
"""
|
||||
验证 API 的普通用户、管理员及敏感操作权限边界。
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import inspect
|
||||
import io
|
||||
@@ -35,12 +39,16 @@ from app.schemas.types import SystemConfigKey
|
||||
|
||||
|
||||
def _dependency_of(func, parameter_name: str):
|
||||
"""读取 FastAPI 函数参数上声明的依赖函数。"""
|
||||
"""
|
||||
读取 FastAPI 函数参数上声明的依赖函数。
|
||||
"""
|
||||
return inspect.signature(func).parameters[parameter_name].default.dependency
|
||||
|
||||
|
||||
def _build_request() -> Request:
|
||||
"""构造最小测试请求。"""
|
||||
"""
|
||||
构造最小测试请求。
|
||||
"""
|
||||
return Request(
|
||||
{
|
||||
"type": "http",
|
||||
@@ -55,7 +63,9 @@ def _build_request() -> Request:
|
||||
|
||||
|
||||
def test_system_sensitive_read_endpoints_require_superuser():
|
||||
"""系统敏感读取接口必须只允许管理员访问。"""
|
||||
"""
|
||||
系统敏感读取接口必须只允许管理员访问。
|
||||
"""
|
||||
assert _dependency_of(system_endpoint.get_env_setting, "_") is get_current_active_superuser_async
|
||||
assert _dependency_of(system_endpoint.get_setting, "_") is get_current_active_superuser_async
|
||||
assert _dependency_of(system_endpoint.query_settings, "_") is get_current_active_superuser_async
|
||||
@@ -67,7 +77,9 @@ def test_system_sensitive_read_endpoints_require_superuser():
|
||||
|
||||
|
||||
def test_github_token_endpoints_require_superuser():
|
||||
"""GitHub Token 状态和写入接口必须只允许管理员调用。"""
|
||||
"""
|
||||
GitHub Token 状态和写入接口必须只允许管理员调用。
|
||||
"""
|
||||
endpoints = [
|
||||
github_endpoint.get_community_profile,
|
||||
github_endpoint.get_auth_status,
|
||||
@@ -81,14 +93,18 @@ def test_github_token_endpoints_require_superuser():
|
||||
|
||||
|
||||
def test_system_public_read_endpoints_require_active_user():
|
||||
"""公开读取接口只要求登录且启用的用户。"""
|
||||
"""
|
||||
公开读取接口只要求登录且启用的用户。
|
||||
"""
|
||||
assert _dependency_of(system_endpoint.ping, "_") is get_current_active_user_async
|
||||
assert _dependency_of(system_endpoint.get_public_setting, "_") is get_current_active_user_async
|
||||
assert _dependency_of(storage_endpoint.storage_options, "_") is get_current_active_user
|
||||
|
||||
|
||||
def test_rule_query_and_mutation_endpoints_keep_separate_permissions():
|
||||
"""规则查询允许活动用户,规则定义修改仍只允许管理员。"""
|
||||
"""
|
||||
规则查询允许活动用户,规则定义修改仍只允许管理员。
|
||||
"""
|
||||
read_endpoints = [
|
||||
rule_endpoint.query_builtin_rules,
|
||||
rule_endpoint.query_custom_rules,
|
||||
@@ -112,7 +128,9 @@ def test_rule_query_and_mutation_endpoints_keep_separate_permissions():
|
||||
|
||||
|
||||
def test_rule_group_mutation_routes_accept_slashes_in_group_names():
|
||||
"""规则组名称允许包含斜杠时,更新和删除路由必须接收完整名称。"""
|
||||
"""
|
||||
规则组名称允许包含斜杠时,更新和删除路由必须接收完整名称。
|
||||
"""
|
||||
routes = {
|
||||
(route.endpoint, frozenset(route.methods)): route.path
|
||||
for route in rule_endpoint.router.routes
|
||||
@@ -124,7 +142,9 @@ def test_rule_group_mutation_routes_accept_slashes_in_group_names():
|
||||
|
||||
|
||||
def test_dashboard_endpoints_require_superuser():
|
||||
"""仪表板页面相关接口必须只允许管理员访问。"""
|
||||
"""
|
||||
仪表板页面相关接口必须只允许管理员访问。
|
||||
"""
|
||||
assert _dependency_of(dashboard_endpoint.statistic, "_") is get_current_active_superuser
|
||||
assert _dependency_of(dashboard_endpoint.storage, "_") is get_current_active_superuser
|
||||
assert _dependency_of(dashboard_endpoint.processes, "_") is get_current_active_superuser
|
||||
@@ -138,7 +158,9 @@ def test_dashboard_endpoints_require_superuser():
|
||||
|
||||
|
||||
def test_plugin_dashboard_endpoints_require_superuser():
|
||||
"""插件仪表板接口必须只允许管理员访问。"""
|
||||
"""
|
||||
插件仪表板接口必须只允许管理员访问。
|
||||
"""
|
||||
assert _dependency_of(plugin_endpoint.plugin_dashboard_meta, "_") is get_current_active_superuser
|
||||
assert _dependency_of(plugin_endpoint.plugin_dashboard_by_key, "_") is get_current_active_superuser
|
||||
assert _dependency_of(plugin_endpoint.plugin_dashboard, "_") is get_current_active_superuser
|
||||
@@ -148,25 +170,33 @@ def test_plugin_dashboard_endpoints_require_superuser():
|
||||
|
||||
|
||||
def test_site_destructive_commands_require_superuser():
|
||||
"""CookieCloud 同步和站点重置必须保持超级管理员边界。"""
|
||||
"""
|
||||
CookieCloud 同步和站点重置必须保持超级管理员边界。
|
||||
"""
|
||||
assert _dependency_of(site_endpoint.cookie_cloud_sync, "_") is get_current_active_superuser_async
|
||||
assert _dependency_of(site_endpoint.reset, "_") is get_current_active_superuser_async
|
||||
|
||||
|
||||
def test_transfer_history_clear_requires_superuser():
|
||||
"""清空全部旧整理历史必须保持超级管理员边界。"""
|
||||
"""
|
||||
清空全部旧整理历史必须保持超级管理员边界。
|
||||
"""
|
||||
assert _dependency_of(history_endpoint.clear_transfer_history, "_") is get_current_active_superuser
|
||||
assert _dependency_of(history_endpoint.empty_transfer_history, "_") is get_current_active_superuser
|
||||
|
||||
|
||||
def test_webpush_send_requires_superuser_and_subscribe_accepts_login_user():
|
||||
"""主动推送到全部浏览器订阅只允许管理员,订阅本身保持登录用户可用。"""
|
||||
"""
|
||||
主动推送到全部浏览器订阅只允许管理员,订阅本身保持登录用户可用。
|
||||
"""
|
||||
assert _dependency_of(message_endpoint.send_notification, "_") is get_current_active_superuser
|
||||
assert _dependency_of(message_endpoint.subscribe, "_") is verify_token
|
||||
|
||||
|
||||
def test_manage_page_endpoints_accept_manage_permission():
|
||||
"""管理页面接口允许具备 manage 权限的普通用户访问。"""
|
||||
"""
|
||||
管理页面接口允许具备 manage 权限的普通用户访问。
|
||||
"""
|
||||
sync_endpoints = [
|
||||
storage_endpoint.directory_settings,
|
||||
storage_endpoint.list_files,
|
||||
@@ -207,14 +237,20 @@ def test_manage_page_endpoints_accept_manage_permission():
|
||||
|
||||
|
||||
def test_system_public_setting_allows_only_non_sensitive_keys(monkeypatch):
|
||||
"""公开系统设置接口只能读取明确列入白名单的非敏感配置。"""
|
||||
"""
|
||||
公开系统设置接口只能读取明确列入白名单的非敏感配置。
|
||||
"""
|
||||
calls = []
|
||||
|
||||
class FakeSystemConfigOper:
|
||||
"""返回测试配置值的系统配置桩。"""
|
||||
"""
|
||||
返回测试配置值的系统配置桩。
|
||||
"""
|
||||
|
||||
def get(self, key):
|
||||
"""返回测试配置值。"""
|
||||
"""
|
||||
返回测试配置值。
|
||||
"""
|
||||
calls.append(key)
|
||||
return [{"path": "/downloads"}]
|
||||
|
||||
@@ -244,7 +280,9 @@ def test_system_public_setting_allows_only_non_sensitive_keys(monkeypatch):
|
||||
|
||||
|
||||
def test_system_public_setting_storages_returns_name_and_type_only(monkeypatch):
|
||||
"""公开设置读取存储时只返回名称和类型,与 /storage/options 一致,不含连接配置。"""
|
||||
"""
|
||||
公开设置读取存储时只返回名称和类型,与 /storage/options 一致,不含连接配置。
|
||||
"""
|
||||
storages = [
|
||||
{"type": "local", "name": "本地存储", "config": {}},
|
||||
{
|
||||
@@ -288,7 +326,9 @@ def test_system_public_setting_storages_returns_name_and_type_only(monkeypatch):
|
||||
],
|
||||
)
|
||||
def test_system_public_setting_returns_other_keys_as_stored(monkeypatch, config_key):
|
||||
"""存储以外的公开配置仍按原值返回。"""
|
||||
"""
|
||||
存储以外的公开配置仍按原值返回。
|
||||
"""
|
||||
stored = [{"name": "配置项", "config": {"nested": True}}]
|
||||
monkeypatch.setattr(
|
||||
system_endpoint,
|
||||
@@ -303,20 +343,28 @@ def test_system_public_setting_returns_other_keys_as_stored(monkeypatch, config_
|
||||
|
||||
|
||||
def test_system_ping_returns_success():
|
||||
"""服务存活检测接口返回标准成功响应。"""
|
||||
"""
|
||||
服务存活检测接口返回标准成功响应。
|
||||
"""
|
||||
response = asyncio.run(system_endpoint.ping())
|
||||
|
||||
assert response.success is True
|
||||
|
||||
|
||||
def test_login_sets_resource_token_cookie(monkeypatch):
|
||||
"""登录成功时应立即写入资源 Cookie,避免插件静态文件抢先加载失败。"""
|
||||
"""
|
||||
登录成功时应立即写入资源 Cookie,避免插件静态文件抢先加载失败。
|
||||
"""
|
||||
|
||||
class FakeUserChain:
|
||||
"""返回登录成功用户的用户链桩。"""
|
||||
"""
|
||||
返回登录成功用户的用户链桩。
|
||||
"""
|
||||
|
||||
def user_authenticate(self, username, password, mfa_code=None):
|
||||
"""返回认证成功结果。"""
|
||||
"""
|
||||
返回认证成功结果。
|
||||
"""
|
||||
return True, SimpleNamespace(
|
||||
id=1,
|
||||
name=username,
|
||||
@@ -348,14 +396,20 @@ def test_login_sets_resource_token_cookie(monkeypatch):
|
||||
|
||||
|
||||
def test_plugin_static_file_requires_resource_token_by_default(monkeypatch):
|
||||
"""普通插件静态资源必须校验资源令牌。"""
|
||||
"""
|
||||
普通插件静态资源必须校验资源令牌。
|
||||
"""
|
||||
calls = []
|
||||
|
||||
class FakePluginManager:
|
||||
"""返回空认证提供方的插件管理器桩。"""
|
||||
"""
|
||||
返回空认证提供方的插件管理器桩。
|
||||
"""
|
||||
|
||||
def get_plugin_auth_providers(self):
|
||||
"""返回插件认证入口列表。"""
|
||||
"""
|
||||
返回插件认证入口列表。
|
||||
"""
|
||||
return []
|
||||
|
||||
monkeypatch.setattr(plugin_endpoint, "get_plugin_manager", FakePluginManager)
|
||||
@@ -371,14 +425,20 @@ def test_plugin_static_file_requires_resource_token_by_default(monkeypatch):
|
||||
|
||||
|
||||
def test_plugin_auth_remote_accepts_versioned_entry_url(monkeypatch):
|
||||
"""认证远程入口带缓存版本参数时仍允许匿名读取。"""
|
||||
"""
|
||||
认证远程入口带缓存版本参数时仍允许匿名读取。
|
||||
"""
|
||||
calls = []
|
||||
|
||||
class FakePluginManager:
|
||||
"""返回带版本参数的认证远程入口桩。"""
|
||||
"""
|
||||
返回带版本参数的认证远程入口桩。
|
||||
"""
|
||||
|
||||
def get_plugin_auth_providers(self):
|
||||
"""返回认证远程入口列表。"""
|
||||
"""
|
||||
返回认证远程入口列表。
|
||||
"""
|
||||
return [{
|
||||
"remote": {
|
||||
"id": "AuthPlugin",
|
||||
@@ -399,14 +459,20 @@ def test_plugin_auth_remote_accepts_versioned_entry_url(monkeypatch):
|
||||
|
||||
|
||||
def test_plugin_auth_remote_files_allow_anonymous_bootstrap(monkeypatch):
|
||||
"""插件登录认证远程组件需要允许登录前匿名加载。"""
|
||||
"""
|
||||
插件登录认证远程组件需要允许登录前匿名加载。
|
||||
"""
|
||||
calls = []
|
||||
|
||||
class FakePluginManager:
|
||||
"""返回认证插件 remote 信息的插件管理器桩。"""
|
||||
"""
|
||||
返回认证插件 remote 信息的插件管理器桩。
|
||||
"""
|
||||
|
||||
def get_plugin_auth_providers(self):
|
||||
"""返回插件认证入口列表。"""
|
||||
"""
|
||||
返回插件认证入口列表。
|
||||
"""
|
||||
return [
|
||||
{
|
||||
"remote": {
|
||||
@@ -436,7 +502,9 @@ def test_plugin_auth_remote_files_allow_anonymous_bootstrap(monkeypatch):
|
||||
|
||||
|
||||
def test_upload_avatar_rejects_other_user_for_non_superuser():
|
||||
"""普通用户不能通过 user_id 参数修改其他用户头像。"""
|
||||
"""
|
||||
普通用户不能通过 user_id 参数修改其他用户头像。
|
||||
"""
|
||||
current_user = SimpleNamespace(id=1, is_superuser=False)
|
||||
upload_file = SimpleNamespace(file=io.BytesIO(b"avatar"), filename="avatar.png")
|
||||
|
||||
@@ -455,22 +523,30 @@ def test_upload_avatar_rejects_other_user_for_non_superuser():
|
||||
|
||||
|
||||
def test_upload_avatar_returns_filename_in_data(monkeypatch):
|
||||
"""头像上传成功时应通过 data 返回文件名,message 只保留消息文本。"""
|
||||
"""
|
||||
头像上传成功时应通过 data 返回文件名,message 只保留消息文本。
|
||||
"""
|
||||
|
||||
fake_user = SimpleNamespace()
|
||||
current_user = SimpleNamespace(id=1, is_superuser=False)
|
||||
upload_file = SimpleNamespace(file=io.BytesIO(b"avatar"), filename="avatar.png")
|
||||
|
||||
class FakeService:
|
||||
"""记录头像查询和更新的用户服务桩。"""
|
||||
"""
|
||||
记录头像查询和更新的用户服务桩。
|
||||
"""
|
||||
|
||||
async def get_by_id(self, user_id: int):
|
||||
"""按 ID 返回测试用户。"""
|
||||
"""
|
||||
按 ID 返回测试用户。
|
||||
"""
|
||||
assert user_id == 1
|
||||
return fake_user
|
||||
|
||||
async def update(self, user_id: int, values: dict[str, str]):
|
||||
"""记录用户头像更新。"""
|
||||
"""
|
||||
记录用户头像更新。
|
||||
"""
|
||||
assert user_id == 1
|
||||
fake_user.values = values
|
||||
return fake_user
|
||||
|
||||
Reference in New Issue
Block a user