feat: add invite rewards and redeem codes
This commit is contained in:
@@ -0,0 +1,194 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime, timezone
|
||||
from types import SimpleNamespace
|
||||
from uuid import UUID, uuid4
|
||||
|
||||
import pytest
|
||||
|
||||
from v1.points.invite_rewards import grant_invite_rewards_for_creem_payment
|
||||
from v1.points.service import PointsService
|
||||
|
||||
|
||||
@dataclass
|
||||
class _FakeAccount:
|
||||
balance: int = 0
|
||||
frozen_balance: int = 0
|
||||
lifetime_earned: int = 0
|
||||
lifetime_spent: int = 0
|
||||
version: int = 0
|
||||
|
||||
|
||||
class _FakePointsRepository:
|
||||
def __init__(self) -> None:
|
||||
self.accounts: dict[UUID, _FakeAccount] = {}
|
||||
self.redeem_code: SimpleNamespace | None = None
|
||||
self.referral: SimpleNamespace | None = None
|
||||
self.user_emails: dict[UUID, str] = {}
|
||||
self.ledgers: list[object] = []
|
||||
self.audit_ledgers: list[object] = []
|
||||
self.system_audits: list[dict[str, object]] = []
|
||||
self.committed = False
|
||||
|
||||
async def get_redeem_code_for_update(self, *, code: str) -> SimpleNamespace | None:
|
||||
if self.redeem_code is None or self.redeem_code.code != code:
|
||||
return None
|
||||
return self.redeem_code
|
||||
|
||||
async def get_or_create_user_points_for_update(
|
||||
self, *, user_id: UUID
|
||||
) -> _FakeAccount:
|
||||
return self.accounts.setdefault(user_id, _FakeAccount())
|
||||
|
||||
async def append_ledger(self, *, command: object, balance_after: int) -> None:
|
||||
self.ledgers.append(command)
|
||||
|
||||
async def append_audit_ledger(self, *, command: object) -> None:
|
||||
self.audit_ledgers.append(command)
|
||||
|
||||
async def append_system_audit_log(
|
||||
self,
|
||||
*,
|
||||
actor_user_id: UUID | None,
|
||||
target_user_id: UUID | None,
|
||||
action: str,
|
||||
entity_type: str,
|
||||
entity_id: UUID | None,
|
||||
metadata: dict[str, object],
|
||||
) -> None:
|
||||
self.system_audits.append(
|
||||
{
|
||||
"actor_user_id": actor_user_id,
|
||||
"target_user_id": target_user_id,
|
||||
"action": action,
|
||||
"entity_type": entity_type,
|
||||
"entity_id": entity_id,
|
||||
"metadata": metadata,
|
||||
}
|
||||
)
|
||||
|
||||
async def commit(self) -> None:
|
||||
self.committed = True
|
||||
|
||||
async def get_referral_by_invitee_for_update(
|
||||
self, *, invitee_user_id: UUID
|
||||
) -> SimpleNamespace | None:
|
||||
if self.referral is None or self.referral.invitee_user_id != invitee_user_id:
|
||||
return None
|
||||
return self.referral
|
||||
|
||||
async def get_user_email(self, *, user_id: UUID) -> str | None:
|
||||
return self.user_emails.get(user_id)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_redeem_code_credits_account_and_audits() -> None:
|
||||
user_id = uuid4()
|
||||
code_id = uuid4()
|
||||
repo = _FakePointsRepository()
|
||||
repo.redeem_code = SimpleNamespace(
|
||||
id=code_id,
|
||||
code="ABCD2345",
|
||||
status="active",
|
||||
credits=100,
|
||||
package_product_code="starter_pack",
|
||||
package_name_snapshot="starter_pack",
|
||||
redeemed_by_user_id=None,
|
||||
redeemed_at=None,
|
||||
redeem_event_id=None,
|
||||
)
|
||||
|
||||
result = await PointsService(repository=repo).redeem_code(
|
||||
user_id=user_id,
|
||||
user_email="buyer@example.com",
|
||||
raw_code="abcd2345",
|
||||
)
|
||||
|
||||
assert result.credits == 100
|
||||
assert result.balance_after == 100
|
||||
assert repo.accounts[user_id].balance == 100
|
||||
assert repo.redeem_code.status == "redeemed"
|
||||
assert repo.redeem_code.redeemed_by_user_id == user_id
|
||||
assert len(repo.ledgers) == 1
|
||||
assert len(repo.audit_ledgers) == 1
|
||||
assert repo.system_audits[0]["action"] == "redeem_code.activate"
|
||||
assert repo.committed is True
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_creem_payment_after_binding_grants_invite_rewards_to_both_users() -> (
|
||||
None
|
||||
):
|
||||
inviter_id = uuid4()
|
||||
invitee_id = uuid4()
|
||||
referral_id = uuid4()
|
||||
creem_transaction_id = uuid4()
|
||||
paid_at = datetime.now(timezone.utc)
|
||||
repo = _FakePointsRepository()
|
||||
repo.user_emails[inviter_id] = "inviter@example.com"
|
||||
repo.referral = SimpleNamespace(
|
||||
id=referral_id,
|
||||
inviter_user_id=inviter_id,
|
||||
invitee_user_id=invitee_id,
|
||||
first_creem_transaction_id=None,
|
||||
first_creem_paid_at=None,
|
||||
inviter_reward_event_id=None,
|
||||
invitee_reward_event_id=None,
|
||||
inviter_reward_granted_at=None,
|
||||
invitee_reward_granted_at=None,
|
||||
)
|
||||
|
||||
await grant_invite_rewards_for_creem_payment(
|
||||
repository=repo,
|
||||
invitee_user_id=invitee_id,
|
||||
invitee_email="invitee@example.com",
|
||||
creem_transaction_id=creem_transaction_id,
|
||||
paid_at=paid_at,
|
||||
)
|
||||
|
||||
assert repo.accounts[inviter_id].balance == 40
|
||||
assert repo.accounts[invitee_id].balance == 40
|
||||
assert repo.referral.first_creem_transaction_id == creem_transaction_id
|
||||
assert repo.referral.inviter_reward_granted_at == paid_at
|
||||
assert repo.referral.invitee_reward_granted_at == paid_at
|
||||
assert len(repo.ledgers) == 2
|
||||
assert len(repo.audit_ledgers) == 2
|
||||
assert repo.system_audits[0]["action"] == "invite.reward_grant"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_creem_payment_after_existing_completed_payment_still_grants_binding_reward() -> (
|
||||
None
|
||||
):
|
||||
inviter_id = uuid4()
|
||||
invitee_id = uuid4()
|
||||
repo = _FakePointsRepository()
|
||||
repo.referral = SimpleNamespace(
|
||||
id=uuid4(),
|
||||
inviter_user_id=inviter_id,
|
||||
invitee_user_id=invitee_id,
|
||||
first_creem_transaction_id=None,
|
||||
first_creem_paid_at=None,
|
||||
inviter_reward_event_id=None,
|
||||
invitee_reward_event_id=None,
|
||||
inviter_reward_granted_at=None,
|
||||
invitee_reward_granted_at=None,
|
||||
)
|
||||
|
||||
creem_transaction_id = uuid4()
|
||||
paid_at = datetime.now(timezone.utc)
|
||||
await grant_invite_rewards_for_creem_payment(
|
||||
repository=repo,
|
||||
invitee_user_id=invitee_id,
|
||||
invitee_email="invitee@example.com",
|
||||
creem_transaction_id=creem_transaction_id,
|
||||
paid_at=paid_at,
|
||||
)
|
||||
|
||||
assert repo.accounts[inviter_id].balance == 40
|
||||
assert repo.accounts[invitee_id].balance == 40
|
||||
assert repo.referral.first_creem_transaction_id == creem_transaction_id
|
||||
assert repo.referral.inviter_reward_granted_at == paid_at
|
||||
assert len(repo.ledgers) == 2
|
||||
assert len(repo.audit_ledgers) == 2
|
||||
Reference in New Issue
Block a user