From f3a9410333fa68e35c63f62ab5919e595e6a73ac Mon Sep 17 00:00:00 2001 From: xiaoxia-agent Date: Tue, 15 Sep 2026 20:21:21 +0800 Subject: [PATCH] feat(points): P2 step 2.6 - wire ai_avatar_render points gate (ai_digital_human per_unit=15) --- apps/api/app/api/routes/ai_avatar_render.py | 3 ++ tests/unit/test_ai_avatar_render_points.py | 45 +++++++++++++++++++++ tests/unit/test_ai_avatar_render_routes.py | 10 +++++ 3 files changed, 58 insertions(+) create mode 100644 tests/unit/test_ai_avatar_render_points.py diff --git a/apps/api/app/api/routes/ai_avatar_render.py b/apps/api/app/api/routes/ai_avatar_render.py index 63f12ef6d..eceb30d24 100644 --- a/apps/api/app/api/routes/ai_avatar_render.py +++ b/apps/api/app/api/routes/ai_avatar_render.py @@ -14,6 +14,7 @@ import logging from datetime import UTC, datetime from app.auth import AuthenticatedUser, get_current_user +from packages.middleware.points_gate import points_gate from app.dependencies import get_db_session from app.schemas.ai_avatar_render import ( AiAvatarRenderJobResponse, @@ -42,10 +43,12 @@ def _get_service(db: Session = Depends(get_db_session)) -> AiAvatarRenderService @router.post("", response_model=AiAvatarRenderJobResponse, status_code=201) +@points_gate("ai_digital_human", per_unit=15) def create_render_job( body: CreateAiAvatarRenderRequest, current_user: AuthenticatedUser = Depends(get_current_user), svc: AiAvatarRenderService = Depends(_get_service), + db: Session = Depends(get_db_session), ): """提交 AI 数字人渲染任务. diff --git a/tests/unit/test_ai_avatar_render_points.py b/tests/unit/test_ai_avatar_render_points.py new file mode 100644 index 000000000..5299b1599 --- /dev/null +++ b/tests/unit/test_ai_avatar_render_points.py @@ -0,0 +1,45 @@ +"""AI数字人渲染 积分扣点单元测试 (#1895 P2 step 2.6)""" +from __future__ import annotations + +from unittest.mock import MagicMock, patch + +import pytest + +import packages.middleware.points_gate as _pg_module + + +@pytest.fixture(autouse=True) +def _enable(monkeypatch): + monkeypatch.setattr(_pg_module, "_points_gate_enabled", lambda: True) + yield + + +class TestAiAvatarRenderPoints: + def test_ai_digital_human_per_unit(self): + from packages.domain.points_rules import calculate_points_cost + cost = calculate_points_cost("ai_digital_human", is_member=False, duration_minutes=1) + assert cost >= 15 + + def test_decorator_attached(self): + from app.api.routes.ai_avatar_render import create_render_job + assert hasattr(create_render_job, "__wrapped__"), "missing @points_gate" + + def test_insufficient_raises_402(self): + from fastapi import HTTPException + from app.api.routes.ai_avatar_render import create_render_job + from app.schemas.ai_avatar_render import CreateAiAvatarRenderRequest + + db = MagicMock() + cu = MagicMock() + cu.user.id = "u1" + cu.user.is_member = False + cu.user.member_type = None + svc = MagicMock() + body = CreateAiAvatarRenderRequest(lipsync_job_id="lip1") + with patch("packages.domain.points_service.PointsService") as MS: + msvc = MagicMock() + msvc.deduct_points.return_value = {"success": False, "balance": 0} + MS.return_value = msvc + with pytest.raises(HTTPException) as ei: + create_render_job(body=body, current_user=cu, svc=svc, db=db) + assert ei.value.status_code == 402 diff --git a/tests/unit/test_ai_avatar_render_routes.py b/tests/unit/test_ai_avatar_render_routes.py index 78f632b00..542fe622e 100644 --- a/tests/unit/test_ai_avatar_render_routes.py +++ b/tests/unit/test_ai_avatar_render_routes.py @@ -10,6 +10,16 @@ from unittest.mock import MagicMock, patch import pytest +import packages.middleware.points_gate as _pg_module + + +@pytest.fixture(autouse=True) +def _disable_points_gate(monkeypatch): + """默认关闭积分闸门,避免影响既有用例。""" + monkeypatch.setattr(_pg_module, "_points_gate_enabled", lambda: False) + yield + + os.environ.setdefault("JWT_SECRET_KEY", "dev-secret-key-for-testing")