diff options
Diffstat (limited to 'tests/managers/thl/test_contest')
| -rw-r--r-- | tests/managers/thl/test_contest/__init__.py | 0 | ||||
| -rw-r--r-- | tests/managers/thl/test_contest/test_leaderboard.py | 138 | ||||
| -rw-r--r-- | tests/managers/thl/test_contest/test_milestone.py | 296 | ||||
| -rw-r--r-- | tests/managers/thl/test_contest/test_raffle.py | 474 |
4 files changed, 908 insertions, 0 deletions
diff --git a/tests/managers/thl/test_contest/__init__.py b/tests/managers/thl/test_contest/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/managers/thl/test_contest/__init__.py diff --git a/tests/managers/thl/test_contest/test_leaderboard.py b/tests/managers/thl/test_contest/test_leaderboard.py new file mode 100644 index 0000000..80a88a5 --- /dev/null +++ b/tests/managers/thl/test_contest/test_leaderboard.py @@ -0,0 +1,138 @@ +from datetime import datetime, timezone, timedelta +from zoneinfo import ZoneInfo + +from generalresearch.currency import USDCent +from generalresearch.models.thl.contest.definitions import ( + ContestStatus, + ContestEndReason, +) +from generalresearch.models.thl.contest.leaderboard import ( + LeaderboardContest, + LeaderboardContestCreate, +) +from generalresearch.models.thl.product import Product +from generalresearch.models.thl.user import User +from test_utils.managers.contest.conftest import ( + leaderboard_contest_in_db as contest_in_db, + leaderboard_contest_create as contest_create, +) + + +class TestLeaderboardContestCRUD: + + def test_create( + self, + contest_create: LeaderboardContestCreate, + product_user_wallet_yes: Product, + thl_lm, + contest_manager, + ): + c = contest_manager.create( + product_id=product_user_wallet_yes.uuid, contest_create=contest_create + ) + c_out = contest_manager.get(c.uuid) + assert c == c_out + + assert isinstance(c, LeaderboardContest) + assert c.prize_count == 2 + assert c.status == ContestStatus.ACTIVE + # We have it set in the fixture as the daily contest for 2025-01-01 + assert c.end_condition.ends_at == datetime( + 2025, 1, 1, 23, 59, 59, 999999, tzinfo=ZoneInfo("America/New_York") + ).astimezone(tz=timezone.utc) + timedelta(minutes=90) + + def test_enter( + self, + user_with_wallet: User, + contest_in_db: LeaderboardContest, + thl_lm, + contest_manager, + user_manager, + thl_redis, + ): + contest = contest_in_db + user = user_with_wallet + + c: LeaderboardContest = contest_manager.get(contest_uuid=contest.uuid) + + c = contest_manager.get_leaderboard_user_view( + contest_uuid=contest.uuid, + user=user, + redis_client=thl_redis, + user_manager=user_manager, + ) + assert c.user_rank is None + + lbm = c.get_leaderboard_manager() + lbm.hit_complete_count(user.product_user_id) + + c = contest_manager.get_leaderboard_user_view( + contest_uuid=contest.uuid, + user=user, + redis_client=thl_redis, + user_manager=user_manager, + ) + assert c.user_rank == 1 + + def test_contest_ends( + self, + user_with_wallet: User, + contest_in_db: LeaderboardContest, + thl_lm, + contest_manager, + user_manager, + thl_redis, + ): + # The contest should be over. We need to trigger it. + contest = contest_in_db + contest._redis_client = thl_redis + contest._user_manager = user_manager + user = user_with_wallet + + lbm = contest.get_leaderboard_manager() + lbm.hit_complete_count(user.product_user_id) + + c = contest_manager.get_leaderboard_user_view( + contest_uuid=contest.uuid, + user=user, + redis_client=thl_redis, + user_manager=user_manager, + ) + assert c.user_rank == 1 + + bp_wallet = thl_lm.get_account_or_create_bp_wallet_by_uuid(user.product_id) + bp_wallet_balance = thl_lm.get_account_balance(account=bp_wallet) + assert bp_wallet_balance == 0 + user_wallet = thl_lm.get_account_or_create_user_wallet(user=user) + user_balance = thl_lm.get_account_balance(user_wallet) + assert user_balance == 0 + + decision, reason = contest.should_end() + assert decision + assert reason == ContestEndReason.ENDS_AT + + contest_manager.end_contest_if_over(contest=contest, ledger_manager=thl_lm) + + c: LeaderboardContest = contest_manager.get(contest_uuid=contest.uuid) + assert c.status == ContestStatus.COMPLETED + print(c) + + user_contest = contest_manager.get_leaderboard_user_view( + contest_uuid=contest.uuid, + user=user, + redis_client=thl_redis, + user_manager=user_manager, + ) + assert len(user_contest.user_winnings) == 1 + w = user_contest.user_winnings[0] + assert w.product_user_id == user.product_user_id + assert w.prize.cash_amount == USDCent(15_00) + + # The prize is $15.00, so the user should get $15, paid by the bp + assert thl_lm.get_account_balance(account=user_wallet) == 15_00 + # contest wallet is 0, and the BP gets 20c + contest_wallet = thl_lm.get_account_or_create_contest_wallet_by_uuid( + contest_uuid=c.uuid + ) + assert thl_lm.get_account_balance(account=contest_wallet) == 0 + assert thl_lm.get_account_balance(account=bp_wallet) == -15_00 diff --git a/tests/managers/thl/test_contest/test_milestone.py b/tests/managers/thl/test_contest/test_milestone.py new file mode 100644 index 0000000..7312a64 --- /dev/null +++ b/tests/managers/thl/test_contest/test_milestone.py @@ -0,0 +1,296 @@ +from datetime import datetime, timezone + +from generalresearch.models.thl.contest.definitions import ( + ContestStatus, + ContestEndReason, +) +from generalresearch.models.thl.contest.milestone import ( + MilestoneContest, + MilestoneContestCreate, + MilestoneUserView, + ContestEntryTrigger, +) +from generalresearch.models.thl.product import Product +from generalresearch.models.thl.user import User +from test_utils.managers.contest.conftest import ( + milestone_contest as contest, + milestone_contest_in_db as contest_in_db, + milestone_contest_create as contest_create, + milestone_contest_factory as contest_factory, +) + + +class TestMilestoneContest: + + def test_should_end(self, contest: MilestoneContest, thl_lm, contest_manager): + # contest is active and has no entries + should, msg = contest.should_end() + assert not should, msg + + # Change so that the contest ends now + contest.end_condition.ends_at = datetime.now(tz=timezone.utc) + should, msg = contest.should_end() + assert should + assert msg == ContestEndReason.ENDS_AT + + # Change the win amount it thinks it past over the target + contest.end_condition.ends_at = None + contest.end_condition.max_winners = 10 + contest.win_count = 10 + should, msg = contest.should_end() + assert should + assert msg == ContestEndReason.MAX_WINNERS + + +class TestMilestoneContestCRUD: + + def test_create( + self, + contest_create: MilestoneContestCreate, + product_user_wallet_yes: Product, + thl_lm, + contest_manager, + ): + c = contest_manager.create( + product_id=product_user_wallet_yes.uuid, contest_create=contest_create + ) + c_out = contest_manager.get(c.uuid) + assert c == c_out + + assert isinstance(c, MilestoneContest) + assert c.prize_count == 2 + assert c.status == ContestStatus.ACTIVE + assert c.end_condition.max_winners == 5 + assert c.entry_trigger == ContestEntryTrigger.TASK_COMPLETE + assert c.target_amount == 3 + assert c.win_count == 0 + + def test_enter( + self, + user_with_wallet: User, + contest_in_db: MilestoneContest, + thl_lm, + contest_manager, + ): + # Users CANNOT directly enter a milestone contest through the api, + # but we'll call this manager method when a trigger is hit. + contest = contest_in_db + user = user_with_wallet + + contest_manager.enter_milestone_contest( + contest_uuid=contest.uuid, + user=user, + country_iso="us", + ledger_manager=thl_lm, + incr=1, + ) + + c: MilestoneContest = contest_manager.get(contest_uuid=contest.uuid) + assert c.status == ContestStatus.ACTIVE + assert not hasattr(c, "current_amount") + assert not hasattr(c, "current_participants") + + c: MilestoneUserView = contest_manager.get_milestone_user_view( + contest_uuid=contest.uuid, user=user_with_wallet + ) + assert c.user_amount == 1 + + # Contest wallet should have 0 bc there is no ledger + contest_wallet = thl_lm.get_account_or_create_contest_wallet_by_uuid( + contest_uuid=contest.uuid + ) + assert thl_lm.get_account_balance(contest_wallet) == 0 + + # Enter again! + contest_manager.enter_milestone_contest( + contest_uuid=contest.uuid, + user=user, + country_iso="us", + ledger_manager=thl_lm, + incr=1, + ) + c: MilestoneUserView = contest_manager.get_milestone_user_view( + contest_uuid=contest.uuid, user=user_with_wallet + ) + assert c.user_amount == 2 + + # We should have ONE entry with a value of 2 + e = contest_manager.get_entries_by_contest_id(c.id) + assert len(e) == 1 + assert e[0].amount == 2 + + def test_enter_win( + self, + user_with_wallet: User, + contest_in_db: MilestoneContest, + thl_lm, + contest_manager, + ): + # User enters contest, which brings the USER'S total amount above the limit, + # and the user reaches the milestone + contest = contest_in_db + user = user_with_wallet + + user_wallet = thl_lm.get_account_or_create_user_wallet(user=user) + user_balance = thl_lm.get_account_balance(account=user_wallet) + bp_wallet = thl_lm.get_account_or_create_bp_wallet_by_uuid( + product_uuid=user.product_id + ) + bp_wallet_balance = thl_lm.get_account_balance(account=bp_wallet) + + c: MilestoneUserView = contest_manager.get_milestone_user_view( + contest_uuid=contest.uuid, user=user_with_wallet + ) + assert c.user_amount == 0 + res, msg = c.is_user_eligible(country_iso="us") + assert res, msg + + # User reaches the milestone after 3 completes/whatevers. + for _ in range(3): + contest_manager.enter_milestone_contest( + contest_uuid=contest.uuid, + user=user, + country_iso="us", + ledger_manager=thl_lm, + incr=1, + ) + + # to be clear, the contest itself doesn't end! + c: MilestoneContest = contest_manager.get(contest_uuid=contest.uuid) + assert c.status == ContestStatus.ACTIVE + + c: MilestoneUserView = contest_manager.get_milestone_user_view( + contest_uuid=contest.uuid, user=user_with_wallet + ) + assert c.user_amount == 3 + res, msg = c.is_user_eligible(country_iso="us") + assert not res + assert msg == "User should have won already" + + assert len(c.user_winnings) == 2 + assert c.win_count == 1 + + # The prize was awarded! User should have won $1.00 + assert thl_lm.get_account_balance(user_wallet) - user_balance == 100 + # Which was paid from the BP's balance + assert thl_lm.get_account_balance(bp_wallet) - bp_wallet_balance == -100 + + # winnings = cm.get_winnings_by_user(user=user) + # assert len(winnings) == 1 + # win = winnings[0] + # assert win.product_user_id == user.product_user_id + + def test_enter_ends( + self, + user_factory, + product_user_wallet_yes: Product, + contest_in_db: MilestoneContest, + thl_lm, + contest_manager, + ): + # Multiple users reach the milestone. Contest ends after 5 wins. + users = [user_factory(product=product_user_wallet_yes) for _ in range(5)] + contest = contest_in_db + + for u in users: + contest_manager.enter_milestone_contest( + contest_uuid=contest.uuid, + user=u, + country_iso="us", + ledger_manager=thl_lm, + incr=3, + ) + + c: MilestoneContest = contest_manager.get(contest_uuid=contest.uuid) + assert c.status == ContestStatus.COMPLETED + assert c.end_reason == ContestEndReason.MAX_WINNERS + + def test_trigger( + self, + user_with_wallet: User, + contest_in_db: MilestoneContest, + thl_lm, + contest_manager, + ): + # Pretend user just got a complete + cnt = contest_manager.hit_milestone_triggers( + country_iso="us", + user=user_with_wallet, + event=ContestEntryTrigger.TASK_COMPLETE, + ledger_manager=thl_lm, + ) + assert cnt == 1 + + # Assert this contest got entered + c: MilestoneUserView = contest_manager.get_milestone_user_view( + contest_uuid=contest_in_db.uuid, user=user_with_wallet + ) + assert c.user_amount == 1 + + +class TestMilestoneContestUserViews: + def test_list_user_eligible_country( + self, user_with_wallet: User, contest_factory, thl_lm, contest_manager + ): + # No contests exists + cs = contest_manager.get_many_by_user_eligible( + user=user_with_wallet, country_iso="us" + ) + assert len(cs) == 0 + + # Create a contest. It'll be in the US/CA + contest_factory(country_isos={"us", "ca"}) + + # Not eligible in mexico + cs = contest_manager.get_many_by_user_eligible( + user=user_with_wallet, country_iso="mx" + ) + assert len(cs) == 0 + cs = contest_manager.get_many_by_user_eligible( + user=user_with_wallet, country_iso="us" + ) + assert len(cs) == 1 + + # Create another, any country + contest_factory(country_isos=None) + cs = contest_manager.get_many_by_user_eligible( + user=user_with_wallet, country_iso="mx" + ) + assert len(cs) == 1 + cs = contest_manager.get_many_by_user_eligible( + user=user_with_wallet, country_iso="us" + ) + assert len(cs) == 2 + + def test_list_user_eligible( + self, user_with_money: User, contest_factory, thl_lm, contest_manager + ): + # User reaches milestone after 1 complete + c = contest_factory(target_amount=1) + user = user_with_money + + cs = contest_manager.get_many_by_user_eligible( + user=user_with_money, country_iso="us" + ) + assert len(cs) == 1 + + contest_manager.enter_milestone_contest( + contest_uuid=c.uuid, user=user, country_iso="us", ledger_manager=thl_lm + ) + + # User isn't eligible anymore + cs = contest_manager.get_many_by_user_eligible( + user=user_with_money, country_iso="us" + ) + assert len(cs) == 0 + + # But it comes back in the list entered + cs = contest_manager.get_many_by_user_entered(user=user_with_money) + assert len(cs) == 1 + c = cs[0] + assert c.user_amount == 1 + assert isinstance(c, MilestoneUserView) + assert not hasattr(c, "current_win_probability") + + # They won one contest with 2 prizes + assert len(contest_manager.get_winnings_by_user(user_with_money)) == 2 diff --git a/tests/managers/thl/test_contest/test_raffle.py b/tests/managers/thl/test_contest/test_raffle.py new file mode 100644 index 0000000..060055a --- /dev/null +++ b/tests/managers/thl/test_contest/test_raffle.py @@ -0,0 +1,474 @@ +from datetime import datetime, timezone + +import pytest +from pydantic import ValidationError +from pytest import approx + +from generalresearch.currency import USDCent +from generalresearch.managers.thl.ledger_manager.exceptions import ( + LedgerTransactionConditionFailedError, +) +from generalresearch.models.thl.contest import ( + ContestPrize, + ContestEntryRule, + ContestEndCondition, +) +from generalresearch.models.thl.contest.definitions import ( + ContestStatus, + ContestPrizeKind, + ContestEndReason, +) +from generalresearch.models.thl.contest.exceptions import ContestError +from generalresearch.models.thl.contest.raffle import ( + ContestEntry, + ContestEntryType, +) +from generalresearch.models.thl.contest.raffle import ( + RaffleContest, + RaffleContestCreate, + RaffleUserView, +) +from generalresearch.models.thl.product import Product +from generalresearch.models.thl.user import User +from test_utils.managers.contest.conftest import ( + raffle_contest as contest, + raffle_contest_in_db as contest_in_db, + raffle_contest_create as contest_create, + raffle_contest_factory as contest_factory, +) + + +class TestRaffleContest: + + def test_should_end(self, contest: RaffleContest, thl_lm, contest_manager): + # contest is active and has no entries + should, msg = contest.should_end() + assert not should, msg + + # Change so that the contest ends now + contest.end_condition.ends_at = datetime.now(tz=timezone.utc) + should, msg = contest.should_end() + assert should + assert msg == ContestEndReason.ENDS_AT + + # Change the entry amount it thinks it has to over the target + contest.end_condition.ends_at = None + contest.current_amount = USDCent(100) + should, msg = contest.should_end() + assert should + assert msg == ContestEndReason.TARGET_ENTRY_AMOUNT + + +class TestRaffleContestCRUD: + + def test_create( + self, + contest_create: RaffleContestCreate, + product_user_wallet_yes: Product, + thl_lm, + contest_manager, + ): + c = contest_manager.create( + product_id=product_user_wallet_yes.uuid, contest_create=contest_create + ) + c_out = contest_manager.get(c.uuid) + assert c == c_out + + assert isinstance(c, RaffleContest) + assert c.prize_count == 1 + assert c.status == ContestStatus.ACTIVE + assert c.end_condition.target_entry_amount == USDCent(100) + assert c.current_amount == 0 + assert c.current_participants == 0 + + @pytest.mark.parametrize("user_with_money", [{"min_balance": 60}], indirect=True) + def test_enter( + self, + user_with_money: User, + contest_in_db: RaffleContest, + thl_lm, + contest_manager, + ): + # Raffle ends at $1.00. User enters for $0.60 + print(user_with_money.product_id) + print(contest_in_db.product_id) + print(contest_in_db.uuid) + contest = contest_in_db + + user_wallet = thl_lm.get_account_or_create_user_wallet(user=user_with_money) + user_balance = thl_lm.get_account_balance(account=user_wallet) + + entry = ContestEntry( + entry_type=ContestEntryType.CASH, user=user_with_money, amount=USDCent(60) + ) + entry = contest_manager.enter_contest( + contest_uuid=contest.uuid, + entry=entry, + country_iso="us", + ledger_manager=thl_lm, + ) + c: RaffleContest = contest_manager.get(contest_uuid=contest.uuid) + assert c.current_amount == USDCent(60) + assert c.current_participants == 1 + assert c.status == ContestStatus.ACTIVE + + c: RaffleUserView = contest_manager.get_raffle_user_view( + contest_uuid=contest.uuid, user=user_with_money + ) + assert c.user_amount == USDCent(60) + assert c.user_amount_today == USDCent(60) + assert c.projected_win_probability == approx(60 / 100, rel=0.01) + + # Contest wallet should have $0.60 + contest_wallet = thl_lm.get_account_or_create_contest_wallet_by_uuid( + contest_uuid=contest.uuid + ) + assert thl_lm.get_account_balance(account=contest_wallet) == 60 + # User spent 60c + assert user_balance - thl_lm.get_account_balance(account=user_wallet) == 60 + + @pytest.mark.parametrize("user_with_money", [{"min_balance": 120}], indirect=True) + def test_enter_ends( + self, + user_with_money: User, + contest_in_db: RaffleContest, + thl_lm, + contest_manager, + ): + # User enters contest, which brings the total amount above the limit, + # and the contest should end, with a winner selected + contest = contest_in_db + + bp_wallet = thl_lm.get_account_or_create_bp_wallet_by_uuid( + user_with_money.product_id + ) + # I bribed the user, so the balance is not 0 + bp_wallet_balance = thl_lm.get_account_balance(account=bp_wallet) + + for _ in range(2): + entry = ContestEntry( + entry_type=ContestEntryType.CASH, + user=user_with_money, + amount=USDCent(60), + ) + contest_manager.enter_contest( + contest_uuid=contest.uuid, + entry=entry, + country_iso="us", + ledger_manager=thl_lm, + ) + c: RaffleContest = contest_manager.get(contest_uuid=contest.uuid) + assert c.status == ContestStatus.COMPLETED + print(c) + + user_contest = contest_manager.get_raffle_user_view( + contest_uuid=contest.uuid, user=user_with_money + ) + assert user_contest.current_win_probability == 1 + assert user_contest.projected_win_probability == 1 + assert len(user_contest.user_winnings) == 1 + + # todo: make a all winning method + winnings = contest_manager.get_winnings_by_user(user=user_with_money) + assert len(winnings) == 1 + win = winnings[0] + assert win.product_user_id == user_with_money.product_user_id + + # Contest wallet should have gotten zeroed out + contest_wallet = thl_lm.get_account_or_create_contest_wallet_by_uuid( + contest_uuid=contest.uuid + ) + assert thl_lm.get_account_balance(contest_wallet) == 0 + # Expense wallet gets the $1.00 expense + expense_wallet = thl_lm.get_account_or_create_bp_expense_by_uuid( + product_uuid=user_with_money.product_id, expense_name="Prize" + ) + assert thl_lm.get_account_balance(expense_wallet) == -100 + # And the BP gets 20c + assert thl_lm.get_account_balance(bp_wallet) - bp_wallet_balance == 20 + + @pytest.mark.parametrize("user_with_money", [{"min_balance": 120}], indirect=True) + def test_enter_ends_cash_prize( + self, user_with_money: User, contest_factory, thl_lm, contest_manager + ): + # Same as test_enter_ends, but the prize is cash. Just + # testing the ledger methods + c = contest_factory( + prizes=[ + ContestPrize( + name="$1.00 bonus", + kind=ContestPrizeKind.CASH, + estimated_cash_value=USDCent(100), + cash_amount=USDCent(100), + ) + ] + ) + assert c.prizes[0].kind == ContestPrizeKind.CASH + + user_wallet = thl_lm.get_account_or_create_user_wallet(user=user_with_money) + user_balance = thl_lm.get_account_balance(user_wallet) + bp_wallet = thl_lm.get_account_or_create_bp_wallet_by_uuid( + user_with_money.product_id + ) + bp_wallet_balance = thl_lm.get_account_balance(bp_wallet) + + ## Enter Contest + entry = ContestEntry( + entry_type=ContestEntryType.CASH, user=user_with_money, amount=USDCent(120) + ) + entry = contest_manager.enter_contest( + contest_uuid=c.uuid, + entry=entry, + country_iso="us", + ledger_manager=thl_lm, + ) + + # The prize is $1.00, so the user spent $1.20 entering, won, then got $1.00 back + assert ( + thl_lm.get_account_balance(account=user_wallet) == user_balance + 100 - 120 + ) + # contest wallet is 0, and the BP gets 20c + contest_wallet = thl_lm.get_account_or_create_contest_wallet_by_uuid( + contest_uuid=c.uuid + ) + assert thl_lm.get_account_balance(account=contest_wallet) == 0 + assert thl_lm.get_account_balance(account=bp_wallet) - bp_wallet_balance == 20 + + def test_enter_failure( + self, + user_with_wallet: User, + contest_in_db: RaffleContest, + thl_lm, + contest_manager, + ): + c = contest_in_db + user = user_with_wallet + + # Tries to enter $0 + with pytest.raises(ValidationError) as e: + entry = ContestEntry( + entry_type=ContestEntryType.CASH, user=user, amount=USDCent(0) + ) + assert "Input should be greater than 0" in str(e.value) + + # User has no money + entry = ContestEntry( + entry_type=ContestEntryType.CASH, user=user, amount=USDCent(20) + ) + with pytest.raises(LedgerTransactionConditionFailedError) as e: + entry = contest_manager.enter_contest( + contest_uuid=c.uuid, + entry=entry, + country_iso="us", + ledger_manager=thl_lm, + ) + assert e.value.args[0] == "insufficient balance" + + # Tries to enter with the wrong entry type (count, on a cash contest) + entry = ContestEntry(entry_type=ContestEntryType.COUNT, user=user, amount=1) + with pytest.raises(AssertionError) as e: + entry = contest_manager.enter_contest( + contest_uuid=c.uuid, + entry=entry, + country_iso="us", + ledger_manager=thl_lm, + ) + assert "incompatible entry type" in str(e.value) + + @pytest.mark.parametrize("user_with_money", [{"min_balance": 100}], indirect=True) + def test_enter_not_eligible( + self, user_with_money: User, contest_factory, thl_lm, contest_manager + ): + # Max entry amount per user $0.10. Contest still ends at $1.00 + c = contest_factory( + entry_rule=ContestEntryRule( + max_entry_amount_per_user=USDCent(10), + max_daily_entries_per_user=USDCent(8), + ) + ) + c: RaffleContest = contest_manager.get(c.uuid) + assert c.entry_rule.max_entry_amount_per_user == USDCent(10) + assert c.entry_rule.max_daily_entries_per_user == USDCent(8) + + # User tries to enter $0.20 + entry = ContestEntry( + entry_type=ContestEntryType.CASH, user=user_with_money, amount=USDCent(20) + ) + with pytest.raises(ContestError) as e: + entry = contest_manager.enter_contest( + contest_uuid=c.uuid, + entry=entry, + country_iso="us", + ledger_manager=thl_lm, + ) + assert "Entry would exceed max amount per user." in str(e.value) + + # User tries to enter $0.10 + entry = ContestEntry( + entry_type=ContestEntryType.CASH, user=user_with_money, amount=USDCent(10) + ) + with pytest.raises(ContestError) as e: + entry = contest_manager.enter_contest( + contest_uuid=c.uuid, + entry=entry, + country_iso="us", + ledger_manager=thl_lm, + ) + assert "Entry would exceed max amount per user per day." in str(e.value) + + # User enters $0.08 successfully + entry = ContestEntry( + entry_type=ContestEntryType.CASH, user=user_with_money, amount=USDCent(8) + ) + entry = contest_manager.enter_contest( + contest_uuid=c.uuid, + entry=entry, + country_iso="us", + ledger_manager=thl_lm, + ) + + # Then can't anymore + entry = ContestEntry( + entry_type=ContestEntryType.CASH, user=user_with_money, amount=USDCent(1) + ) + with pytest.raises(ContestError) as e: + entry = contest_manager.enter_contest( + contest_uuid=c.uuid, + entry=entry, + country_iso="us", + ledger_manager=thl_lm, + ) + assert "Entry would exceed max amount per user per day." in str(e.value) + + +class TestRaffleContestUserViews: + def test_list_user_eligible_country( + self, user_with_wallet: User, contest_factory, thl_lm, contest_manager + ): + # No contests exists + cs = contest_manager.get_many_by_user_eligible( + user=user_with_wallet, country_iso="us" + ) + assert len(cs) == 0 + + # Create a contest. It'll be in the US/CA + contest_factory(country_isos={"us", "ca"}) + + # Not eligible in mexico + cs = contest_manager.get_many_by_user_eligible( + user=user_with_wallet, country_iso="mx" + ) + assert len(cs) == 0 + cs = contest_manager.get_many_by_user_eligible( + user=user_with_wallet, country_iso="us" + ) + assert len(cs) == 1 + + # Create another, any country + contest_factory(country_isos=None) + cs = contest_manager.get_many_by_user_eligible( + user=user_with_wallet, country_iso="mx" + ) + assert len(cs) == 1 + cs = contest_manager.get_many_by_user_eligible( + user=user_with_wallet, country_iso="us" + ) + assert len(cs) == 2 + + def test_list_user_eligible( + self, user_with_money: User, contest_factory, thl_lm, contest_manager + ): + c = contest_factory( + end_condition=ContestEndCondition(target_entry_amount=USDCent(10)), + entry_rule=ContestEntryRule( + max_entry_amount_per_user=USDCent(1), + ), + ) + cs = contest_manager.get_many_by_user_eligible( + user=user_with_money, country_iso="us" + ) + assert len(cs) == 1 + + entry = ContestEntry( + entry_type=ContestEntryType.CASH, + user=user_with_money, + amount=USDCent(1), + ) + contest_manager.enter_contest( + contest_uuid=c.uuid, + entry=entry, + country_iso="us", + ledger_manager=thl_lm, + ) + + # User isn't eligible anymore + cs = contest_manager.get_many_by_user_eligible( + user=user_with_money, country_iso="us" + ) + assert len(cs) == 0 + + # But it comes back in the list entered + cs = contest_manager.get_many_by_user_entered(user=user_with_money) + assert len(cs) == 1 + c = cs[0] + assert c.user_amount == USDCent(1) + assert c.user_amount_today == USDCent(1) + assert c.current_win_probability == 1 + assert c.projected_win_probability == approx(1 / 10, rel=0.01) + + # And nothing won yet #todo + # cs = cm.get_many_by_user_won(user=user_with_money) + + assert len(contest_manager.get_winnings_by_user(user_with_money)) == 0 + + def test_list_user_winnings( + self, user_with_money: User, contest_factory, thl_lm, contest_manager + ): + c = contest_factory( + end_condition=ContestEndCondition(target_entry_amount=USDCent(100)), + ) + entry = ContestEntry( + entry_type=ContestEntryType.CASH, + user=user_with_money, + amount=USDCent(100), + ) + contest_manager.enter_contest( + contest_uuid=c.uuid, + entry=entry, + country_iso="us", + ledger_manager=thl_lm, + ) + # Contest ends after 100 entry, user enters 100 entry, user wins! + ws = contest_manager.get_winnings_by_user(user_with_money) + assert len(ws) == 1 + w = ws[0] + assert w.user.user_id == user_with_money.user_id + assert w.prize == c.prizes[0] + assert w.awarded_cash_amount is None + + cs = contest_manager.get_many_by_user_won(user_with_money) + assert len(cs) == 1 + c = cs[0] + w = c.user_winnings[0] + assert w.prize == c.prizes[0] + assert w.user.user_id == user_with_money.user_id + + +class TestRaffleContestCRUDCount: + # This is a COUNT contest. No cash moves. Not really fleshed out what we'd do with this. + @pytest.mark.skip + def test_enter( + self, user_with_wallet: User, contest_factory, thl_lm, contest_manager + ): + c = contest_factory(entry_type=ContestEntryType.COUNT) + entry = ContestEntry( + entry_type=ContestEntryType.COUNT, + user=user_with_wallet, + amount=1, + ) + contest_manager.enter_contest( + contest_uuid=c.uuid, + entry=entry, + country_iso="us", + ledger_manager=thl_lm, + ) |
