aboutsummaryrefslogtreecommitdiff
path: root/test_utils/managers
diff options
context:
space:
mode:
authorMax Nanis2026-03-06 16:49:46 -0500
committerMax Nanis2026-03-06 16:49:46 -0500
commit91d040211a4ed6e4157896256a762d3854777b5e (patch)
treecd95922ea4257dc8d3f4e4cbe8534474709a20dc /test_utils/managers
downloadgeneralresearch-91d040211a4ed6e4157896256a762d3854777b5e.tar.gz
generalresearch-91d040211a4ed6e4157896256a762d3854777b5e.zip
Initial commitv3.3.4
Diffstat (limited to 'test_utils/managers')
-rw-r--r--test_utils/managers/__init__.py0
-rw-r--r--test_utils/managers/cashout_methods.py76
-rw-r--r--test_utils/managers/conftest.py701
-rw-r--r--test_utils/managers/contest/__init__.py0
-rw-r--r--test_utils/managers/contest/conftest.py295
-rw-r--r--test_utils/managers/ledger/__init__.py0
-rw-r--r--test_utils/managers/ledger/conftest.py678
-rw-r--r--test_utils/managers/upk/__init__.py0
-rw-r--r--test_utils/managers/upk/conftest.py161
-rw-r--r--test_utils/managers/upk/marketplace_category.csv.gzbin0 -> 100990 bytes
-rw-r--r--test_utils/managers/upk/marketplace_item.csv.gzbin0 -> 3225 bytes
-rw-r--r--test_utils/managers/upk/marketplace_property.csv.gzbin0 -> 3315 bytes
-rw-r--r--test_utils/managers/upk/marketplace_propertycategoryassociation.csv.gzbin0 -> 2079 bytes
-rw-r--r--test_utils/managers/upk/marketplace_propertycountry.csv.gzbin0 -> 71359 bytes
-rw-r--r--test_utils/managers/upk/marketplace_propertyitemrange.csv.gzbin0 -> 65389 bytes
-rw-r--r--test_utils/managers/upk/marketplace_propertymarketplaceassociation.csv.gzbin0 -> 4272 bytes
-rw-r--r--test_utils/managers/upk/marketplace_question.csv.gzbin0 -> 283465 bytes
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
new file mode 100644
index 0000000..0f8ec1c
--- /dev/null
+++ b/test_utils/managers/upk/marketplace_category.csv.gz
Binary files differ
diff --git a/test_utils/managers/upk/marketplace_item.csv.gz b/test_utils/managers/upk/marketplace_item.csv.gz
new file mode 100644
index 0000000..c12c5d8
--- /dev/null
+++ b/test_utils/managers/upk/marketplace_item.csv.gz
Binary files differ
diff --git a/test_utils/managers/upk/marketplace_property.csv.gz b/test_utils/managers/upk/marketplace_property.csv.gz
new file mode 100644
index 0000000..a781d1d
--- /dev/null
+++ b/test_utils/managers/upk/marketplace_property.csv.gz
Binary files differ
diff --git a/test_utils/managers/upk/marketplace_propertycategoryassociation.csv.gz b/test_utils/managers/upk/marketplace_propertycategoryassociation.csv.gz
new file mode 100644
index 0000000..5b4ea19
--- /dev/null
+++ b/test_utils/managers/upk/marketplace_propertycategoryassociation.csv.gz
Binary files differ
diff --git a/test_utils/managers/upk/marketplace_propertycountry.csv.gz b/test_utils/managers/upk/marketplace_propertycountry.csv.gz
new file mode 100644
index 0000000..5d2a637
--- /dev/null
+++ b/test_utils/managers/upk/marketplace_propertycountry.csv.gz
Binary files differ
diff --git a/test_utils/managers/upk/marketplace_propertyitemrange.csv.gz b/test_utils/managers/upk/marketplace_propertyitemrange.csv.gz
new file mode 100644
index 0000000..84f4f0e
--- /dev/null
+++ b/test_utils/managers/upk/marketplace_propertyitemrange.csv.gz
Binary files differ
diff --git a/test_utils/managers/upk/marketplace_propertymarketplaceassociation.csv.gz b/test_utils/managers/upk/marketplace_propertymarketplaceassociation.csv.gz
new file mode 100644
index 0000000..6b9fd1c
--- /dev/null
+++ b/test_utils/managers/upk/marketplace_propertymarketplaceassociation.csv.gz
Binary files differ
diff --git a/test_utils/managers/upk/marketplace_question.csv.gz b/test_utils/managers/upk/marketplace_question.csv.gz
new file mode 100644
index 0000000..bcfc3ad
--- /dev/null
+++ b/test_utils/managers/upk/marketplace_question.csv.gz
Binary files differ