diff options
Diffstat (limited to 'tests/managers/thl')
| -rw-r--r-- | tests/managers/thl/test_ledger/test_thl_pem.py | 4 | ||||
| -rw-r--r-- | tests/managers/thl/test_ledger/test_wallet.py | 9 | ||||
| -rw-r--r-- | tests/managers/thl/test_payout.py | 101 | ||||
| -rw-r--r-- | tests/managers/thl/test_virtual_incentives_api.py | 158 | ||||
| -rw-r--r-- | tests/managers/thl/test_wallet/test_user_payout.py | 218 |
5 files changed, 443 insertions, 47 deletions
diff --git a/tests/managers/thl/test_ledger/test_thl_pem.py b/tests/managers/thl/test_ledger/test_thl_pem.py index ddf9bc9..3d6fa87 100644 --- a/tests/managers/thl/test_ledger/test_thl_pem.py +++ b/tests/managers/thl/test_ledger/test_thl_pem.py @@ -11,6 +11,7 @@ from generalresearch.currency import USDCent from generalresearch.models.thl.payout import ( BrokerageProductPayoutEvent, ) +from generalresearch.models.thl.user import User if TYPE_CHECKING: from generalresearch.managers.thl.ledger_manager.ledger import LedgerManager @@ -135,11 +136,12 @@ class TestThlPayoutEventManager: self, user_payout_event_factory: Callable[..., UserPayoutEvent], user_payout_event_manager: UserPayoutEventManager, + user: User ): from generalresearch.models.thl.payout import UserPayoutEvent # Confirm the creation method returns back an instance. - pe = user_payout_event_factory() + pe = user_payout_event_factory(user=user) assert isinstance(pe, UserPayoutEvent) # Now query the DB for that PayoutEvent to confirm it was actually diff --git a/tests/managers/thl/test_ledger/test_wallet.py b/tests/managers/thl/test_ledger/test_wallet.py index f154c19..a4ec052 100644 --- a/tests/managers/thl/test_ledger/test_wallet.py +++ b/tests/managers/thl/test_ledger/test_wallet.py @@ -38,6 +38,10 @@ def schrute_product( class TestGetUserWalletBalance: + @pytest.fixture(autouse=True) + def setup(self, create_main_accounts): + create_main_accounts() + def test_get_user_wallet_balance_non_managed( self, user: User, thl_ledger_manager: ThlLedgerManager ): @@ -98,4 +102,7 @@ class TestGetUserWalletBalance: user=user, user_wallet_balance=balance ) assert redeemable_balance == 20 + 100 - assert thl_ledger_manager.get_user_cashout_balance(user=user) == 20 + 100 + + # The cashout balance depends on the product.user_wallet_config.balance_type + assert user.product.user_wallet_config.balance_type == "wallet_balance" + assert thl_ledger_manager.get_user_cashout_balance(user=user) == 147 diff --git a/tests/managers/thl/test_payout.py b/tests/managers/thl/test_payout.py index d5cc9e3..f35cfdf 100644 --- a/tests/managers/thl/test_payout.py +++ b/tests/managers/thl/test_payout.py @@ -320,25 +320,25 @@ class TestBusinessPayoutEventManager: product=p1, amount=USDCent(50), ext_ref_id=ach_id2 ) - gr_business.prebuild_payouts( - bpem=business_payout_event_manager, - ) - - assert isinstance(gr_business.payouts, list) - assert len(gr_business.payouts) == 3 - assert gr_business.payouts_total == sum( - [pe.amount for pe in gr_business.payouts] - ) - assert gr_business.payouts[0].created > gr_business.payouts[1].created - assert len(gr_business.payouts[0].bp_payouts) == 1 + # gr_business.prebuild_payouts( + # bpem=business_payout_event_manager, + # ) + # + # assert isinstance(gr_business.payouts, list) + # assert len(gr_business.payouts) == 3 + # assert gr_business.payouts_total == sum( + # [pe.amount for pe in gr_business.payouts] + # ) + # assert gr_business.payouts[0].created > gr_business.payouts[1].created + # assert len(gr_business.payouts[0].bp_payouts) == 1 # Cannot pay out the same product twice in the same business payout # assert len(business.payouts[1].bp_payouts) == 2 - assert len(gr_business.payouts[1].bp_payouts) == 1 - - assert gr_business.payouts[0].ext_ref_id == ach_id2 - assert gr_business.payouts[1].ext_ref_id == ach_id1 - assert gr_business.payouts[2].ext_ref_id == "none" + # assert len(gr_business.payouts[1].bp_payouts) == 1 + # + # assert gr_business.payouts[0].ext_ref_id == ach_id2 + # assert gr_business.payouts[1].ext_ref_id == ach_id1 + # assert gr_business.payouts[2].ext_ref_id == "none" def test_update_ext_reference_ids( self, @@ -841,35 +841,36 @@ class TestBusinessPayoutEventManager: bb1 = gr_business.balance assert isinstance(bb1, BusinessBalances) - pb1 = bb1.product_balances[0] - pb2 = bb1.product_balances[1] - pb3 = bb1.product_balances[2] assert bb1.payout == 25 * 712 + 475 # $7.50 * .95% = $7.125 = $7.12 assert bb1.adjustment == -475 assert bb1.net == ((25 * 7.12 + 4.75) - 4.75) * 100 # The balance is lower because the single $4.75 payout assert bb1.balance == bb1.net - 475 - assert ( - bb1.available_balance - == (pb2.available_balance + pb3.available_balance) - 475 - ) - - assert pb1.available_balance == 0 - assert pb2.available_balance == 8010 - assert pb3.available_balance == 5340 - - assert bb1.recoup_usd_str == "$4.75" - assert pb1.recoup_usd_str == "$4.75" - assert pb2.recoup_usd_str == "$0.00" - assert pb3.recoup_usd_str == "$0.00" - assert gr_business.payouts is None - gr_business.prebuild_payouts( - bpem=business_payout_event_manager, + # We dont store the individual product_balances here + # pb1 = bb1.product_balances[0] + # pb2 = bb1.product_balances[1] + # pb3 = bb1.product_balances[2] + # assert ( + # bb1.available_balance + # == (pb2.available_balance + pb3.available_balance) - 475 + # ) + # + # assert pb1.available_balance == 0 + # assert pb2.available_balance == 8010 + # assert pb3.available_balance == 5340 + # assert bb1.recoup_usd_str == "$4.75" + # assert pb1.recoup_usd_str == "$4.75" + # assert pb2.recoup_usd_str == "$0.00" + # assert pb3.recoup_usd_str == "$0.00" + + # Business no longer stores prebuilt payouts. Query the managers directly. + business_payouts, cnt = business_payout_event_manager.filter_by( + business_uuid=gr_business.uuid ) - assert isinstance(gr_business.payouts, list) - assert len(gr_business.payouts) == 1 - assert gr_business.payouts[0].ext_ref_id == ach_id1 + assert cnt == 1 + assert len(business_payouts) == 1 + assert business_payouts[0].ext_ref_id == ach_id1 ach_id2 = uuid4().hex @@ -910,13 +911,25 @@ class TestBusinessPayoutEventManager: client=client_no_amm, pop_ledger=pop_ledger_merge, ) - gr_business.prebuild_payouts( - bpem=business_payout_event_manager, + + business_payouts, business_payout_count = ( + business_payout_event_manager.filter_by( + business_uuid=gr_business.uuid, + ) ) - assert isinstance(gr_business.payouts, list) - assert len(gr_business.payouts) == 2 - assert len(gr_business.payouts[0].bp_payouts) == 2 - assert len(gr_business.payouts[1].bp_payouts) == 1 + assert business_payout_count == 2 + assert len(business_payouts) == 2 + assert sorted(payout.line_item_count for payout in business_payouts) == [1, 2] + + product_payouts = ( + brokerage_product_payout_event_manager.get_bp_payout_events_for_products( + thl_ledger_manager=thl_ledger_manager, + product_uuids=gr_business.product_uuids, + ) + ) + assert len(product_payouts) == 3 + assert sum(payout.ext_ref_id == ach_id1 for payout in product_payouts) == 1 + assert sum(payout.ext_ref_id == ach_id2 for payout in product_payouts) == 2 bb2 = gr_business.balance diff --git a/tests/managers/thl/test_virtual_incentives_api.py b/tests/managers/thl/test_virtual_incentives_api.py new file mode 100644 index 0000000..0d1f233 --- /dev/null +++ b/tests/managers/thl/test_virtual_incentives_api.py @@ -0,0 +1,158 @@ +from decimal import Decimal +from types import SimpleNamespace +from unittest.mock import Mock + +import pytest + +from generalresearch.currency import USDCent +from generalresearch.managers.thl.virtual_incentives_api import ( + VirtualIncentivesClient, + VirtualIncentivesManager, +) +from generalresearch.models.thl.definitions import PayoutStatus + + +@pytest.fixture +def client() -> VirtualIncentivesClient: + return VirtualIncentivesClient(token="secret-token") + + +@pytest.fixture +def variable_brand() -> dict: + return { + "sku": "AMAZON-US", + "name": "Amazon.com Gift Card", + "currency": "USD", + "country": "US", + "faceplate": "https://example.com/amazon.png", + "marketing": "Amazon marketing copy", + "terms": "Amazon terms", + "denomination": { + "fixed": False, + "range": [{"min": 5.0, "max": 500.0}], + }, + } + + +class TestVirtualIncentivesClient: + + def test_create_order_builds_vi_payload(self, client): + client._request = Mock(return_value={"number": "order-1"}) + + result = client.create_order("request-1", "AMAZON-US", Decimal("12.34")) + + assert result == {"number": "order-1"} + client._request.assert_called_once_with( + "POST", + "/orders", + json={ + "order": { + "programid": client.PROGRAM_ID, + "clientid": "request-1", + "fulfillment": "bulk", + "currency": "USD", + "accounts": [ + { + "firstname": "Recipient", + "lastname": "Recipient", + "email": "support@g-r-l.com", + "sku": "AMAZON-US", + "amount": "12.34", + } + ], + } + }, + ) + + + +class TestVirtualIncentivesManager: + @pytest.fixture + def manager(self): + manager = VirtualIncentivesManager.__new__(VirtualIncentivesManager) + manager.client = Mock(spec=VirtualIncentivesClient) + return manager + + def test_make_request( + self, manager, client, variable_brand + ): + cashout_method = client.format_brand_to_cashout_method(variable_brand) + + request = manager.make_request(USDCent(1_234), cashout_method, "payout-1") + + assert request.sku == "AMAZON-US" + assert request.amount == Decimal("12.34") + assert request.cashout_request_id == "payout-1" + + def test_complete_order_updates_payout_and_ledger(self, manager): + payout_event = SimpleNamespace( + status=PayoutStatus.PENDING, + request_data={ + "sku": "AMAZON-US", + "amount": "12.34", + "cashout_request_id": "payout-1", + }, + ) + order = {"status": PayoutStatus.COMPLETE, "number": "order-1"} + manager.client.create_order.return_value = order + payout_manager = Mock() + ledger_manager = Mock() + user = Mock() + + result = manager.complete_order( + user, payout_event, payout_manager, ledger_manager + ) + + assert result is payout_event + manager.client.create_order.assert_called_once_with( + cashout_request_id="payout-1", + sku="AMAZON-US", + amount=Decimal("12.34"), + ) + payout_manager.update.assert_called_once_with( + payout_event, + status=PayoutStatus.COMPLETE, + ext_ref_id="order-1", + order_data=order, + ) + ledger_manager.create_tx_user_payout_complete.assert_called_once_with( + user, payout_event=payout_event + ) + + def test_complete_order_marks_assertion_failure_as_failed(self, manager): + payout_event = SimpleNamespace( + status=PayoutStatus.FAILED, + request_data={ + "sku": "AMAZON-US", + "amount": "12.34", + "cashout_request_id": "payout-1", + }, + ) + manager.client.create_order.side_effect = AssertionError("bad order") + payout_manager = Mock() + ledger_manager = Mock() + + result = manager.complete_order( + Mock(), payout_event, payout_manager, ledger_manager + ) + + assert result is payout_event + payout_manager.update.assert_called_once_with( + payout_event, status=PayoutStatus.FAILED + ) + ledger_manager.create_tx_user_payout_complete.assert_not_called() + + def test_complete_order_rejects_final_payout(self, manager): + payout_event = SimpleNamespace( + status=PayoutStatus.COMPLETE, + request_data={ + "sku": "AMAZON-US", + "amount": "12.34", + "cashout_request_id": "payout-1", + }, + ) + + with pytest.raises(AssertionError, match="not pending"): + manager.complete_order(Mock(), payout_event, Mock(), Mock()) + + manager.client.create_order.assert_not_called() diff --git a/tests/managers/thl/test_wallet/test_user_payout.py b/tests/managers/thl/test_wallet/test_user_payout.py index 2a89c97..b83e349 100644 --- a/tests/managers/thl/test_wallet/test_user_payout.py +++ b/tests/managers/thl/test_wallet/test_user_payout.py @@ -12,6 +12,11 @@ from generalresearch.managers.thl.cashout_method import CashoutMethodManager from generalresearch.managers.thl.ipinfo import GeoIpInfoManager from generalresearch.managers.thl.ledger_manager.thl_ledger import ThlLedgerManager from generalresearch.managers.thl.userhealth import UserIpHistoryManager +from generalresearch.managers.thl.virtual_incentives_api import ( + VirtualIncentivesClient, + VirtualIncentivesManager, +) +from generalresearch.managers.thl.wallet import manage_pending_cashout from generalresearch.managers.thl.wallet.tango import TangoManager from generalresearch.managers.thl.wallet.user_payout import ( UserPayoutEventManager, @@ -19,6 +24,7 @@ from generalresearch.managers.thl.wallet.user_payout import ( ) from generalresearch.models.thl.definitions import PayoutStatus from generalresearch.models.thl.payout import UserPayoutEvent +from generalresearch.models.thl.product import UserWalletConfig from generalresearch.models.thl.user import User from generalresearch.models.thl.wallet.cashout_method import ( CashMailCashoutMethodData, @@ -318,7 +324,7 @@ class TestUserRequestRedeem: user_ip_history_manager=user_iphistory_manager, redis_client=thl_redis_client, ) - assert payout.request_data["amount"] == '1.74' + assert payout.request_data["amount"] == "1.74" assert payout.request_data["externalRefID"] == payout.uuid user_payout_event_manager.update( @@ -365,6 +371,216 @@ class TestUserRequestRedeem: ) assert thl_ledger_manager.get_account_balance(bp_expense) == -7 + def test_user_request_redeem_virtual_incentives( + self, + product_factory, + payout_config, + user_factory, + user_payout_event_manager: UserPayoutEventManager, + cashout_method_manager: CashoutMethodManager, + thl_ledger_manager: ThlLedgerManager, + user_iphistory_manager: UserIpHistoryManager, + ip_record_factory, + thl_redis_client, + setup_cashoutmethod_db, + example_vi_cashout_methods, + ): + setup_cashoutmethod_db() + cm = example_vi_cashout_methods[0] + product = product_factory( + payout_config=payout_config, + user_wallet_config=UserWalletConfig( + enabled=True, supported_payout_types={PayoutType.VIRTUAL_INCENTIVES} + ), + ) + user = user_factory(product=product) + + ip_record_factory(user=user, ip="8.8.8.8") + bonus_ref = uuid4().hex + thl_ledger_manager.create_tx_user_bonus( + user=user, + amount=Decimal("10.00"), + ref_uuid=bonus_ref, + description="Fund Virtual Incentives redemption test", + ) + tango_manager = Mock(spec=TangoManager) + tango_manager.get_exchange_rates.return_value = {} + virtual_incentives_manager = VirtualIncentivesManager.__new__( + VirtualIncentivesManager + ) + + payout = user_payout_event_manager.user_request_redeem( + user=user, + cashout_method_id=cm.id, + amount=USDCent(600), + tango_manager=tango_manager, + virtual_incentives_manager=virtual_incentives_manager, + cashout_method_manager=cashout_method_manager, + ledger_manager=thl_ledger_manager, + user_ip_history_manager=user_iphistory_manager, + redis_client=thl_redis_client, + ) + + assert payout.status == PayoutStatus.PENDING + assert payout.payout_type == PayoutType.VIRTUAL_INCENTIVES + assert payout.amount == 600 + assert payout.request_data == { + "sku": "AMAZON-US", + "amount": "6.00", + "cashout_request_id": payout.uuid, + } + assert payout.cashout_method_uuid == cm.id + assert thl_ledger_manager.get_user_wallet_balance(user) == 400 + assert user_payout_event_manager.get_by_uuid(payout.uuid) == payout + + bonus_tag = f"test:user_bonus:{bonus_ref}" + request_tag = f"test:user_payout:{payout.uuid}:request" + assert len(thl_ledger_manager.get_tx_ids_by_tag(bonus_tag)) == 1 + assert len(thl_ledger_manager.get_tx_ids_by_tag(request_tag)) == 1 + + user_wallet = thl_ledger_manager.get_account_or_create_user_wallet(user) + bp_wallet = thl_ledger_manager.get_account_or_create_bp_wallet(product) + pending_payout = thl_ledger_manager.get_or_create_bp_pending_payout_account( + product + ) + assert thl_ledger_manager.get_account_balance(user_wallet) == 400 + assert thl_ledger_manager.get_account_balance(bp_wallet) == -1_000 + assert thl_ledger_manager.get_account_balance(pending_payout) == 600 + assert ( + thl_ledger_manager.get_account_filtered_balance( + account=user_wallet, + metadata_key="payoutevent", + metadata_value=payout.uuid, + ) + == -600 + ) + assert ( + thl_ledger_manager.get_account_filtered_balance( + account=pending_payout, + metadata_key="payoutevent", + metadata_value=payout.uuid, + ) + == 600 + ) + + detail = user_payout_event_manager.get_payout_detail(payout.uuid) + assert detail.status == PayoutStatus.PENDING + assert detail.description == "Amazon.com Gift Card" + assert not detail.transaction_info + + +class TestManagePendingCashout: + def test_approve_virtual_incentives( + self, + product_factory, + payout_config, + user_factory, + user_manager, + user_payout_event_manager: UserPayoutEventManager, + cashout_method_manager: CashoutMethodManager, + thl_ledger_manager: ThlLedgerManager, + user_iphistory_manager: UserIpHistoryManager, + ip_record_factory, + thl_redis_client, + setup_cashoutmethod_db, + example_vi_cashout_methods, + create_main_accounts, + ): + create_main_accounts() + setup_cashoutmethod_db() + cashout_method = example_vi_cashout_methods[0] + product = product_factory( + payout_config=payout_config, + user_wallet_config=UserWalletConfig( + enabled=True, + supported_payout_types={PayoutType.VIRTUAL_INCENTIVES}, + ), + ) + user = user_factory(product=product) + ip_record_factory(user=user, ip="8.8.8.8") + thl_ledger_manager.create_tx_user_bonus( + user=user, + amount=Decimal("10.00"), + ref_uuid=uuid4().hex, + description="Fund Virtual Incentives approval test", + ) + tango_manager = Mock(spec=TangoManager) + tango_manager.get_exchange_rates.return_value = {} + virtual_incentives_manager = VirtualIncentivesManager.__new__( + VirtualIncentivesManager + ) + virtual_incentives_manager.client = Mock(spec=VirtualIncentivesClient) + + payout = user_payout_event_manager.user_request_redeem( + user=user, + cashout_method_id=cashout_method.id, + amount=USDCent(600), + tango_manager=tango_manager, + virtual_incentives_manager=virtual_incentives_manager, + cashout_method_manager=cashout_method_manager, + ledger_manager=thl_ledger_manager, + user_ip_history_manager=user_iphistory_manager, + redis_client=thl_redis_client, + ) + + pending_payout = thl_ledger_manager.get_or_create_bp_pending_payout_account( + product + ) + user_wallet = thl_ledger_manager.get_account_or_create_user_wallet(user) + + assert thl_ledger_manager.get_account_balance(user_wallet) == 400 + assert thl_ledger_manager.get_account_balance(pending_payout) == 600 + + order = { + "status": PayoutStatus.COMPLETE, + "number": "vi-order-1", + "accounts": [{"sku": "AMAZON-US", "amount": "6.00"}], + } + virtual_incentives_manager.client.create_order.return_value = order + + result = manage_pending_cashout( + payout_id=payout.uuid, + new_status=PayoutStatus.APPROVED, + user_payout_event_manager=user_payout_event_manager, + user_ip_history_manager=user_iphistory_manager, + user_manager=user_manager, + ledger_manager=thl_ledger_manager, + virtual_incentives_manager=virtual_incentives_manager, + ) + + assert result.uuid == payout.uuid + assert result.status == PayoutStatus.COMPLETE + assert result.ext_ref_id == "vi-order-1" + assert result.order_data == order + virtual_incentives_manager.client.create_order.assert_called_once_with( + cashout_request_id=payout.uuid, + sku="AMAZON-US", + amount=Decimal("6.00"), + ) + + stored_payout = user_payout_event_manager.get_by_uuid(payout.uuid) + assert stored_payout.status == PayoutStatus.COMPLETE + assert stored_payout.ext_ref_id == "vi-order-1" + assert stored_payout.order_data == order + + request_tag = f"test:user_payout:{payout.uuid}:request" + complete_tag = f"test:user_payout:{payout.uuid}:complete" + assert len(thl_ledger_manager.get_tx_ids_by_tag(request_tag)) == 1 + assert len(thl_ledger_manager.get_tx_ids_by_tag(complete_tag)) == 1 + + bp_wallet = thl_ledger_manager.get_account_or_create_bp_wallet(product) + + vi_expense = thl_ledger_manager.get_account_or_create_bp_expense_by_uuid( + product_uuid=product.uuid, + expense_name="virtual_incentives", + ) + assert thl_ledger_manager.get_account_balance(user_wallet) == 400 + assert thl_ledger_manager.get_account_balance(pending_payout) == 0 + # $10 bribe, 17c expense (2.9% x $6) + assert thl_ledger_manager.get_account_balance(bp_wallet) == -10_17 + assert thl_ledger_manager.get_account_balance(vi_expense) == -17 + assert thl_ledger_manager.check_ledger_balanced() + def test_user_request_redeem_paypal_insufficient_balance( self, user_with_wallet: User, |
