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