Files
eryao/backend/tests/unit/test_register_bonus_first_registration.py
T
qzl c79c773d67 feat(notification): add target_mode enum constraint and merge register-notifications script
- Add NotificationTargetMode enum (new_users/exist_users/all_users/user_ids)
- Create Alembic migrations: drop duplicate indexes, add target_mode column
- Merge register-notifications.sh into dev-migrate.sh sync-notifications subcommand
- Shorten notification config path: static/notification/notifications -> static/notifications
- Update registration flow to dispatch notifications by target_mode
- Add is_first_registration to RegisterBonusResult for first-time user detection
- Remove dead code: link_published_notifications_to_user
- Update welcome_points.yaml to target new_users only
- Add 44 unit tests + 1 integration test, all passing
2026-04-16 17:50:57 +08:00

126 lines
3.9 KiB
Python

from __future__ import annotations
from uuid import uuid4
import pytest
from models.register_bonus_claims import RegisterBonusClaims
from v1.points.service import PointsService
class _FakeAccount:
balance: int = 100
frozen_balance: int = 0
lifetime_earned: int = 0
lifetime_spent: int = 0
version: int = 0
class _FakePointsRepository:
def __init__(self, *, claim: RegisterBonusClaims | None = None) -> None:
self.account = _FakeAccount()
self.claim = claim
self.claimed = False
self.appended_ledger: list[object] = []
self.appended_audit: list[object] = []
async def get_or_create_user_points_for_update(
self, *, user_id: object
) -> _FakeAccount:
return self.account
async def has_ledger_event(self, *, user_id: object, event_id: str) -> bool:
return False
async def append_ledger(self, *, command: object, balance_after: int) -> None:
self.appended_ledger.append(command)
async def append_audit_ledger(self, *, command: object) -> None:
self.appended_audit.append(command)
async def has_audit_event(self, *, event_id: str) -> bool:
return False
async def claim_register_bonus(
self,
*,
email_hash: str,
user_email_snapshot: str,
first_user_id_snapshot: object,
grant_event_id: str,
) -> bool:
if self.claimed:
return False
self.claimed = True
return True
async def get_register_bonus_claim(
self, *, email_hash: str
) -> RegisterBonusClaims | None:
return self.claim
class TestRegisterBonusIsFirstRegistration:
@pytest.mark.asyncio
async def test_first_registration_sets_true(self) -> None:
repo = _FakePointsRepository(claim=None)
service = PointsService(repository=repo) # type: ignore[arg-type]
result = await service.grant_register_bonus_if_eligible(
user_id=uuid4(), user_email="new@example.com"
)
assert result.granted is True
assert result.is_first_registration is True
@pytest.mark.asyncio
async def test_reregistered_with_existing_claim_sets_false(self) -> None:
existing_claim = RegisterBonusClaims(
email_hash="abc",
user_email_snapshot="old@example.com",
first_user_id_snapshot=uuid4(),
balance_snapshot=50,
grant_event_id="evt",
)
repo = _FakePointsRepository(claim=existing_claim)
service = PointsService(repository=repo) # type: ignore[arg-type]
result = await service.grant_register_bonus_if_eligible(
user_id=uuid4(), user_email="old@example.com"
)
assert result.granted is False
assert result.is_first_registration is False
@pytest.mark.asyncio
async def test_reregistered_claim_without_first_user_id_sets_true(self) -> None:
claim_no_snapshot = RegisterBonusClaims(
email_hash="abc",
user_email_snapshot="edge@example.com",
first_user_id_snapshot=None,
balance_snapshot=50,
grant_event_id="evt",
)
repo = _FakePointsRepository(claim=claim_no_snapshot)
service = PointsService(repository=repo) # type: ignore[arg-type]
result = await service.grant_register_bonus_if_eligible(
user_id=uuid4(), user_email="edge@example.com"
)
assert result.granted is False
assert result.is_first_registration is True
@pytest.mark.asyncio
async def test_claim_competition_failure_sets_false(self) -> None:
repo = _FakePointsRepository(claim=None)
repo.claimed = True
service = PointsService(repository=repo) # type: ignore[arg-type]
result = await service.grant_register_bonus_if_eligible(
user_id=uuid4(), user_email="race@example.com"
)
assert result.granted is False
assert result.is_first_registration is False