diff options
| author | Max Nanis | 2026-03-06 16:49:46 -0500 |
|---|---|---|
| committer | Max Nanis | 2026-03-06 16:49:46 -0500 |
| commit | 91d040211a4ed6e4157896256a762d3854777b5e (patch) | |
| tree | cd95922ea4257dc8d3f4e4cbe8534474709a20dc /test_utils/managers | |
| download | generalresearch-91d040211a4ed6e4157896256a762d3854777b5e.tar.gz generalresearch-91d040211a4ed6e4157896256a762d3854777b5e.zip | |
Initial commitv3.3.4
Diffstat (limited to 'test_utils/managers')
17 files changed, 1911 insertions, 0 deletions
diff --git a/test_utils/managers/__init__.py b/test_utils/managers/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test_utils/managers/__init__.py diff --git a/test_utils/managers/cashout_methods.py b/test_utils/managers/cashout_methods.py new file mode 100644 index 0000000..c338676 --- /dev/null +++ b/test_utils/managers/cashout_methods.py @@ -0,0 +1,76 @@ +from generalresearch.models.thl.wallet import PayoutType, Currency +from generalresearch.models.thl.wallet.cashout_method import ( + CashoutMethod, + TangoCashoutMethodData, + AmtCashoutMethodData, +) +import random + +from uuid import uuid4 + + +def random_ext_id(base: str = "U02"): + suffix = random.randint(0, 99999) + return f"{base}{suffix:05d}" + + +EXAMPLE_TANGO_CASHOUT_METHODS = [ + CashoutMethod( + id=uuid4().hex, + last_updated="2021-06-23T20:45:38.239182Z", + is_live=True, + type=PayoutType.TANGO, + ext_id=random_ext_id(), + name="Safeway eGift Card $25", + data=TangoCashoutMethodData( + value_type="fixed", countries=["US"], utid=random_ext_id() + ), + user=None, + image_url="https://d30s7yzk2az89n.cloudfront.net/images/brands/b694446-1200w-326ppi.png", + original_currency=Currency.USD, + min_value=2500, + max_value=2500, + ), + CashoutMethod( + id=uuid4().hex, + last_updated="2021-06-23T20:45:38.239182Z", + is_live=True, + type=PayoutType.TANGO, + ext_id=random_ext_id(), + name="Amazon.it Gift Certificate", + data=TangoCashoutMethodData( + value_type="variable", countries=["IT"], utid="U006961" + ), + user=None, + image_url="https://d30s7yzk2az89n.cloudfront.net/images/brands/b405753-1200w-326ppi.png", + original_currency=Currency.EUR, + min_value=1, + max_value=10000, + ), +] + +AMT_ASSIGNMENT_CASHOUT_METHOD = CashoutMethod( + id=uuid4().hex, + last_updated="2021-06-23T20:45:38.239182Z", + is_live=True, + type=PayoutType.AMT, + ext_id=None, + name="AMT Assignment", + data=AmtCashoutMethodData(), + user=None, + min_value=1, + max_value=5, +) + +AMT_BONUS_CASHOUT_METHOD = CashoutMethod( + id=uuid4().hex, + last_updated="2021-06-23T20:45:38.239182Z", + is_live=True, + type=PayoutType.AMT, + ext_id=None, + name="AMT Bonus", + data=AmtCashoutMethodData(), + user=None, + min_value=7, + max_value=4000, +) diff --git a/test_utils/managers/conftest.py b/test_utils/managers/conftest.py new file mode 100644 index 0000000..3a237d1 --- /dev/null +++ b/test_utils/managers/conftest.py @@ -0,0 +1,701 @@ +from typing import Callable, TYPE_CHECKING + +import pymysql +import pytest + +from generalresearch.managers.base import Permission +from generalresearch.models import Source +from test_utils.managers.cashout_methods import ( + EXAMPLE_TANGO_CASHOUT_METHODS, + AMT_ASSIGNMENT_CASHOUT_METHOD, + AMT_BONUS_CASHOUT_METHOD, +) + +if TYPE_CHECKING: + from generalresearch.grliq.managers.forensic_data import ( + GrlIqDataManager, + ) + from generalresearch.grliq.managers.forensic_events import ( + GrlIqEventManager, + ) + from generalresearch.grliq.managers.forensic_results import ( + GrlIqCategoryResultsReader, + ) + from generalresearch.managers.thl.userhealth import AuditLogManager + from generalresearch.managers.thl.payout import ( + BusinessPayoutEventManager, + ) + from generalresearch.managers.thl.maxmind.basic import ( + MaxmindBasicManager, + ) + + from generalresearch.managers.gr.authentication import ( + GRUserManager, + GRTokenManager, + ) + from generalresearch.managers.gr.business import ( + BusinessManager, + BusinessAddressManager, + BusinessBankAccountManager, + ) + from generalresearch.managers.gr.team import ( + TeamManager, + MembershipManager, + ) + from generalresearch.managers.thl.contest_manager import ContestManager + from generalresearch.managers.thl.ipinfo import ( + GeoIpInfoManager, + IPGeonameManager, + IPInformationManager, + ) + from generalresearch.managers.thl.ledger_manager.ledger import ( + LedgerTransactionManager, + LedgerManager, + LedgerAccountManager, + ) + from generalresearch.managers.thl.ledger_manager.thl_ledger import ( + ThlLedgerManager, + ) + from generalresearch.managers.thl.maxmind import MaxmindManager + from generalresearch.managers.thl.payout import PayoutEventManager + from generalresearch.managers.thl.payout import ( + PayoutEventManager, + UserPayoutEventManager, + BrokerageProductPayoutEventManager, + ) + from generalresearch.managers.thl.product import ProductManager + from generalresearch.managers.thl.session import SessionManager + from generalresearch.managers.thl.user_manager.user_manager import ( + UserManager, + ) + from generalresearch.managers.thl.user_manager.user_metadata_manager import ( + UserMetadataManager, + ) + from generalresearch.managers.thl.userhealth import ( + AuditLogManager, + IPRecordManager, + UserIpHistoryManager, + IPGeonameManager, + IPInformationManager, + IPRecordManager, + ) + from generalresearch.managers.thl.wall import ( + WallManager, + WallCacheManager, + ) + from generalresearch.managers.thl.task_adjustment import ( + TaskAdjustmentManager, + ) + + +# === THL === + + +@pytest.fixture(scope="session") +def ltxm(thl_web_rw, thl_redis_config) -> "LedgerTransactionManager": + assert "/unittest-" in thl_web_rw.dsn.path + + from generalresearch.managers.thl.ledger_manager.ledger import ( + LedgerTransactionManager, + ) + + return LedgerTransactionManager( + sql_helper=thl_web_rw, + permissions=[Permission.CREATE, Permission.READ], + testing=True, + redis_config=thl_redis_config, + ) + + +@pytest.fixture(scope="session") +def lam(thl_web_rw, thl_redis_config) -> "LedgerAccountManager": + assert "/unittest-" in thl_web_rw.dsn.path + + from generalresearch.managers.thl.ledger_manager.ledger import ( + LedgerAccountManager, + ) + + return LedgerAccountManager( + pg_config=thl_web_rw, + permissions=[Permission.CREATE, Permission.READ], + testing=True, + redis_config=thl_redis_config, + ) + + +@pytest.fixture(scope="session") +def lm(thl_web_rw, thl_redis_config) -> "LedgerManager": + assert "/unittest-" in thl_web_rw.dsn.path + + from generalresearch.managers.thl.ledger_manager.ledger import ( + LedgerManager, + ) + + return LedgerManager( + pg_config=thl_web_rw, + permissions=[ + Permission.CREATE, + Permission.READ, + Permission.UPDATE, + Permission.DELETE, + ], + testing=True, + redis_config=thl_redis_config, + ) + + +@pytest.fixture(scope="session") +def thl_lm(thl_web_rw, thl_redis_config) -> "ThlLedgerManager": + assert "/unittest-" in thl_web_rw.dsn.path + + from generalresearch.managers.thl.ledger_manager.thl_ledger import ( + ThlLedgerManager, + ) + + return ThlLedgerManager( + pg_config=thl_web_rw, + permissions=[ + Permission.CREATE, + Permission.READ, + Permission.UPDATE, + Permission.DELETE, + ], + testing=True, + redis_config=thl_redis_config, + ) + + +@pytest.fixture(scope="session") +def payout_event_manager(thl_web_rw, thl_redis_config) -> "PayoutEventManager": + assert "/unittest-" in thl_web_rw.dsn.path + + from generalresearch.managers.thl.payout import PayoutEventManager + + return PayoutEventManager( + pg_config=thl_web_rw, + permissions=[Permission.CREATE, Permission.READ], + redis_config=thl_redis_config, + ) + + +@pytest.fixture(scope="session") +def user_payout_event_manager(thl_web_rw, thl_redis_config) -> "UserPayoutEventManager": + assert "/unittest-" in thl_web_rw.dsn.path + + from generalresearch.managers.thl.payout import UserPayoutEventManager + + return UserPayoutEventManager( + pg_config=thl_web_rw, + permissions=[Permission.CREATE, Permission.READ], + redis_config=thl_redis_config, + ) + + +@pytest.fixture(scope="session") +def brokerage_product_payout_event_manager( + thl_web_rw, thl_redis_config +) -> "BrokerageProductPayoutEventManager": + assert "/unittest-" in thl_web_rw.dsn.path + + from generalresearch.managers.thl.payout import ( + BrokerageProductPayoutEventManager, + ) + + return BrokerageProductPayoutEventManager( + pg_config=thl_web_rw, + permissions=[Permission.CREATE, Permission.READ], + redis_config=thl_redis_config, + ) + + +@pytest.fixture(scope="session") +def business_payout_event_manager( + thl_web_rw, thl_redis_config +) -> "BusinessPayoutEventManager": + assert "/unittest-" in thl_web_rw.dsn.path + + from generalresearch.managers.thl.payout import ( + BusinessPayoutEventManager, + ) + + return BusinessPayoutEventManager( + pg_config=thl_web_rw, + permissions=[Permission.CREATE, Permission.READ], + redis_config=thl_redis_config, + ) + + +@pytest.fixture(scope="session") +def product_manager(thl_web_rw) -> "ProductManager": + assert "/unittest-" in thl_web_rw.dsn.path + + from generalresearch.managers.thl.product import ProductManager + + return ProductManager(pg_config=thl_web_rw) + + +@pytest.fixture(scope="session") +def user_manager(settings, thl_web_rw, thl_web_rr) -> "UserManager": + assert "/unittest-" in thl_web_rw.dsn.path + assert "/unittest-" in thl_web_rr.dsn.path + + from generalresearch.managers.thl.user_manager.user_manager import ( + UserManager, + ) + + return UserManager( + pg_config=thl_web_rw, + pg_config_rr=thl_web_rr, + redis=settings.redis, + ) + + +@pytest.fixture(scope="session") +def user_metadata_manager(thl_web_rw) -> "UserMetadataManager": + assert "/unittest-" in thl_web_rw.dsn.path + + from generalresearch.managers.thl.user_manager.user_metadata_manager import ( + UserMetadataManager, + ) + + return UserMetadataManager(pg_config=thl_web_rw) + + +@pytest.fixture(scope="session") +def session_manager(thl_web_rw) -> "SessionManager": + assert "/unittest-" in thl_web_rw.dsn.path + + from generalresearch.managers.thl.session import SessionManager + + return SessionManager(pg_config=thl_web_rw) + + +@pytest.fixture(scope="session") +def wall_manager(thl_web_rw) -> "WallManager": + assert "/unittest-" in thl_web_rw.dsn.path + + from generalresearch.managers.thl.wall import WallManager + + return WallManager(pg_config=thl_web_rw) + + +@pytest.fixture(scope="session") +def wall_cache_manager(thl_web_rw, thl_redis_config) -> "WallCacheManager": + # assert "/unittest-" in thl_web_rw.dsn.path + + from generalresearch.managers.thl.wall import WallCacheManager + + return WallCacheManager(pg_config=thl_web_rw, redis_config=thl_redis_config) + + +@pytest.fixture(scope="session") +def task_adjustment_manager(thl_web_rw) -> "TaskAdjustmentManager": + # assert "/unittest-" in thl_web_rw.dsn.path + + from generalresearch.managers.thl.task_adjustment import ( + TaskAdjustmentManager, + ) + + return TaskAdjustmentManager(pg_config=thl_web_rw) + + +@pytest.fixture(scope="session") +def contest_manager(thl_web_rw) -> "ContestManager": + assert "/unittest-" in thl_web_rw.dsn.path + + from generalresearch.managers.thl.contest_manager import ContestManager + + return ContestManager( + pg_config=thl_web_rw, + permissions=[ + Permission.CREATE, + Permission.READ, + Permission.UPDATE, + Permission.DELETE, + ], + ) + + +@pytest.fixture(scope="session") +def category_manager(thl_web_rw): + assert "/unittest-" in thl_web_rw.dsn.path + from generalresearch.managers.thl.category import CategoryManager + + return CategoryManager(pg_config=thl_web_rw) + + +@pytest.fixture(scope="session") +def buyer_manager(thl_web_rw): + # assert "/unittest-" in thl_web_rw.dsn.path + from generalresearch.managers.thl.buyer import BuyerManager + + return BuyerManager(pg_config=thl_web_rw) + + +@pytest.fixture(scope="session") +def survey_manager(thl_web_rw): + # assert "/unittest-" in thl_web_rw.dsn.path + from generalresearch.managers.thl.survey import SurveyManager + + return SurveyManager(pg_config=thl_web_rw) + + +@pytest.fixture(scope="session") +def surveystat_manager(thl_web_rw): + # assert "/unittest-" in thl_web_rw.dsn.path + from generalresearch.managers.thl.survey import SurveyStatManager + + return SurveyStatManager(pg_config=thl_web_rw) + + +@pytest.fixture(scope="session") +def surveypenalty_manager(thl_redis_config): + from generalresearch.managers.thl.survey_penalty import SurveyPenaltyManager + + return SurveyPenaltyManager(redis_config=thl_redis_config) + + +@pytest.fixture(scope="session") +def upk_schema_manager(thl_web_rw): + assert "/unittest-" in thl_web_rw.dsn.path + from generalresearch.managers.thl.profiling.schema import ( + UpkSchemaManager, + ) + + return UpkSchemaManager(pg_config=thl_web_rw) + + +@pytest.fixture(scope="session") +def user_upk_manager(thl_web_rw, thl_redis_config): + assert "/unittest-" in thl_web_rw.dsn.path + from generalresearch.managers.thl.profiling.user_upk import ( + UserUpkManager, + ) + + return UserUpkManager(pg_config=thl_web_rw, redis_config=thl_redis_config) + + +@pytest.fixture(scope="session") +def question_manager(thl_web_rw, thl_redis_config): + assert "/unittest-" in thl_web_rw.dsn.path + from generalresearch.managers.thl.profiling.question import ( + QuestionManager, + ) + + return QuestionManager(pg_config=thl_web_rw) + + +@pytest.fixture(scope="session") +def uqa_manager(thl_web_rw, thl_redis_config): + assert "/unittest-" in thl_web_rw.dsn.path + from generalresearch.managers.thl.profiling.uqa import UQAManager + + return UQAManager(redis_config=thl_redis_config, pg_config=thl_web_rw) + + +@pytest.fixture(scope="function") +def uqa_manager_clear_cache(uqa_manager, user): + # On successive py-test/jenkins runs, the cache may contain + # the previous run's info (keyed under the same user_id) + uqa_manager.clear_cache(user) + yield + uqa_manager.clear_cache(user) + + +@pytest.fixture(scope="session") +def audit_log_manager(thl_web_rw) -> "AuditLogManager": + assert "/unittest-" in thl_web_rw.dsn.path + + from generalresearch.managers.thl.userhealth import AuditLogManager + + return AuditLogManager(pg_config=thl_web_rw) + + +@pytest.fixture(scope="session") +def ip_geoname_manager(thl_web_rw) -> "IPGeonameManager": + assert "/unittest-" in thl_web_rw.dsn.path + + from generalresearch.managers.thl.ipinfo import IPGeonameManager + + return IPGeonameManager(pg_config=thl_web_rw) + + +@pytest.fixture(scope="session") +def ip_information_manager(thl_web_rw) -> "IPInformationManager": + assert "/unittest-" in thl_web_rw.dsn.path + + from generalresearch.managers.thl.ipinfo import IPInformationManager + + return IPInformationManager(pg_config=thl_web_rw) + + +@pytest.fixture(scope="session") +def ip_record_manager(thl_web_rw, thl_redis_config) -> "IPRecordManager": + assert "/unittest-" in thl_web_rw.dsn.path + + from generalresearch.managers.thl.userhealth import IPRecordManager + + return IPRecordManager(pg_config=thl_web_rw, redis_config=thl_redis_config) + + +@pytest.fixture(scope="session") +def user_iphistory_manager(thl_web_rw, thl_redis_config) -> "UserIpHistoryManager": + assert "/unittest-" in thl_web_rw.dsn.path + + from generalresearch.managers.thl.userhealth import ( + UserIpHistoryManager, + ) + + return UserIpHistoryManager(pg_config=thl_web_rw, redis_config=thl_redis_config) + + +@pytest.fixture(scope="function") +def user_iphistory_manager_clear_cache(user_iphistory_manager, user): + # On successive py-test/jenkins runs, the cache may contain + # the previous run's info (keyed under the same user_id) + user_iphistory_manager.delete_user_ip_history_cache(user_id=user.user_id) + yield + user_iphistory_manager.delete_user_ip_history_cache(user_id=user.user_id) + + +@pytest.fixture(scope="session") +def geoipinfo_manager(thl_web_rw, thl_redis_config) -> "GeoIpInfoManager": + assert "/unittest-" in thl_web_rw.dsn.path + + from generalresearch.managers.thl.ipinfo import GeoIpInfoManager + + return GeoIpInfoManager(pg_config=thl_web_rw, redis_config=thl_redis_config) + + +@pytest.fixture(scope="session") +def maxmind_basic_manager() -> "MaxmindBasicManager": + from generalresearch.managers.thl.maxmind.basic import ( + MaxmindBasicManager, + ) + + return MaxmindBasicManager(data_dir="/tmp/") + + +@pytest.fixture(scope="session") +def maxmind_manager(thl_web_rw, thl_redis_config) -> "MaxmindManager": + assert "/unittest-" in thl_web_rw.dsn.path + + from generalresearch.managers.thl.maxmind import MaxmindManager + + return MaxmindManager(pg_config=thl_web_rw, redis_config=thl_redis_config) + + +@pytest.fixture(scope="session") +def cashout_method_manager(thl_web_rw): + assert "/unittest-" in thl_web_rw.dsn.path + from generalresearch.managers.thl.cashout_method import ( + CashoutMethodManager, + ) + + return CashoutMethodManager(pg_config=thl_web_rw) + + +@pytest.fixture(scope="session") +def event_manager(thl_redis_config): + from generalresearch.managers.events import EventManager + + return EventManager(redis_config=thl_redis_config) + + +@pytest.fixture(scope="session") +def user_streak_manager(thl_web_rw): + assert "/unittest-" in thl_web_rw.dsn.path + from generalresearch.managers.thl.user_streak import ( + UserStreakManager, + ) + + return UserStreakManager(pg_config=thl_web_rw) + + +@pytest.fixture(scope="session") +def uqa_db_index(thl_web_rw): + # There were some custom indices created not through django. + # Make sure the index used in the index hint exists + assert "/unittest-" in thl_web_rw.dsn.path + + # query = f"""create index idx_user_id + # on `{thl_web_rw.db}`.marketplace_userquestionanswer (user_id);""" + # try: + # thl_web_rw.execute_sql_query(query, commit=True) + # except pymysql.OperationalError as e: + # if "Duplicate key name 'idx_user_id'" not in str(e): + # raise + return None + + +@pytest.fixture(scope="session") +def delete_cashoutmethod_db(thl_web_rw) -> Callable: + def _delete_cashoutmethod_db(): + thl_web_rw.execute_write( + query="DELETE FROM accounting_cashoutmethod;", + ) + + return _delete_cashoutmethod_db + + +@pytest.fixture(scope="session") +def setup_cashoutmethod_db(cashout_method_manager, delete_cashoutmethod_db): + delete_cashoutmethod_db() + for x in EXAMPLE_TANGO_CASHOUT_METHODS: + cashout_method_manager.create(x) + cashout_method_manager.create(AMT_ASSIGNMENT_CASHOUT_METHOD) + cashout_method_manager.create(AMT_BONUS_CASHOUT_METHOD) + return None + + +# === THL: Marketplaces === + + +@pytest.fixture(scope="session") +def spectrum_manager(spectrum_rw): + from generalresearch.managers.spectrum.survey import ( + SpectrumSurveyManager, + ) + + return SpectrumSurveyManager(sql_helper=spectrum_rw) + + +# === GR === +@pytest.fixture(scope="session") +def business_manager(gr_db, gr_redis_config) -> "BusinessManager": + from generalresearch.redis_helper import RedisConfig + + assert "/unittest-" in gr_db.dsn.path + assert isinstance(gr_redis_config, RedisConfig) + + from generalresearch.managers.gr.business import BusinessManager + + return BusinessManager( + pg_config=gr_db, + redis_config=gr_redis_config, + ) + + +@pytest.fixture(scope="session") +def business_address_manager(gr_db) -> "BusinessAddressManager": + assert "/unittest-" in gr_db.dsn.path + + from generalresearch.managers.gr.business import BusinessAddressManager + + return BusinessAddressManager(pg_config=gr_db) + + +@pytest.fixture(scope="session") +def business_bank_account_manager(gr_db) -> "BusinessBankAccountManager": + assert "/unittest-" in gr_db.dsn.path + + from generalresearch.managers.gr.business import ( + BusinessBankAccountManager, + ) + + return BusinessBankAccountManager(pg_config=gr_db) + + +@pytest.fixture(scope="session") +def team_manager(gr_db, gr_redis_config) -> "TeamManager": + assert "/unittest-" in gr_db.dsn.path + + from generalresearch.managers.gr.team import TeamManager + + return TeamManager(pg_config=gr_db, redis_config=gr_redis_config) + + +@pytest.fixture(scope="session") +def gr_um(gr_db, gr_redis_config) -> "GRUserManager": + assert "/unittest-" in gr_db.dsn.path + + from generalresearch.managers.gr.authentication import GRUserManager + + return GRUserManager(pg_config=gr_db, redis_config=gr_redis_config) + + +@pytest.fixture(scope="session") +def gr_tm(gr_db) -> "GRTokenManager": + assert "/unittest-" in gr_db.dsn.path + + from generalresearch.managers.gr.authentication import GRTokenManager + + return GRTokenManager(pg_config=gr_db) + + +@pytest.fixture(scope="session") +def membership_manager(gr_db) -> "MembershipManager": + assert "/unittest-" in gr_db.dsn.path + + from generalresearch.managers.gr.team import MembershipManager + + return MembershipManager(pg_config=gr_db) + + +# === GRL IQ === + + +@pytest.fixture(scope="session") +def grliq_dm(grliq_db) -> "GrlIqDataManager": + assert "/unittest-" in grliq_db.dsn.path + + from generalresearch.grliq.managers.forensic_data import ( + GrlIqDataManager, + ) + + return GrlIqDataManager(postgres_config=grliq_db) + + +@pytest.fixture(scope="session") +def grliq_em(grliq_db) -> "GrlIqEventManager": + assert "/unittest-" in grliq_db.dsn.path + + from generalresearch.grliq.managers.forensic_events import ( + GrlIqEventManager, + ) + + return GrlIqEventManager(postgres_config=grliq_db) + + +@pytest.fixture(scope="session") +def grliq_crr(grliq_db) -> "GrlIqCategoryResultsReader": + assert "/unittest-" in grliq_db.dsn.path + + from generalresearch.grliq.managers.forensic_results import ( + GrlIqCategoryResultsReader, + ) + + return GrlIqCategoryResultsReader(postgres_config=grliq_db) + + +@pytest.fixture(scope="session") +def delete_buyers_surveys(thl_web_rw, buyer_manager): + # assert "/unittest-" in thl_web_rw.dsn.path + thl_web_rw.execute_write( + """ + DELETE FROM marketplace_surveystat + WHERE survey_id IN ( + SELECT id + FROM marketplace_survey + WHERE source = %(source)s + );""", + params={"source": Source.TESTING.value}, + ) + thl_web_rw.execute_write( + """ + DELETE FROM marketplace_survey + WHERE buyer_id IN ( + SELECT id + FROM marketplace_buyer + WHERE source = %(source)s + );""", + params={"source": Source.TESTING.value}, + ) + thl_web_rw.execute_write( + """ + DELETE from marketplace_buyer + WHERE source=%(source)s; + """, + params={"source": Source.TESTING.value}, + ) + buyer_manager.populate_caches() diff --git a/test_utils/managers/contest/__init__.py b/test_utils/managers/contest/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test_utils/managers/contest/__init__.py diff --git a/test_utils/managers/contest/conftest.py b/test_utils/managers/contest/conftest.py new file mode 100644 index 0000000..c2d4ef6 --- /dev/null +++ b/test_utils/managers/contest/conftest.py @@ -0,0 +1,295 @@ +from datetime import datetime, timezone +from decimal import Decimal +from typing import Callable, TYPE_CHECKING +from uuid import uuid4 + +import pytest + +from generalresearch.currency import USDCent + +if TYPE_CHECKING: + from generalresearch.models.thl.contest.contest import Contest + from generalresearch.models.thl.contest import ( + ContestPrize, + ContestEndCondition, + ) + + from generalresearch.models.thl.contest.definitions import ( + ContestType, + ContestPrizeKind, + ) + from generalresearch.models.thl.contest.io import contest_create_to_contest + from generalresearch.models.thl.contest.leaderboard import ( + LeaderboardContestCreate, + ) + from generalresearch.models.thl.contest.milestone import ( + MilestoneContestCreate, + ContestEntryTrigger, + MilestoneContestEndCondition, + ) + from generalresearch.models.thl.contest.raffle import ( + ContestEntryType, + ) + from generalresearch.models.thl.contest.raffle import ( + RaffleContestCreate, + ) + from generalresearch.models.thl.product import Product + from generalresearch.models.thl.user import User + + +@pytest.fixture +def raffle_contest_create() -> "RaffleContestCreate": + from generalresearch.models.thl.contest.raffle import ( + RaffleContestCreate, + ) + from generalresearch.models.thl.contest import ( + ContestPrize, + ContestEndCondition, + ) + from generalresearch.models.thl.contest.definitions import ( + ContestType, + ContestPrizeKind, + ) + from generalresearch.models.thl.contest.raffle import ( + ContestEntryType, + ) + + # This is what we'll get from the fastapi endpoint + return RaffleContestCreate( + name="test", + contest_type=ContestType.RAFFLE, + entry_type=ContestEntryType.CASH, + prizes=[ + ContestPrize( + name="iPod 64GB White", + kind=ContestPrizeKind.PHYSICAL, + estimated_cash_value=USDCent(100), + ) + ], + end_condition=ContestEndCondition(target_entry_amount=USDCent(100)), + ) + + +@pytest.fixture +def raffle_contest_in_db( + product_user_wallet_yes: "Product", + raffle_contest_create: "RaffleContestCreate", + contest_manager, +) -> "Contest": + return contest_manager.create( + product_id=product_user_wallet_yes.uuid, contest_create=raffle_contest_create + ) + + +@pytest.fixture +def raffle_contest( + product_user_wallet_yes: "Product", raffle_contest_create: "RaffleContestCreate" +) -> "Contest": + from generalresearch.models.thl.contest.io import contest_create_to_contest + + return contest_create_to_contest( + product_id=product_user_wallet_yes.uuid, contest_create=raffle_contest_create + ) + + +@pytest.fixture(scope="function") +def raffle_contest_factory( + product_user_wallet_yes: "Product", + raffle_contest_create: "RaffleContestCreate", + contest_manager, +) -> Callable: + def _create_contest(**kwargs): + raffle_contest_create.update(**kwargs) + return contest_manager.create( + product_id=product_user_wallet_yes.uuid, + contest_create=raffle_contest_create, + ) + + return _create_contest + + +@pytest.fixture +def milestone_contest_create() -> "MilestoneContestCreate": + from generalresearch.models.thl.contest import ( + ContestPrize, + ) + from generalresearch.models.thl.contest.definitions import ( + ContestType, + ContestPrizeKind, + ) + from generalresearch.models.thl.contest.milestone import ( + MilestoneContestCreate, + ContestEntryTrigger, + MilestoneContestEndCondition, + ) + + # This is what we'll get from the fastapi endpoint + return MilestoneContestCreate( + name="Win a 50% bonus for 7 days and a $1 bonus after your first 3 completes!", + description="only valid for the first 5 users", + contest_type=ContestType.MILESTONE, + prizes=[ + ContestPrize( + name="50% for 7 days", + kind=ContestPrizeKind.PROMOTION, + estimated_cash_value=USDCent(0), + ), + ContestPrize( + name="$1 Bonus", + kind=ContestPrizeKind.CASH, + cash_amount=USDCent(1_00), + estimated_cash_value=USDCent(1_00), + ), + ], + end_condition=MilestoneContestEndCondition( + ends_at=datetime(year=2030, month=1, day=1, tzinfo=timezone.utc), + max_winners=5, + ), + entry_trigger=ContestEntryTrigger.TASK_COMPLETE, + target_amount=3, + ) + + +@pytest.fixture +def milestone_contest_in_db( + product_user_wallet_yes: "Product", + milestone_contest_create: "MilestoneContestCreate", + contest_manager, +) -> "Contest": + return contest_manager.create( + product_id=product_user_wallet_yes.uuid, contest_create=milestone_contest_create + ) + + +@pytest.fixture +def milestone_contest( + product_user_wallet_yes: "Product", + milestone_contest_create: "MilestoneContestCreate", +) -> "Contest": + from generalresearch.models.thl.contest.io import contest_create_to_contest + + return contest_create_to_contest( + product_id=product_user_wallet_yes.uuid, contest_create=milestone_contest_create + ) + + +@pytest.fixture(scope="function") +def milestone_contest_factory( + product_user_wallet_yes: "Product", + milestone_contest_create: "MilestoneContestCreate", + contest_manager, +) -> Callable: + def _create_contest(**kwargs): + milestone_contest_create.update(**kwargs) + return contest_manager.create( + product_id=product_user_wallet_yes.uuid, + contest_create=milestone_contest_create, + ) + + return _create_contest + + +@pytest.fixture +def leaderboard_contest_create( + product_user_wallet_yes: "Product", +) -> "LeaderboardContestCreate": + from generalresearch.models.thl.contest.leaderboard import ( + LeaderboardContestCreate, + ) + from generalresearch.models.thl.contest import ( + ContestPrize, + ) + from generalresearch.models.thl.contest.definitions import ( + ContestType, + ContestPrizeKind, + ) + + # This is what we'll get from the fastapi endpoint + return LeaderboardContestCreate( + name="test", + contest_type=ContestType.LEADERBOARD, + prizes=[ + ContestPrize( + name="$15 Cash", + estimated_cash_value=USDCent(15_00), + cash_amount=USDCent(15_00), + kind=ContestPrizeKind.CASH, + leaderboard_rank=1, + ), + ContestPrize( + name="$10 Cash", + estimated_cash_value=USDCent(10_00), + cash_amount=USDCent(10_00), + kind=ContestPrizeKind.CASH, + leaderboard_rank=2, + ), + ], + leaderboard_key=f"leaderboard:{product_user_wallet_yes.uuid}:us:daily:2025-01-01:complete_count", + ) + + +@pytest.fixture +def leaderboard_contest_in_db( + product_user_wallet_yes: "Product", + leaderboard_contest_create: "LeaderboardContestCreate", + contest_manager, +) -> "Contest": + return contest_manager.create( + product_id=product_user_wallet_yes.uuid, + contest_create=leaderboard_contest_create, + ) + + +@pytest.fixture +def leaderboard_contest( + product_user_wallet_yes: "Product", + leaderboard_contest_create: "LeaderboardContestCreate", +): + from generalresearch.models.thl.contest.io import contest_create_to_contest + + return contest_create_to_contest( + product_id=product_user_wallet_yes.uuid, + contest_create=leaderboard_contest_create, + ) + + +@pytest.fixture(scope="function") +def leaderboard_contest_factory( + product_user_wallet_yes: "Product", + leaderboard_contest_create: "LeaderboardContestCreate", + contest_manager, +) -> Callable: + def _create_contest(**kwargs): + leaderboard_contest_create.update(**kwargs) + return contest_manager.create( + product_id=product_user_wallet_yes.uuid, + contest_create=leaderboard_contest_create, + ) + + return _create_contest + + +@pytest.fixture +def user_with_money( + request, user_factory, product_user_wallet_yes: "Product", thl_lm +) -> "User": + from generalresearch.models.thl.user import User + + params = getattr(request, "param", dict()) or {} + min_balance = int(params.get("min_balance", USDCent(1_00))) + + user: User = user_factory(product=product_user_wallet_yes) + wallet = thl_lm.get_account_or_create_user_wallet(user) + balance = thl_lm.get_account_balance(wallet) + todo = min_balance - balance + if todo > 0: + # # Put money in user's wallet + thl_lm.create_tx_user_bonus( + user=user, + ref_uuid=uuid4().hex, + description="bonus", + amount=Decimal(todo) / 100, + ) + print(f"wallet balance: {thl_lm.get_user_wallet_balance(user=user)}") + + return user diff --git a/test_utils/managers/ledger/__init__.py b/test_utils/managers/ledger/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test_utils/managers/ledger/__init__.py diff --git a/test_utils/managers/ledger/conftest.py b/test_utils/managers/ledger/conftest.py new file mode 100644 index 0000000..b96d612 --- /dev/null +++ b/test_utils/managers/ledger/conftest.py @@ -0,0 +1,678 @@ +from datetime import datetime +from decimal import Decimal +from random import randint +from typing import Optional, Dict, Callable, TYPE_CHECKING +from uuid import uuid4 + +import pytest + +from generalresearch.currency import USDCent +from test_utils.models.conftest import ( + product_factory, + user, + product, + user_factory, + product_user_wallet_no, + wall, + product_amt_true, + product_user_wallet_yes, + session_factory, + session, + wall_factory, + payout_config, +) + +_ = ( + user_factory, + product_user_wallet_no, + wall, + product_amt_true, + product_user_wallet_yes, + session_factory, + session, + wall_factory, + payout_config, +) + +if TYPE_CHECKING: + from generalresearch.currency import LedgerCurrency + from generalresearch.models.thl.ledger import ( + Direction, + AccountType, + LedgerTransaction, + ) + from generalresearch.models.thl.ledger import ( + LedgerEntry, + LedgerAccount, + ) + from generalresearch.models.thl.payout import UserPayoutEvent + + +@pytest.fixture(scope="function") +def ledger_account(request, lm, currency) -> "LedgerAccount": + from generalresearch.models.thl.ledger import ( + Direction, + AccountType, + LedgerAccount, + ) + + account_type = getattr(request, "account_type", AccountType.CASH) + direction = getattr(request, "direction", Direction.CREDIT) + + acct_uuid = uuid4().hex + qn = ":".join([currency, account_type, acct_uuid]) + + acct_model = LedgerAccount( + uuid=acct_uuid, + display_name=f"test-{acct_uuid}", + currency=currency, + qualified_name=qn, + account_type=account_type, + normal_balance=direction, + ) + return lm.create_account(account=acct_model) + + +@pytest.fixture(scope="function") +def ledger_account_factory(request, thl_lm, lm, currency) -> Callable: + from generalresearch.models.thl.ledger import ( + Direction, + AccountType, + LedgerAccount, + ) + + def _ledger_account_factory( + product, + account_type: AccountType = AccountType.CASH, + direction: Direction = Direction.CREDIT, + ): + thl_lm.get_account_or_create_bp_wallet(product=product) + acct_uuid = uuid4().hex + qn = ":".join([currency, account_type, acct_uuid]) + + acct_model = LedgerAccount( + uuid=acct_uuid, + display_name=f"test-{acct_uuid}", + currency=currency, + qualified_name=qn, + account_type=account_type, + normal_balance=direction, + ) + return lm.create_account(account=acct_model) + + return _ledger_account_factory + + +@pytest.fixture(scope="function") +def ledger_account_credit(request, lm, currency) -> "LedgerAccount": + from generalresearch.models.thl.ledger import Direction, AccountType + + account_type = AccountType.REVENUE + acct_uuid = uuid4().hex + + qn = ":".join([currency, account_type, acct_uuid]) + from generalresearch.models.thl.ledger import LedgerAccount + + acct_model = LedgerAccount( + uuid=acct_uuid, + display_name=f"test-{acct_uuid}", + currency=currency, + qualified_name=qn, + account_type=account_type, + normal_balance=Direction.CREDIT, + ) + return lm.create_account(account=acct_model) + + +@pytest.fixture(scope="function") +def ledger_account_debit(request, lm, currency) -> "LedgerAccount": + from generalresearch.models.thl.ledger import Direction, AccountType + + account_type = AccountType.EXPENSE + acct_uuid = uuid4().hex + + qn = ":".join([currency, account_type, acct_uuid]) + from generalresearch.models.thl.ledger import LedgerAccount + + acct_model = LedgerAccount( + uuid=acct_uuid, + display_name=f"test-{acct_uuid}", + currency=currency, + qualified_name=qn, + account_type=account_type, + normal_balance=Direction.DEBIT, + ) + return lm.create_account(account=acct_model) + + +@pytest.fixture(scope="function") +def tag(request, lm) -> str: + from generalresearch.currency import LedgerCurrency + + return ( + request.param + if hasattr(request, "tag") + else f"{LedgerCurrency.TEST}:{uuid4().hex}" + ) + + +@pytest.fixture(scope="function") +def usd_cent(request) -> USDCent: + amount = randint(99, 9_999) + return request.param if hasattr(request, "usd_cent") else USDCent(amount) + + +@pytest.fixture(scope="function") +def bp_payout_event( + product, usd_cent, business_payout_event_manager, thl_lm +) -> "UserPayoutEvent": + return business_payout_event_manager.create_bp_payout_event( + thl_ledger_manager=thl_lm, + product=product, + amount=usd_cent, + skip_wallet_balance_check=True, + skip_one_per_day_check=True, + ) + + +@pytest.fixture +def bp_payout_event_factory(brokerage_product_payout_event_manager, thl_lm) -> Callable: + from generalresearch.models.thl.product import Product + from generalresearch.currency import USDCent + + def _create_bp_payout_event( + product: Product, usd_cent: USDCent, ext_ref_id: Optional[str] = None + ): + return brokerage_product_payout_event_manager.create_bp_payout_event( + thl_ledger_manager=thl_lm, + product=product, + amount=usd_cent, + ext_ref_id=ext_ref_id, + skip_wallet_balance_check=True, + skip_one_per_day_check=True, + ) + + return _create_bp_payout_event + + +@pytest.fixture(scope="function") +def currency(lm) -> "LedgerCurrency": + # return request.param if hasattr(request, "currency") else LedgerCurrency.TEST + return lm.currency + + +@pytest.fixture(scope="function") +def tx_metadata(request) -> Optional[Dict[str, str]]: + return ( + request.param + if hasattr(request, "tx_metadata") + else {f"key-{uuid4().hex[:10]}": uuid4().hex} + ) + + +@pytest.fixture(scope="function") +def ledger_tx( + request, + ledger_account_credit, + ledger_account_debit, + tag, + currency, + tx_metadata, + lm, +) -> "LedgerTransaction": + from generalresearch.models.thl.ledger import Direction, LedgerEntry + + amount = int(Decimal("1.00") * 100) + + entries = [ + LedgerEntry( + direction=Direction.CREDIT, + account_uuid=ledger_account_credit.uuid, + amount=amount, + ), + LedgerEntry( + direction=Direction.DEBIT, + account_uuid=ledger_account_debit.uuid, + amount=amount, + ), + ] + + return lm.create_tx(entries=entries, tag=tag, metadata=tx_metadata) + + +@pytest.fixture(scope="function") +def create_main_accounts(lm, currency) -> Callable: + def _create_main_accounts(): + from generalresearch.models.thl.ledger import ( + LedgerAccount, + Direction, + AccountType, + ) + + account = LedgerAccount( + display_name="Cash flow task complete", + qualified_name=f"{currency.value}:revenue:task_complete", + normal_balance=Direction.CREDIT, + account_type=AccountType.REVENUE, + currency=lm.currency, + ) + lm.get_account_or_create(account=account) + + account = LedgerAccount( + display_name="Operating Cash Account", + qualified_name=f"{currency.value}:cash", + normal_balance=Direction.DEBIT, + account_type=AccountType.CASH, + currency=currency, + ) + + lm.get_account_or_create(account=account) + + return _create_main_accounts + + +@pytest.fixture(scope="function") +def delete_ledger_db(thl_web_rw) -> Callable: + def _delete_ledger_db(): + for table in [ + "ledger_transactionmetadata", + "ledger_entry", + "ledger_transaction", + "ledger_account", + ]: + thl_web_rw.execute_write( + query=f"DELETE FROM {table};", + ) + + return _delete_ledger_db + + +@pytest.fixture(scope="function") +def wipe_main_accounts(thl_web_rw, lm, currency) -> Callable: + def _wipe_main_accounts(): + db_table = thl_web_rw.db_name + qual_names = [ + f"{currency.value}:revenue:task_complete", + f"{currency.value}:cash", + ] + + res = thl_web_rw.execute_sql_query( + query=f""" + SELECT lt.id as ltid, le.id as leid, tmd.id as tmdid, la.uuid as lauuid + FROM `{db_table}`.`ledger_transaction` AS lt + LEFT JOIN `{db_table}`.ledger_entry le + ON lt.id = le.transaction_id + LEFT JOIN `{db_table}`.ledger_account la + ON la.uuid = le.account_id + LEFT JOIN `{db_table}`.ledger_transactionmetadata tmd + ON lt.id = tmd.transaction_id + WHERE la.qualified_name IN %s + """, + params=[qual_names], + ) + + lt = {x["ltid"] for x in res if x["ltid"]} + le = {x["leid"] for x in res if x["leid"]} + tmd = {x["tmdid"] for x in res if x["tmdid"]} + la = {x["lauuid"] for x in res if x["lauuid"]} + + thl_web_rw.execute_sql_query( + query=f""" + DELETE FROM `{db_table}`.`ledger_transactionmetadata` + WHERE id IN %s + """, + params=[tmd], + commit=True, + ) + + thl_web_rw.execute_sql_query( + query=f""" + DELETE FROM `{db_table}`.`ledger_entry` + WHERE id IN %s + """, + params=[le], + commit=True, + ) + + thl_web_rw.execute_sql_query( + query=f""" + DELETE FROM `{db_table}`.`ledger_transaction` + WHERE id IN %s + """, + params=[lt], + commit=True, + ) + + thl_web_rw.execute_sql_query( + query=f""" + DELETE FROM `{db_table}`.`ledger_account` + WHERE uuid IN %s + """, + params=[la], + commit=True, + ) + + return _wipe_main_accounts + + +@pytest.fixture(scope="function") +def account_cash(lm, currency) -> "LedgerAccount": + from generalresearch.models.thl.ledger import ( + LedgerAccount, + Direction, + AccountType, + ) + + account = LedgerAccount( + display_name="Operating Cash Account", + qualified_name=f"{currency.value}:cash", + normal_balance=Direction.DEBIT, + account_type=AccountType.CASH, + currency=currency, + ) + return lm.get_account_or_create(account=account) + + +@pytest.fixture(scope="function") +def account_revenue_task_complete(lm, currency) -> "LedgerAccount": + from generalresearch.models.thl.ledger import ( + LedgerAccount, + Direction, + AccountType, + ) + + account = LedgerAccount( + display_name="Cash flow task complete", + qualified_name=f"{currency.value}:revenue:task_complete", + normal_balance=Direction.CREDIT, + account_type=AccountType.REVENUE, + currency=currency, + ) + return lm.get_account_or_create(account=account) + + +@pytest.fixture(scope="function") +def account_expense_tango(lm, currency) -> "LedgerAccount": + from generalresearch.models.thl.ledger import ( + LedgerAccount, + Direction, + AccountType, + ) + + account = LedgerAccount( + display_name="Tango Fee", + qualified_name=f"{currency.value}:expense:tango_fee", + normal_balance=Direction.DEBIT, + account_type=AccountType.EXPENSE, + currency=currency, + ) + return lm.get_account_or_create(account=account) + + +@pytest.fixture(scope="function") +def user_account_user_wallet(lm, user, currency) -> "LedgerAccount": + from generalresearch.models.thl.ledger import ( + LedgerAccount, + Direction, + AccountType, + ) + + account = LedgerAccount( + display_name=f"{user.uuid} Wallet", + qualified_name=f"{currency.value}:user_wallet:{user.uuid}", + normal_balance=Direction.CREDIT, + account_type=AccountType.USER_WALLET, + reference_type="user", + reference_uuid=user.uuid, + currency=currency, + ) + return lm.get_account_or_create(account=account) + + +@pytest.fixture(scope="function") +def product_account_bp_wallet(lm, product, currency) -> "LedgerAccount": + from generalresearch.models.thl.ledger import ( + LedgerAccount, + Direction, + AccountType, + ) + + account = LedgerAccount.model_validate( + dict( + display_name=f"{product.name} Wallet", + qualified_name=f"{currency.value}:bp_wallet:{product.uuid}", + normal_balance=Direction.CREDIT, + account_type=AccountType.BP_WALLET, + reference_type="bp", + reference_uuid=product.uuid, + currency=currency, + ) + ) + return lm.get_account_or_create(account=account) + + +@pytest.fixture(scope="function") +def setup_accounts(product_factory, lm, user, currency) -> None: + from generalresearch.models.thl.ledger import ( + LedgerAccount, + Direction, + AccountType, + ) + + # BP's wallet and a revenue from their commissions account. + p1 = product_factory() + + account = LedgerAccount( + display_name=f"Revenue from {p1.name} commission", + qualified_name=f"{currency.value}:revenue:bp_commission:{p1.uuid}", + normal_balance=Direction.CREDIT, + account_type=AccountType.REVENUE, + reference_type="bp", + reference_uuid=p1.uuid, + currency=currency, + ) + lm.get_account_or_create(account=account) + + account = LedgerAccount.model_validate( + dict( + display_name=f"{p1.name} Wallet", + qualified_name=f"{currency.value}:bp_wallet:{p1.uuid}", + normal_balance=Direction.CREDIT, + account_type=AccountType.BP_WALLET, + reference_type="bp", + reference_uuid=p1.uuid, + currency=currency, + ) + ) + lm.get_account_or_create(account=account) + + # BP's wallet, user's wallet, and a revenue from their commissions account. + p2 = product_factory() + account = LedgerAccount( + display_name=f"Revenue from {p2.name} commission", + qualified_name=f"{currency.value}:revenue:bp_commission:{p2.uuid}", + normal_balance=Direction.CREDIT, + account_type=AccountType.REVENUE, + reference_type="bp", + reference_uuid=p2.uuid, + currency=currency, + ) + lm.get_account_or_create(account) + + account = LedgerAccount( + display_name=f"{p2.name} Wallet", + qualified_name=f"{currency.value}:bp_wallet:{p2.uuid}", + normal_balance=Direction.CREDIT, + account_type=AccountType.BP_WALLET, + reference_type="bp", + reference_uuid=p2.uuid, + currency=currency, + ) + lm.get_account_or_create(account) + + account = LedgerAccount( + display_name=f"{user.uuid} Wallet", + qualified_name=f"{currency.value}:user_wallet:{user.uuid}", + normal_balance=Direction.CREDIT, + account_type=AccountType.USER_WALLET, + reference_type="user", + reference_uuid=user.uuid, + currency="test", + ) + lm.get_account_or_create(account=account) + + +@pytest.fixture(scope="function") +def session_with_tx_factory( + user_factory, + product, + session_factory, + session_manager, + wall_manager, + utc_hour_ago, + thl_lm, +) -> Callable: + from generalresearch.models.thl.session import ( + Status, + Session, + StatusCode1, + ) + from generalresearch.models.thl.user import User + + def _session_with_tx_factory( + user: User, + final_status: Status = Status.COMPLETE, + wall_req_cpi: Decimal = Decimal(".50"), + started: datetime = utc_hour_ago, + ) -> Session: + s: Session = session_factory( + user=user, + wall_count=2, + final_status=final_status, + wall_req_cpi=wall_req_cpi, + started=started, + ) + last_wall = s.wall_events[-1] + + wall_manager.finish( + wall=last_wall, + status=Status.COMPLETE, + status_code_1=StatusCode1.COMPLETE, + finished=last_wall.finished, + ) + + status, status_code_1 = s.determine_session_status() + thl_net, commission_amount, bp_pay, user_pay = s.determine_payments() + session_manager.finish_with_status( + session=s, + finished=last_wall.finished, + payout=bp_pay, + user_payout=user_pay, + status=status, + status_code_1=status_code_1, + ) + + thl_lm.create_tx_task_complete( + wall=last_wall, + user=user, + created=last_wall.finished, + force=True, + ) + + thl_lm.create_tx_bp_payment(session=s, created=last_wall.finished, force=True) + + return s + + return _session_with_tx_factory + + +@pytest.fixture(scope="function") +def adj_to_fail_with_tx_factory(session_manager, wall_manager, thl_lm) -> Callable: + from generalresearch.models.thl.session import ( + Session, + ) + from datetime import timedelta + from generalresearch.models.thl.definitions import WallAdjustedStatus + + def _adj_to_fail_with_tx_factory( + session: Session, + created: datetime, + ) -> None: + w1 = wall_manager.get_wall_events(session_id=session.id)[-1] + + # This is defined in `thl-grpc/thl/user_quality_history/recons.py:150` + # so we can't use it as part of this test anyway to add rows to the + # thl_taskadjustment table anyway.. until we created a + # TaskAdjustment Manager to put into py-utils! + + # create_task_adjustment_event( + # wall, + # user, + # adjusted_status, + # amount_usd=amount_usd, + # alert_time=alert_time, + # ext_status_code=ext_status_code, + # ) + + wall_manager.adjust_status( + wall=w1, + adjusted_status=WallAdjustedStatus.ADJUSTED_TO_FAIL, + adjusted_cpi=Decimal("0.00"), + adjusted_timestamp=created, + ) + + thl_lm.create_tx_task_adjustment( + wall=w1, + user=session.user, + created=created + timedelta(milliseconds=1), + ) + + session.wall_events = wall_manager.get_wall_events(session_id=session.id) + session_manager.adjust_status(session=session) + + thl_lm.create_tx_bp_adjustment( + session=session, created=created + timedelta(milliseconds=2) + ) + + return None + + return _adj_to_fail_with_tx_factory + + +@pytest.fixture(scope="function") +def adj_to_complete_with_tx_factory(session_manager, wall_manager, thl_lm) -> Callable: + from generalresearch.models.thl.session import ( + Session, + ) + from datetime import timedelta + from generalresearch.models.thl.definitions import WallAdjustedStatus + + def _adj_to_complete_with_tx_factory( + session: Session, + created: datetime, + ) -> None: + w1 = wall_manager.get_wall_events(session_id=session.id)[-1] + + wall_manager.adjust_status( + wall=w1, + adjusted_status=WallAdjustedStatus.ADJUSTED_TO_COMPLETE, + adjusted_cpi=w1.req_cpi, + adjusted_timestamp=created, + ) + + thl_lm.create_tx_task_adjustment( + wall=w1, + user=session.user, + created=created + timedelta(milliseconds=1), + ) + + session.wall_events = wall_manager.get_wall_events(session_id=session.id) + session_manager.adjust_status(session=session) + + thl_lm.create_tx_bp_adjustment( + session=session, created=created + timedelta(milliseconds=2) + ) + + return None + + return _adj_to_complete_with_tx_factory diff --git a/test_utils/managers/upk/__init__.py b/test_utils/managers/upk/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test_utils/managers/upk/__init__.py diff --git a/test_utils/managers/upk/conftest.py b/test_utils/managers/upk/conftest.py new file mode 100644 index 0000000..61be924 --- /dev/null +++ b/test_utils/managers/upk/conftest.py @@ -0,0 +1,161 @@ +import os +import time +from typing import Optional +from uuid import UUID + +import pandas as pd +import pytest + +from generalresearch.pg_helper import PostgresConfig + + +def insert_data_from_csv( + thl_web_rw: PostgresConfig, + table_name: str, + fp: Optional[str] = None, + disable_fk_checks: bool = False, + df: Optional[pd.DataFrame] = None, +): + assert fp is not None or df is not None and not (fp is not None and df is not None) + if fp: + df = pd.read_csv(fp, dtype=str) + df = df.where(pd.notnull(df), None) + cols = list(df.columns) + col_str = ", ".join(cols) + values_str = ", ".join(["%s"] * len(cols)) + if "id" in df.columns and len(df["id"].iloc[0]) == 36: + df["id"] = df["id"].map(lambda x: UUID(x).hex) + args = df.to_dict("tight")["data"] + + with thl_web_rw.make_connection() as conn: + with conn.cursor() as c: + if disable_fk_checks: + c.execute("SET CONSTRAINTS ALL DEFERRED") + c.executemany( + f"INSERT INTO {table_name} ({col_str}) VALUES ({values_str})", + params_seq=args, + ) + conn.commit() + + +@pytest.fixture(scope="session") +def category_data(thl_web_rw, category_manager) -> None: + fp = os.path.join(os.path.dirname(__file__), "marketplace_category.csv.gz") + insert_data_from_csv( + thl_web_rw, + fp=fp, + table_name="marketplace_category", + disable_fk_checks=True, + ) + # Don't strictly need to do this, but probably we should + category_manager.populate_caches() + cats = category_manager.categories.values() + path_id = {c.path: c.id for c in cats} + data = [ + {"id": c.id, "parent_id": path_id[c.parent_path]} for c in cats if c.parent_path + ] + query = """ + UPDATE marketplace_category + SET parent_id = %(parent_id)s + WHERE id = %(id)s; + """ + with thl_web_rw.make_connection() as conn: + with conn.cursor() as c: + c.executemany(query=query, params_seq=data) + conn.commit() + + +@pytest.fixture(scope="session") +def property_data(thl_web_rw) -> None: + fp = os.path.join(os.path.dirname(__file__), "marketplace_property.csv.gz") + insert_data_from_csv(thl_web_rw, fp=fp, table_name="marketplace_property") + + +@pytest.fixture(scope="session") +def item_data(thl_web_rw) -> None: + fp = os.path.join(os.path.dirname(__file__), "marketplace_item.csv.gz") + insert_data_from_csv(thl_web_rw, fp=fp, table_name="marketplace_item") + + +@pytest.fixture(scope="session") +def propertycategoryassociation_data( + thl_web_rw, category_data, property_data, category_manager +) -> None: + table_name = "marketplace_propertycategoryassociation" + fp = os.path.join(os.path.dirname(__file__), f"{table_name}.csv.gz") + # Need to lookup category pk from uuid + category_manager.populate_caches() + df = pd.read_csv(fp, dtype=str) + df["category_id"] = df["category_id"].map( + lambda x: category_manager.categories[x].id + ) + insert_data_from_csv(thl_web_rw, df=df, table_name=table_name) + + +@pytest.fixture(scope="session") +def propertycountry_data(thl_web_rw, property_data) -> None: + fp = os.path.join(os.path.dirname(__file__), "marketplace_propertycountry.csv.gz") + insert_data_from_csv(thl_web_rw, fp=fp, table_name="marketplace_propertycountry") + + +@pytest.fixture(scope="session") +def propertymarketplaceassociation_data(thl_web_rw, property_data) -> None: + table_name = "marketplace_propertymarketplaceassociation" + fp = os.path.join(os.path.dirname(__file__), f"{table_name}.csv.gz") + insert_data_from_csv(thl_web_rw, fp=fp, table_name=table_name) + + +@pytest.fixture(scope="session") +def propertyitemrange_data(thl_web_rw, property_data, item_data) -> None: + table_name = "marketplace_propertyitemrange" + fp = os.path.join(os.path.dirname(__file__), f"{table_name}.csv.gz") + insert_data_from_csv(thl_web_rw, fp=fp, table_name=table_name) + + +@pytest.fixture(scope="session") +def question_data(thl_web_rw) -> None: + table_name = "marketplace_question" + fp = os.path.join(os.path.dirname(__file__), f"{table_name}.csv.gz") + insert_data_from_csv( + thl_web_rw, fp=fp, table_name=table_name, disable_fk_checks=True + ) + + +@pytest.fixture(scope="session") +def clear_upk_tables(thl_web_rw): + tables = [ + "marketplace_propertyitemrange", + "marketplace_propertymarketplaceassociation", + "marketplace_propertycategoryassociation", + "marketplace_category", + "marketplace_item", + "marketplace_property", + "marketplace_propertycountry", + "marketplace_question", + ] + table_str = ", ".join(tables) + + with thl_web_rw.make_connection() as conn: + with conn.cursor() as c: + c.execute(f"TRUNCATE {table_str} RESTART IDENTITY CASCADE;") + conn.commit() + + +@pytest.fixture(scope="session") +def upk_data( + clear_upk_tables, + category_data, + property_data, + item_data, + propertycategoryassociation_data, + propertycountry_data, + propertymarketplaceassociation_data, + propertyitemrange_data, + question_data, +) -> None: + # Wait a second to make sure the HarmonizerCache refresh loop pulls these in + time.sleep(2) + + +def test_fixtures(upk_data): + pass diff --git a/test_utils/managers/upk/marketplace_category.csv.gz b/test_utils/managers/upk/marketplace_category.csv.gz Binary files differnew file mode 100644 index 0000000..0f8ec1c --- /dev/null +++ b/test_utils/managers/upk/marketplace_category.csv.gz diff --git a/test_utils/managers/upk/marketplace_item.csv.gz b/test_utils/managers/upk/marketplace_item.csv.gz Binary files differnew file mode 100644 index 0000000..c12c5d8 --- /dev/null +++ b/test_utils/managers/upk/marketplace_item.csv.gz diff --git a/test_utils/managers/upk/marketplace_property.csv.gz b/test_utils/managers/upk/marketplace_property.csv.gz Binary files differnew file mode 100644 index 0000000..a781d1d --- /dev/null +++ b/test_utils/managers/upk/marketplace_property.csv.gz diff --git a/test_utils/managers/upk/marketplace_propertycategoryassociation.csv.gz b/test_utils/managers/upk/marketplace_propertycategoryassociation.csv.gz Binary files differnew file mode 100644 index 0000000..5b4ea19 --- /dev/null +++ b/test_utils/managers/upk/marketplace_propertycategoryassociation.csv.gz diff --git a/test_utils/managers/upk/marketplace_propertycountry.csv.gz b/test_utils/managers/upk/marketplace_propertycountry.csv.gz Binary files differnew file mode 100644 index 0000000..5d2a637 --- /dev/null +++ b/test_utils/managers/upk/marketplace_propertycountry.csv.gz diff --git a/test_utils/managers/upk/marketplace_propertyitemrange.csv.gz b/test_utils/managers/upk/marketplace_propertyitemrange.csv.gz Binary files differnew file mode 100644 index 0000000..84f4f0e --- /dev/null +++ b/test_utils/managers/upk/marketplace_propertyitemrange.csv.gz diff --git a/test_utils/managers/upk/marketplace_propertymarketplaceassociation.csv.gz b/test_utils/managers/upk/marketplace_propertymarketplaceassociation.csv.gz Binary files differnew file mode 100644 index 0000000..6b9fd1c --- /dev/null +++ b/test_utils/managers/upk/marketplace_propertymarketplaceassociation.csv.gz diff --git a/test_utils/managers/upk/marketplace_question.csv.gz b/test_utils/managers/upk/marketplace_question.csv.gz Binary files differnew file mode 100644 index 0000000..bcfc3ad --- /dev/null +++ b/test_utils/managers/upk/marketplace_question.csv.gz |
