From 64ee0459e4e3d3fe87ad65387fcbcb147147ac1b Mon Sep 17 00:00:00 2001 From: Fringg Date: Wed, 4 Mar 2026 07:55:26 +0300 Subject: [PATCH] fix: second round review fixes for account merge - Rename _compute_auth_methods to compute_auth_methods (public API) - Add Literal type to _handle_subscription_merge param - Add Literal type to keep_from in route handler - Add Path(min_length=32, max_length=64) on merge_token params - Import Path and Literal in account_linking routes --- app/cabinet/routes/account_linking.py | 14 +++++++------- app/services/account_merge_service.py | 6 +++--- tests/services/test_account_merge_service.py | 16 ++++++++-------- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/app/cabinet/routes/account_linking.py b/app/cabinet/routes/account_linking.py index bc4dacd5..1641aca7 100644 --- a/app/cabinet/routes/account_linking.py +++ b/app/cabinet/routes/account_linking.py @@ -5,10 +5,10 @@ Router 2 (`merge_router`): Public endpoints for merge preview and execution. """ from datetime import UTC, datetime -from typing import Any +from typing import Any, Literal import structlog -from fastapi import APIRouter, Depends, HTTPException, status +from fastapi import APIRouter, Depends, HTTPException, Path, status from pydantic import BaseModel, Field from sqlalchemy.ext.asyncio import AsyncSession @@ -18,7 +18,7 @@ from app.database.crud.user import ( set_user_oauth_provider_id, ) from app.database.models import User -from app.services.account_merge_service import _compute_auth_methods, execute_merge, get_merge_preview +from app.services.account_merge_service import compute_auth_methods, execute_merge, get_merge_preview from ..auth.merge_service import ( MERGE_TOKEN_TTL_SECONDS, @@ -137,7 +137,7 @@ def _get_provider_identifier(user: User, provider: str) -> str | None: def _count_auth_methods(user: User) -> int: """Count how many auth methods the user has linked.""" - return len(_compute_auth_methods(user)) + return len(compute_auth_methods(user)) # --------------------------------------------------------------------------- @@ -364,7 +364,7 @@ merge_router = APIRouter(prefix='/auth/merge', tags=['Cabinet Account Merge']) @merge_router.get('/{merge_token}', response_model=MergePreviewResponse) async def get_merge_preview_endpoint( - merge_token: str, + merge_token: str = Path(..., min_length=32, max_length=64), db: AsyncSession = Depends(get_cabinet_db), ) -> MergePreviewResponse: """Preview the result of merging two accounts before confirming.""" @@ -407,8 +407,8 @@ async def get_merge_preview_endpoint( @merge_router.post('/{merge_token}', response_model=MergeResponse) async def execute_merge_endpoint( - merge_token: str, request: MergeRequest, + merge_token: str = Path(..., min_length=32, max_length=64), db: AsyncSession = Depends(get_cabinet_db), ) -> MergeResponse: """Execute account merge. Consumes the merge token (one-time use).""" @@ -433,7 +433,7 @@ async def execute_merge_endpoint( ) # Convert user_id to 'primary'/'secondary' string for execute_merge() - keep_from: str = 'primary' if request.keep_subscription_from == primary_user_id else 'secondary' + keep_from: Literal['primary', 'secondary'] = 'primary' if request.keep_subscription_from == primary_user_id else 'secondary' # 3. Execute merge try: diff --git a/app/services/account_merge_service.py b/app/services/account_merge_service.py index 6fcd3490..8f4628c4 100644 --- a/app/services/account_merge_service.py +++ b/app/services/account_merge_service.py @@ -60,7 +60,7 @@ _PARTNER_STATUS_PRIORITY: dict[str, int] = { } -def _compute_auth_methods(user: User) -> list[str]: +def compute_auth_methods(user: User) -> list[str]: """Вычисляет список методов авторизации пользователя.""" methods: list[str] = [] if user.telegram_id: @@ -104,7 +104,7 @@ def _build_user_preview(user: User) -> dict[str, Any]: 'username': user.username, 'first_name': user.first_name, 'email': user.email, - 'auth_methods': _compute_auth_methods(user), + 'auth_methods': compute_auth_methods(user), 'balance_kopeks': user.balance_kopeks, 'subscription': _build_subscription_preview(user.subscription), 'created_at': user.created_at, @@ -214,7 +214,7 @@ async def _handle_subscription_merge( db: AsyncSession, primary: User, secondary: User, - keep_subscription_from: str, + keep_subscription_from: Literal['primary', 'secondary'], ) -> None: """Обрабатывает мерж подписок между двумя аккаунтами. diff --git a/tests/services/test_account_merge_service.py b/tests/services/test_account_merge_service.py index 44445191..f4af8710 100644 --- a/tests/services/test_account_merge_service.py +++ b/tests/services/test_account_merge_service.py @@ -10,7 +10,7 @@ from app.services import account_merge_service from app.services.account_merge_service import ( _build_subscription_preview, _build_user_preview, - _compute_auth_methods, + compute_auth_methods, execute_merge, get_merge_preview, ) @@ -105,26 +105,26 @@ def _make_db() -> SimpleNamespace: # --------------------------------------------------------------------------- -# _compute_auth_methods +# compute_auth_methods # --------------------------------------------------------------------------- class TestComputeAuthMethods: def test_no_methods(self): user = _make_user() - assert _compute_auth_methods(user) == [] + assert compute_auth_methods(user) == [] def test_telegram_only(self): user = _make_user(telegram_id=12345) - assert _compute_auth_methods(user) == ['telegram'] + assert compute_auth_methods(user) == ['telegram'] def test_email_only(self): user = _make_user(email='test@example.com', password_hash='hash123') - assert _compute_auth_methods(user) == ['email'] + assert compute_auth_methods(user) == ['email'] def test_email_without_password_not_counted(self): user = _make_user(email='test@example.com') - assert _compute_auth_methods(user) == [] + assert compute_auth_methods(user) == [] def test_all_methods(self): user = _make_user( @@ -136,11 +136,11 @@ class TestComputeAuthMethods: discord_id='d123', vk_id=99999, ) - assert _compute_auth_methods(user) == ['telegram', 'email', 'google', 'yandex', 'discord', 'vk'] + assert compute_auth_methods(user) == ['telegram', 'email', 'google', 'yandex', 'discord', 'vk'] def test_oauth_only(self): user = _make_user(google_id='g123', discord_id='d123') - assert _compute_auth_methods(user) == ['google', 'discord'] + assert compute_auth_methods(user) == ['google', 'discord'] # ---------------------------------------------------------------------------