feat(points): P2 step 3 - 注册送50积分 #1945

Merged
auto-approve-bot merged 9 commits from feat/points-auth-bonus into develop 2026-09-16 03:52:16 +08:00
3 changed files with 136 additions and 1 deletions
+18 -1
View File
@@ -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,
+111
View File
@@ -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"
+7
View File
@@ -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):
"""默认关闭积分闸门,避免影响既有用例。"""