diff --git a/app/api/endpoints/user.py b/app/api/endpoints/user.py index 2fde892a..4a256641 100644 --- a/app/api/endpoints/user.py +++ b/app/api/endpoints/user.py @@ -119,7 +119,7 @@ async def upload_avatar( if not user: return schemas.Response(success=False, message="用户不存在") await user.async_update(db, {"avatar": f"data:image/ico;base64,{file_base64}"}) - return schemas.Response(success=True, message=file.filename) + return schemas.Response(success=True, data={"filename": file.filename}) @router.get("/config/{key}", summary="查询用户配置", response_model=schemas.Response) diff --git a/docs/mcp-api.md b/docs/mcp-api.md index c588568b..ae8108d6 100644 --- a/docs/mcp-api.md +++ b/docs/mcp-api.md @@ -124,7 +124,7 @@ MoviePilot 也提供普通 REST API 给前端和自动化客户端使用。所 通用 REST 响应包含 `success`、`message`、`message_i18n`、`data` 字段。为兼容 App 和第三方客户端,`message` 继续保留原中文或原始后端文本;新版前端可发送 `X-MoviePilot-Locale: zh-CN|zh-TW|en-US` 或 `Accept-Language`,并优先展示 `message_i18n`。未提供语言头或翻译缺失时,`message_i18n` 会回退为原文本。 -`GET /api/v1/login/wallpaper` 及对应的 v2 路径会将壁纸 URL 放在 `data` 字段中,`message` 不再承载业务数据。 +`GET /api/v1/login/wallpaper` 及对应的 v2 路径会将壁纸 URL 放在 `data` 字段中。`POST /api/v1/user/avatar/{user_id}` 及对应的 v2 路径会以 `data.filename` 返回原始文件名。上述接口的 `message` 均不再承载业务数据。 FastAPI 的 HTTP 异常在 v1、v2 均统一使用 `message`,不再返回顶层 `detail` / `detail_i18n`。 diff --git a/skills/moviepilot-api/SKILL.md b/skills/moviepilot-api/SKILL.md index 45d3eb3c..db53c682 100644 --- a/skills/moviepilot-api/SKILL.md +++ b/skills/moviepilot-api/SKILL.md @@ -1,6 +1,6 @@ --- name: moviepilot-api -version: 8 +version: 9 description: >- Use this skill when you need to call MoviePilot REST API endpoints directly with the bundled Python client. Covers MoviePilot HTTP endpoints across media @@ -501,7 +501,7 @@ The list endpoint returns local cache totals plus `shared_recognized` and | GET | `/api/v1/user/{username}` | User detail | | DELETE | `/api/v1/user/id/{user_id}` | Delete user by ID | | DELETE | `/api/v1/user/name/{user_name}` | Delete user by username | -| POST | `/api/v1/user/avatar/{user_id}` | Upload avatar. Body: multipart/form-data | +| POST | `/api/v1/user/avatar/{user_id}` | Upload avatar. Body: multipart/form-data; original filename is returned in `data.filename` | | GET | `/api/v1/user/config/{key}` | Get user config | | POST | `/api/v1/user/config/{key}` | Update user config | diff --git a/tests/test_api_authorization.py b/tests/test_api_authorization.py index 8fae0c65..c010e2d5 100644 --- a/tests/test_api_authorization.py +++ b/tests/test_api_authorization.py @@ -284,3 +284,46 @@ def test_upload_avatar_rejects_other_user_for_non_superuser(): assert exc_info.value.status_code == 400 assert exc_info.value.detail == "用户权限不足" + + +def test_upload_avatar_returns_filename_in_data(monkeypatch): + """头像上传成功时应通过 data 返回文件名,message 只保留消息文本。""" + + class FakeUser: + """记录头像更新内容的用户桩。""" + + def __init__(self): + self.values = None + + async def async_update(self, db: object, values: dict[str, str]) -> None: + """记录待写入的头像数据。""" + self.values = values + + class FakeUserModel: + """返回固定用户的模型桩。""" + + @classmethod + async def async_get(cls, db: object, user_id: int) -> FakeUser: + """按用户 ID 返回测试用户。""" + assert user_id == 1 + return fake_user + + fake_user = FakeUser() + current_user = SimpleNamespace(id=1, is_superuser=False) + upload_file = SimpleNamespace(file=io.BytesIO(b"avatar"), filename="avatar.png") + monkeypatch.setattr(user_endpoint, "User", FakeUserModel) + + response = asyncio.run( + user_endpoint.upload_avatar( + user_id=1, + db=object(), + file=upload_file, + current_user=current_user, + ) + ) + + assert response.success is True + assert response.data == {"filename": "avatar.png"} + assert response.message is None + assert response.message_i18n is None + assert fake_user.values == {"avatar": "data:image/ico;base64,b'YXZhdGFy'"} diff --git a/tests/test_builtin_skill_boundaries.py b/tests/test_builtin_skill_boundaries.py index a62138dc..31404e58 100644 --- a/tests/test_builtin_skill_boundaries.py +++ b/tests/test_builtin_skill_boundaries.py @@ -23,7 +23,7 @@ def test_modified_builtin_skills_have_incremented_versions() -> None: """本次修改过的内置技能必须递增版本,确保用户端同步更新。""" expected_versions = { "database-operation": "3", - "moviepilot-api": "8", + "moviepilot-api": "9", "moviepilot-cli": "6", "moviepilot-update": "3", "transfer-failed-retry": "2",