diff --git a/apps/api/app/api/routes/auth.py b/apps/api/app/api/routes/auth.py index 44cdaa6b8..5403c6809 100755 --- a/apps/api/app/api/routes/auth.py +++ b/apps/api/app/api/routes/auth.py @@ -13,7 +13,7 @@ from typing import Optional import jwt from app.auth import AuthenticatedUser, blacklist_token, get_current_user from app.config import settings -from app.dependencies import get_auth_email_service, get_auth_session_store, get_user_repository +from app.dependencies import get_auth_email_service, get_auth_session_store, get_db_session, get_user_repository from fastapi import APIRouter, Depends, Header, HTTPException, Request, status from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer from pydantic import BaseModel, EmailStr, field_validator @@ -126,6 +126,7 @@ async def register( request: RegisterRequest, user_repository: UserRepository = Depends(get_user_repository), email_service=Depends(get_auth_email_service), + db=Depends(get_db_session), ) -> RegisterResponse: use_case = RegisterUserUseCase( user_repository=user_repository, @@ -143,6 +144,22 @@ async def register( if error or response is None: raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=_translate_auth_error(error)) + # 新用户注册赠送 50 积分(失败不影响注册) + if settings.points_enabled: + try: + from packages.domain.points_service import PointsService + _svc = PointsService() + _svc.add_points( + user_id=response.user_id, + amount=50, + source="task_reward", + db=db, + description="新用户注册赠送", + ) + except Exception as _bonus_err: + import logging + logging.getLogger(__name__).warning("注册送积分失败: user_id=%s err=%s", response.user_id, _bonus_err) + return RegisterResponse( user_id=response.user_id, email=response.email, diff --git a/tests/unit/test_auth_register_points.py b/tests/unit/test_auth_register_points.py new file mode 100644 index 000000000..8ca54d5d2 --- /dev/null +++ b/tests/unit/test_auth_register_points.py @@ -0,0 +1,111 @@ +"""注册送积分单元测试 (#1895 P2 step 3)""" + +from __future__ import annotations + +from unittest.mock import MagicMock, patch + +import pytest + + +@pytest.fixture +def mock_settings(monkeypatch): + """默认关闭 points_enabled,不影响现有用例。""" + from app.config import settings + + monkeypatch.setattr(settings, "points_enabled", False) + return settings + + +class TestRegisterBonusPoints: + @pytest.mark.asyncio + async def test_bonus_when_enabled(self, mock_settings): + """开启积分时注册成功送50分。""" + from app.api.routes import auth + from app.api.routes.auth import RegisterRequest + + mock_settings.points_enabled = True + + mock_uc = MagicMock() + mock_resp = MagicMock() + mock_resp.user_id = "new-user-1" + mock_resp.email = "n***@example.com" + mock_resp.username = "newuser" + mock_resp.display_name = "New User" + mock_uc.execute.return_value = (mock_resp, None) + + mock_svc = MagicMock() + + def _mock_uc_cls(*args, **kwargs): + return mock_uc + + db = MagicMock() + + with ( + patch("app.api.routes.auth.RegisterUserUseCase", side_effect=_mock_uc_cls), + patch("packages.domain.points_service.PointsService", return_value=mock_svc), + ): + req = RegisterRequest(email="n***@example.com", password="Secret123!", username="newuser") + resp = await auth.register(request=req, user_repository=MagicMock(), email_service=MagicMock(), db=db) + assert resp.user_id == "new-user-1" + mock_svc.add_points.assert_called_once() + call_kwargs = mock_svc.add_points.call_args.kwargs + assert call_kwargs["user_id"] == "new-user-1" + assert call_kwargs["amount"] == 50 + assert call_kwargs["source"] == "task_reward" + + @pytest.mark.asyncio + async def test_no_bonus_when_disabled(self, mock_settings): + """关闭积分时不送分。""" + from app.api.routes import auth + from app.api.routes.auth import RegisterRequest + + mock_uc = MagicMock() + mock_resp = MagicMock() + mock_resp.user_id = "new-user-2" + mock_resp.email = "n***@example.com" + mock_resp.username = "newuser2" + mock_resp.display_name = "New User 2" + mock_uc.execute.return_value = (mock_resp, None) + + def _mock_uc_cls(*args, **kwargs): + return mock_uc + + db = MagicMock() + with ( + patch("app.api.routes.auth.RegisterUserUseCase", side_effect=_mock_uc_cls), + patch("packages.domain.points_service.PointsService") as MockSvc, + ): + req = RegisterRequest(email="n***@example.com", password="Secret123!", username="newuser2") + resp = await auth.register(request=req, user_repository=MagicMock(), email_service=MagicMock(), db=db) + MockSvc.assert_not_called() + assert resp.user_id == "new-user-2" + + @pytest.mark.asyncio + async def test_bonus_failure_does_not_break_register(self, mock_settings): + """送积分失败不应影响注册流程。""" + from app.api.routes import auth + from app.api.routes.auth import RegisterRequest + + mock_settings.points_enabled = True + mock_uc = MagicMock() + mock_resp = MagicMock() + mock_resp.user_id = "new-user-3" + mock_resp.email = "n***@example.com" + mock_resp.username = "newuser3" + mock_resp.display_name = "New User 3" + mock_uc.execute.return_value = (mock_resp, None) + + mock_svc = MagicMock() + mock_svc.add_points.side_effect = Exception("DB error") + + def _mock_uc_cls(*args, **kwargs): + return mock_uc + + db = MagicMock() + with ( + patch("app.api.routes.auth.RegisterUserUseCase", side_effect=_mock_uc_cls), + patch("packages.domain.points_service.PointsService", return_value=mock_svc), + ): + req = RegisterRequest(email="n***@example.com", password="Secret123!", username="newuser3") + resp = await auth.register(request=req, user_repository=MagicMock(), email_service=MagicMock(), db=db) + assert resp.user_id == "new-user-3" diff --git a/tests/unit/test_generation_tasks.py b/tests/unit/test_generation_tasks.py index 239e1480d..e6c8fb3c0 100755 --- a/tests/unit/test_generation_tasks.py +++ b/tests/unit/test_generation_tasks.py @@ -19,6 +19,13 @@ from packages.application.generation_tasks import ( from packages.domain import GenerationTask +@pytest.fixture(autouse=True) +def _disable_points_gate(monkeypatch): + """默认关闭积分闸门,避免影响既有用例。""" + monkeypatch.setattr(_pg_module, "_points_gate_enabled", lambda: False) + yield + + @pytest.fixture(autouse=True) def _disable_points_gate(monkeypatch): """默认关闭积分闸门,避免影响既有用例。"""