aboutsummaryrefslogtreecommitdiff
path: root/tests/managers
diff options
context:
space:
mode:
Diffstat (limited to 'tests/managers')
-rw-r--r--tests/managers/thl/test_ledger/test_thl_pem.py4
-rw-r--r--tests/managers/thl/test_ledger/test_wallet.py9
-rw-r--r--tests/managers/thl/test_payout.py101
-rw-r--r--tests/managers/thl/test_virtual_incentives_api.py158
-rw-r--r--tests/managers/thl/test_wallet/test_user_payout.py218
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,