aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorGreg Stupp2026-09-18 22:39:18 +0000
committerGreg Stupp2026-09-18 22:39:18 +0000
commitb373b992cae3a9628a9cf64387c7b8e286d6e1e0 (patch)
tree5e21759ea2c6925e8f54328fbbac88b6be007648 /tests
parent32f30b5ddc600e22e071f7659f1872a18ebf42c7 (diff)
parente3c6a824f4176402e40217c5ce70f837dedc5c3d (diff)
downloadgeneralresearch-99239335cd649b2ee53c8dc09875872dad094b86.tar.gz
generalresearch-99239335cd649b2ee53c8dc09875872dad094b86.zip
Merges pull request #4 v3.5.0
User Cashout Stuff pulled out of grpc, user ip history enrichments with grip (no maxmind)
Diffstat (limited to 'tests')
-rw-r--r--tests/managers/thl/test_cashout_method.py4
-rw-r--r--tests/managers/thl/test_ipinfo.py201
-rw-r--r--tests/managers/thl/test_ledger/test_lm_tx.py4
-rw-r--r--tests/managers/thl/test_ledger/test_lm_tx_locks.py21
-rw-r--r--tests/managers/thl/test_ledger/test_thl_lm_tx.py27
-rw-r--r--tests/managers/thl/test_ledger/test_thl_pem.py51
-rw-r--r--tests/managers/thl/test_payout.py49
-rw-r--r--tests/managers/thl/test_session_manager.py6
-rw-r--r--tests/managers/thl/test_survey_penalty.py2
-rw-r--r--tests/managers/thl/test_user_manager/test_base.py28
-rw-r--r--tests/managers/thl/test_user_manager/test_redis.py15
-rw-r--r--tests/managers/thl/test_userhealth.py187
-rw-r--r--tests/managers/thl/test_wallet/__init__.py0
-rw-r--r--tests/managers/thl/test_wallet/test_user_payout.py435
-rw-r--r--tests/models/gr/test_business.py6
-rw-r--r--tests/models/gr/test_team.py4
-rw-r--r--tests/models/test_finance.py11
-rw-r--r--tests/models/thl/test_user_iphistory.py7
18 files changed, 660 insertions, 398 deletions
diff --git a/tests/managers/thl/test_cashout_method.py b/tests/managers/thl/test_cashout_method.py
index fc364f2..bb978f8 100644
--- a/tests/managers/thl/test_cashout_method.py
+++ b/tests/managers/thl/test_cashout_method.py
@@ -13,7 +13,6 @@ from generalresearch.models.thl.wallet.cashout_method import (
from generalresearch.models.thl.wallet.definitions import PayoutType
if TYPE_CHECKING:
- from generalresearch.config import GRLBaseSettings
from generalresearch.managers.thl.cashout_method import (
CashoutMethodManager,
)
@@ -24,7 +23,6 @@ if TYPE_CHECKING:
class TestTangoCashoutMethods:
-
def test_create_and_get(
self,
cashout_method_manager: CashoutMethodManager,
@@ -51,9 +49,7 @@ class TestTangoCashoutMethods:
assert len(res) == 2
-
class TestUserCashoutMethods:
-
def test(
self,
cashout_method_manager: CashoutMethodManager,
diff --git a/tests/managers/thl/test_ipinfo.py b/tests/managers/thl/test_ipinfo.py
index c021eb9..d84cbf7 100644
--- a/tests/managers/thl/test_ipinfo.py
+++ b/tests/managers/thl/test_ipinfo.py
@@ -1,160 +1,47 @@
-from collections.abc import Callable
-from typing import TYPE_CHECKING
-
-import faker
-
-from generalresearch.managers.thl.ipinfo import (
- GeoIpInfoManager,
- IPGeonameManager,
- IPInformationManager,
-)
-from generalresearch.models.thl.ipinfo import (
- GeoIPInformation,
- IPGeoname,
- IPInformation,
-)
-
-if TYPE_CHECKING:
- from generalresearch.pg_helper import PostgresConfig
- from generalresearch.redis_helper import RedisConfig
-
-fake = faker.Faker()
-
-
-class TestIPGeonameManager:
-
- def test_init(
- self, thl_web_rr: PostgresConfig, ip_geoname_manager: IPGeonameManager
- ):
-
- instance = IPGeonameManager(pg_config=thl_web_rr)
- assert isinstance(instance, IPGeonameManager)
- assert isinstance(ip_geoname_manager, IPGeonameManager)
-
- def test_create(
- self,
- ip_geoname_factory: Callable[..., IPGeoname],
- ip_geoname_manager: IPGeonameManager,
- ):
-
- instance = ip_geoname_factory()
- assert isinstance(instance, IPGeoname)
-
- res = ip_geoname_manager.fetch_geoname_ids(filter_ids=[instance.geoname_id])
- assert res[0].model_dump_json() == instance.model_dump_json()
-
-
-class TestIPInformationManager:
-
- def test_init(
- self, thl_web_rr: PostgresConfig, ip_information_manager: IPInformationManager
- ):
- instance = IPInformationManager(pg_config=thl_web_rr)
- assert isinstance(instance, IPInformationManager)
- assert isinstance(ip_information_manager, IPInformationManager)
-
- def test_create(
- self,
- ip_information_factory: Callable[..., IPInformation],
- ip_information_manager: IPInformationManager,
- ):
- instance = ip_information_factory()
- assert isinstance(instance, IPInformation)
-
- res = ip_information_manager.fetch_ip_information(filter_ips=[instance.ip])
- assert res[0].model_dump_json() == instance.model_dump_json()
-
- def test_prefetch_geoname(
- self,
- ip_information: IPInformation,
- ip_geoname: IPGeoname,
- thl_web_rr: PostgresConfig,
- ):
- assert isinstance(ip_information, IPInformation)
-
- assert ip_information.geoname_id == ip_geoname.geoname_id
- assert ip_information.geoname is None
-
- ip_information.prefetch_geoname(pg_config=thl_web_rr)
- assert isinstance(ip_information.geoname, IPGeoname)
+from generalresearch.managers.thl.ipinfo import GeoIpInfoManager
+from generalresearch.models.thl.ipinfo import GeoIPInformation
class TestGeoIpInfoManager:
- def test_init(
- self,
- thl_web_rr: PostgresConfig,
- thl_redis_config: RedisConfig,
- geoipinfo_manager: GeoIpInfoManager,
- ):
- instance = GeoIpInfoManager(pg_config=thl_web_rr, redis_config=thl_redis_config)
- assert isinstance(instance, GeoIpInfoManager)
- assert isinstance(geoipinfo_manager, GeoIpInfoManager)
-
- def test_multi(
- self,
- ip_information_factory: Callable[..., IPInformation],
- ip_geoname: IPGeoname,
- geoipinfo_manager: GeoIpInfoManager,
- ):
- ip = fake.ipv4_public()
- ip_information_factory(ip=ip, geoname=ip_geoname)
- ips = [ip]
-
- # This only looks up in redis. They don't exist yet
- res = geoipinfo_manager.get_cache_multi(ip_addresses=ips)
- assert res == {ip: None}
-
- # Looks up in redis, if not exists, looks in mysql, then sets
- # the caches that didn't exist.
- res = geoipinfo_manager.get_multi(ip_addresses=ips)
- assert res[ip] is not None
-
- ip2 = fake.ipv4_public()
- ip_information_factory(ip=ip2, geoname=ip_geoname)
- ips = [ip, ip2]
- res = geoipinfo_manager.get_cache_multi(ip_addresses=ips)
- assert res[ip] is not None
- assert res[ip2] is None
- res = geoipinfo_manager.get_multi(ip_addresses=ips)
- assert res[ip] is not None
- assert res[ip2] is not None
- res = geoipinfo_manager.get_cache_multi(ip_addresses=ips)
- assert res[ip] is not None
- assert res[ip2] is not None
-
- def test_multi_ipv6(
- self,
- ip_information_factory: Callable[..., IPInformation],
- ip_geoname: IPGeoname,
- geoipinfo_manager: GeoIpInfoManager,
- ):
- ip = fake.ipv6()
- # Make another IP that will be in the same /64 block.
- ip2 = ip[:-1] + "a" if ip[-1] != "a" else ip[:-1] + "b"
- ip_information_factory(ip=ip, geoname=ip_geoname)
- ips = [ip, ip2]
- print(f"{ips=}")
-
- # This only looks up in redis. They don't exist yet
- res = geoipinfo_manager.get_cache_multi(ip_addresses=ips)
- assert res == {ip: None, ip2: None}
-
- # Looks up in redis, if not exists, looks in mysql, then sets
- # the caches that didn't exist.
- res = geoipinfo_manager.get_multi(ip_addresses=ips)
-
- res1 = res[ip]
- assert isinstance(res1, GeoIPInformation)
- assert res1.ip == ip
- assert res1.lookup_prefix == "/64"
-
- res2 = res[ip2]
- assert isinstance(res2, GeoIPInformation)
- assert res2.ip == ip2
- assert res2.lookup_prefix == "/64"
- # they should be the same basically, except for the ip
-
- def test_doesnt_exist(self, geoipinfo_manager: GeoIpInfoManager):
- ip = fake.ipv4_public()
- res = geoipinfo_manager.get_multi(ip_addresses=[ip])
- assert res == {ip: None}
+ def test_get(self, geoip_info_manager: GeoIpInfoManager):
+ result = geoip_info_manager.get("8.8.8.8")
+
+ assert result == GeoIPInformation(
+ ip="8.8.8.8",
+ country_iso="us",
+ is_anonymous=False,
+ autonomous_system_number=15169,
+ autonomous_system_organization="Google",
+ access_type=None,
+ )
+ geoip_info_manager.grip_mmdb.lookup.assert_called_once_with("8.8.8.8")
+
+ def test_get_multi(self, geoip_info_manager: GeoIpInfoManager):
+ result = geoip_info_manager.get_multi(["8.8.8.8", "1.1.1.1", "8.8.8.8"])
+
+ assert result == {
+ "8.8.8.8": GeoIPInformation(
+ ip="8.8.8.8",
+ country_iso="us",
+ is_anonymous=False,
+ autonomous_system_number=15169,
+ autonomous_system_organization="Google",
+ access_type=None,
+ ),
+ "1.1.1.1": GeoIPInformation(
+ ip="1.1.1.1",
+ country_iso="au",
+ is_anonymous=True,
+ autonomous_system_number=13335,
+ autonomous_system_organization="Cloudflare",
+ access_type=None,
+ ),
+ }
+ assert geoip_info_manager.grip_mmdb.lookup.call_count == 2
+ assert {
+ call.args[0] for call in geoip_info_manager.grip_mmdb.lookup.call_args_list
+ } == {"8.8.8.8", "1.1.1.1"}
+
+ def test_get_multi_empty(self, geoip_info_manager: GeoIpInfoManager):
+ assert geoip_info_manager.get_multi([]) == {}
+ geoip_info_manager.grip_mmdb.lookup.assert_not_called()
diff --git a/tests/managers/thl/test_ledger/test_lm_tx.py b/tests/managers/thl/test_ledger/test_lm_tx.py
index 025f6ac..d98b838 100644
--- a/tests/managers/thl/test_ledger/test_lm_tx.py
+++ b/tests/managers/thl/test_ledger/test_lm_tx.py
@@ -206,12 +206,12 @@ class TestLedgerManagerGetTx:
self, ledger_tx: LedgerTransaction, tag: str, ledger_manager: LedgerManager
):
# (1) search for a random tag
- res = ledger_manager.get_tx_by_tag(tag="aaa:bbb")
+ res = ledger_manager.get_txs_by_tag(tag="aaa:bbb")
assert isinstance(res, list)
assert len(res) == 0
# (2) search for the tag that was used during ledger_transaction creation
- res = ledger_manager.get_tx_by_tag(tag=tag)
+ res = ledger_manager.get_txs_by_tag(tag=tag)
assert isinstance(res, list)
assert len(res) == 1
diff --git a/tests/managers/thl/test_ledger/test_lm_tx_locks.py b/tests/managers/thl/test_ledger/test_lm_tx_locks.py
index 166598e..91d9cbc 100644
--- a/tests/managers/thl/test_ledger/test_lm_tx_locks.py
+++ b/tests/managers/thl/test_ledger/test_lm_tx_locks.py
@@ -38,7 +38,6 @@ logger = logging.getLogger("LedgerManager")
class TestLedgerLocks:
-
def test_a(
self,
user_factory: Callable[..., User],
@@ -108,16 +107,16 @@ class TestLedgerLocks:
f1 = generate_condition_mp_payment(wall=w1)
f2 = generate_condition_mp_payment(wall=w2)
f3 = generate_condition_mp_payment(wall=w3)
- assert f1(ledger_manager) is False
- assert f2(lm=ledger_manager) is False
- assert f3(lm=ledger_manager) is True
+ assert f1(ledger_manager) == (False, "duplicate tag")
+ assert f2(lm=ledger_manager) == (False, "duplicate tag")
+ assert f3(lm=ledger_manager)[0] == True
condition = f3
create_tx_func = lambda: thl_ledger_manager.create_tx_task_complete_(
wall=w3, user=user
)
assert isinstance(create_tx_func, Callable)
- assert f3(ledger_manager) is True
+ assert f3(ledger_manager)[0] is True
ledger_manager.redis_client.delete(flag_name)
ledger_manager.redis_client.delete(lock_name)
@@ -125,13 +124,14 @@ class TestLedgerLocks:
tx = thl_ledger_manager.create_tx_protected(
lock_key=lock_key, condition=condition, create_tx_func=create_tx_func
)
- assert f3(ledger_manager) is False
+ assert f3(ledger_manager) == (False, "duplicate tag")
# purposely hold the lock open
tx = None
ledger_manager.redis_client.set(lock_name, "1")
- with caplog.at_level(logging.ERROR), pytest.raises(
- expected_exception=LedgerTransactionCreateLockError
+ with (
+ caplog.at_level(logging.ERROR),
+ pytest.raises(expected_exception=LedgerTransactionCreateLockError),
):
tx = thl_ledger_manager.create_tx_protected(
lock_key=lock_key,
@@ -231,8 +231,9 @@ class TestLedgerLocks:
# Purposely hold the lock open
ledger_manager.redis_client.set(name=lock_name, value="1")
- with caplog.at_level(logging.DEBUG), pytest.raises(
- expected_exception=LedgerTransactionCreateLockError
+ with (
+ caplog.at_level(logging.DEBUG),
+ pytest.raises(expected_exception=LedgerTransactionCreateLockError),
):
tx = thl_ledger_manager.create_tx_task_complete(
wall=wall3, user=user, created=wall3.started
diff --git a/tests/managers/thl/test_ledger/test_thl_lm_tx.py b/tests/managers/thl/test_ledger/test_thl_lm_tx.py
index 96ea0d6..22a7b73 100644
--- a/tests/managers/thl/test_ledger/test_thl_lm_tx.py
+++ b/tests/managers/thl/test_ledger/test_thl_lm_tx.py
@@ -806,6 +806,24 @@ class TestThlLedgerTxManager:
assert thl_ledger_manager.get_session_attempt_credit(session.uuid) == 5
assert thl_ledger_manager.get_user_attempt_credit_balance(user) == 5
+ attempt_credit_account = (
+ thl_ledger_manager.get_account_or_create_user_attempt_credit(user)
+ )
+ user_txs = thl_ledger_manager.get_user_txs(
+ user, account_uuid=attempt_credit_account.uuid
+ )
+ assert user_txs.total == 1
+ assert user_txs.transactions[0].tx_type == TransactionType.USER_ATTEMPT_CREDIT
+ assert user_txs.transactions[0].amount == 5
+ assert user_txs.transactions[0].balance_after == 5
+ assert user_txs.summary.user_attempt_credit.entry_count == 1
+ assert user_txs.summary.user_attempt_credit.total_amount == 5
+
+ user_txs_by_name = thl_ledger_manager.get_user_txs(
+ user, qualified_name=attempt_credit_account.qualified_name
+ )
+ assert user_txs_by_name == user_txs
+
with pytest.raises(
LedgerTransactionConditionFailedError, match=r"^duplicate tag$"
):
@@ -887,6 +905,15 @@ class TestThlLedgerTxManager:
assert thl_ledger_manager.get_user_wallet_balance(user) == round(user_pay * 100)
assert thl_ledger_manager.check_ledger_balanced()
+ attempt_credit_account = (
+ thl_ledger_manager.get_account_or_create_user_attempt_credit(user)
+ )
+ user_txs = thl_ledger_manager.get_user_txs(
+ user, account_uuid=attempt_credit_account.uuid
+ )
+ assert [tx.amount for tx in user_txs.transactions] == [5, -5]
+ assert [tx.balance_after for tx in user_txs.transactions] == [5, 0]
+
def test_bp_payment_with_zero_attempt_credit_balance(
self,
session_factory: Callable[..., Session],
diff --git a/tests/managers/thl/test_ledger/test_thl_pem.py b/tests/managers/thl/test_ledger/test_thl_pem.py
index fb35aa4..348a02d 100644
--- a/tests/managers/thl/test_ledger/test_thl_pem.py
+++ b/tests/managers/thl/test_ledger/test_thl_pem.py
@@ -8,13 +8,9 @@ from uuid import UUID, uuid4
import pytest
from generalresearch.currency import USDCent
-from generalresearch.models.thl.definitions import PayoutStatus
from generalresearch.models.thl.payout import (
BrokerageProductPayoutEvent,
)
-from generalresearch.models.thl.wallet.cashout_method import (
- CashoutRequestInfo,
-)
if TYPE_CHECKING:
from generalresearch.managers.thl.ledger_manager.ledger import LedgerManager
@@ -23,6 +19,7 @@ if TYPE_CHECKING:
)
from generalresearch.managers.thl.payout import (
BrokerageProductPayoutEventManager,
+ BusinessPayoutEventManager,
UserPayoutEventManager,
)
from generalresearch.models.thl.payout import UserPayoutEvent
@@ -30,7 +27,6 @@ if TYPE_CHECKING:
class TestThlPayoutEventManager:
-
def test_get_by_uuid(
self, brokerage_product_payout_event_manager: BrokerageProductPayoutEventManager
):
@@ -50,8 +46,9 @@ class TestThlPayoutEventManager:
bp_payout_event_factory: Callable[..., BrokerageProductPayoutEvent],
thl_ledger_manager: ThlLedgerManager,
brokerage_product_payout_event_manager: BrokerageProductPayoutEventManager,
+ create_main_accounts,
):
-
+ create_main_accounts()
N_PRODUCTS = randint(3, 10)
N_PAYOUT_EVENTS = randint(3, 10)
amounts = []
@@ -122,38 +119,6 @@ class TestThlPayoutEventManager:
assert len(res) == (N_PRODUCTS * N_PAYOUT_EVENTS)
assert sum([i.amount for i in res]) == sum(amounts)
- @pytest.mark.skip
- def test_get_payout_detail(self, user_payout_event_manager: UserPayoutEventManager):
- """This fails because the description coming back is None, but then
- it tries to return a PayoutEvent which validates that the
- description can't be None
- """
- from generalresearch.models.thl.payout import (
- PayoutType,
- )
-
- rand_amount = randint(a=99, b=999)
-
- pe = user_payout_event_manager.create(
- debit_account_uuid=uuid4().hex,
- account_reference_type="str-type-random",
- account_reference_uuid=uuid4().hex,
- cashout_method_uuid=uuid4().hex,
- description="Best payout !",
- amount=rand_amount,
- status=PayoutStatus.PENDING,
- ext_ref_id="123",
- payout_type=PayoutType.CASH_IN_MAIL,
- request_data={"foo": 123},
- order_data={},
- )
-
- res = user_payout_event_manager.get_payout_detail(pe_uuid=pe.uuid)
- assert isinstance(res, CashoutRequestInfo)
-
- # def test_filter_by(self):
- # raise NotImplementedError
-
def test_create(
self,
user_payout_event_factory: Callable[..., UserPayoutEvent],
@@ -185,6 +150,7 @@ class TestThlPayoutEventManager:
create_main_accounts: Callable[..., None],
thl_ledger_manager: ThlLedgerManager,
brokerage_product_payout_event_manager: BrokerageProductPayoutEventManager,
+ business_payout_event_manager: BusinessPayoutEventManager,
ledger_manager: LedgerManager,
):
@@ -198,13 +164,13 @@ class TestThlPayoutEventManager:
# Save a Brokerage Product Payout, so we have something in the
# Payout Event table and the respective ledger TX and Entry rows for it
- pe = brokerage_product_payout_event_manager.create_bp_payout_event(
+ bus_pe = business_payout_event_manager.create_bp_payout_event(
thl_ledger_manager=thl_ledger_manager,
product=product,
amount=USDCent(rand_amount),
- skip_wallet_balance_check=True,
- skip_one_per_day_check=True,
+ ext_ref_id=uuid4().hex
)
+ pe = bus_pe.bp_payouts[0]
assert isinstance(pe, BrokerageProductPayoutEvent)
# Now try to query for it!
@@ -215,7 +181,7 @@ class TestThlPayoutEventManager:
res = thl_ledger_manager.get_tx_bp_payouts(account_uuids=[uuid4().hex])
assert len(res) == 0
- # Confirm it added to the users balance. The amount is negative because
+ # Confirm it added to the user's balance. The amount is negative because
# money was sent to the Brokerage product: Product, but they didn't have
# any activity that earned them money
bal = ledger_manager.get_account_balance(account=account_bp_wallet)
@@ -223,7 +189,6 @@ class TestThlPayoutEventManager:
class TestBPPayoutEvent:
-
def test_get_bp_bp_payout_events_for_products(
self,
product_factory: Callable[..., Product],
diff --git a/tests/managers/thl/test_payout.py b/tests/managers/thl/test_payout.py
index 52bbbec..be121d8 100644
--- a/tests/managers/thl/test_payout.py
+++ b/tests/managers/thl/test_payout.py
@@ -34,9 +34,9 @@ if TYPE_CHECKING:
BrokerageProductPayoutEventManager,
BusinessPayoutEventManager,
PayoutEventManager,
- UserPayoutEventManager,
)
from generalresearch.managers.thl.product import ProductManager
+ from generalresearch.managers.thl.wallet.user_payout import UserPayoutEventManager
from generalresearch.models.gr.business import Business
from generalresearch.models.thl.ledger import LedgerAccount
from generalresearch.models.thl.payout import (
@@ -163,7 +163,9 @@ class TestPayout:
thl_ledger_manager: ThlLedgerManager,
utc_now: datetime,
pending_bp_pe: BrokerageProductPayoutEvent,
+ create_main_accounts,
):
+ create_main_accounts()
thl_ledger_manager.get_account_or_create_bp_wallet(product=product)
brokerage_product_payout_event_manager.create_tx_bp_payout_from_payout_event(
@@ -386,7 +388,7 @@ class TestBusinessPayoutEventManager:
ledger_collection.initial_load(client=None, sync=True)
pop_ledger_merge.build(client=client_no_amm, ledger_coll=ledger_collection)
gr_business.prebuild_balance(
- thl_pg_config=thl_web_rr,
+ product_manager=product_manager,
lm=thl_ledger_manager,
ds=mnt_filepath,
client=client_no_amm,
@@ -470,6 +472,7 @@ class TestBusinessPayoutEventManager:
self, business_payout_event_manager: BusinessPayoutEventManager, request
):
# TODO: Generate this file at random
+ # @max file is not commited
fp = os.path.join(
request.config.rootpath, "data/pytest_recoup_proportional.csv"
)
@@ -688,7 +691,7 @@ class TestBusinessPayoutEventManager:
ledger_collection.initial_load(client=None, sync=True)
pop_ledger_merge.build(client=client_no_amm, ledger_coll=ledger_collection)
gr_business.prebuild_balance(
- thl_pg_config=thl_web_rr,
+ product_manager=product_manager,
lm=ledger_manager,
ds=mnt_filepath,
client=client_no_amm,
@@ -860,17 +863,18 @@ class TestBusinessPayoutEventManager:
assert gr_business.payouts is None
gr_business.prebuild_payouts(
- thl_pg_config=thl_web_rr,
- thl_lm=thl_ledger_manager,
bpem=business_payout_event_manager,
)
assert isinstance(gr_business.payouts, list)
assert len(gr_business.payouts) == 1
assert gr_business.payouts[0].ext_ref_id == ach_id1
+ ach_id2 = uuid4().hex
+
bp1 = business_payout_event_manager.create_from_ach_or_wire(
business=gr_business,
amount=USDCent(bb1.available_balance),
+ transaction_id=ach_id2,
pm=product_manager,
thl_lm=thl_ledger_manager,
created=start + timedelta(days=1, hours=5),
@@ -882,16 +886,12 @@ class TestBusinessPayoutEventManager:
bp1_tx = brokerage_product_payout_event_manager.check_for_ledger_tx(
thl_ledger_manager=thl_ledger_manager,
payout_event=bp1.bp_payouts[0],
- product_id=bp1.bp_payouts[0].product_id,
- amount=bp1.bp_payouts[0].amount,
)
assert bp1_tx
bp2_tx = brokerage_product_payout_event_manager.check_for_ledger_tx(
thl_ledger_manager=thl_ledger_manager,
payout_event=bp1.bp_payouts[1],
- product_id=bp1.bp_payouts[1].product_id,
- amount=bp1.bp_payouts[1].amount,
)
assert bp2_tx
@@ -901,22 +901,22 @@ class TestBusinessPayoutEventManager:
ledger_collection.initial_load(client=None, sync=True)
pop_ledger_merge.build(client=client_no_amm, ledger_coll=ledger_collection)
- business.prebuild_balance(
- thl_pg_config=thl_web_rr,
+ gr_business.prebuild_balance(
+ product_manager=product_manager,
lm=ledger_manager,
ds=mnt_filepath,
client=client_no_amm,
pop_ledger=pop_ledger_merge,
)
- business.prebuild_payouts(
+ gr_business.prebuild_payouts(
bpem=business_payout_event_manager,
)
- assert isinstance(business.payouts, list)
- assert len(business.payouts) == 2
- assert len(business.payouts[0].bp_payouts) == 2
- assert len(business.payouts[1].bp_payouts) == 1
+ 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
- bb2 = business.balance
+ bb2 = gr_business.balance
# Okay os we have the balance before, and after the Business Payout
# of bb1.available_balance worth..
@@ -993,13 +993,12 @@ class TestBusinessPayoutEventManager:
wall_req_cpi=Decimal("50.00"),
started=start + timedelta(days=1, hours=2, minutes=1 + idx),
)
- payout_event_manager.set_account_lookup_table(thl_lm=thl_ledger_manager)
# Now that we paid out the business: Business, let's confirm the updated balances
ledger_collection.initial_load(client=None, sync=True)
pop_ledger_merge.build(client=client_no_amm, ledger_coll=ledger_collection)
gr_business.prebuild_balance(
- thl_pg_config=thl_web_rr,
+ product_manager=product_manager,
lm=ledger_manager,
ds=mnt_filepath,
client=client_no_amm,
@@ -1039,6 +1038,7 @@ class TestBusinessPayoutEventManager:
pm=product_manager,
thl_lm=thl_ledger_manager,
created=start + timedelta(days=1, hours=3),
+ transaction_id=uuid4().hex,
)
assert isinstance(bp1, BusinessPayoutEvent)
assert len(bp1.bp_payouts) == 3
@@ -1054,7 +1054,7 @@ class TestBusinessPayoutEventManager:
# balance.available_balance are reflective of having a $250 ACH/Wire
# sent to the Business
gr_business.prebuild_balance(
- thl_pg_config=thl_web_rr,
+ product_manager=product_manager,
lm=ledger_manager,
ds=mnt_filepath,
client=client_no_amm,
@@ -1120,14 +1120,13 @@ class TestBusinessPayoutEventManager:
wall_req_cpi=Decimal("7.50"),
started=start + timedelta(days=1, hours=1 + iidx, minutes=1 + idx),
)
- payout_event_manager.set_account_lookup_table(thl_lm=thl_ledger_manager)
rm_ledger_collection()
rm_pop_ledger_merge()
ledger_collection.initial_load(client=None, sync=True)
pop_ledger_merge.build(client=client_no_amm, ledger_coll=ledger_collection)
gr_business.prebuild_balance(
- thl_pg_config=thl_web_rr,
+ product_manager=product_manager,
lm=ledger_manager,
ds=mnt_filepath,
client=client_no_amm,
@@ -1148,7 +1147,7 @@ class TestBusinessPayoutEventManager:
ledger_collection.initial_load(client=None, sync=True)
pop_ledger_merge.build(client=client_no_amm, ledger_coll=ledger_collection)
gr_business.prebuild_balance(
- thl_pg_config=thl_web_rr,
+ product_manager=product_manager,
lm=ledger_manager,
ds=mnt_filepath,
client=client_no_amm,
@@ -1172,12 +1171,10 @@ class TestBusinessPayoutEventManager:
ledger_collection.initial_load(client=None, sync=True)
pop_ledger_merge.build(client=client_no_amm, ledger_coll=ledger_collection)
gr_business.prebuild_payouts(
- thl_pg_config=thl_web_rr,
- thl_lm=thl_ledger_manager,
bpem=business_payout_event_manager,
)
gr_business.prebuild_balance(
- thl_pg_config=thl_web_rr,
+ product_manager=product_manager,
lm=ledger_manager,
ds=mnt_filepath,
client=client_no_amm,
diff --git a/tests/managers/thl/test_session_manager.py b/tests/managers/thl/test_session_manager.py
index 60edcb9..4b122ec 100644
--- a/tests/managers/thl/test_session_manager.py
+++ b/tests/managers/thl/test_session_manager.py
@@ -117,6 +117,7 @@ class TestSessionManagerFilter:
def test_team(
self,
product_factory: Callable[..., Product],
+ product_manager,
user_factory: Callable[..., User],
gr_team: Team,
session_manager: SessionManager,
@@ -129,7 +130,7 @@ class TestSessionManagerFilter:
u = user_factory(product=p1)
session_manager.create(started=utc_hour_ago, user=u, uuid_id=uuid4().hex)
- gr_team.prefetch_products(thl_pg_config=thl_web_rr)
+ gr_team.prefetch_products(product_manager=product_manager)
assert len(gr_team.product_uuids) == 1
res = session_manager.filter(product_uuids=gr_team.product_uuids)
assert len(res) == 5
@@ -142,6 +143,7 @@ class TestSessionManagerFilter:
session_manager: SessionManager,
utc_hour_ago: datetime,
thl_web_rr: PostgresConfig,
+ product_manager,
):
p1 = product_factory(business=gr_business)
@@ -149,7 +151,7 @@ class TestSessionManagerFilter:
u = user_factory(product=p1)
session_manager.create(started=utc_hour_ago, user=u, uuid_id=uuid4().hex)
- gr_business.prefetch_products(thl_pg_config=thl_web_rr)
+ gr_business.prefetch_products(product_manager=product_manager)
assert len(gr_business.product_uuids) == 1
res = session_manager.filter(product_uuids=gr_business.product_uuids)
assert len(res) == 5
diff --git a/tests/managers/thl/test_survey_penalty.py b/tests/managers/thl/test_survey_penalty.py
index 04f69d2..71cea16 100644
--- a/tests/managers/thl/test_survey_penalty.py
+++ b/tests/managers/thl/test_survey_penalty.py
@@ -103,7 +103,7 @@ class TestSurveyPenalty:
assert res == {"t:a": 0.1, "t:b": 0.2, "u:b": 0.1}
assert surveypenalty_manager.cache.currsize == 1
cached_key = tuple(list(next(iter(surveypenalty_manager.cache.keys())))[1:])
- assert cached_key == ("product_id", product_uuid, "team_id", team_id_random)
+ assert cached_key == (("product_id", product_uuid), ("team_id", team_id_random))
# Both don't exist, return nothing
res = surveypenalty_manager.get_penalties_for(
diff --git a/tests/managers/thl/test_user_manager/test_base.py b/tests/managers/thl/test_user_manager/test_base.py
index 5d12052..2a7e879 100644
--- a/tests/managers/thl/test_user_manager/test_base.py
+++ b/tests/managers/thl/test_user_manager/test_base.py
@@ -56,43 +56,43 @@ class TestUserManager:
def test_get_user_no_inmemory(self):
self.user_manager.clear_user_inmemory_cache(self.user)
- self.user_manager.get_user.__wrapped__.cache_clear()
+ self.user_manager.cache_clear()
u = self.user_manager.get_user(user_id=self.user.user_id)
# this should hit mysql
assert u == self.user
- cache_info = self.user_manager.get_user.__wrapped__.cache_info()
- assert cache_info.hits == 0, cache_info
- assert cache_info.misses == 1, cache_info
+ cache_info = self.user_manager.cache_info()
+ assert cache_info['hits'] == 0, cache_info
+ assert cache_info['misses'] == 1, cache_info
# this should hit the lru cache
u = self.user_manager.get_user(user_id=self.user.user_id)
assert u == self.user
- cache_info = self.user_manager.get_user.__wrapped__.cache_info()
- assert cache_info.hits == 1, cache_info
- assert cache_info.misses == 1, cache_info
+ cache_info = self.user_manager.cache_info()
+ assert cache_info['hits'] == 1, cache_info
+ assert cache_info['misses'] == 1, cache_info
def test_get_user_with_inmemory(self):
# user_manager = self.get_user_manager()
self.user_manager.set_user_inmemory_cache(self.user)
- self.user_manager.get_user.__wrapped__.cache_clear()
+ self.user_manager.cache_clear()
u = self.user_manager.get_user(user_id=self.user.user_id)
# this should hit inmemory cache
assert u == self.user
- cache_info = self.user_manager.get_user.__wrapped__.cache_info()
- assert cache_info.hits == 0, cache_info
- assert cache_info.misses == 1, cache_info
+ cache_info = self.user_manager.cache_info()
+ assert cache_info['hits'] == 0, cache_info
+ assert cache_info['misses'] == 1, cache_info
# this should hit the lru cache
u = self.user_manager.get_user(user_id=self.user.user_id)
assert u == self.user
- cache_info = self.user_manager.get_user.__wrapped__.cache_info()
- assert cache_info.hits == 1, cache_info
- assert cache_info.misses == 1, cache_info
+ cache_info = self.user_manager.cache_info()
+ assert cache_info['hits'] == 1, cache_info
+ assert cache_info['misses'] == 1, cache_info
class TestBlockUserManager:
diff --git a/tests/managers/thl/test_user_manager/test_redis.py b/tests/managers/thl/test_user_manager/test_redis.py
index f6b59c9..89d0a7a 100644
--- a/tests/managers/thl/test_user_manager/test_redis.py
+++ b/tests/managers/thl/test_user_manager/test_redis.py
@@ -8,15 +8,14 @@ from generalresearch.managers.base import Permission
from generalresearch.managers.thl.user_manager.redis_user_manager import (
RedisUserManager,
)
+from generalresearch.redis_helper import RedisConfig
if TYPE_CHECKING:
- from generalresearch.config import GRLBaseSettings
from generalresearch.models.thl.user import User
from generalresearch.pg_helper import PostgresConfig
class TestUserManagerRedis:
-
def test_get_notset(self, redis_user_manager: RedisUserManager, user: User):
redis_user_manager.clear_user(user=user)
assert redis_user_manager.get_user(user_id=user.user_id) is None
@@ -48,10 +47,10 @@ class TestUserManagerRedis:
def test_get_with_cache_prefix(
self,
- settings: GRLBaseSettings,
user: User,
thl_web_rw: PostgresConfig,
thl_web_rr: PostgresConfig,
+ thl_redis_config: RedisConfig,
):
"""
Confirm the prefix functionality is working; we do this so it
@@ -67,19 +66,19 @@ class TestUserManagerRedis:
pg_config=thl_web_rw,
pg_config_rr=thl_web_rr,
sql_permissions=[Permission.UPDATE, Permission.CREATE],
- redis=settings.redis,
- redis_timeout=settings.redis_timeout,
+ redis=thl_redis_config.dsn,
+ redis_timeout=1,
)
um2 = UserManager(
pg_config=thl_web_rw,
pg_config_rr=thl_web_rr,
sql_permissions=[Permission.UPDATE, Permission.CREATE],
- redis=settings.redis,
- redis_timeout=settings.redis_timeout,
+ redis=thl_redis_config.dsn,
+ redis_timeout=1,
cache_prefix="user-lookup-v2",
)
-
+ user = user.to_user_ref()
um1.get_or_create_user(
product_id=user.product_id, product_user_id=user.product_user_id
)
diff --git a/tests/managers/thl/test_userhealth.py b/tests/managers/thl/test_userhealth.py
index 268b110..bd2152a 100644
--- a/tests/managers/thl/test_userhealth.py
+++ b/tests/managers/thl/test_userhealth.py
@@ -3,11 +3,13 @@ from __future__ import annotations
from collections.abc import Callable
from datetime import UTC, datetime
from typing import TYPE_CHECKING
+from unittest.mock import Mock
from uuid import uuid4
import faker
import pytest
+from generalresearch.managers.thl.ipinfo import GeoIpInfoManager
from generalresearch.managers.thl.userhealth import (
AuditLogManager,
IPRecordManager,
@@ -23,10 +25,6 @@ from generalresearch.models.thl.user_iphistory import (
from generalresearch.models.thl.userhealth import AuditLog, AuditLogLevel
if TYPE_CHECKING:
- from generalresearch.models.thl.ipinfo import (
- IPGeoname,
- IPInformation,
- )
from generalresearch.models.thl.product import Product
from generalresearch.models.thl.user import User
from generalresearch.pg_helper import PostgresConfig
@@ -36,7 +34,6 @@ fake = faker.Faker()
class TestAuditLog:
-
def test_init(self, thl_web_rr: PostgresConfig, audit_log_manager: AuditLogManager):
alm = AuditLogManager(pg_config=thl_web_rr)
@@ -56,7 +53,7 @@ class TestAuditLog:
user_id=user.user_id, level=level, event_type=uuid4().hex
)
assert isinstance(instance, AuditLog)
- assert instance.id != 1
+ assert instance.id != 0
def test_get_by_id(self, audit_log: AuditLog, audit_log_manager: AuditLogManager):
@@ -225,14 +222,16 @@ class TestAuditLog:
class TestIPRecordManager:
-
def test_init(
self,
thl_web_rr: PostgresConfig,
thl_redis_config: RedisConfig,
ip_record_manager: IPRecordManager,
):
- instance = IPRecordManager(pg_config=thl_web_rr, redis_config=thl_redis_config)
+ instance = IPRecordManager(
+ pg_config=thl_web_rr,
+ redis_config=thl_redis_config,
+ )
assert isinstance(instance, IPRecordManager)
assert isinstance(ip_record_manager, IPRecordManager)
@@ -240,10 +239,11 @@ class TestIPRecordManager:
self,
ip_record_manager: IPRecordManager,
user: User,
- ip_information: IPInformation,
ip_record_factory: Callable[..., IPRecord],
):
- instance = ip_record_factory(user_id=user.user_id, ip=ip_information.ip)
+ ip = fake.ipv4_public()
+
+ instance = ip_record_factory(user=user, ip=ip)
assert isinstance(instance, IPRecord)
assert isinstance(instance.forwarded_ips, list)
@@ -260,40 +260,12 @@ class TestIPRecordManager:
def test_prefetch_info(
self,
ip_record_factory: Callable[..., IPRecord],
- ip_information_factory: Callable[..., IPInformation],
- ip_geoname: IPGeoname,
user: User,
thl_web_rr: PostgresConfig,
thl_redis_config: RedisConfig,
):
-
- ip = fake.ipv4_public()
- ip_information_factory(ip=ip, geoname=ip_geoname)
- ipr: IPRecord = ip_record_factory(user_id=user.user_id, ip=ip)
- assert isinstance(ipr, IPRecord)
-
- assert ipr.information is None
- assert len(ipr.forwarded_ip_records) >= 1
- assert isinstance(ipr.forwarded_ip_records, list)
- fipr = ipr.forwarded_ip_records[0]
- assert fipr.information is None
-
- ipr.prefetch_ipinfo(
- pg_config=thl_web_rr,
- redis_config=thl_redis_config,
- include_forwarded=True,
- )
- assert isinstance(ipr.information, GeoIPInformation)
- assert ipr.information.ip == ipr.ip == ip
- assert fipr.information is None, "the ipinfo doesn't exist in the db yet"
-
- ip_information_factory(ip=fipr.ip, geoname=ip_geoname)
- ipr.prefetch_ipinfo(
- pg_config=thl_web_rr,
- redis_config=thl_redis_config,
- include_forwarded=True,
- )
- assert fipr.information is not None
+ # No more prefetch info here. Moved into UserIPHistory.enrich_ips
+ pass
@pytest.mark.usefixtures("user_iphistory_manager_clear_cache")
@@ -302,57 +274,86 @@ class TestUserIpHistoryManager:
self,
thl_web_rr: PostgresConfig,
thl_redis_config: RedisConfig,
+ geoip_info_manager: GeoIpInfoManager,
user_iphistory_manager: UserIpHistoryManager,
):
instance = UserIpHistoryManager(
- pg_config=thl_web_rr, redis_config=thl_redis_config
+ pg_config=thl_web_rr,
+ redis_config=thl_redis_config,
+ geoip_info_manager=geoip_info_manager,
)
assert isinstance(instance, UserIpHistoryManager)
assert isinstance(user_iphistory_manager, UserIpHistoryManager)
- def test_latest_record(
+ def test_latest_record_and_enrich(
self,
user_iphistory_manager: UserIpHistoryManager,
user: User,
ip_record_factory: Callable[..., IPRecord],
- ip_information_factory: Callable[..., IPInformation],
- ip_geoname: IPGeoname,
+ geoip_information_factory: Callable[..., GeoIPInformation],
+ geoip_info_manager: GeoIpInfoManager,
):
ip = fake.ipv4_public()
- ip_information_factory(ip=ip, geoname_id=ip_geoname.geoname_id, is_anonymous=True)
- ipr1: IPRecord = ip_record_factory(user_id=user.user_id, ip=ip)
+ information = geoip_information_factory(
+ ip=ip, is_anonymous=True, country_iso="de"
+ )
+ lookup_results = {ip: information}
+ geoip_info_manager.get_multi = Mock(
+ side_effect=lambda ip_addresses: {
+ address: lookup_results[address] for address in ip_addresses
+ }
+ )
- ipr = user_iphistory_manager.get_user_latest_ip_record(user=user)
- assert isinstance(ipr, IPRecord)
+ ipr1 = ip_record_factory(user=user, ip=ip)
+ ipr = user_iphistory_manager.get_user_latest_ip_record(
+ user=user,
+ )
assert ipr.ip == ipr1.ip
assert ipr.is_anonymous
assert isinstance(ipr.information, GeoIPInformation)
- assert ipr.information.lookup_prefix == "/32"
- ip = fake.ipv6()
- ip_information_factory(ip=ip, geoname_id=ip_geoname.geoname_id)
- ipr2: IPRecord = ip_record_factory(user_id=user.user_id, ip=ip)
+ assert (
+ user_iphistory_manager.get_user_latest_country(
+ user=user,
+ )
+ == "de"
+ )
+
+ ip2 = fake.ipv6()
+ ipr2: IPRecord = ip_record_factory(user=user, ip=ip2)
+ lookup_results[ipr2.ip] = geoip_information_factory(
+ ip=ipr2.ip,
+ country_iso="us",
+ is_anonymous=False,
+ )
- ipr = user_iphistory_manager.get_user_latest_ip_record(user=user)
- assert isinstance(ipr, IPRecord)
+ ipr = user_iphistory_manager.get_user_latest_ip_record(
+ user=user,
+ )
assert ipr.ip == ipr2.ip
assert isinstance(ipr.information, GeoIPInformation)
- assert ipr.information.lookup_prefix == "/64"
assert ipr.information is not None
assert not ipr.is_anonymous
- country_iso = user_iphistory_manager.get_user_latest_country(user=user)
- assert country_iso == ip_geoname.country_iso
+ assert (
+ user_iphistory_manager.get_user_latest_country(
+ user=user,
+ )
+ == "us"
+ )
- iph = user_iphistory_manager.get_user_ip_history(user_id=user.user_id)
+ iph = user_iphistory_manager.get_user_ip_history(user=user)
assert isinstance(iph, UserIPHistory)
assert isinstance(iph.ips, list)
assert iph.ips[0].information is not None
assert iph.ips[1].information is not None
- assert iph.ips[0].country_iso == country_iso
- assert iph.ips[0].is_anonymous
- assert iph.ips[0].ip == ipr1.ip
- assert iph.ips[1].ip == ipr2.ip
+ assert iph.ips[0].country_iso == "us"
+ assert iph.ips[1].country_iso == "de"
+ assert not iph.ips[0].is_anonymous
+ assert iph.ips[1].is_anonymous
+ # ordered by created DESCENDING!!!!!!!!!!!!!!1
+ assert iph.ips[0].ip == ipr2.ip
+ assert iph.ips[1].ip == ipr1.ip
def test_virgin(
self,
@@ -360,65 +361,9 @@ class TestUserIpHistoryManager:
user_iphistory_manager: UserIpHistoryManager,
ip_record_factory: Callable[..., IPRecord],
):
- iph = user_iphistory_manager.get_user_ip_history(user_id=user.user_id)
+ iph = user_iphistory_manager.get_user_ip_history(user=user)
assert len(iph.ips) == 0
- ip_record_factory(user_id=user.user_id, ip=fake.ipv4_public())
- iph = user_iphistory_manager.get_user_ip_history(user_id=user.user_id)
+ ip_record_factory(user=user, ip=fake.ipv4_public())
+ iph = user_iphistory_manager.get_user_ip_history(user=user)
assert len(iph.ips) == 1
-
- def test_out_of_order(
- self,
- ip_record_factory: Callable[..., IPRecord],
- user: User,
- user_iphistory_manager: UserIpHistoryManager,
- ip_information_factory: Callable[..., IPInformation],
- ip_geoname: IPGeoname,
- ):
- # Create the user-ip association BEFORE the ip even exists in the ipinfo table
- ip = fake.ipv4_public()
- ip_record_factory(user_id=user.user_id, ip=ip)
- iph = user_iphistory_manager.get_user_ip_history(user_id=user.user_id)
- assert isinstance(iph, UserIPHistory)
- assert isinstance(iph.ips, list)
- assert len(iph.ips) == 1
- ipr = iph.ips[0]
- assert ipr.information is None
- assert not ipr.is_anonymous
-
- ip_information_factory(ip=ip, geoname_id=ip_geoname.geoname_id, is_anonymous=True)
- iph = user_iphistory_manager.get_user_ip_history(user_id=user.user_id)
- assert isinstance(iph, UserIPHistory)
- assert isinstance(iph.ips, list)
- assert len(iph.ips) == 1
- ipr = iph.ips[0]
- assert ipr.information is not None
- assert ipr.is_anonymous
-
- def test_out_of_order_ipv6(
- self,
- ip_record_factory: Callable[..., IPRecord],
- user: User,
- user_iphistory_manager: UserIpHistoryManager,
- ip_information_factory: Callable[..., IPInformation],
- ip_geoname: IPGeoname,
- ):
- # Create the user-ip association BEFORE the ip even exists in the ipinfo table
- ip = fake.ipv6()
- ip_record_factory(user_id=user.user_id, ip=ip)
- iph = user_iphistory_manager.get_user_ip_history(user_id=user.user_id)
- assert isinstance(iph, UserIPHistory)
- assert isinstance(iph.ips, list)
- assert len(iph.ips) == 1
- ipr = iph.ips[0]
- assert ipr.information is None
- assert not ipr.is_anonymous
-
- ip_information_factory(ip=ip, geoname_id=ip_geoname.geoname_id, is_anonymous=True)
- iph = user_iphistory_manager.get_user_ip_history(user_id=user.user_id)
- assert isinstance(iph, UserIPHistory)
- assert isinstance(iph.ips, list)
- assert len(iph.ips) == 1
- ipr = iph.ips[0]
- assert ipr.information is not None
- assert ipr.is_anonymous
diff --git a/tests/managers/thl/test_wallet/__init__.py b/tests/managers/thl/test_wallet/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/managers/thl/test_wallet/__init__.py
diff --git a/tests/managers/thl/test_wallet/test_user_payout.py b/tests/managers/thl/test_wallet/test_user_payout.py
new file mode 100644
index 0000000..bbc97a3
--- /dev/null
+++ b/tests/managers/thl/test_wallet/test_user_payout.py
@@ -0,0 +1,435 @@
+from datetime import datetime
+from decimal import Decimal
+from random import randint
+from unittest.mock import Mock
+from uuid import uuid4
+
+import pytest
+from faker import Faker
+
+from generalresearch.currency import USDCent
+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.wallet.tango import TangoManager
+from generalresearch.managers.thl.wallet.user_payout import (
+ UserPayoutEventManager,
+ make_request_paypal,
+)
+from generalresearch.models.thl.definitions import PayoutStatus
+from generalresearch.models.thl.payout import UserPayoutEvent
+from generalresearch.models.thl.user import User
+from generalresearch.models.thl.wallet.cashout_method import (
+ CashMailCashoutMethodData,
+ CashMailCashoutMethodRequestData,
+ CashoutRequestInfo,
+ PaypalCashoutMethodData,
+ PaypalCashoutMethodRequestData,
+ USDeliveryAddress,
+)
+from generalresearch.models.thl.wallet.definitions import PayoutType
+
+fake = Faker()
+
+
+class TestUserPayoutEventManager:
+ def test_get_by_uuid_and_create(
+ self,
+ user: User,
+ user_payout_event_manager: UserPayoutEventManager,
+ thl_ledger_manager: ThlLedgerManager,
+ utc_now: datetime,
+ ):
+ user_account = thl_ledger_manager.get_account_or_create_user_wallet(user=user)
+ request_data = PaypalCashoutMethodRequestData.model_validate(
+ {
+ "email": fake.email(),
+ "interface": "api",
+ }
+ )
+ pe1: UserPayoutEvent = user_payout_event_manager.create(
+ debit_account_uuid=user_account.uuid,
+ payout_type=PayoutType.PAYPAL,
+ cashout_method_uuid=uuid4().hex,
+ amount=100,
+ created=utc_now,
+ request_data=request_data.model_dump(mode="json"),
+ )
+ # these get added by the query
+ pe1.account_reference_type = "user"
+ pe1.account_reference_uuid = user.uuid
+
+ pe2 = user_payout_event_manager.get_by_uuid(pe_uuid=pe1.uuid)
+
+ assert pe1 == pe2
+
+ def test_get_payout_detail_cash_in_mail(
+ self,
+ user_with_wallet: User,
+ user_payout_event_manager: UserPayoutEventManager,
+ cashout_method_manager: CashoutMethodManager,
+ thl_ledger_manager: ThlLedgerManager,
+ ):
+ user = user_with_wallet
+ data = CashMailCashoutMethodData(
+ delivery_address=USDeliveryAddress.model_validate(
+ {
+ "name_or_attn": "Josh Ackerman",
+ "address": "123 Fake St",
+ "city": "San Francisco",
+ "state": "CA",
+ "postal_code": "12345",
+ }
+ )
+ )
+ cm = cashout_method_manager.create_cash_in_mail_cashout_method(
+ data=data, user=user
+ )
+ user_account = thl_ledger_manager.get_account_or_create_user_wallet(user=user)
+
+ rand_amount = randint(a=99, b=999)
+ request_data = CashMailCashoutMethodRequestData.model_validate(
+ cm.data.model_dump()
+ )
+
+ pe = user_payout_event_manager.create(
+ debit_account_uuid=user_account.uuid,
+ cashout_method_uuid=cm.id,
+ amount=rand_amount,
+ ext_ref_id=uuid4().hex,
+ payout_type=PayoutType.CASH_IN_MAIL,
+ request_data=request_data.model_dump(mode="json"),
+ )
+
+ res = user_payout_event_manager.get_payout_detail(pe_uuid=pe.uuid)
+ assert isinstance(res, CashoutRequestInfo)
+
+ def test_get_payout_detail_paypal(
+ self,
+ user_with_wallet: User,
+ user_payout_event_manager: UserPayoutEventManager,
+ cashout_method_manager: CashoutMethodManager,
+ thl_ledger_manager: ThlLedgerManager,
+ ):
+ user = user_with_wallet
+ data = PaypalCashoutMethodData(email=fake.email())
+ cm = cashout_method_manager.create_paypal_cashout_method(data=data, user=user)
+ user_account = thl_ledger_manager.get_account_or_create_user_wallet(user=user)
+
+ rand_amount = randint(a=99, b=999)
+ request_data = make_request_paypal(cm)
+ pe = user_payout_event_manager.create(
+ debit_account_uuid=user_account.uuid,
+ cashout_method_uuid=cm.id,
+ amount=rand_amount,
+ ext_ref_id=uuid4().hex,
+ payout_type=PayoutType.PAYPAL,
+ request_data=request_data.model_dump(mode="json"),
+ )
+
+ res = user_payout_event_manager.get_payout_detail(pe_uuid=pe.uuid)
+ assert isinstance(res, CashoutRequestInfo)
+
+
+class TestUserRequestRedeem:
+ @staticmethod
+ def request_paypal_payout(
+ *,
+ user: User,
+ amount: USDCent,
+ user_payout_event_manager: UserPayoutEventManager,
+ cashout_method_manager: CashoutMethodManager,
+ thl_ledger_manager: ThlLedgerManager,
+ user_iphistory_manager: UserIpHistoryManager,
+ ip_record_factory,
+ thl_redis_client,
+ ip: str = "8.8.8.8",
+ fund_amount: Decimal = Decimal("10.00"),
+ ) -> UserPayoutEvent:
+ email = fake.email()
+ cashout_method = cashout_method_manager.create_paypal_cashout_method(
+ data=PaypalCashoutMethodData(email=email),
+ user=user,
+ )
+ ip_record_factory(user=user, ip=ip)
+ thl_ledger_manager.create_tx_user_bonus(
+ user=user,
+ amount=fund_amount,
+ ref_uuid=uuid4().hex,
+ description="Fund PayPal redemption test",
+ )
+
+ tango_manager = Mock(spec=TangoManager)
+ tango_manager.get_exchange_rates.return_value = {}
+
+ return user_payout_event_manager.user_request_redeem(
+ user=user,
+ cashout_method_id=cashout_method.id,
+ amount=amount,
+ tango_manager=tango_manager,
+ cashout_method_manager=cashout_method_manager,
+ ledger_manager=thl_ledger_manager,
+ user_ip_history_manager=user_iphistory_manager,
+ redis_client=thl_redis_client,
+ )
+
+ def test_user_request_redeem_paypal(
+ self,
+ user_with_wallet: User,
+ user_payout_event_manager: UserPayoutEventManager,
+ cashout_method_manager: CashoutMethodManager,
+ thl_ledger_manager: ThlLedgerManager,
+ user_iphistory_manager: UserIpHistoryManager,
+ geoip_info_manager: GeoIpInfoManager,
+ ip_record_factory,
+ thl_redis_client,
+ ):
+ user = user_with_wallet
+ payout = self.request_paypal_payout(
+ user=user,
+ amount=USDCent(600),
+ user_payout_event_manager=user_payout_event_manager,
+ cashout_method_manager=cashout_method_manager,
+ thl_ledger_manager=thl_ledger_manager,
+ user_iphistory_manager=user_iphistory_manager,
+ ip_record_factory=ip_record_factory,
+ thl_redis_client=thl_redis_client,
+ )
+
+ assert payout.status == PayoutStatus.PENDING
+ assert payout.payout_type == PayoutType.PAYPAL
+ assert payout.amount == 600
+ assert payout.request_data["interface"] == "api"
+ assert thl_ledger_manager.get_user_wallet_balance(user) == 400
+ assert user_payout_event_manager.get_by_uuid(payout.uuid) == payout
+
+ detail = user_payout_event_manager.get_payout_detail(payout.uuid)
+ assert not detail.transaction_info
+ assert detail.description == "PayPal"
+
+ def test_user_request_redeem_paypal_complete(
+ self,
+ user_with_wallet: User,
+ user_payout_event_manager: UserPayoutEventManager,
+ cashout_method_manager: CashoutMethodManager,
+ thl_ledger_manager: ThlLedgerManager,
+ user_iphistory_manager: UserIpHistoryManager,
+ geoip_info_manager: GeoIpInfoManager,
+ ip_record_factory,
+ thl_redis_client,
+ create_main_accounts,
+ ):
+ create_main_accounts()
+ user = user_with_wallet
+ payout = self.request_paypal_payout(
+ user=user,
+ amount=USDCent(600),
+ user_payout_event_manager=user_payout_event_manager,
+ cashout_method_manager=cashout_method_manager,
+ thl_ledger_manager=thl_ledger_manager,
+ user_iphistory_manager=user_iphistory_manager,
+ ip_record_factory=ip_record_factory,
+ thl_redis_client=thl_redis_client,
+ )
+
+ user_payout_event_manager.update(
+ payout_event=payout,
+ status=PayoutStatus.APPROVED,
+ ext_ref_id="paypal-batch-id",
+ )
+ thl_ledger_manager.create_tx_user_payout_complete(
+ user=user,
+ payout_event=payout,
+ fee_amount=Decimal("0.25"),
+ )
+ user_payout_event_manager.update(
+ payout_event=payout,
+ status=PayoutStatus.COMPLETE,
+ order_data={"transaction_id": "paypal-transaction-id"},
+ )
+
+ detail = user_payout_event_manager.get_payout_detail(pe_uuid=payout.uuid)
+ assert detail.status == PayoutStatus.COMPLETE
+ assert detail.transaction_info == {"transaction_id": "paypal-transaction-id"}
+
+ assert thl_ledger_manager.get_user_wallet_balance(user_with_wallet) == 400
+ bp_expense = thl_ledger_manager.get_account_or_create_bp_expense_by_uuid(
+ product_uuid=user.to_user_ref().product_id, expense_name="paypal"
+ )
+ assert thl_ledger_manager.get_account_balance(bp_expense) == -25
+
+ def test_user_request_redeem_tango_complete(
+ self,
+ user_with_wallet: User,
+ user_payout_event_manager: UserPayoutEventManager,
+ cashout_method_manager: CashoutMethodManager,
+ thl_ledger_manager: ThlLedgerManager,
+ user_iphistory_manager: UserIpHistoryManager,
+ ip_record_factory,
+ thl_redis_client,
+ create_main_accounts,
+ setup_cashoutmethod_db,
+ example_tango_cashout_methods,
+ ):
+ create_main_accounts()
+ setup_cashoutmethod_db()
+ user = user_with_wallet
+ # This is the italian Amazon.it gift card
+ cashout_method = example_tango_cashout_methods[1]
+ # This is an IP that we have hard-coded by the geoip_info_manager as Italy
+ ip_record_factory(user=user, ip="2.2.2.2")
+
+ thl_ledger_manager.create_tx_user_bonus(
+ user=user,
+ amount=Decimal("5.00"),
+ ref_uuid=uuid4().hex,
+ description="Fund Tango redemption test",
+ )
+
+ tango_client = Mock()
+ tango_client.get_exchange_rates.return_value = {
+ "exchangeRates": [
+ {"rewardCurrency": "USD", "baseCurrency": "EUR", "baseFx": 1.14792}
+ ]
+ }
+ tango_manager = TangoManager(
+ tango_client=tango_client,
+ tango_account_id="test-account",
+ tango_customer_id="test-customer",
+ cashout_method_manager=cashout_method_manager,
+ )
+ request = tango_manager.make_request(
+ amount=USDCent(2_00),
+ cashout_method=cashout_method,
+ payout_event_id=uuid4().hex,
+ )
+ assert request.amount == Decimal(2.0 / 1.14792).quantize(
+ Decimal("0.01")
+ ) # ~ 1.74 EUR
+
+ payout = user_payout_event_manager.user_request_redeem(
+ user=user,
+ cashout_method_id=cashout_method.id,
+ amount=USDCent(2_00),
+ tango_manager=tango_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.request_data["amount"] == '1.74'
+ assert payout.request_data["externalRefID"] == payout.uuid
+
+ user_payout_event_manager.update(
+ payout_event=payout,
+ status=PayoutStatus.APPROVED,
+ ext_ref_id="tango-order-id",
+ )
+ thl_ledger_manager.create_tx_user_payout_complete(
+ user=user,
+ payout_event=payout,
+ )
+ user_payout_event_manager.update(
+ payout_event=payout,
+ status=PayoutStatus.COMPLETE,
+ order_data={
+ "reward": {
+ "credentialList": [
+ {
+ "credentialType": "giftCardCode",
+ "credentialValue": "TEST-CODE",
+ }
+ ],
+ "redemptionInstructions": "Use this code at checkout.",
+ }
+ },
+ )
+
+ detail = user_payout_event_manager.get_payout_detail(pe_uuid=payout.uuid)
+ assert payout.payout_type == PayoutType.TANGO
+ assert detail.status == PayoutStatus.COMPLETE
+ assert detail.transaction_info == {
+ "credential_list": [
+ {
+ "credentialType": "giftCardCode",
+ "credentialValue": "TEST-CODE",
+ }
+ ],
+ "redemption_instructions": "Use this code at checkout.",
+ }
+ assert thl_ledger_manager.get_user_wallet_balance(user) == 300
+ bp_expense = thl_ledger_manager.get_account_or_create_bp_expense_by_uuid(
+ product_uuid=user.to_user_ref().product_id,
+ expense_name="tango",
+ )
+ assert thl_ledger_manager.get_account_balance(bp_expense) == -7
+
+ def test_user_request_redeem_paypal_insufficient_balance(
+ self,
+ user_with_wallet: User,
+ user_payout_event_manager: UserPayoutEventManager,
+ cashout_method_manager: CashoutMethodManager,
+ thl_ledger_manager: ThlLedgerManager,
+ user_iphistory_manager: UserIpHistoryManager,
+ ip_record_factory,
+ thl_redis_client,
+ ):
+ with pytest.raises(AssertionError, match="more than their redeemable balance"):
+ self.request_paypal_payout(
+ user=user_with_wallet,
+ amount=USDCent(1_100),
+ user_payout_event_manager=user_payout_event_manager,
+ cashout_method_manager=cashout_method_manager,
+ thl_ledger_manager=thl_ledger_manager,
+ user_iphistory_manager=user_iphistory_manager,
+ ip_record_factory=ip_record_factory,
+ thl_redis_client=thl_redis_client,
+ )
+
+ def test_user_request_redeem_paypal_blocked_user(
+ self,
+ user_with_wallet: User,
+ user_payout_event_manager: UserPayoutEventManager,
+ cashout_method_manager: CashoutMethodManager,
+ thl_ledger_manager: ThlLedgerManager,
+ user_iphistory_manager: UserIpHistoryManager,
+ ip_record_factory,
+ thl_redis_client,
+ ):
+ user_with_wallet.blocked = True
+
+ with pytest.raises(AssertionError, match="Blocked user"):
+ self.request_paypal_payout(
+ user=user_with_wallet,
+ amount=USDCent(600),
+ user_payout_event_manager=user_payout_event_manager,
+ cashout_method_manager=cashout_method_manager,
+ thl_ledger_manager=thl_ledger_manager,
+ user_iphistory_manager=user_iphistory_manager,
+ ip_record_factory=ip_record_factory,
+ thl_redis_client=thl_redis_client,
+ )
+
+ def test_user_request_redeem_paypal_anonymous_user(
+ self,
+ user_with_wallet: User,
+ user_payout_event_manager: UserPayoutEventManager,
+ cashout_method_manager: CashoutMethodManager,
+ thl_ledger_manager: ThlLedgerManager,
+ user_iphistory_manager: UserIpHistoryManager,
+ ip_record_factory,
+ thl_redis_client,
+ ):
+ with pytest.raises(AssertionError, match="Anonymous user"):
+ self.request_paypal_payout(
+ user=user_with_wallet,
+ amount=USDCent(600),
+ user_payout_event_manager=user_payout_event_manager,
+ cashout_method_manager=cashout_method_manager,
+ thl_ledger_manager=thl_ledger_manager,
+ user_iphistory_manager=user_iphistory_manager,
+ ip_record_factory=ip_record_factory,
+ thl_redis_client=thl_redis_client,
+ ip="1.1.1.1",
+ )
diff --git a/tests/models/gr/test_business.py b/tests/models/gr/test_business.py
index e38850d..8de2a47 100644
--- a/tests/models/gr/test_business.py
+++ b/tests/models/gr/test_business.py
@@ -11,11 +11,6 @@ from uuid import uuid4
import pandas as pd
import pytest
from dask.distributed import Client as DaskClient
-
-# noinspection PyUnresolvedReferences
-from distributed.utils_test import (
- client_no_amm,
-)
from pytest import approx
from generalresearch.currency import USDCent
@@ -699,6 +694,7 @@ class TestBusinessBalance:
pop_ledger_merge: PopLedgerMerge,
adj_to_fail_with_tx_factory: Callable[..., None],
delete_df_collection: Callable[..., None],
+ client_no_amm: DaskClient,
):
"""
- Product 1 $2.50 Complete
diff --git a/tests/models/gr/test_team.py b/tests/models/gr/test_team.py
index e853817..7c93209 100644
--- a/tests/models/gr/test_team.py
+++ b/tests/models/gr/test_team.py
@@ -107,7 +107,7 @@ class TestTeam:
self,
gr_team: Team,
gr_business: Business,
- team_manager: TeamManager,
+ gr_team_manager: TeamManager,
gr_business_manager: BusinessManager,
):
@@ -117,7 +117,7 @@ class TestTeam:
assert isinstance(gr_team.businesses, list)
assert len(gr_team.businesses) == 0
- team_manager.add_business(team=gr_team, business=gr_business)
+ gr_team_manager.add_business(team=gr_team, business=gr_business)
assert len(gr_team.businesses) == 0
gr_team.prefetch_businesses(gr_business_manager=gr_business_manager)
assert len(gr_team.businesses) == 1
diff --git a/tests/models/test_finance.py b/tests/models/test_finance.py
index a1da961..53df014 100644
--- a/tests/models/test_finance.py
+++ b/tests/models/test_finance.py
@@ -2,6 +2,7 @@ from __future__ import annotations
from collections.abc import Callable
from datetime import UTC, datetime, timedelta
+from decimal import Decimal
from itertools import product as iter_product
from random import randint
from typing import TYPE_CHECKING
@@ -839,16 +840,22 @@ class TestBusinessBalanceData:
delete_df_collection(coll=ledger_collection)
rm_ledger_collection()
+ # We're expecting 5 time periods x 5 $.50 complete (minus 5% commission = $0.48)
+ # = $12.00
for _ in range(5):
u: User = user_factory(product=product, created=ledger_collection.start)
for item in ledger_collection.items:
+ # A time may be randomly outside the date range, as the
+ # session_with_tx_factory adds time to each wall event it creates
item_time = fake.date_time_between(
start_date=item.start,
- end_date=item.finish,
+ end_date=item.finish - timedelta(minutes=10),
tzinfo=UTC,
)
- session_with_tx_factory(started=item_time, user=u)
+ session_with_tx_factory(
+ started=item_time, user=u, wall_req_cpi=Decimal("0.50")
+ )
item.initial_load(overwrite=True)
# Confirm any of the items are archived
diff --git a/tests/models/thl/test_user_iphistory.py b/tests/models/thl/test_user_iphistory.py
index b8a0be3..efd5168 100644
--- a/tests/models/thl/test_user_iphistory.py
+++ b/tests/models/thl/test_user_iphistory.py
@@ -1,11 +1,13 @@
from __future__ import annotations
from datetime import UTC, datetime, timedelta
+from uuid import uuid4
from generalresearch.models.thl.user_iphistory import (
UserIPHistory,
UserIPRecord,
)
+from generalresearch.models.thl.user_ref import UserRef
def test_collapse_ip_records():
@@ -33,7 +35,10 @@ def test_collapse_ip_records():
),
UserIPRecord(ip="1.2.3.6", created=now + timedelta(minutes=7)),
]
- iph = UserIPHistory(user_id=1, ips=records)
+ iph = UserIPHistory(
+ user=UserRef(user_id=1, product_user_id=uuid4().hex, product_id=uuid4().hex),
+ ips=records,
+ )
res = iph.collapse_ip_records()
# We should be left with one of the 1.2.3.5 ipv4s,