aboutsummaryrefslogtreecommitdiff
path: root/test_utils
diff options
context:
space:
mode:
Diffstat (limited to 'test_utils')
-rw-r--r--test_utils/conftest.py266
-rw-r--r--test_utils/grliq/conftest.py214
-rw-r--r--test_utils/incite/collections/conftest.py9
-rw-r--r--test_utils/incite/conftest.py17
-rw-r--r--test_utils/incite/mergers/conftest.py73
-rw-r--r--test_utils/managers/cashout_methods.py75
-rw-r--r--test_utils/managers/conftest.py269
-rw-r--r--test_utils/managers/contest/conftest.py10
-rw-r--r--test_utils/managers/gr/conftest.py87
-rw-r--r--test_utils/managers/ledger/conftest.py14
-rw-r--r--test_utils/managers/network/conftest.py0
-rw-r--r--test_utils/managers/thl/conftest.py189
-rw-r--r--test_utils/managers/upk/conftest.py3
-rw-r--r--test_utils/models/conftest.py472
-rw-r--r--test_utils/models/contest/conftest.py102
-rw-r--r--test_utils/models/gr/conftest.py337
-rw-r--r--test_utils/models/ledger/conftest.py274
-rw-r--r--test_utils/models/network/__init__.py0
-rw-r--r--test_utils/models/network/conftest.py144
-rw-r--r--test_utils/models/thl/conftest.py995
-rw-r--r--test_utils/models/upk/conftest.py16
-rw-r--r--test_utils/precision/__init__.py (renamed from test_utils/managers/network/__init__.py)0
-rw-r--r--test_utils/precision/conftest.py129
-rw-r--r--test_utils/spectrum/conftest.py255
-rw-r--r--test_utils/spectrum/surveys_json.py140
25 files changed, 2334 insertions, 1756 deletions
diff --git a/test_utils/conftest.py b/test_utils/conftest.py
index 378b9cc..33a7e77 100644
--- a/test_utils/conftest.py
+++ b/test_utils/conftest.py
@@ -5,24 +5,27 @@ import shutil
import stat
import subprocess
import sys
-import tempfile
-from datetime import datetime, timedelta, timezone
+from collections.abc import Callable, Generator
+from datetime import UTC, datetime, timedelta
from os.path import join as pjoin
from pathlib import Path
-from typing import Callable, Generator
+from typing import TYPE_CHECKING
from uuid import uuid4
import pytest
from _pytest.config import Config
from dotenv import load_dotenv
from pydantic import MariaDBDsn, PostgresDsn, TypeAdapter
+from pytest import FixtureRequest, TempPathFactory
-from generalresearch.config import GRLBaseSettings
from generalresearch.currency import USDCent
from generalresearch.models.custom_types import InternalHostname, PostgresDict
-from generalresearch.pg_helper import PostgresConfig
from generalresearch.sql_helper import SqlHelper
+if TYPE_CHECKING:
+ from generalresearch.config import GRLBaseSettings
+ from generalresearch.pg_helper import PostgresConfig
+
@pytest.fixture(scope="session")
def env_file_path(pytestconfig: Config) -> Path:
@@ -93,7 +96,7 @@ def postgres_instance(settings: GRLBaseSettings) -> Generator[PostgresDsn]:
from psycopg import connect
from psycopg.sql import SQL, Identifier
- now = datetime.now(timezone.utc)
+ now = datetime.now(UTC)
ts: str = now.strftime("%Y-%m-%d")
db_name = f"unittest-{ts}-{uuid4().hex[:6]}"
@@ -152,40 +155,51 @@ def postgres_instance_host(
yield value
-# @pytest.fixture(scope="session")
-# def git_key_path(settings: GRLBaseSettings) -> Path:
-# return Path('/tmp/')
-
-
@pytest.fixture(scope="session")
def git_key_path(
+ tmp_path_factory: TempPathFactory,
settings: GRLBaseSettings,
) -> Generator[Path]:
+ # We are using the tmp_path_factory because unlike the tmp_path (which
+ # is function scoped), this is session scoped.
- assert settings.git_creds
- with tempfile.NamedTemporaryFile(mode="w", delete=False, suffix="_id_rsa") as f:
- f.write(settings.git_creds)
- key_path = f.name
-
- os.chmod(key_path, stat.S_IRUSR | stat.S_IWUSR)
+ assert settings.git_creds, "Must define key to download alternative models"
+ fn = tmp_path_factory.mktemp("keys") / "git_creds"
+ key_content = settings.git_creds.replace("\\n", "\n")
+ fn.write_text(key_content, encoding="utf-8")
+ os.chmod(fn, stat.S_IRUSR | stat.S_IWUSR)
- yield Path(key_path)
+ yield Path(fn)
- os.unlink(key_path)
+ os.unlink(fn)
@pytest.fixture(scope="session")
-def gr_repo(git_key_path: Path) -> Callable[..., Path]:
- repo_url = "ssh://code.g-r-l.com/general-research/gr-carer.git"
- repo_path = Path("/tmp/gr-carer")
+def gr_repo(
+ git_key_path: Path,
+ tmp_path_factory: TempPathFactory,
+) -> Callable[..., Path | None]:
+ repo_url = "ssh://code.g-r-l.com:6611/general-research/gr-carer.git"
+
+ _ran = {}
+
+ fn = tmp_path_factory.mktemp("repos")
+ repo_path = fn / "gr-carer"
def _inner() -> Path:
+
+ if _ran.get(repo_url, False):
+ print(f"Already ran django_db_factory.{repo_url}")
+ return repo_path
+
+ _ran[repo_url] = True
+
ssh_cmd = (
- f"ssh -i {git_key_path} "
+ f'ssh -i "{git_key_path}" '
"-o IdentitiesOnly=yes "
- "-o StrictHostKeyChecking=no " # or accept-new, see note below
+ "-o StrictHostKeyChecking=no "
)
- env = {"GIT_SSH_COMMAND": ssh_cmd}
+ env = {**os.environ, "GIT_SSH_COMMAND": ssh_cmd}
if repo_path.exists():
subprocess.run(["git", "-C", str(repo_path), "pull"], check=True, env=env)
@@ -202,51 +216,140 @@ def gr_repo(git_key_path: Path) -> Callable[..., Path]:
@pytest.fixture(scope="session")
+def django_settings_file(
+ postgres_instance_dict: PostgresDict,
+) -> Callable[..., Path]:
+
+ def _inner(
+ settings_dir: Path, extra_installed_apps: list[str] | None = None
+ ) -> Path:
+ installed_apps = [
+ "django.contrib.postgres",
+ "django.contrib.contenttypes",
+ ] + (extra_installed_apps or [])
+ """
+ This returns the directory path of where the settings file is in,
+ not the path of the settings file itself
+ """
+
+ settings_content = f"""DATABASES = {{
+ "default": {{
+ "ENGINE": "django.db.backends.postgresql",
+ "NAME": {postgres_instance_dict["name"]!r},
+ "USER": {postgres_instance_dict["username"]!r},
+ "PASSWORD": {postgres_instance_dict["password"]!r},
+ "HOST": {postgres_instance_dict["host"]!r},
+ "PORT": {postgres_instance_dict["port"]!r},
+ }}
+}}
+INSTALLED_APPS = {installed_apps!r}
+DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
+LANGUAGE_CODE = "en-us"
+TIME_ZONE = "UTC"
+USE_I18N = True
+USE_L10N = True
+USE_TZ = True
+"""
+ settings_file_path = settings_dir / "test_settings.py"
+ settings_file_path.unlink(missing_ok=True)
+ settings_file_path.write_text(settings_content)
+
+ return settings_dir
+
+ return _inner
+
+
+@pytest.fixture(scope="session")
def django_db_factory(
+ request: FixtureRequest,
postgres_instance: PostgresDsn,
- postgres_instance_dict: PostgresDict,
gr_repo: Callable[..., Path],
-) -> Callable[..., PostgresDsn]:
-
- import django
- from django.conf import settings as django_settings
- from django.core.management import call_command
-
- def _inner(django_project: str = "generalresearch.thl_django"):
-
- if "gr" in django_project:
- # We need model files that are NOT in this repo.
- gr_path = gr_repo()
- sys.path.insert(0, str(gr_path))
-
- print(sys.path)
-
- # 1. Bootstrapping Django settings
- if not django_settings.configured:
- django_settings.configure(
- DATABASES={
- "default": {
- "ENGINE": "django.db.backends.postgresql",
- "NAME": postgres_instance_dict["name"],
- "USER": postgres_instance_dict["username"],
- "PASSWORD": postgres_instance_dict["password"],
- "HOST": postgres_instance_dict["host"],
- "PORT": postgres_instance_dict["port"],
- }
- },
- INSTALLED_APPS=[
- "django.contrib.postgres",
- "django.contrib.contenttypes",
- django_project,
+ django_settings_file: Callable[..., Path],
+ postgres_instance_dict: PostgresDict,
+ tmp_path_factory: TempPathFactory,
+) -> Callable[..., PostgresDsn | None]:
+
+ _ran = {}
+
+ def _inner(
+ django_project: str = "generalresearch.thl_django",
+ ) -> PostgresDsn | None:
+
+ if _ran.get(django_project, False):
+ print(f"Already ran django_db_factory:{django_project}")
+ return postgres_instance
+ _ran[django_project] = True
+
+ # This is the generalresearch project root path, it's
+ # 1 directory up from test_utils/, or tests/
+ base_dir = Path(request.config.rootpath).parent
+
+ if django_project == "generalresearch.thl_django":
+ _cwd = base_dir
+ _manage_path = "generalresearch.thl_django.app.manage"
+ _settings_dir = base_dir / "generalresearch/thl_django/app"
+ _settings_module = "generalresearch.thl_django.app.test_settings"
+ django_settings_file(
+ settings_dir=_settings_dir,
+ extra_installed_apps=[
+ "generalresearch.thl_django",
],
)
- django.setup()
- # for model in apps.get_models():
- # print(f"Discovered model: {model._meta.label}")
+ elif django_project == "gr.common":
+ _cwd = gr_repo()
+ _manage_path = "gr.app.manage"
+ _settings_dir = gr_repo() / "gr/app"
+ _settings_module = "gr.app.test_settings"
+ django_settings_file(
+ settings_dir=_settings_dir, extra_installed_apps=["gr.common"]
+ )
+
+ else:
+ raise ValueError("Not implemented yet.")
+
+ assert _settings_dir
+
+ env = {"DJANGO_SETTINGS_MODULE": str(_settings_module)}
+ res1 = subprocess.run(
+ [
+ sys.executable,
+ "-m",
+ _manage_path,
+ "makemigrations",
+ f"--settings={_settings_module}",
+ ],
+ cwd=str(_cwd),
+ env=env,
+ capture_output=True,
+ text=True,
+ check=True,
+ )
+
+ if res1.returncode != 0:
+ print("STDOUT:", res1.stdout)
+ print("STDERR:", res1.stderr)
+ res1.check_returncode()
+
+ res2 = subprocess.run(
+ [
+ sys.executable,
+ "-m",
+ _manage_path,
+ "migrate",
+ f"--settings={_settings_module}",
+ ],
+ env=env,
+ cwd=str(_cwd),
+ capture_output=True,
+ text=True,
+ check=True,
+ )
- # 2. Run migrations directly during fixture activation
- call_command("migrate")
+ if res2.returncode != 0:
+ print("STDOUT:", res2.stdout)
+ print("STDERR:", res2.stderr)
+ res2.check_returncode()
# 3. Return the Dsn so the factory gives a way to connect
return postgres_instance
@@ -276,37 +379,37 @@ def spectrum_rw(settings: GRLBaseSettings) -> SqlHelper:
@pytest.fixture
def start() -> datetime:
- return datetime(year=1900, month=1, day=1, tzinfo=timezone.utc)
+ return datetime(year=1900, month=1, day=1, tzinfo=UTC)
@pytest.fixture
def utc_now() -> datetime:
- return datetime.now(tz=timezone.utc)
+ return datetime.now(tz=UTC)
@pytest.fixture
def utc_hour_ago() -> datetime:
- return datetime.now(tz=timezone.utc) - timedelta(hours=1)
+ return datetime.now(tz=UTC) - timedelta(hours=1)
@pytest.fixture
def utc_day_ago() -> datetime:
- return datetime.now(tz=timezone.utc) - timedelta(hours=24)
+ return datetime.now(tz=UTC) - timedelta(hours=24)
@pytest.fixture
def utc_90days_ago() -> datetime:
- return datetime.now(tz=timezone.utc) - timedelta(days=90)
+ return datetime.now(tz=UTC) - timedelta(days=90)
@pytest.fixture
def utc_60days_ago() -> datetime:
- return datetime.now(tz=timezone.utc) - timedelta(days=60)
+ return datetime.now(tz=UTC) - timedelta(days=60)
@pytest.fixture
def utc_30days_ago() -> datetime:
- return datetime.now(tz=timezone.utc) - timedelta(days=30)
+ return datetime.now(tz=UTC) - timedelta(days=30)
# === Clean up ===
@@ -317,12 +420,12 @@ def delete_df_collection(
thl_web_rw: PostgresConfig, create_main_accounts: Callable[..., None]
) -> Callable[..., None]:
- from generalresearch.incite.collections import (
+ from generalresearch.incite.collections.base import (
DFCollection,
DFCollectionType,
)
- def _inner(coll: "DFCollection"):
+ def _inner(coll: DFCollection):
match coll.data_type:
case DFCollectionType.LEDGER:
for table in [
@@ -337,16 +440,15 @@ def delete_df_collection(
create_main_accounts()
case DFCollectionType.WALL | DFCollectionType.SESSION:
- with thl_web_rw.make_connection() as conn:
- with conn.cursor() as c:
- c.execute("SET CONSTRAINTS ALL DEFERRED")
- for table in [
- "thl_wall",
- "thl_session",
- ]:
- c.execute(
- query=f"DELETE FROM {table};",
- )
+ with thl_web_rw.make_connection() as conn, conn.cursor() as c:
+ c.execute("SET CONSTRAINTS ALL DEFERRED")
+ for table in [
+ "thl_wall",
+ "thl_session",
+ ]:
+ c.execute(
+ query=f"DELETE FROM {table};",
+ )
case DFCollectionType.USER:
for table in ["thl_usermetadata", "thl_user"]:
diff --git a/test_utils/grliq/conftest.py b/test_utils/grliq/conftest.py
index e8175a5..249b068 100644
--- a/test_utils/grliq/conftest.py
+++ b/test_utils/grliq/conftest.py
@@ -1,27 +1,34 @@
from __future__ import annotations
-from datetime import datetime, timedelta, timezone
-from typing import Callable
+from collections.abc import Callable
+from datetime import UTC, datetime, timedelta
+from typing import TYPE_CHECKING, Any
from uuid import uuid4
import pytest
from pydantic import PostgresDsn
-from generalresearch.config import GRLBaseSettings
-from generalresearch.grliq.managers import DUMMY_GRLIQ_DATA
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.grliq.models.forensic_data import GrlIqData
+from generalresearch.grliq.models.forensic_result import (
+ GrlIqCheckerResults,
+ GrlIqForensicCategoryResult,
+)
from generalresearch.pg_helper import PostgresConfig
-# === Miscellaneous ===
+if TYPE_CHECKING:
+ from generalresearch.config import GRLBaseSettings
+ from generalresearch.grliq.managers.forensic_events import (
+ GrlIqEventManager,
+ )
+
+
+# --- Assets ---
@pytest.fixture(scope="function")
@@ -42,56 +49,28 @@ def grliq_db(postgres_instance: PostgresDsn) -> PostgresConfig:
)
-# === Managers ===
+# --- GRLIQ Data ---
@pytest.fixture(scope="session")
-def grliq_dm(grliq_db: PostgresConfig) -> GrlIqDataManager:
+def grliq_data_manager(grliq_db: PostgresConfig) -> GrlIqDataManager:
assert grliq_db.dsn.path
assert "/unittest-" in grliq_db.dsn.path
return GrlIqDataManager(postgres_config=grliq_db)
@pytest.fixture(scope="session")
-def grliq_em(grliq_db: PostgresConfig) -> GrlIqEventManager:
- assert grliq_db.dsn.path
- 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: PostgresConfig) -> GrlIqCategoryResultsReader:
- assert grliq_db.dsn.path
- assert "/unittest-" in grliq_db.dsn.path
-
- return GrlIqCategoryResultsReader(postgres_config=grliq_db)
-
-
-# === Models ===
-
-
-@pytest.fixture(scope="function")
-def grliq_data() -> GrlIqData:
- from generalresearch.grliq.managers import DUMMY_GRLIQ_DATA
-
- g: GrlIqData = DUMMY_GRLIQ_DATA[1]["data"]
-
- g.id = None
- g.uuid = uuid4().hex
- g.created_at = datetime.now(tz=timezone.utc)
- g.timestamp = g.created_at - timedelta(seconds=10)
- return g
+def grliq_dm(grliq_data_manager: GrlIqDataManager) -> GrlIqDataManager:
+ return grliq_data_manager
@pytest.fixture
-def grliq_data_factory(grliq_dm: GrlIqDataManager) -> Callable[..., GrlIqData]:
+def grliq_data_factory(
+ grliq_data_manager: GrlIqDataManager, grliq_data_list: list[dict[str, Any]]
+) -> Callable[..., GrlIqData]:
def _inner(
+ save: bool = True,
is_attempt_allowed: bool = True,
product_id: str | None = None,
product_user_id: str | None = None,
@@ -109,30 +88,127 @@ def grliq_data_factory(grliq_dm: GrlIqDataManager) -> Callable[..., GrlIqData]:
:param mid: the thl_session:uuid / mid for the attempt.
:return:
"""
- import copy
-
- res: GrlIqData = copy.deepcopy(DUMMY_GRLIQ_DATA[int(is_attempt_allowed)])
-
- product_id = product_id or uuid4().hex
- product_user_id = product_user_id or uuid4().hex
- uuid = uuid or uuid4().hex
- mid = mid or uuid4().hex
- created_at = created_at or datetime.now(tz=timezone.utc)
-
- res["data"].product_id = product_id
- res["data"].product_user_id = product_user_id
- res["data"].uuid = uuid
- res["data"].mid = mid
- res["data"].created_at = created_at
- res["result_data"].uuid = uuid
- res["category_result"].uuid = uuid
-
- return grliq_dm.create(
- iq_data=res["data"],
- result_data=res["result_data"],
- category_result=res["category_result"],
- fraud_score=res["category_result"].fraud_score,
- is_attempt_allowed=res["category_result"].is_attempt_allowed(),
- )
+
+ if save:
+ res: dict = grliq_data_list[int(is_attempt_allowed)]
+
+ product_id = product_id or uuid4().hex
+ product_user_id = product_user_id or uuid4().hex
+ uuid = uuid or uuid4().hex
+ mid = mid or uuid4().hex
+ created_at = created_at or datetime.now(tz=UTC)
+
+ res["data"].product_id = product_id
+ res["data"].product_user_id = product_user_id
+ res["data"].uuid = uuid
+ res["data"].mid = mid
+ res["data"].created_at = created_at
+ res["result_data"].uuid = uuid
+ res["category_result"].uuid = uuid
+
+ return grliq_data_manager.create(
+ iq_data=res["data"],
+ result_data=res["result_data"],
+ category_result=res["category_result"],
+ fraud_score=res["category_result"].fraud_score,
+ is_attempt_allowed=res["category_result"].is_attempt_allowed(),
+ )
+ else:
+ raise ValueError("Unsaved GRLIQ Data not supported yet")
return _inner
+
+
+@pytest.fixture(scope="function")
+def grliq_data(grliq_data_list: list[dict[str, Any]]) -> GrlIqData:
+
+ g: GrlIqData = grliq_data_list[1]["data"]
+
+ g.id = None
+ g.uuid = uuid4().hex
+ g.created_at = datetime.now(tz=UTC)
+ g.timestamp = g.created_at - timedelta(seconds=10)
+ return g
+
+
+@pytest.fixture(scope="function")
+def unsaved_grliq_data(grliq_data_list: list[dict[str, Any]]) -> GrlIqData:
+ raise ValueError("Not supported")
+
+
+# --- GRLIQ Event ---
+
+
+@pytest.fixture(scope="session")
+def grliq_event_manager(grliq_db: PostgresConfig) -> GrlIqEventManager:
+ assert grliq_db.dsn.path
+ 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_em(grliq_event_manager: GrlIqEventManager) -> GrlIqEventManager:
+ return grliq_event_manager
+
+
+# --- GRLIQ Category Results Reader ---
+
+
+@pytest.fixture(scope="session")
+def grliq_category_results_reader(
+ grliq_db: PostgresConfig,
+) -> GrlIqCategoryResultsReader:
+ assert grliq_db.dsn.path
+ assert "/unittest-" in grliq_db.dsn.path
+
+ return GrlIqCategoryResultsReader(postgres_config=grliq_db)
+
+
+@pytest.fixture(scope="session")
+def grliq_crr(
+ grliq_category_results_reader: GrlIqCategoryResultsReader,
+) -> GrlIqCategoryResultsReader:
+ return grliq_category_results_reader
+
+
+# === Models ===
+
+
+# === Miscellaneous ===
+
+
+@pytest.fixture(scope="session")
+def grliq_data_list() -> list[dict[str, Any]]:
+ return [
+ {
+ "data": GrlIqData.model_validate_json(
+ """{"mid": "3722ed29314940fabd37b42d808dcf5a", "uuid": "b11441da5a854dfbb8401d4c32e56db5", "phase": "offerwall-enter", "events": null, "vendor": "Google Inc.", "app_name": "Netscape", "calendar": "gregory", "language": "en-US", "platform": "Linux x86_64", "timezone": "America/Mexico_City", "client_ip": "131.196.250.250", "timestamp": "2025-02-27T16:05:34-06:00", "webrtc_ip": "131.196.250.250", "created_at": "2025-02-27T22:05:35.370589Z", "language_2": "en-US", "language_3": null, "platform_2": "Linux x86_64", "platform_3": null, "prefetched": true, "product_id": "d0606a0b5d034a8d81b1e3579d1f76fd", "webgl_flag": true, "webgl_hash": "da27e1b9b660057a3f5e185d3f5deabe", "canvas_hash": "14ed764326ec454d976c322261d99f16", "color_gamut": "3", "country_iso": "mx", "inner_width": 612, "outer_width": 1813, "product_sub": "20030107", "audio_codecs": "1,1,1,1,1,3,1,3,1,3,3,1,1,3,3,3,3,1,3,3,3,2,1,1", "cookie_check": "", "graphics_api": "WebKit WebGL", "inner_height": 1174, "mouse_events": null, "ontouchstart": false, "outer_height": 1261, "plugins_hash": "4c05fa2f766a444d4f253ead792c8b0e|2", "screen_width": 2560, "video_codecs": "1,3,3,3,3,3,3,3,3,3,1,1,1,1,1,1,3,1,1,1,3,3,1", "webgl_hash_2": "fc73fd5db75e2c36222fe34251be3971", "webrtc_error": false, "window_opera": false, "battery_level": 0.9, "canvas_hash_2": "bd11ebbf5c26fd20e0217820b4159752", "dynamic_range": false, "error_message": "Cannot read", "forced_colors": false, "math_result_1": "1.9275814160560204e-50", "math_result_2": "1.6182817135715877", "screen_height": 1440, "webgl_check_1": true, "webgl_context": "webgl2", "window_chrome": true, "connection_rtt": 150, "history_length": 16, "user_agent_str": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36", "web_sql_exists": false, "calender_locale": "en-US", "connection_type": "", "inverted_colors": true, "navigator_brave": false, "product_user_id": "d1d55df1-959e-4740-b77c-fa1f4fc457ae", "request_headers": {"host": "test", "accept": "*/*", "connection": "keep-alive", "user-agent": "python-httpx/0.27.0", "content-length": "3646", "accept-encoding": "gzip, deflate", "x-forwarded-for": "131.196.250.250"}, "timezone_offset": 360, "webrtc_local_ip": "50486637-6b64-4812-b10a-0a75337c31bd.local", "battery_charging": true, "client_ip_detail": {"continent_code": "EU", "continent_name": "Europe", "country_name": "France", "is_in_european_union": true, "ip": "131.196.250.250", "isp": null, "latitude": null, "city_name": null, "longitude": null, "time_zone": null, "user_type": null, "country_iso": "mx", "postal_code": null, "is_anonymous": null, "accuracy_radius": null, "static_ip_score": null, "subdivision_1_iso": null, "subdivision_2_iso": null, "subdivision_1_name": null, "subdivision_2_name": null, "registered_country_iso": null}, "max_touch_points": 0, "numbering_system": "latn", "path_fingerprint": 3252, "prefers_contrast": "0", "rendering_engine": "WebKit", "timezone_success": "pass", "user_agent_hints": {"model": null, "brands": [{"brand": "Google Chrome", "version": "131"}, {"brand": "Chromium", "version": "131"}, {"brand": "Not_A Brand", "version": "24"}], "mobile": false, "bitness": "64", "platform": "Linux", "brands_full": [{"brand": "Google Chrome", "version": "131.0.6778.204"}, {"brand": "Chromium", "version": "131.0.6778.204"}, {"brand": "Not_A Brand", "version": "24.0.0.0"}], "architecture": "x86", "platform_version": "6.2.0"}, "user_agent_str_2": null, "webgl_extensions": "EXT_clip_control|EXT_color_buffer_float|EXT_color_buffer_half_float|EXT_conservative_depth|EXT_depth_clamp|EXT_disjoint_timer_query_webgl2|EXT_float_blend|EXT_polygon_offset_clamp|EXT_render_snorm|EXT_texture_compression_bptc|EXT_texture_compression_rgtc|EXT_texture_filter_anisotropic|EXT_texture_mirror_clamp_to_edge|EXT_texture_norm16|KHR_parallel_shader_compile|NV_shader_noperspective_interpolation|OES_draw_buffers_indexed|OES_sample_variables|OES_shader_multisample_interpolation|OES_texture_float_linear|OVR_multiview2|WEBGL_blend_func_extended|WEBGL_clip_cull_distance|WEBGL_compressed_texture_astc|WEBGL_compressed_texture_etc|WEBGL_compressed_texture_etc1|WEBGL_compressed_texture_s3tc|WEBGL_compressed_texture_s3tc_srgb|WEBGL_debug_renderer_info|WEBGL_debug_shaders|WEBGL_lose_context|WEBGL_multi_draw|WEBGL_polygon_mode|WEBGL_provoking_vertex|WEBGL_stencil_texturing", "webrtc_ip_detail": {"continent_code": "EU", "continent_name": "Europe", "country_name": "France", "is_in_european_union": true, "ip": "131.196.250.250", "isp": null, "latitude": null, "city_name": null, "longitude": null, "time_zone": null, "user_type": null, "country_iso": "mx", "postal_code": null, "is_anonymous": null, "accuracy_radius": null, "static_ip_score": null, "subdivision_1_iso": null, "subdivision_2_iso": null, "subdivision_1_name": null, "subdivision_2_name": null, "registered_country_iso": null}, "chrome_extensions": "", "execution_time_ms": 371.0999999642372, "graphics_renderer": "WebGL 2.0 (OpenGL ES 3.0 Chromium)", "keyboard_detected": true, "mime_types_length": 2, "request_fs_exists": true, "audio_context_flag": "pass", "audio_context_hash": "9307303774dec3248c18a939392090da", "canvas_fingerprint": 258, "canvas_pixel_check": false, "device_pixel_ratio": 1.0, "indexedDbData_blob": true, "navigator_keys_len": 79, "no_edge_pdf_plugin": false, "screen_avail_width": 2560, "webdriver_detected": false, "window_orientation": 0, "connection_downlink": 10.0, "navigator_webdriver": false, "non_native_function": false, "screen_avail_height": 1400, "supported_fonts_str": "72|768|262144|1073741824|0|0|540672|73728|7340032|1342177280|117446656|256|16|0|543|4290797636|1677723648|4168998400|0|1048576|262144|268500994|1342177280|262144|125829376|37888000|0|435363842|0|2147483648|109543424|1880099872|268435471", "text_2d_fingerprint": "bfcce91c9e71d11af7b14dbee4c75f83", "webrtc_is_supported": "pass", "canvas_support_level": "full", "do_not_track_enabled": "1", "hardware_concurrency": 12, "keyboard_layout_size": 48, "prefers_color_scheme": false, "webgl_max_anisotropy": 16, "battery_charging_time": 0.0, "browser_by_properties": "c", "eval_to_string_length": 33, "performance_loop_time": 0.09999996423721313, "session_storage_check": "pass", "unmasked_vendor_webgl": "Google Inc. (Intel)", "hardware_concurrency_2": 12, "hardware_concurrency_3": null, "localStorage_available": true, "memory_jsHeapSizeLimit": 4294705152, "mozilla_web_app_exists": false, "navigator_deviceMemory": 8.0, "navigator_java_enabled": false, "prefers_reduced_motion": false, "storage_estimate_quota": 1178717110272, "webdriver_detected_msg": "", "window_active_x_object": false, "window_external_exists": true, "color_depth_pixel_depth": "24-24", "indexedDbData_available": true, "navigator_cookieEnabled": true, "unmasked_renderer_webgl": "ANGLE (Intel, Mesa Intel(R) Graphics (RPL-P), OpenGL 4.6)", "battery_discharging_time": 0.0, "connection_effectiveType": "4g", "non_native_function_flag": "", "speech_synthesis_voice_1": "Google Bahasa Indonesia", "window_client_information": true, "audio_compressor_reduction": 20.538288116455078, "navigator_mediaDevices_len": 3, "audio_intensity_fingerprint": 124.04347527516074, "speech_synthesis_voice_hash": "8010ee3313813de521e48e63bd5a6f13", "microsoft_credentials_exists": false, "window_installTrigger_exists": false, "speech_synthesis_voices_count": 19, "webgl_shading_language_version": "WebGL GLSL ES 3.00 (OpenGL ES GLSL ES 3.0 Chromium)", "error_message_stack_access_count": 0, "speech_synthesis_avail_voices_count": 19, "error_message_stack_access_count_worker": 0}"""
+ ),
+ "result_data": GrlIqCheckerResults.model_validate_json(
+ """{"uuid": "b11441da5a854dfbb8401d4c32e56db5", "check_codecs": {"score": 0}, "check_timezone": {"score": 0}, "check_timestamp": {"score": 0}, "check_user_type": {"score": 0}, "check_ip_changes": {"score": 0}, "check_ip_country": {"score": 0}, "check_environment": {"score": 0}, "check_ip_timezone": {"score": 0}, "check_isp_changes": {"score": 0}, "check_useragent_js": {"score": 0}, "check_required_fonts": {"score": 0}, "check_user_anonymous": {"score": 0}, "check_webrtc_success": {"score": 0}, "check_seen_timestamps": {"msg": "duplicate timestamp", "score": 100}, "check_country_timezone": {"score": 0}, "check_prohibited_fonts": {"score": 0}, "check_timezone_changes": {"score": 0}, "check_execution_time_ms": {"msg": "duplicate execution_time_ms", "score": 100}, "check_fingerprint_reuse": {"score": 0}, "check_fingerprint_cycling": {"score": 0}, "check_ip_webrtc_ip_detail": {"score": 0}, "check_environment_critical": {"score": 0}, "check_useragent_other_enums": {"score": 0}, "check_useragent_ip_properties": {"score": 0}, "check_useragent_data_properties": {"score": 0}, "check_useragent_device_family_brand": {"score": 0}}"""
+ ),
+ "category_result": GrlIqForensicCategoryResult.model_validate_json(
+ """{"uuid": "b11441da5a854dfbb8401d4c32e56db5", "is_bot": 0, "is_tampered": 100, "is_velocity": 0, "is_anonymous": 0, "suspicious_ip": 0, "is_oscillating": 0, "is_teleporting": 0, "is_inconsistent": 0, "platform_ip_inconsistent": 0}"""
+ ),
+ "fraud_score": 100,
+ "is_attempt_allowed": False,
+ },
+ {
+ "data": GrlIqData.model_validate_json(
+ """{"mid": "35f6f5c30bc74ea7ac4aca7b40a02352", "uuid": "d54509f2f310499f8ab74839b10b2a41", "phase": "offerwall-enter", "events": null, "vendor": "Google Inc.", "app_name": "Netscape", "calendar": "gregory", "language": "en-US", "platform": "Linux x86_64", "timezone": "America/Los_Angeles", "client_ip": "104.9.125.144", "timestamp": "2025-02-28T11:34:39-08:00", "webrtc_ip": "172.56.209.195", "created_at": "2025-02-28T19:34:39.681872Z", "language_2": "en-US", "language_3": null, "platform_2": "Linux x86_64", "platform_3": null, "prefetched": true, "product_id": "d0606a0b5d034a8d81b1e3579d1f76fd", "webgl_flag": true, "webgl_hash": "da27e1b9b660057a3f5e185d3f5deabe", "canvas_hash": "e6e4d17da26050ce85ad00d3c6ea999e", "color_gamut": "3", "country_iso": "us", "inner_width": 841, "outer_width": 1680, "product_sub": "20030107", "audio_codecs": "1,1,1,1,1,3,1,3,1,3,3,1,1,3,3,3,3,1,3,3,3,2,1,1", "cookie_check": "", "graphics_api": "WebKit WebGL", "inner_height": 891, "mouse_events": null, "ontouchstart": false, "outer_height": 978, "plugins_hash": "4c05fa2f766a444d4f253ead792c8b0e|2", "screen_width": 1680, "video_codecs": "1,3,3,3,3,3,3,3,3,3,1,1,1,1,1,1,3,1,1,1,3,3,1", "webgl_hash_2": "fc73fd5db75e2c36222fe34251be3971", "webrtc_error": false, "window_opera": false, "battery_level": 0.41, "canvas_hash_2": "e0559d49b1864985cafc0d1c3a6b053c", "dynamic_range": false, "error_message": "Cannot read", "forced_colors": false, "math_result_1": "1.9275814160560204e-50", "math_result_2": "1.6182817135715877", "screen_height": 1050, "webgl_check_1": true, "webgl_context": "webgl2", "window_chrome": true, "connection_rtt": 100, "history_length": 11, "user_agent_str": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36", "web_sql_exists": false, "calender_locale": "en-US", "connection_type": "", "inverted_colors": true, "navigator_brave": false, "product_user_id": "test-unit", "request_headers": {"dnt": "1", "host": "127.0.0.1:8081", "accept": "application/json, lk/null q=0.1", "origin": "http://127.0.0.1:8080", "referer": "http://127.0.0.1:8080/", "sec-ch-ua": "\\"Google Chrome\\";v=\\"131\\", \\"Chromium\\";v=\\"131\\", \\"Not_A Brand\\";v=\\"24\\"", "connection": "keep-alive", "user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36", "content-type": "application/json", "content-length": "3313", "sec-fetch-dest": "empty", "sec-fetch-mode": "cors", "sec-fetch-site": "same-site", "accept-encoding": "gzip, deflate, br, zstd", "accept-language": "en-US,en;q=0.9", "sec-ch-ua-mobile": "?0", "sec-ch-ua-platform": "\\"Linux\\""}, "timezone_offset": 480, "webrtc_local_ip": "10.253.217.45,[2607:fb91:20c5:c6af:cda0:10b4:830a:a85e]", "battery_charging": false, "client_ip_detail": {"continent_code": "EU", "continent_name": "Europe", "country_name": "France", "is_in_european_union": true, "ip": "104.9.125.144", "isp": "AT&T Internet", "latitude": 37.3897, "city_name": "Mountain View", "longitude": -122.083, "time_zone": "America/Los_Angeles", "user_type": "residential", "country_iso": "us", "postal_code": "94041", "is_anonymous": false, "accuracy_radius": 5, "static_ip_score": 40.3, "subdivision_1_iso": "CA", "subdivision_2_iso": null, "subdivision_1_name": "California", "subdivision_2_name": null, "registered_country_iso": "us"}, "max_touch_points": 0, "numbering_system": "latn", "path_fingerprint": 3252, "prefers_contrast": "0", "rendering_engine": "WebKit", "timezone_success": "pass", "user_agent_hints": {"model": null, "brands": [{"brand": "Google Chrome", "version": "131"}, {"brand": "Chromium", "version": "131"}, {"brand": "Not_A Brand", "version": "24"}], "mobile": false, "bitness": "64", "platform": "Linux", "brands_full": [{"brand": "Google Chrome", "version": "131.0.6778.204"}, {"brand": "Chromium", "version": "131.0.6778.204"}, {"brand": "Not_A Brand", "version": "24.0.0.0"}], "architecture": "x86", "platform_version": "6.2.0"}, "user_agent_str_2": null, "webgl_extensions": "EXT_clip_control|EXT_color_buffer_float|EXT_color_buffer_half_float|EXT_conservative_depth|EXT_depth_clamp|EXT_disjoint_timer_query_webgl2|EXT_float_blend|EXT_polygon_offset_clamp|EXT_render_snorm|EXT_texture_compression_bptc|EXT_texture_compression_rgtc|EXT_texture_filter_anisotropic|EXT_texture_mirror_clamp_to_edge|EXT_texture_norm16|KHR_parallel_shader_compile|NV_shader_noperspective_interpolation|OES_draw_buffers_indexed|OES_sample_variables|OES_shader_multisample_interpolation|OES_texture_float_linear|OVR_multiview2|WEBGL_blend_func_extended|WEBGL_clip_cull_distance|WEBGL_compressed_texture_astc|WEBGL_compressed_texture_etc|WEBGL_compressed_texture_etc1|WEBGL_compressed_texture_s3tc|WEBGL_compressed_texture_s3tc_srgb|WEBGL_debug_renderer_info|WEBGL_debug_shaders|WEBGL_lose_context|WEBGL_multi_draw|WEBGL_polygon_mode|WEBGL_provoking_vertex|WEBGL_stencil_texturing", "webrtc_ip_detail": {"continent_code": "EU", "continent_name": "Europe", "country_name": "France", "is_in_european_union": true, "ip": "172.56.209.195", "isp": null, "latitude": null, "city_name": null, "longitude": null, "time_zone": null, "user_type": null, "country_iso": "us", "postal_code": null, "is_anonymous": null, "accuracy_radius": null, "static_ip_score": null, "subdivision_1_iso": null, "subdivision_2_iso": null, "subdivision_1_name": null, "subdivision_2_name": null, "registered_country_iso": null}, "chrome_extensions": "", "execution_time_ms": 924.5, "graphics_renderer": "WebGL 2.0 (OpenGL ES 3.0 Chromium)", "keyboard_detected": true, "mime_types_length": 2, "request_fs_exists": true, "audio_context_flag": "pass", "audio_context_hash": "9307303774dec3248c18a939392090da", "canvas_fingerprint": 258, "canvas_pixel_check": false, "device_pixel_ratio": 1.0, "indexedDbData_blob": true, "navigator_keys_len": 79, "no_edge_pdf_plugin": false, "screen_avail_width": 1680, "webdriver_detected": false, "window_orientation": 0, "connection_downlink": 10.0, "navigator_webdriver": false, "non_native_function": false, "screen_avail_height": 1010, "supported_fonts_str": "72|17152|327680|1073741824|0|0|540736|73728|7340032|1342177280|117446657|256|16|0|262687|4290797636|1677723648|4168998400|0|1048576|262144|268500994|1342177280|262144|125829376|37888000|0|435363842|0|2147483648|109543680|1880099888|301989903", "text_2d_fingerprint": "bfcce91c9e71d11af7b14dbee4c75f83", "webrtc_is_supported": "pass", "canvas_support_level": "full", "do_not_track_enabled": "1", "hardware_concurrency": 12, "keyboard_layout_size": 48, "prefers_color_scheme": false, "webgl_max_anisotropy": 16, "battery_charging_time": 0.0, "browser_by_properties": "c", "eval_to_string_length": 33, "performance_loop_time": 0.09999999962747097, "session_storage_check": "pass", "unmasked_vendor_webgl": "Google Inc. (Intel)", "hardware_concurrency_2": 12, "hardware_concurrency_3": null, "localStorage_available": true, "memory_jsHeapSizeLimit": 4294705152, "mozilla_web_app_exists": false, "navigator_deviceMemory": 8.0, "navigator_java_enabled": false, "prefers_reduced_motion": false, "storage_estimate_quota": 1178717110272, "webdriver_detected_msg": "", "window_active_x_object": false, "window_external_exists": true, "color_depth_pixel_depth": "24-24", "indexedDbData_available": true, "navigator_cookieEnabled": true, "unmasked_renderer_webgl": "ANGLE (Intel, Mesa Intel(R) Graphics (RPL-P), OpenGL 4.6)", "battery_discharging_time": 4844.0, "connection_effectiveType": "4g", "non_native_function_flag": "", "speech_synthesis_voice_1": "Google Bahasa Indonesia", "window_client_information": true, "audio_compressor_reduction": 20.538288116455078, "navigator_mediaDevices_len": 8, "audio_intensity_fingerprint": 124.04347527516074, "speech_synthesis_voice_hash": "8010ee3313813de521e48e63bd5a6f13", "microsoft_credentials_exists": false, "window_installTrigger_exists": false, "speech_synthesis_voices_count": 19, "webgl_shading_language_version": "WebGL GLSL ES 3.00 (OpenGL ES GLSL ES 3.0 Chromium)", "error_message_stack_access_count": 2, "speech_synthesis_avail_voices_count": 19, "error_message_stack_access_count_worker": 2}"""
+ ),
+ "result_data": GrlIqCheckerResults.model_validate_json(
+ """{"uuid": "d54509f2f310499f8ab74839b10b2a41", "check_codecs": {"score": 0}, "check_timezone": {"score": 0}, "check_timestamp": {"score": 0}, "check_user_type": {"score": 0}, "check_ip_changes": {"score": 0}, "check_ip_country": {"score": 0}, "check_environment": {"msg": "error_message_stack_access_count: 2", "score": 100}, "check_ip_timezone": {"score": 0}, "check_isp_changes": {"score": 0}, "check_useragent_js": {"score": 0}, "check_required_fonts": {"score": 0}, "check_user_anonymous": {"score": 0}, "check_webrtc_success": {"score": 0}, "check_seen_timestamps": {"score": 0}, "check_country_timezone": {"score": 0}, "check_prohibited_fonts": {"score": 0}, "check_timezone_changes": {"score": 0}, "check_execution_time_ms": {"score": 0}, "check_fingerprint_reuse": {"score": 0}, "check_fingerprint_cycling": {"score": 0}, "check_ip_webrtc_ip_detail": {"score": 0}, "check_environment_critical": {"score": 0}, "check_useragent_other_enums": {"score": 0}, "check_useragent_ip_properties": {"score": 0}, "check_useragent_data_properties": {"score": 0}, "check_useragent_device_family_brand": {"score": 0}}"""
+ ),
+ "category_result": GrlIqForensicCategoryResult.model_validate_json(
+ """{"uuid": "d54509f2f310499f8ab74839b10b2a41", "is_bot": 0, "is_tampered": 0, "is_velocity": 0, "is_anonymous": 0, "suspicious_ip": 0, "is_oscillating": 0, "is_teleporting": 0, "is_inconsistent": 10, "platform_ip_inconsistent": 0}"""
+ ),
+ "fraud_score": 10,
+ "is_attempt_allowed": True,
+ },
+ ]
diff --git a/test_utils/incite/collections/conftest.py b/test_utils/incite/collections/conftest.py
index 88eef72..499f90b 100644
--- a/test_utils/incite/collections/conftest.py
+++ b/test_utils/incite/collections/conftest.py
@@ -1,16 +1,16 @@
from __future__ import annotations
+from collections.abc import Callable
from datetime import datetime, timedelta
-from typing import TYPE_CHECKING, Callable
+from typing import TYPE_CHECKING
import pytest
-from generalresearch.pg_helper import PostgresConfig
from test_utils.conftest import clear_directory
if TYPE_CHECKING:
from generalresearch.incite.base import DFCollectionType, GRLDatasets
- from generalresearch.incite.collections import DFCollection
+ from generalresearch.incite.collections.base import DFCollection
from generalresearch.incite.collections.thl_web import (
AuditLogDFCollection,
LedgerDFCollection,
@@ -19,6 +19,7 @@ if TYPE_CHECKING:
UserDFCollection,
WallDFCollection,
)
+ from generalresearch.pg_helper import PostgresConfig
@pytest.fixture
@@ -196,7 +197,7 @@ def df_collection(
utc_90days_ago: datetime,
thl_web_rr: PostgresConfig,
) -> DFCollection:
- from generalresearch.incite.collections import DFCollection
+ from generalresearch.incite.collections.base import DFCollection
start = utc_90days_ago.replace(microsecond=0)
diff --git a/test_utils/incite/conftest.py b/test_utils/incite/conftest.py
index 12e57c5..bcf0511 100644
--- a/test_utils/incite/conftest.py
+++ b/test_utils/incite/conftest.py
@@ -1,11 +1,12 @@
from __future__ import annotations
-from datetime import datetime, timedelta, timezone
+from collections.abc import Callable
+from datetime import UTC, datetime, timedelta
from os.path import join as pjoin
from pathlib import Path
from random import choice as randchoice
from shutil import rmtree
-from typing import TYPE_CHECKING, Callable
+from typing import TYPE_CHECKING
from uuid import uuid4
import pytest
@@ -15,11 +16,11 @@ from faker import Faker
if TYPE_CHECKING:
from generalresearch.config import GRLBaseSettings
from generalresearch.incite.base import GRLDatasets
- from generalresearch.incite.collections import (
+ from generalresearch.incite.collections.base import (
DFCollectionItem,
DFCollectionType,
)
- from generalresearch.incite.mergers import MergeType
+ from generalresearch.incite.mergers.base import MergeType
from generalresearch.models.admin.request import (
ReportRequest,
)
@@ -130,14 +131,14 @@ def duration() -> timedelta | None:
@pytest.fixture
def df_collection_data_type() -> DFCollectionType:
- from generalresearch.incite.collections import DFCollectionType
+ from generalresearch.incite.collections.base import DFCollectionType
return DFCollectionType.TEST
@pytest.fixture
def merge_type() -> MergeType:
- from generalresearch.incite.mergers import MergeType
+ from generalresearch.incite.mergers.base import MergeType
return MergeType.TEST
@@ -155,7 +156,7 @@ def incite_item_factory(
observations: int = 3,
user: User | None = None,
):
- from generalresearch.incite.collections import (
+ from generalresearch.incite.collections.base import (
DFCollection,
DFCollectionType,
)
@@ -166,7 +167,7 @@ def incite_item_factory(
for _ in range(5):
item_time = fake.date_time_between(
- start_date=item.start, end_date=item.finish, tzinfo=timezone.utc
+ start_date=item.start, end_date=item.finish, tzinfo=UTC
)
match data_type:
diff --git a/test_utils/incite/mergers/conftest.py b/test_utils/incite/mergers/conftest.py
index e9970c2..fb95c81 100644
--- a/test_utils/incite/mergers/conftest.py
+++ b/test_utils/incite/mergers/conftest.py
@@ -1,38 +1,41 @@
from __future__ import annotations
+from collections.abc import Callable
from datetime import datetime, timedelta
-from typing import Callable
+from typing import TYPE_CHECKING
import pytest
-from generalresearch.incite.base import GRLDatasets
-from generalresearch.incite.mergers import MergeType
-from generalresearch.incite.mergers.foundations.enriched_session import (
- EnrichedSessionMerge,
-)
-from generalresearch.incite.mergers.foundations.enriched_task_adjust import (
- EnrichedTaskAdjustMerge,
-)
-from generalresearch.incite.mergers.foundations.enriched_wall import (
- EnrichedWallMerge,
-)
-from generalresearch.incite.mergers.foundations.user_id_product import (
- UserIdProductMerge,
-)
-from generalresearch.incite.mergers.pop_ledger import (
- PopLedgerMerge,
- PopLedgerMergeItem,
-)
-from generalresearch.incite.mergers.ym_survey_wall import (
- YMSurveyWallMerge,
- YMSurveyWallMergeCollectionItem,
-)
-from generalresearch.incite.mergers.ym_wall_summary import (
- YMWallSummaryMerge,
- YMWallSummaryMergeItem,
-)
from test_utils.conftest import clear_directory
+if TYPE_CHECKING:
+ from generalresearch.incite.base import GRLDatasets
+ from generalresearch.incite.mergers.base import MergeType
+ from generalresearch.incite.mergers.foundations.enriched_session import (
+ EnrichedSessionMerge,
+ )
+ from generalresearch.incite.mergers.foundations.enriched_task_adjust import (
+ EnrichedTaskAdjustMerge,
+ )
+ from generalresearch.incite.mergers.foundations.enriched_wall import (
+ EnrichedWallMerge,
+ )
+ from generalresearch.incite.mergers.foundations.user_id_product import (
+ UserIdProductMerge,
+ )
+ from generalresearch.incite.mergers.pop_ledger import (
+ PopLedgerMerge,
+ PopLedgerMergeItem,
+ )
+ from generalresearch.incite.mergers.ym_survey_wall import (
+ YMSurveyWallMerge,
+ YMSurveyWallMergeCollectionItem,
+ )
+ from generalresearch.incite.mergers.ym_wall_summary import (
+ YMWallSummaryMerge,
+ YMWallSummaryMergeItem,
+ )
+
# --------------------------
# Merges
# --------------------------
@@ -55,7 +58,7 @@ def pop_ledger_merge(
duration: timedelta,
) -> PopLedgerMerge:
- from generalresearch.incite.mergers import MergeType
+ from generalresearch.incite.mergers.base import MergeType
from generalresearch.incite.mergers.pop_ledger import PopLedgerMerge
return PopLedgerMerge(
@@ -85,7 +88,7 @@ def ym_survey_wall_merge(
mnt_filepath: GRLDatasets,
start: datetime,
) -> YMSurveyWallMerge:
- from generalresearch.incite.mergers import MergeType
+ from generalresearch.incite.mergers.base import MergeType
from generalresearch.incite.mergers.ym_survey_wall import YMSurveyWallMerge
return YMSurveyWallMerge(
@@ -116,7 +119,7 @@ def ym_wall_summary_merge(
duration: timedelta,
start: datetime,
) -> YMWallSummaryMerge:
- from generalresearch.incite.mergers import MergeType
+ from generalresearch.incite.mergers.base import MergeType
from generalresearch.incite.mergers.ym_wall_summary import YMWallSummaryMerge
return YMWallSummaryMerge(
@@ -152,7 +155,7 @@ def enriched_session_merge(
duration: timedelta,
start: datetime,
) -> EnrichedSessionMerge:
- from generalresearch.incite.mergers import MergeType
+ from generalresearch.incite.mergers.base import MergeType
from generalresearch.incite.mergers.foundations.enriched_session import (
EnrichedSessionMerge,
)
@@ -172,7 +175,7 @@ def enriched_task_adjust_merge(
duration: timedelta,
start: datetime,
) -> EnrichedTaskAdjustMerge:
- from generalresearch.incite.mergers import MergeType
+ from generalresearch.incite.mergers.base import MergeType
from generalresearch.incite.mergers.foundations.enriched_task_adjust import (
EnrichedTaskAdjustMerge,
)
@@ -194,7 +197,7 @@ def enriched_wall_merge(
duration: timedelta,
start: datetime,
) -> EnrichedWallMerge:
- from generalresearch.incite.mergers import MergeType
+ from generalresearch.incite.mergers.base import MergeType
from generalresearch.incite.mergers.foundations.enriched_wall import (
EnrichedWallMerge,
)
@@ -214,7 +217,7 @@ def user_id_product_merge(
offset: str,
start: datetime,
) -> UserIdProductMerge:
- from generalresearch.incite.mergers import MergeType
+ from generalresearch.incite.mergers.base import MergeType
from generalresearch.incite.mergers.foundations.user_id_product import (
UserIdProductMerge,
)
@@ -240,7 +243,7 @@ def merge_collection(
duration: timedelta,
start: datetime,
):
- from generalresearch.incite.mergers import MergeCollection
+ from generalresearch.incite.mergers.base import MergeCollection
return MergeCollection(
merge_type=merge_type,
diff --git a/test_utils/managers/cashout_methods.py b/test_utils/managers/cashout_methods.py
index b201e8c..e69de29 100644
--- a/test_utils/managers/cashout_methods.py
+++ b/test_utils/managers/cashout_methods.py
@@ -1,75 +0,0 @@
-import random
-from uuid import uuid4
-
-from generalresearch.models.thl.wallet import Currency, PayoutType
-from generalresearch.models.thl.wallet.cashout_method import (
- CashoutMethod,
- TangoCashoutMethodData,
-)
-
-
-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
index d2e5d20..391e6bf 100644
--- a/test_utils/managers/conftest.py
+++ b/test_utils/managers/conftest.py
@@ -1,39 +1,42 @@
from __future__ import annotations
-from typing import Callable
+import random
+from collections.abc import Callable
+from datetime import datetime
+from typing import TYPE_CHECKING
+from uuid import uuid4
import pytest
-from generalresearch.managers.gr.business import (
- BusinessAddressManager,
- BusinessBankAccountManager,
- BusinessManager,
+from generalresearch.managers.thl.cashout_method import (
+ CashoutMethodManager,
)
-from generalresearch.managers.gr.team import (
- MembershipManager,
- TeamManager,
+from generalresearch.managers.thl.user_streak import (
+ UserStreakManager,
)
-from generalresearch.managers.spectrum.survey import SpectrumSurveyManager
-from generalresearch.managers.thl.buyer import BuyerManager
-from generalresearch.managers.thl.ipinfo import (
- GeoIpInfoManager,
- IPGeonameManager,
- IPInformationManager,
-)
-from generalresearch.managers.thl.profiling.uqa import UQAManager
-from generalresearch.managers.thl.userhealth import (
- AuditLogManager,
- IPRecordManager,
- UserIpHistoryManager,
-)
-from generalresearch.models import Source
-from generalresearch.models.thl.user import User
-from generalresearch.pg_helper import PostgresConfig
-from generalresearch.redis_helper import RedisConfig
-from generalresearch.sql_helper import SqlHelper
-from test_utils.managers.cashout_methods import (
- EXAMPLE_TANGO_CASHOUT_METHODS,
+from generalresearch.models.definitions import Source
+from generalresearch.models.thl.wallet.cashout_method import (
+ CashoutMethod,
+ TangoCashoutMethodData,
)
+from generalresearch.models.thl.wallet.definitions import Currency, PayoutType
+
+if TYPE_CHECKING:
+ from generalresearch.managers.spectrum.survey import SpectrumSurveyManager
+ from generalresearch.managers.thl.buyer import BuyerManager
+ from generalresearch.managers.thl.ipinfo import (
+ GeoIpInfoManager,
+ IPGeonameManager,
+ )
+ from generalresearch.managers.thl.userhealth import (
+ AuditLogManager,
+ IPRecordManager,
+ UserIpHistoryManager,
+ )
+ from generalresearch.models.thl.user import User
+ from generalresearch.pg_helper import PostgresConfig
+ from generalresearch.redis_helper import RedisConfig
+ from generalresearch.sql_helper import SqlHelper
# === THL ===
@@ -59,16 +62,6 @@ def ip_geoname_manager(thl_web_rw: PostgresConfig) -> IPGeonameManager:
@pytest.fixture(scope="session")
-def ip_information_manager(thl_web_rw: PostgresConfig) -> IPInformationManager:
- assert thl_web_rw.dsn.path
- 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: PostgresConfig, thl_redis_config: RedisConfig
) -> IPRecordManager:
@@ -95,7 +88,7 @@ def user_iphistory_manager(
@pytest.fixture(scope="function")
-def user_iphistory_manager_clear_cache(user_iphistory_manager, user):
+def user_iphistory_manager_clear_cache(user_iphistory_manager, user: 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)
@@ -116,12 +109,9 @@ def geoipinfo_manager(
@pytest.fixture(scope="session")
-def cashout_method_manager(thl_web_rw: PostgresConfig):
+def cashout_method_manager(thl_web_rw: PostgresConfig) -> CashoutMethodManager:
assert thl_web_rw.dsn.path
assert "/unittest-" in thl_web_rw.dsn.path
- from generalresearch.managers.thl.cashout_method import (
- CashoutMethodManager,
- )
return CashoutMethodManager(pg_config=thl_web_rw)
@@ -134,12 +124,9 @@ def event_manager(thl_redis_config: RedisConfig):
@pytest.fixture(scope="session")
-def user_streak_manager(thl_web_rw: PostgresConfig):
+def user_streak_manager(thl_web_rw: PostgresConfig) -> UserStreakManager:
assert thl_web_rw.dsn.path
assert "/unittest-" in thl_web_rw.dsn.path
- from generalresearch.managers.thl.user_streak import (
- UserStreakManager,
- )
return UserStreakManager(pg_config=thl_web_rw)
@@ -171,123 +158,117 @@ def delete_cashoutmethod_db(thl_web_rw: PostgresConfig) -> Callable[..., None]:
@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)
-
- # TODO: convert these ids into instances to use.
- # settings.amt_bonus_cashout_method_id
- # settings.amt_assignment_cashout_method_id
-
- # cashout_method_manager.create(AMT_ASSIGNMENT_CASHOUT_METHOD)
- # cashout_method_manager.create(AMT_BONUS_CASHOUT_METHOD)
- raise NotImplementedError("Need to implement setup_cashoutmethod_db")
-
-
-# === THL: Marketplaces ===
+def setup_cashoutmethod_db(
+ cashout_method_manager: CashoutMethodManager,
+ delete_cashoutmethod_db: Callable[..., None],
+ example_tango_cashout_methods: list[CashoutMethod],
+) -> Callable[..., None]:
+ def _inner():
+ delete_cashoutmethod_db()
-@pytest.fixture(scope="session")
-def spectrum_manager(spectrum_rw: SqlHelper) -> SpectrumSurveyManager:
- from generalresearch.managers.spectrum.survey import (
- SpectrumSurveyManager,
- )
+ for x in example_tango_cashout_methods:
+ cashout_method_manager.create(x)
- return SpectrumSurveyManager(sql_helper=spectrum_rw)
+ return _inner
-# === GR ===
@pytest.fixture(scope="session")
-def business_manager(
- gr_db: PostgresConfig, gr_redis_config: RedisConfig
-) -> BusinessManager:
- from generalresearch.redis_helper import RedisConfig
+def random_ext_id_factory(base: str = "U02") -> Callable[..., str]:
- assert gr_db.dsn.path
- assert "/unittest-" in gr_db.dsn.path
- assert isinstance(gr_redis_config, RedisConfig)
+ def _inner() -> str:
+ suffix = random.randint(0, 99999)
+ return f"{base}{suffix:05d}"
- from generalresearch.managers.gr.business import BusinessManager
-
- return BusinessManager(
- pg_config=gr_db,
- redis_config=gr_redis_config,
- )
+ return _inner
@pytest.fixture(scope="session")
-def business_address_manager(gr_db: PostgresConfig) -> BusinessAddressManager:
- assert gr_db.dsn.path
- assert "/unittest-" in gr_db.dsn.path
+def example_tango_cashout_methods(
+ random_ext_id_factory: Callable[..., str],
+) -> list[CashoutMethod]:
+ return [
+ CashoutMethod(
+ id=uuid4().hex,
+ last_updated=datetime.fromisoformat("2021-06-23T20:45:38.239182Z"),
+ is_live=True,
+ type=PayoutType.TANGO,
+ ext_id='U025035',
+ name="Safeway eGift Card $25",
+ data=TangoCashoutMethodData(
+ value_type="fixed", countries=["US"], utid='U025035'
+ ),
+ 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=datetime.fromisoformat("2021-06-23T20:45:38.239182Z"),
+ is_live=True,
+ type=PayoutType.TANGO,
+ ext_id='U006961',
+ 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,
+ ),
+ ]
- from generalresearch.managers.gr.business import BusinessAddressManager
- return BusinessAddressManager(pg_config=gr_db)
+# === THL: Marketplaces ===
@pytest.fixture(scope="session")
-def business_bank_account_manager(
- gr_db: PostgresConfig,
-) -> BusinessBankAccountManager:
- assert gr_db.dsn.path
- assert "/unittest-" in gr_db.dsn.path
-
- from generalresearch.managers.gr.business import (
- BusinessBankAccountManager,
+def spectrum_survey_manager(spectrum_rw: SqlHelper) -> SpectrumSurveyManager:
+ from generalresearch.managers.spectrum.survey import (
+ SpectrumSurveyManager,
)
- return BusinessBankAccountManager(pg_config=gr_db)
-
-
-@pytest.fixture(scope="session")
-def team_manager(gr_db: PostgresConfig, gr_redis_config: RedisConfig) -> TeamManager:
- assert gr_db.dsn.path
- 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)
+ return SpectrumSurveyManager(sql_helper=spectrum_rw)
@pytest.fixture(scope="session")
-def membership_manager(gr_db: PostgresConfig) -> MembershipManager:
- assert gr_db.dsn.path
- assert "/unittest-" in gr_db.dsn.path
-
- from generalresearch.managers.gr.team import MembershipManager
-
- return MembershipManager(pg_config=gr_db)
+def delete_buyers_surveys(
+ thl_web_rw: PostgresConfig, buyer_manager: BuyerManager
+) -> Callable[..., None]:
+ def _inner():
+ # 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()
-@pytest.fixture(scope="session")
-def delete_buyers_surveys(thl_web_rw: PostgresConfig, buyer_manager: BuyerManager):
- # 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()
+ return _inner
diff --git a/test_utils/managers/contest/conftest.py b/test_utils/managers/contest/conftest.py
index 67935e7..b29cf18 100644
--- a/test_utils/managers/contest/conftest.py
+++ b/test_utils/managers/contest/conftest.py
@@ -1,8 +1,14 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
import pytest
from generalresearch.managers.base import Permission
from generalresearch.managers.thl.contest_manager import ContestManager
-from generalresearch.pg_helper import PostgresConfig
+
+if TYPE_CHECKING:
+ from generalresearch.pg_helper import PostgresConfig
@pytest.fixture(scope="session")
@@ -11,8 +17,6 @@ def contest_manager(thl_web_rw: PostgresConfig) -> ContestManager:
assert thl_web_rw.dsn.path
assert "/unittest-" in thl_web_rw.dsn.path
- from generalresearch.managers.thl.contest_manager import ContestManager
-
return ContestManager(
pg_config=thl_web_rw,
permissions=[
diff --git a/test_utils/managers/gr/conftest.py b/test_utils/managers/gr/conftest.py
index 37da164..09e08f5 100644
--- a/test_utils/managers/gr/conftest.py
+++ b/test_utils/managers/gr/conftest.py
@@ -1,64 +1,76 @@
from __future__ import annotations
-from typing import Callable
+import subprocess
+from collections.abc import Callable, Generator
+from random import randint
+from typing import TYPE_CHECKING
import pytest
-import redis.asyncio as redis_async
+import redis
from pydantic import PostgresDsn
-from redis import Redis
-from generalresearch.config import GRLBaseSettings
-from generalresearch.managers.gr.authentication import GRTokenManager, GRUserManager
from generalresearch.managers.gr.business import (
BusinessAddressManager,
BusinessBankAccountManager,
BusinessManager,
)
+from generalresearch.managers.gr.team import MembershipManager
from generalresearch.pg_helper import PostgresConfig
from generalresearch.redis_helper import RedisConfig
+if TYPE_CHECKING:
+ from generalresearch.config import GRLBaseSettings
+ from generalresearch.managers.gr.authentication import GRTokenManager, GRUserManager
+ from generalresearch.managers.gr.team import TeamManager
+
# === Msc ===
-@pytest.fixture(scope="session")
-def gr_redis(settings: GRLBaseSettings) -> Redis:
- assert "unittest" in str(settings.gr_redis) or "127.0.0.1" in str(settings.gr_redis)
- return Redis.from_url(
- url=str(settings.gr_redis),
- decode_responses=True,
- socket_timeout=settings.redis_timeout,
- socket_connect_timeout=settings.redis_timeout,
- )
-@pytest.fixture
-def gr_redis_async(settings: GRLBaseSettings) -> redis_async.Redis:
- assert "unittest" in str(settings.gr_redis) or "127.0.0.1" in str(settings.gr_redis)
+@pytest.fixture(scope="session")
+def gr_redis_config_db() -> str:
+ # need to update 'databases' in /etc/redis/redis.conf
+ # or this won't work and you'll have no indication why ...
+ return str(randint(99, 1_023))
- return redis_async.Redis.from_url(
- str(settings.gr_redis),
- decode_responses=True,
- socket_timeout=0.20,
- socket_connect_timeout=0.20,
+
+@pytest.fixture(scope="session")
+def gr_redis_config(
+ settings: GRLBaseSettings, gr_redis_config_db: str
+) -> Generator[RedisConfig]:
+ assert "unittest" in str(settings.testing_redis) or "127.0.0.1" in str(
+ settings.testing_redis
)
+ uri = f"redis://{settings.testing_redis}/{gr_redis_config_db}"
-@pytest.fixture(scope="session")
-def gr_redis_config(settings: GRLBaseSettings) -> RedisConfig:
- assert "unittest" in str(settings.gr_redis) or "127.0.0.1" in str(settings.gr_redis)
+ res = subprocess.run(
+ ["redis-cli", "-u", uri, "SET", "jenkins_lock", "1", "NX", "EX", "3600"],
+ check=True,
+ text=True,
+ capture_output=True,
+ )
+
+ if res.stdout.strip() != "OK":
+ raise ValueError("Redis already locked... aborting.")
- return RedisConfig(
- dsn=settings.gr_redis,
+ yield RedisConfig(
+ dsn=uri,
decode_responses=True,
socket_timeout=settings.redis_timeout,
socket_connect_timeout=settings.redis_timeout,
)
+ r = redis.from_url(uri)
+ r.flushdb()
+
@pytest.fixture(scope="session")
def gr_db(django_db_factory: Callable[..., PostgresDsn]) -> PostgresConfig:
+ _dsn = django_db_factory("gr.common")
return PostgresConfig(
- dsn=django_db_factory("gr_carer"),
+ dsn=_dsn,
connect_timeout=1,
statement_timeout=5,
)
@@ -80,7 +92,17 @@ def gr_user_manager(
@pytest.fixture(scope="session")
-def gr_team_manager(gr_db: PostgresConfig) -> GRTokenManager:
+def gr_team_manager(gr_db: PostgresConfig, gr_redis_config: RedisConfig) -> TeamManager:
+ assert gr_db.dsn.path
+ 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_token_manager(gr_db: PostgresConfig) -> GRTokenManager:
assert gr_db.dsn.path
assert "/unittest-" in gr_db.dsn.path
@@ -108,3 +130,10 @@ def gr_business_address_manager(
gr_db: PostgresConfig,
) -> BusinessAddressManager:
return BusinessAddressManager(pg_config=gr_db)
+
+
+@pytest.fixture(scope="session")
+def gr_membership_manager(
+ gr_db: PostgresConfig,
+) -> MembershipManager:
+ return MembershipManager(pg_config=gr_db)
diff --git a/test_utils/managers/ledger/conftest.py b/test_utils/managers/ledger/conftest.py
index ce8348e..c60ee1b 100644
--- a/test_utils/managers/ledger/conftest.py
+++ b/test_utils/managers/ledger/conftest.py
@@ -1,18 +1,24 @@
from __future__ import annotations
+from typing import TYPE_CHECKING
+
import pytest
from generalresearch.managers.base import Permission
from generalresearch.managers.thl.ledger_manager.ledger import (
- LedgerAccountManager,
LedgerManager,
- LedgerTransactionManager,
)
from generalresearch.managers.thl.ledger_manager.thl_ledger import (
ThlLedgerManager,
)
-from generalresearch.pg_helper import PostgresConfig
-from generalresearch.redis_helper import RedisConfig
+
+if TYPE_CHECKING:
+ from generalresearch.managers.thl.ledger_manager.ledger import (
+ LedgerAccountManager,
+ LedgerTransactionManager,
+ )
+ from generalresearch.pg_helper import PostgresConfig
+ from generalresearch.redis_helper import RedisConfig
# --- Ledger ---
diff --git a/test_utils/managers/network/conftest.py b/test_utils/managers/network/conftest.py
deleted file mode 100644
index e69de29..0000000
--- a/test_utils/managers/network/conftest.py
+++ /dev/null
diff --git a/test_utils/managers/thl/conftest.py b/test_utils/managers/thl/conftest.py
index 5b70961..355a39d 100644
--- a/test_utils/managers/thl/conftest.py
+++ b/test_utils/managers/thl/conftest.py
@@ -1,44 +1,108 @@
from __future__ import annotations
-from typing import Callable
+import subprocess
+from collections.abc import Callable, Generator
+from random import randint
+from typing import TYPE_CHECKING
import pytest
+import redis
from pydantic import PostgresDsn
-from generalresearch.config import GRLBaseSettings
from generalresearch.managers.base import Permission
-from generalresearch.managers.thl.buyer import BuyerManager
-from generalresearch.managers.thl.category import CategoryManager
-from generalresearch.managers.thl.payout import (
- BrokerageProductPayoutEventManager,
- BusinessPayoutEventManager,
- PayoutEventManager,
- UserPayoutEventManager,
+from generalresearch.managers.thl.user_manager.mysql_user_manager import (
+ MysqlUserManager,
)
-from generalresearch.managers.thl.product import ProductManager
-from generalresearch.managers.thl.session import SessionManager
-from generalresearch.managers.thl.task_adjustment import (
- TaskAdjustmentManager,
-)
-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.wall import (
- WallCacheManager,
- WallManager,
+from generalresearch.managers.thl.user_manager.redis_user_manager import (
+ RedisUserManager,
)
from generalresearch.pg_helper import PostgresConfig
from generalresearch.redis_helper import RedisConfig
+if TYPE_CHECKING:
+ from generalresearch.config import GRLBaseSettings
+ from generalresearch.managers.thl.buyer import BuyerManager
+ from generalresearch.managers.thl.category import CategoryManager
+ from generalresearch.managers.thl.ipinfo import (
+ IPGeonameManager,
+ IPInformationManager,
+ )
+ from generalresearch.managers.thl.payout import (
+ BrokerageProductPayoutEventManager,
+ BusinessPayoutEventManager,
+ PayoutEventManager,
+ UserPayoutEventManager,
+ )
+ from generalresearch.managers.thl.product import ProductManager
+ from generalresearch.managers.thl.session import SessionManager
+ from generalresearch.managers.thl.task_adjustment import (
+ TaskAdjustmentManager,
+ )
+ 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,
+ )
+ from generalresearch.managers.thl.wall import (
+ WallCacheManager,
+ WallManager,
+ )
+
+# === Msc ===
+
+
+@pytest.fixture(scope="session")
+def thl_redis_config_db() -> str:
+ return str(randint(99, 1_023))
+
+
+@pytest.fixture(scope="session")
+def thl_redis_config(
+ settings: GRLBaseSettings, thl_redis_config_db: str
+) -> Generator[RedisConfig]:
+ assert "unittest" in str(settings.testing_redis) or "127.0.0.1" in str(
+ settings.testing_redis
+ )
+
+ uri = f"redis://{settings.testing_redis}/{thl_redis_config_db}"
+
+ res = subprocess.run(
+ ["redis-cli", "-u", uri, "SET", "jenkins_lock", "1", "NX", "EX", "3600"],
+ check=True,
+ text=True,
+ capture_output=True,
+ )
+
+ if res.stdout.strip() != "OK":
+ raise ValueError("Redis already locked... aborting.")
+
+ yield RedisConfig(
+ dsn=uri,
+ decode_responses=True,
+ socket_timeout=settings.redis_timeout,
+ socket_connect_timeout=settings.redis_timeout,
+ )
+
+ r = redis.from_url(uri)
+ r.flushdb()
+
+
+@pytest.fixture(scope="session")
+def thl_redis_client(thl_redis_config):
+ return thl_redis_config.create_redis_client()
+
@pytest.fixture(scope="session")
def thl_web_rr(django_db_factory: Callable[..., PostgresDsn]) -> PostgresConfig:
+ _dsn = django_db_factory("generalresearch.thl_django")
return PostgresConfig(
- dsn=django_db_factory("generalresearch.thl_django"),
+ dsn=_dsn,
connect_timeout=1,
statement_timeout=5,
)
@@ -49,14 +113,7 @@ def thl_web_rw(thl_web_rr: PostgresConfig) -> PostgresConfig:
return thl_web_rr
-@pytest.fixture(scope="session")
-def thl_redis_config(settings: GRLBaseSettings) -> RedisConfig:
- return RedisConfig(
- dsn=settings.thl_redis,
- decode_responses=True,
- socket_timeout=settings.redis_timeout,
- socket_connect_timeout=settings.redis_timeout,
- )
+# === Managers ===
@pytest.fixture(scope="session")
@@ -109,6 +166,13 @@ def brokerage_product_payout_event_manager(
)
+@pytest.fixture()
+def audit_log_manager(thl_web_rw: PostgresConfig) -> AuditLogManager:
+ from generalresearch.managers.thl.userhealth import AuditLogManager
+
+ return AuditLogManager(pg_config=thl_web_rw)
+
+
@pytest.fixture(scope="session")
def business_payout_event_manager(
thl_web_rw: PostgresConfig, thl_redis_config: RedisConfig
@@ -140,7 +204,10 @@ def product_manager(thl_web_rw: PostgresConfig) -> ProductManager:
@pytest.fixture(scope="session")
def user_manager(
- settings: GRLBaseSettings, thl_web_rw: PostgresConfig, thl_web_rr: PostgresConfig
+ settings: GRLBaseSettings,
+ thl_web_rw: PostgresConfig,
+ thl_web_rr: PostgresConfig,
+ thl_redis_config: RedisConfig,
) -> UserManager:
assert thl_web_rw.dsn
assert thl_web_rw.dsn.path
@@ -149,16 +216,32 @@ def user_manager(
assert "/unittest-" in thl_web_rw.dsn.path
assert "/unittest-" in thl_web_rr.dsn.path
+ from generalresearch.managers.thl.user_manager.rate_limit import UserManagerLimiter
from generalresearch.managers.thl.user_manager.user_manager import (
UserManager,
)
- return UserManager(
+ um = UserManager(
pg_config=thl_web_rw,
pg_config_rr=thl_web_rr,
redis=settings.redis,
)
+ # rc = thl_redis_config.create_redis_client()
+ um.user_manager_limiter = UserManagerLimiter(redis=thl_redis_config.dsn)
+
+ return um
+
+
+@pytest.fixture(scope="session")
+def mysql_user_manager(thl_web_rw: PostgresConfig) -> MysqlUserManager:
+ return MysqlUserManager(pg_config=thl_web_rw, is_read_replica=False)
+
+
+@pytest.fixture(scope="session")
+def redis_user_manager(thl_redis_config: RedisConfig) -> RedisUserManager:
+ return RedisUserManager(redis_dsn=thl_redis_config.dsn)
+
@pytest.fixture(scope="session")
def user_metadata_manager(thl_web_rw: PostgresConfig) -> UserMetadataManager:
@@ -256,3 +339,41 @@ def surveypenalty_manager(thl_redis_config: RedisConfig):
from generalresearch.managers.thl.survey_penalty import SurveyPenaltyManager
return SurveyPenaltyManager(redis_config=thl_redis_config)
+
+
+# --- IP Geolocation ---
+
+
+@pytest.fixture
+def ip_geoname_manager(thl_web_rw: PostgresConfig) -> IPGeonameManager:
+ from generalresearch.managers.thl.ipinfo import IPGeonameManager
+
+ return IPGeonameManager(pg_config=thl_web_rw)
+
+
+# --- IP Information ---
+
+
+@pytest.fixture(scope="session")
+def ip_information_manager(thl_web_rw: PostgresConfig) -> IPInformationManager:
+ assert thl_web_rw.dsn.path
+ assert "/unittest-" in thl_web_rw.dsn.path
+
+ from generalresearch.managers.thl.ipinfo import IPInformationManager
+
+ return IPInformationManager(pg_config=thl_web_rw)
+
+
+# --- IP Record ---
+
+
+@pytest.fixture(scope="session")
+def ip_record_manager(
+ thl_web_rw: PostgresConfig, thl_redis_config: RedisConfig
+) -> IPRecordManager:
+ assert thl_web_rw.dsn.path
+ 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)
diff --git a/test_utils/managers/upk/conftest.py b/test_utils/managers/upk/conftest.py
index d8f956c..23af1b3 100644
--- a/test_utils/managers/upk/conftest.py
+++ b/test_utils/managers/upk/conftest.py
@@ -1,4 +1,5 @@
-from typing import Callable, Generator
+from collections.abc import Callable, Generator
+from typing import TYPE_CHECKING
import pytest
diff --git a/test_utils/models/conftest.py b/test_utils/models/conftest.py
index 9925a9e..ffce272 100644
--- a/test_utils/models/conftest.py
+++ b/test_utils/models/conftest.py
@@ -1,331 +1,27 @@
from __future__ import annotations
-from datetime import datetime, timedelta, timezone
-from decimal import Decimal
-from random import choice as randchoice
-from random import randint
-from typing import TYPE_CHECKING, Callable
+from collections.abc import Callable
+from typing import TYPE_CHECKING
from uuid import uuid4
import pytest
-from fastapi import Request
-from pydantic import AwareDatetime, PositiveInt
+from pytest import FixtureRequest as Request
-from generalresearch.models import Source
-from generalresearch.models.thl.definitions import (
- WALL_ALLOWED_STATUS_STATUS_CODE,
- Status,
-)
+from generalresearch.models.definitions import Source
from generalresearch.models.thl.survey.model import Buyer, Survey
-from generalresearch.pg_helper import PostgresConfig
-from generalresearch.redis_helper import RedisConfig
if TYPE_CHECKING:
- from generalresearch.currency import USDCent
- from generalresearch.managers.gr.authentication import GRTokenManager, GRUserManager
- from generalresearch.managers.gr.business import (
- BusinessAddressManager,
- BusinessBankAccountManager,
- BusinessManager,
- )
- from generalresearch.managers.gr.team import MembershipManager, TeamManager
from generalresearch.managers.thl.buyer import BuyerManager
- from generalresearch.managers.thl.ipinfo import (
- IPGeonameManager,
- IPInformationManager,
- )
- from generalresearch.managers.thl.ledger_manager.thl_ledger import ThlLedgerManager
- from generalresearch.managers.thl.payout import (
- BusinessPayoutEventManager,
- )
- from generalresearch.managers.thl.product import ProductManager
- from generalresearch.managers.thl.session import SessionManager
from generalresearch.managers.thl.survey import SurveyManager
- from generalresearch.managers.thl.user_manager.user_manager import UserManager
- from generalresearch.managers.thl.userhealth import AuditLogManager, IPRecordManager
- from generalresearch.managers.thl.wall import WallManager
- from generalresearch.models.gr.authentication import GRToken, GRUser
- from generalresearch.models.gr.business import (
- Business,
- BusinessAddress,
- BusinessBankAccount,
- )
- from generalresearch.models.gr.team import Membership, Team
- from generalresearch.models.thl.ipinfo import IPGeoname, IPInformation
- from generalresearch.models.thl.payout import (
- BrokerageProductPayoutEvent,
- )
from generalresearch.models.thl.product import (
PayoutConfig,
Product,
)
- from generalresearch.models.thl.session import Session, Wall
- from generalresearch.models.thl.user import User
- from generalresearch.models.thl.user_iphistory import IPRecord
- from generalresearch.models.thl.userhealth import AuditLog, AuditLogLevel
# === THL ===
-@pytest.fixture
-def user(
- request,
- product_manager: ProductManager,
- user_manager: UserManager,
- thl_web_rr: PostgresConfig,
-) -> User:
- product = getattr(request, "product", None)
-
- if product is None:
- product = product_manager.create_dummy()
-
- u = user_manager.create_dummy(product_id=product.id)
- u.prefetch_product(pg_config=thl_web_rr)
-
- return u
-
-
-@pytest.fixture
-def user_with_wallet(
- user_factory: Callable[..., User],
- product_user_wallet_yes: Product,
-) -> User:
- # A user on a product with user wallet enabled, but they have no money
- return user_factory(product=product_user_wallet_yes)
-
-
-@pytest.fixture
-def user_with_wallet_amt(
- user_factory: Callable[..., User], product_amt_true: Product
-) -> User:
- # A user on a product with user wallet enabled, on AMT, but they have no money
- return user_factory(product=product_amt_true)
-
-
-@pytest.fixture(scope="function")
-def user_factory(
- user_manager: UserManager, thl_web_rr: PostgresConfig
-) -> Callable[..., User]:
-
- def _inner(product: Product, created: datetime | None = None) -> User:
- u = user_manager.create_dummy(product=product, created=created)
- u.prefetch_product(pg_config=thl_web_rr)
-
- return u
-
- return _inner
-
-
-@pytest.fixture
-def wall_factory(wall_manager: WallManager) -> Callable[..., Wall]:
-
- def _inner(
- session: Session, wall_status: Status, req_cpi: Decimal | None = None
- ) -> Wall:
-
- assert session.started <= datetime.now(
- tz=timezone.utc
- ), "Session can't start in the future"
-
- if session.wall_events:
- # Subsequent Wall events
- wall = session.wall_events[-1]
- assert not wall.finished, "Can't add new Walls until prior finishes"
- # wall_started = last_wall.started + timedelta(milliseconds=1)
- else:
- # First Wall Event in a session
- wall_started = session.started + timedelta(milliseconds=1)
-
- wall = wall_manager.create_dummy(
- session_id=session.id,
- user_id=session.user_id,
- started=wall_started,
- req_cpi=req_cpi,
- )
- session.append_wall_event(w=wall)
-
- options = list(WALL_ALLOWED_STATUS_STATUS_CODE.get(wall_status, {}))
- wall.finish(
- finished=wall.started + timedelta(seconds=randint(a=60 * 2, b=60 * 10)),
- status=wall_status,
- status_code_1=randchoice(options),
- )
-
- return wall
-
- return _inner
-
-
-@pytest.fixture
-def wall(session: Session, user: User, wall_manager: WallManager) -> Wall | None:
- from generalresearch.models.thl.task_status import StatusCode1
-
- wall = wall_manager.create_dummy(session_id=session.id, user_id=user.user_id)
- # thl_session.append_wall_event(wall)
- wall.finish(
- finished=wall.started + timedelta(seconds=randint(a=60 * 2, b=60 * 10)),
- status=Status.COMPLETE,
- status_code_1=StatusCode1.COMPLETE,
- )
- return wall
-
-
-@pytest.fixture
-def session_factory(
- session_manager: SessionManager,
- wall_manager: WallManager,
- utc_hour_ago: datetime,
-) -> Callable[..., Session]:
- from generalresearch.models.thl.session import Source
-
- def _inner(
- user: User,
- # Wall details
- wall_count: int = 5,
- wall_req_cpi: Decimal = Decimal(".50"),
- wall_req_cpis: list[Decimal] | None = None,
- wall_statuses: list[Status] | None = None,
- wall_source: Source = Source.TESTING,
- # Session details
- final_status: Status = Status.COMPLETE,
- started: datetime = utc_hour_ago,
- ) -> Session:
- if wall_req_cpis:
- assert len(wall_req_cpis) == wall_count
- if wall_statuses:
- assert len(wall_statuses) == wall_count
-
- s = session_manager.create_dummy(started=started, user=user, country_iso="us")
- for idx in range(wall_count):
- if idx == 0:
- # First Wall Event in a session
- wall_started = s.started + timedelta(milliseconds=1)
- else:
- # Subsequent Wall events
- last_wall = s.wall_events[-1]
- assert last_wall.finished, "Can't add new Walls until prior finishes"
- wall_started = last_wall.started + timedelta(milliseconds=1)
-
- w = wall_manager.create_dummy(
- session_id=s.id,
- source=wall_source,
- user_id=s.user_id,
- started=wall_started,
- req_cpi=wall_req_cpis[idx] if wall_req_cpis else wall_req_cpi,
- )
- s.append_wall_event(w=w)
-
- # If it's the last wall in the session, respect the final_status
- # value for the Session
- if wall_statuses:
- _final_status = wall_statuses[idx]
- else:
- _final_status = final_status if idx == wall_count - 1 else Status.FAIL
-
- options = list(WALL_ALLOWED_STATUS_STATUS_CODE.get(_final_status, {}))
- wall_manager.finish(
- wall=w,
- status=_final_status,
- status_code_1=randchoice(options),
- finished=w.started + timedelta(seconds=randint(a=60 * 2, b=60 * 10)),
- )
-
- return s
-
- return _inner
-
-
-@pytest.fixture(scope="function")
-def finished_session_factory(
- session_factory: Callable[..., Session],
- session_manager: SessionManager,
- utc_hour_ago: datetime,
-) -> Callable[..., Session]:
- from generalresearch.models.thl.session import Source
-
- def _inner(
- user: User,
- # Wall details
- wall_count: int = 5,
- wall_req_cpi: Decimal = Decimal(".50"),
- wall_req_cpis: list[Decimal] | None = None,
- wall_statuses: list[Status] | None = None,
- wall_source: Source = Source.TESTING,
- # Session details
- final_status: Status = Status.COMPLETE,
- started: datetime = utc_hour_ago,
- ) -> Session:
- s: Session = session_factory(
- user=user,
- wall_count=wall_count,
- wall_req_cpi=wall_req_cpi,
- wall_req_cpis=wall_req_cpis,
- wall_statuses=wall_statuses,
- wall_source=wall_source,
- final_status=final_status,
- started=started,
- )
- status, status_code_1 = s.determine_session_status()
- _, _, bp_pay, user_pay = s.determine_payments()
- session_manager.finish_with_status(
- s,
- finished=s.wall_events[-1].finished,
- payout=bp_pay,
- user_payout=user_pay,
- status=status,
- status_code_1=status_code_1,
- )
- return s
-
- return _inner
-
-
-@pytest.fixture
-def session(
- user: User, session_manager: SessionManager, wall_manager: WallManager
-) -> Session:
-
- session: Session = session_manager.create_dummy(user=user, country_iso="us")
- wall: Wall = wall_manager.create_dummy(
- session_id=session.id,
- user_id=session.user_id,
- started=session.started,
- )
- session.append_wall_event(w=wall)
-
- return session
-
-
-@pytest.fixture
-def product(request: Request, product_manager: ProductManager) -> Product:
-
- team = getattr(request, "team", None)
- business = getattr(request, "business", None)
-
- return product_manager.create_dummy(
- team_id=team.uuid if team else None,
- business_id=business.uuid if business else None,
- )
-
-
-@pytest.fixture
-def product_factory(product_manager: ProductManager) -> Callable[..., Product]:
-
- def _inner(
- team: Team | None = None,
- business: Business | None = None,
- commission_pct: Decimal = Decimal("0.05"),
- ) -> Product:
- return product_manager.create_dummy(
- team_id=team.uuid if team else None,
- business_id=business.uuid if business else None,
- commission_pct=commission_pct,
- )
-
- return _inner
-
-
-@pytest.fixture
+@pytest.fixture()
def payout_config(request: Request) -> PayoutConfig:
from generalresearch.models.thl.product import (
PayoutConfig,
@@ -348,176 +44,38 @@ def payout_config(request: Request) -> PayoutConfig:
@pytest.fixture
def product_user_wallet_yes(
- payout_config: PayoutConfig, product_manager: ProductManager
+ product_factory: Callable[..., Product],
+ payout_config: PayoutConfig,
) -> Product:
from generalresearch.models.thl.product import UserWalletConfig
- return product_manager.create_dummy(
+ return product_factory(
payout_config=payout_config, user_wallet_config=UserWalletConfig(enabled=True)
)
@pytest.fixture
-def product_user_wallet_no(product_manager: ProductManager) -> Product:
+def product_user_wallet_no(
+ product_factory: Callable[..., Product],
+) -> Product:
from generalresearch.models.thl.product import UserWalletConfig
- return product_manager.create_dummy(
- user_wallet_config=UserWalletConfig(enabled=False)
- )
+ return product_factory(user_wallet_config=UserWalletConfig(enabled=False))
@pytest.fixture
def product_amt_true(
- product_manager: ProductManager, payout_config: PayoutConfig
+ product_factory: Callable[..., Product],
+ payout_config: PayoutConfig,
) -> Product:
from generalresearch.models.thl.product import UserWalletConfig
- return product_manager.create_dummy(
+ return product_factory(
user_wallet_config=UserWalletConfig(amt=True, enabled=True),
payout_config=payout_config,
)
-@pytest.fixture
-def bp_payout_factory(
- thl_lm: ThlLedgerManager,
- product_manager: ProductManager,
- business_payout_event_manager: BusinessPayoutEventManager,
-) -> Callable[..., BrokerageProductPayoutEvent]:
-
- def _inner(
- product: Product | None = None,
- amount: USDCent | None = None,
- ext_ref_id: str | None = None,
- created: AwareDatetime | None = None,
- skip_wallet_balance_check: bool = False,
- skip_one_per_day_check: bool = False,
- ) -> BrokerageProductPayoutEvent:
- from generalresearch.currency import USDCent
-
- product = product or product_manager.create_dummy()
- amount = amount or USDCent(randint(1, 99_99))
-
- return business_payout_event_manager.create_bp_payout_event(
- thl_ledger_manager=thl_lm,
- product=product,
- amount=amount,
- ext_ref_id=ext_ref_id,
- created=created,
- skip_wallet_balance_check=skip_wallet_balance_check,
- skip_one_per_day_check=skip_one_per_day_check,
- )
-
- return _inner
-
-
-# === GR ===
-
-
-@pytest.fixture
-def business(request, business_manager: BusinessManager) -> Business:
- return business_manager.create_dummy()
-
-
-@pytest.fixture
-def business_address(
- request, business: Business, business_address_manager: BusinessAddressManager
-) -> BusinessAddress:
- return business_address_manager.create_dummy(business_id=business.id)
-
-
-@pytest.fixture
-def business_bank_account(
- request,
- business: Business,
- business_bank_account_manager: BusinessBankAccountManager,
-) -> BusinessBankAccount:
- return business_bank_account_manager.create_dummy(business_id=business.id)
-
-
-@pytest.fixture
-def team(request, team_manager: TeamManager) -> Team:
- return team_manager.create_dummy()
-
-
-@pytest.fixture
-def audit_log(audit_log_manager: AuditLogManager, user: User) -> AuditLog:
-
- return audit_log_manager.create_dummy(user_id=user.user_id)
-
-
-@pytest.fixture
-def audit_log_factory(
- audit_log_manager: AuditLogManager,
-) -> Callable[..., AuditLog]:
-
- def _inner(
- user_id: PositiveInt,
- level: AuditLogLevel | None = None,
- event_type: str | None = None,
- event_msg: str | None = None,
- event_value: float | None = None,
- ) -> AuditLog:
- return audit_log_manager.create_dummy(
- user_id=user_id,
- level=level,
- event_type=event_type,
- event_msg=event_msg,
- event_value=event_value,
- )
-
- return _inner
-
-
-@pytest.fixture
-def ip_geoname(ip_geoname_manager: IPGeonameManager) -> IPGeoname:
- return ip_geoname_manager.create_dummy()
-
-
-@pytest.fixture
-def ip_information(
- ip_information_manager: IPInformationManager, ip_geoname: IPGeoname
-) -> IPInformation:
- return ip_information_manager.create_dummy(
- geoname_id=ip_geoname.geoname_id, country_iso=ip_geoname.country_iso
- )
-
-
-@pytest.fixture
-def ip_information_factory(
- ip_information_manager: IPInformationManager,
-) -> Callable[..., IPInformation]:
-
- def _inner(ip: str, geoname: IPGeoname, **kwargs) -> IPInformation:
- return ip_information_manager.create_dummy(
- ip=ip,
- geoname_id=geoname.geoname_id,
- country_iso=geoname.country_iso,
- **kwargs,
- )
-
- return _inner
-
-
-@pytest.fixture
-def ip_record(
- ip_record_manager: IPRecordManager, ip_geoname: IPGeoname, user: User
-) -> IPRecord:
-
- return ip_record_manager.create_dummy(user_id=user.user_id)
-
-
-@pytest.fixture
-def ip_record_factory(
- ip_record_manager: IPRecordManager, user: User
-) -> Callable[..., IPRecord]:
-
- def _inner(user_id: PositiveInt, ip: str | None = None) -> IPRecord:
- return ip_record_manager.create_dummy(user_id=user_id, ip=ip)
-
- return _inner
-
-
@pytest.fixture(scope="session")
def buyer(buyer_manager: BuyerManager) -> Buyer:
buyer_code = uuid4().hex
diff --git a/test_utils/models/contest/conftest.py b/test_utils/models/contest/conftest.py
index e750076..18a8e5f 100644
--- a/test_utils/models/contest/conftest.py
+++ b/test_utils/models/contest/conftest.py
@@ -1,50 +1,73 @@
from __future__ import annotations
-from datetime import datetime, timezone
+from collections.abc import Callable
+from datetime import UTC, datetime
from decimal import Decimal
-from typing import Callable
+from typing import TYPE_CHECKING
from uuid import uuid4
import pytest
-from fastapi import Request
+from pytest import FixtureRequest as Request
from generalresearch.currency import USDCent
-from generalresearch.managers.thl.contest_manager import ContestManager
-from generalresearch.managers.thl.ledger_manager.thl_ledger import ThlLedgerManager
-from generalresearch.models.thl.contest.contest import Contest
-from generalresearch.models.thl.contest.leaderboard import (
- LeaderboardContestCreate,
+from generalresearch.models.thl.contest import (
+ ContestEndCondition,
+ ContestPrize,
)
-from generalresearch.models.thl.contest.milestone import (
- MilestoneContestCreate,
+from generalresearch.models.thl.contest.definitions import (
+ ContestPrizeKind,
+ ContestType,
)
from generalresearch.models.thl.contest.raffle import (
+ ContestEntryType,
RaffleContestCreate,
)
-from generalresearch.models.thl.product import Product
-from generalresearch.models.thl.user import User
+
+if TYPE_CHECKING:
+ from generalresearch.managers.thl.contest_manager import ContestManager
+ from generalresearch.managers.thl.ledger_manager.thl_ledger import ThlLedgerManager
+ from generalresearch.models.thl.contest.contest import Contest
+ from generalresearch.models.thl.contest.leaderboard import (
+ LeaderboardContestCreate,
+ )
+ from generalresearch.models.thl.contest.milestone import (
+ MilestoneContestCreate,
+ )
+ from generalresearch.models.thl.contest.raffle import (
+ RaffleContest,
+ )
+ from generalresearch.models.thl.product import Product
+ from generalresearch.models.thl.user import User
# === Miscellaneous ===
# === Managers ===
+# --- Factories ---
+
+
+@pytest.fixture(scope="function")
+def raffle_contest_factory(
+ product_user_wallet_yes: Product,
+ raffle_contest_create: RaffleContestCreate,
+ contest_manager: ContestManager,
+) -> Callable[..., RaffleContest]:
+
+ def _inner(**kwargs):
+ raffle_contest_create.update(**kwargs)
+ return contest_manager.create(
+ product_id=product_user_wallet_yes.uuid,
+ contest_create=raffle_contest_create,
+ )
+
+ return _inner
+
+
# === Models ===
@pytest.fixture
def raffle_contest_create() -> RaffleContestCreate:
- from generalresearch.models.thl.contest import (
- ContestEndCondition,
- ContestPrize,
- )
- from generalresearch.models.thl.contest.definitions import (
- ContestPrizeKind,
- ContestType,
- )
- from generalresearch.models.thl.contest.raffle import (
- ContestEntryType,
- RaffleContestCreate,
- )
# This is what we'll get from the fastapi endpoint
return RaffleContestCreate(
@@ -84,23 +107,6 @@ def raffle_contest(
)
-@pytest.fixture(scope="function")
-def raffle_contest_factory(
- product_user_wallet_yes: Product,
- raffle_contest_create: RaffleContestCreate,
- contest_manager: ContestManager,
-) -> Callable[..., Contest]:
-
- def _inner(**kwargs):
- raffle_contest_create.update(**kwargs)
- return contest_manager.create(
- product_id=product_user_wallet_yes.uuid,
- contest_create=raffle_contest_create,
- )
-
- return _inner
-
-
@pytest.fixture
def milestone_contest_create() -> MilestoneContestCreate:
from generalresearch.models.thl.contest import (
@@ -135,7 +141,7 @@ def milestone_contest_create() -> MilestoneContestCreate:
),
],
end_condition=MilestoneContestEndCondition(
- ends_at=datetime(year=2030, month=1, day=1, tzinfo=timezone.utc),
+ ends_at=datetime(year=2030, month=1, day=1, tzinfo=UTC),
max_winners=5,
),
entry_trigger=ContestEntryTrigger.TASK_COMPLETE,
@@ -269,24 +275,26 @@ def user_with_money(
request: Request,
user_factory: Callable[..., User],
product_user_wallet_yes: Product,
- thl_lm: ThlLedgerManager,
+ thl_ledger_manager: ThlLedgerManager,
) -> User:
params = getattr(request, "param", {}) 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)
+ wallet = thl_ledger_manager.get_account_or_create_user_wallet(user)
+ balance = thl_ledger_manager.get_account_balance(wallet)
todo = min_balance - balance
if todo > 0:
# # Put money in user's wallet
- thl_lm.create_tx_user_bonus(
+ thl_ledger_manager.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)}")
+ print(
+ f"wallet balance: {thl_ledger_manager.get_user_wallet_balance(user=user)}"
+ )
return user
diff --git a/test_utils/models/gr/conftest.py b/test_utils/models/gr/conftest.py
index df97306..f5dcaa1 100644
--- a/test_utils/models/gr/conftest.py
+++ b/test_utils/models/gr/conftest.py
@@ -1,31 +1,34 @@
from __future__ import annotations
-from typing import Callable
+from collections.abc import Callable
+from random import randint
+from typing import TYPE_CHECKING
from uuid import uuid4
import pytest
from pydantic import PositiveInt
from pydantic_extra_types.phone_numbers import PhoneNumber
-from generalresearch.managers.gr.authentication import GRTokenManager, GRUserManager
-from generalresearch.managers.gr.business import (
- BusinessAddressManager,
- BusinessBankAccountManager,
- BusinessManager,
-)
-from generalresearch.managers.gr.team import MembershipManager, TeamManager
from generalresearch.models.custom_types import UUIDStr
-from generalresearch.models.gr.authentication import GRToken, GRUser
-from generalresearch.models.gr.business import (
- Business,
- BusinessAddress,
- BusinessBankAccount,
- BusinessType,
- TransferMethod,
-)
-from generalresearch.models.gr.team import Membership, Team
-from generalresearch.pg_helper import PostgresConfig
-from generalresearch.redis_helper import RedisConfig
+from generalresearch.models.gr.definitions import TransferMethod
+
+if TYPE_CHECKING:
+ from generalresearch.managers.gr.authentication import GRTokenManager, GRUserManager
+ from generalresearch.managers.gr.business import (
+ BusinessAddressManager,
+ BusinessBankAccountManager,
+ BusinessManager,
+ )
+ from generalresearch.managers.gr.team import MembershipManager, TeamManager
+ from generalresearch.models.gr.authentication import GRToken, GRUser
+ from generalresearch.models.gr.business import (
+ Business,
+ BusinessAddress,
+ BusinessBankAccount,
+ )
+ from generalresearch.models.gr.team import Membership, Team
+ from generalresearch.pg_helper import PostgresConfig
+ from generalresearch.redis_helper import RedisConfig
# --- Static ---
@@ -33,68 +36,57 @@ from generalresearch.redis_helper import RedisConfig
# --- Factory / Database ---
-@pytest.fixture
-def gr_user_factory(gr_user_manager: GRUserManager) -> Callable[..., GRUser]:
-
- def _inner(
- sub: str | None = None,
- is_superuser: bool = False,
- ) -> GRUser:
- sub = sub or f"{uuid4().hex}-{uuid4().hex}"
-
- return gr_user_manager.create(
- sub=sub,
- is_superuser=is_superuser,
- )
-
- return _inner
-
-
-@pytest.fixture
-def gr_user_cache(
- gr_user: GRUser,
- gr_db: PostgresConfig,
- thl_web_rr: PostgresConfig,
- gr_redis_config: RedisConfig,
-) -> GRUser:
- gr_user.set_cache(
- pg_config=gr_db, thl_web_rr=thl_web_rr, redis_config=gr_redis_config
- )
- return gr_user
+# --- Business Bank Account ---
@pytest.fixture
def gr_business_bank_account_factory(
- gr_bbam: BusinessBankAccountManager,
+ gr_business_bank_account_manager: BusinessBankAccountManager,
) -> Callable[..., BusinessBankAccount]:
def _inner(
business_id: PositiveInt,
+ save: bool = True,
uuid: UUIDStr | None = None,
transfer_method: TransferMethod | None = None,
account_number: str | None = None,
routing_number: str | None = None,
iban: str | None = None,
swift: str | None = None,
- ):
- from generalresearch.models.gr.business import TransferMethod
-
- return gr_bbam.create(
- business_id=business_id,
- uuid=uuid or uuid4().hex,
- transfer_method=transfer_method or TransferMethod.ACH,
- account_number=account_number or uuid4().hex[:6],
- routing_number=routing_number or uuid4().hex[:6],
- iban=iban or uuid4().hex[:6],
- swift=swift or uuid4().hex[:6],
- )
+ **kwargs,
+ ) -> BusinessBankAccount:
+
+ if save:
+ return gr_business_bank_account_manager.create(
+ business_id=business_id,
+ uuid=uuid or uuid4().hex,
+ transfer_method=transfer_method or TransferMethod.ACH,
+ account_number=account_number or uuid4().hex[:6],
+ routing_number=routing_number or uuid4().hex[:6],
+ iban=iban or uuid4().hex[:6],
+ swift=swift or uuid4().hex[:6],
+ **kwargs,
+ )
+ else:
+ raise ValueError("Unsaved BusinessBankAccount not supported yet")
return _inner
@pytest.fixture
+def gr_business_bank_account(
+ gr_business_bank_account_factory: Callable[..., BusinessBankAccount],
+ gr_business: Business,
+) -> BusinessBankAccount:
+ return gr_business_bank_account_factory(save=True, business_id=gr_business.id)
+
+
+# --- Business Address ---
+
+
+@pytest.fixture
def gr_business_address_factory(
- gr_bam: BusinessAddressManager,
+ gr_business_address_manager: BusinessAddressManager,
) -> Callable[..., BusinessAddress]:
def _inner(
@@ -107,7 +99,7 @@ def gr_business_address_factory(
postal_code: str | None = None,
phone_number: PhoneNumber | None = None,
country: str | None = None,
- ):
+ ) -> BusinessAddress:
uuid = uuid or uuid4().hex
line_1 = line_1 or "abc"
line_2 = line_2 or "bczx"
@@ -117,7 +109,7 @@ def gr_business_address_factory(
phone_number = None
country = country or "US"
- return gr_bam.create(
+ return gr_business_address_manager.create(
business_id=business_id,
uuid=uuid,
line_1=line_1,
@@ -133,54 +125,175 @@ def gr_business_address_factory(
@pytest.fixture
+def gr_business_address(
+ gr_business_address_factory: Callable[..., BusinessAddress], gr_business: Business
+) -> BusinessAddress:
+ return gr_business_address_factory(business_id=gr_business.id)
+
+
+# --- Business ---
+
+
+@pytest.fixture
def gr_business_factory(
- gr_bm: BusinessManager,
+ gr_business_manager: BusinessManager,
) -> Callable[..., Business]:
def _inner(
+ save: bool = True, name: str | None = None, team: Team | None = None, **kwargs
+ ) -> Business:
+ name = name or f"<Unknown {uuid4().hex[:12]}>"
+ tax_number = str(randint(1, 999_999_999))
+
+ if save:
+ return gr_business_manager.create(
+ name=name,
+ kind="c",
+ uuid=uuid4().hex,
+ team=team,
+ tax_number=tax_number,
+ **kwargs,
+ )
+ else:
+ raise ValueError("Unsaved Business not supported yet")
+
+ return _inner
+
+
+@pytest.fixture
+def gr_business(gr_business_factory: Callable[..., Business]) -> Business:
+ return gr_business_factory(save=True)
+
+
+@pytest.fixture
+def unsaved_gr_business(gr_business_factory: Callable[..., Business]) -> Business:
+ return gr_business_factory(save=False)
+
+
+# --- GR Team ---
+
+
+@pytest.fixture
+def gr_team_factory(
+ gr_team_manager: TeamManager,
+) -> Callable[..., Team]:
+
+ def _inner(
+ save: bool = True,
uuid: UUIDStr | None = None,
name: str | None = None,
- team: Team | None = None,
- kind: BusinessType | None = None,
- tax_number: str | None = None,
- ) -> Business:
- from random import randint
+ **kwargs,
+ ) -> Team:
- uuid = uuid or uuid4().hex
- name = name or "< Unknown >"
- tax_number = tax_number or str(randint(1, 999_999_999))
+ name = name or f"<Team ({uuid4().hex[:6]})>"
- return gr_bm.create(
- uuid=uuid, name=name, team=team, kind=kind, tax_number=tax_number
- )
+ if save:
+ return gr_team_manager.create(name=name, uuid=uuid, **kwargs)
+
+ else:
+ raise ValueError("BusinessBankAccount Business not supported yet")
return _inner
@pytest.fixture
-def gr_team(
- gr_tm: TeamManager,
-) -> Callable[..., Team]:
+def gr_team(gr_team_factory: Callable[..., Team]) -> Team:
+ return gr_team_factory(save=True)
- def _inner(uuid: UUIDStr | None = None, name: str | None = None) -> Team:
- uuid = uuid or uuid4().hex
- name = name or f"name-{uuid4().hex[:12]}"
- return gr_tm.create(uuid=uuid, name=name)
+@pytest.fixture
+def unsaved_gr_team(
+ gr_team_factory: Callable[..., Team],
+) -> Team:
+ return gr_team_factory(save=False)
+
+
+# --- GR User ---
+
+
+@pytest.fixture
+def gr_user_factory(gr_user_manager: GRUserManager) -> Callable[..., GRUser]:
+
+ def _inner(
+ save: bool = True,
+ sub: str | None = None,
+ is_superuser: bool = False,
+ ) -> GRUser:
+ sub = sub or f"{uuid4().hex}-{uuid4().hex}"
+
+ if save:
+ return gr_user_manager.create(
+ sub=sub,
+ is_superuser=is_superuser,
+ )
+ else:
+ raise ValueError("Unsaved GR User not supported yet")
return _inner
-@pytest.fixture()
-def gr_user_token(
- gr_user: GRUser, gr_tm: GRTokenManager, gr_db: PostgresConfig
-) -> GRToken:
- gr_tm.create(user_id=gr_user.id)
- gr_user.prefetch_token(pg_config=gr_db)
+@pytest.fixture
+def gr_user_cache(
+ gr_user: GRUser,
+ gr_db: PostgresConfig,
+ thl_web_rr: PostgresConfig,
+ gr_redis_config: RedisConfig,
+) -> GRUser:
+ gr_user.set_cache(
+ pg_config=gr_db, thl_web_rr=thl_web_rr, redis_config=gr_redis_config
+ )
+ return gr_user
- res = gr_user.token
- assert res is not None, "GRToken should exist after creation and prefetching"
- return res
+
+@pytest.fixture
+def gr_user(gr_user_factory: Callable[..., GRUser]) -> GRUser:
+ return gr_user_factory(save=True)
+
+
+@pytest.fixture
+def unsaved_gr_user(
+ gr_user_factory: Callable[..., GRUser],
+) -> GRUser:
+ return gr_user_factory(save=False)
+
+
+# --- GR User Token ---
+
+
+@pytest.fixture
+def gr_user_token_factory(
+ gr_user: GRUser, gr_token_manager: GRTokenManager, gr_db: PostgresConfig
+) -> Callable[..., GRToken]:
+
+ def _inner(
+ save: bool = True,
+ ) -> GRToken:
+
+ if save:
+ assert gr_user.id
+ gr_token_manager.create(user_id=gr_user.id)
+ gr_user.prefetch_token(pg_config=gr_db)
+
+ res = gr_user.token
+ assert res is not None, (
+ "GRToken should exist after creation and prefetching"
+ )
+ return res
+
+ else:
+ raise ValueError("Unsaved GR User not supported yet")
+
+ return _inner
+
+
+@pytest.fixture
+def gr_user_token(gr_user_token_factory: Callable[..., GRToken]) -> GRToken:
+ return gr_user_token_factory(save=True)
+
+
+@pytest.fixture
+def unsaved_gr_user_token(gr_user_token_factory: Callable[..., GRToken]) -> GRToken:
+ return gr_user_token_factory(save=False)
@pytest.fixture()
@@ -188,26 +301,34 @@ def gr_user_token_header(gr_user_token: GRToken) -> dict[str, str]:
return gr_user_token.auth_header
-@pytest.fixture(scope="function")
-def membership(team: Team, gr_user: GRUser, team_manager: TeamManager) -> Membership:
- assert team.id, "Team must be saved"
- assert gr_user.id, "GRUser must be saved"
- return team_manager.add_user(team=team, gr_user=gr_user)
+# --- GR Membership ---
-@pytest.fixture(scope="function")
-def membership_factory(
- team: Team,
- gr_user: GRUser,
- membership_manager: MembershipManager,
- team_manager: TeamManager,
- gr_um: GRUserManager,
+@pytest.fixture()
+def gr_membership_factory(
+ gr_membership_manager: MembershipManager,
) -> Callable[..., Membership]:
- def _inner(**kwargs) -> Membership:
- _team = kwargs.get("team", team_manager.create_dummy())
- _gr_user = kwargs.get("gr_user", gr_um.create_dummy())
-
- return membership_manager.create(team=_team, gr_user=_gr_user)
+ def _inner(
+ gr_team: Team, gr_user: GRUser, save: bool = True, **kwargs
+ ) -> Membership:
+ if save:
+ return gr_membership_manager.create(team=gr_team, gr_user=gr_user, **kwargs)
+ else:
+ raise ValueError("Unsaved GR Membership not supported yet")
return _inner
+
+
+@pytest.fixture()
+def gr_membership(
+ gr_membership_factory: Callable[..., Membership], gr_team: Team, gr_user: GRUser
+) -> Membership:
+ return gr_membership_factory(gr_team=gr_team, gr_user=gr_user, save=True)
+
+
+@pytest.fixture()
+def unsaved_gr_membership(
+ gr_membership_factory: Callable[..., Membership],
+) -> Membership:
+ return gr_membership_factory(save=False)
diff --git a/test_utils/models/ledger/conftest.py b/test_utils/models/ledger/conftest.py
index 5bef113..9ee0df2 100644
--- a/test_utils/models/ledger/conftest.py
+++ b/test_utils/models/ledger/conftest.py
@@ -1,43 +1,39 @@
from __future__ import annotations
+from collections.abc import Callable
from datetime import datetime
from decimal import Decimal
from random import randint
-from typing import TYPE_CHECKING, Callable
+from typing import TYPE_CHECKING
from uuid import uuid4
import pytest
-from fastapi import Request
+from pytest import FixtureRequest as Request
from generalresearch.currency import USDCent
-from generalresearch.managers.base import PostgresManager
-from test_utils.models.conftest import (
- payout_config,
- product_amt_true,
- product_user_wallet_no,
- product_user_wallet_yes,
- session,
- session_factory,
- user_factory,
- wall,
- wall_factory,
-)
-
-_ = (
- 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 test_utils.models.conftest import (
+# payout_config,
+# product_amt_true,
+# product_user_wallet_no,
+# product_user_wallet_yes,
+# )
+
+# _ = (
+# 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.managers.base import PostgresManager
from generalresearch.managers.thl.ledger_manager.ledger import LedgerManager
from generalresearch.managers.thl.ledger_manager.thl_ledger import (
ThlLedgerManager,
@@ -62,7 +58,7 @@ if TYPE_CHECKING:
@pytest.fixture
def ledger_account(
- request: Request, lm: LedgerManager, currency: LedgerCurrency
+ request: Request, ledger_manager: LedgerManager, currency: LedgerCurrency
) -> LedgerAccount:
from generalresearch.models.thl.ledger import (
AccountType,
@@ -84,14 +80,14 @@ def ledger_account(
account_type=account_type,
normal_balance=direction,
)
- return lm.create_account(account=acct_model)
+ return ledger_manager.create_account(account=acct_model)
@pytest.fixture
def ledger_account_factory(
request: Request,
- thl_lm: ThlLedgerManager,
- lm: LedgerManager,
+ thl_ledger_manager: ThlLedgerManager,
+ ledger_manager: LedgerManager,
currency: LedgerCurrency,
) -> Callable[..., LedgerAccount]:
@@ -106,7 +102,7 @@ def ledger_account_factory(
account_type: AccountType = AccountType.CASH,
direction: Direction = Direction.CREDIT,
) -> LedgerAccount:
- thl_lm.get_account_or_create_bp_wallet(product=product)
+ thl_ledger_manager.get_account_or_create_bp_wallet(product=product)
acct_uuid = uuid4().hex
qn = f"{currency}:{account_type}:{acct_uuid}"
@@ -118,14 +114,14 @@ def ledger_account_factory(
account_type=account_type,
normal_balance=direction,
)
- return lm.create_account(account=acct_model)
+ return ledger_manager.create_account(account=acct_model)
return _inner
@pytest.fixture
def ledger_account_credit(
- request: Request, lm: LedgerManager, currency: LedgerCurrency
+ request: Request, ledger_manager: LedgerManager, currency: LedgerCurrency
) -> LedgerAccount:
from generalresearch.models.thl.ledger import AccountType, Direction
@@ -143,12 +139,12 @@ def ledger_account_credit(
account_type=account_type,
normal_balance=Direction.CREDIT,
)
- return lm.create_account(account=acct_model)
+ return ledger_manager.create_account(account=acct_model)
@pytest.fixture
def ledger_account_debit(
- request: Request, lm: LedgerManager, currency: LedgerCurrency
+ request: Request, ledger_manager: LedgerManager, currency: LedgerCurrency
) -> LedgerAccount:
from generalresearch.models.thl.ledger import AccountType, Direction
@@ -166,11 +162,11 @@ def ledger_account_debit(
account_type=account_type,
normal_balance=Direction.DEBIT,
)
- return lm.create_account(account=acct_model)
+ return ledger_manager.create_account(account=acct_model)
@pytest.fixture
-def tag(request: Request, lm: LedgerManager) -> str:
+def tag(request: Request) -> str:
from generalresearch.currency import LedgerCurrency
return (
@@ -190,23 +186,24 @@ def usd_cent(request: Request) -> USDCent:
def bp_payout_event(
product: Product,
usd_cent: USDCent,
- business_payout_event_manager: BusinessPayoutEventManager,
- thl_lm: ThlLedgerManager,
+ brokerage_product_payout_event_manager: BrokerageProductPayoutEvent,
+ thl_ledger_manager: ThlLedgerManager,
) -> BrokerageProductPayoutEvent:
- return business_payout_event_manager.create_bp_payout_event(
- thl_ledger_manager=thl_lm,
+ _ext_ref_id = f"tx-{uuid4().hex[:7]}"
+
+ return brokerage_product_payout_event_manager.create_bp_payout_event(
+ thl_ledger_manager=thl_ledger_manager,
+ ext_ref_id=_ext_ref_id,
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: BrokerageProductPayoutEventManager,
- thl_lm: ThlLedgerManager,
+ thl_ledger_manager: ThlLedgerManager,
) -> Callable[..., BrokerageProductPayoutEvent]:
def _inner(
@@ -214,7 +211,7 @@ def bp_payout_event_factory(
) -> BrokerageProductPayoutEvent:
return brokerage_product_payout_event_manager.create_bp_payout_event(
- thl_ledger_manager=thl_lm,
+ thl_ledger_manager=thl_ledger_manager,
product=product,
amount=usd_cent,
ext_ref_id=ext_ref_id,
@@ -226,10 +223,12 @@ def bp_payout_event_factory(
@pytest.fixture
-def currency(lm: LedgerManager) -> LedgerCurrency:
+def currency(ledger_manager: LedgerManager) -> LedgerCurrency:
# return request.param if hasattr(request, "currency") else LedgerCurrency.TEST
- assert lm.currency, "LedgerManager must have a currency specified for these tests"
- return lm.currency
+ assert (
+ ledger_manager.currency
+ ), "LedgerManager must have a currency specified for these tests"
+ return ledger_manager.currency
@pytest.fixture
@@ -249,7 +248,7 @@ def ledger_tx(
tag: str,
currency: LedgerCurrency,
tx_metadata: dict[str, str] | None,
- lm: LedgerManager,
+ ledger_manager: LedgerManager,
) -> LedgerTransaction:
from generalresearch.models.thl.ledger import Direction, LedgerEntry
@@ -268,12 +267,12 @@ def ledger_tx(
),
]
- return lm.create_tx(entries=entries, tag=tag, metadata=tx_metadata)
+ return ledger_manager.create_tx(entries=entries, tag=tag, metadata=tx_metadata)
@pytest.fixture
def create_main_accounts(
- lm: LedgerManager, currency: LedgerCurrency
+ ledger_manager: LedgerManager, currency: LedgerCurrency
) -> Callable[..., None]:
def _inner() -> None:
@@ -288,9 +287,9 @@ def create_main_accounts(
qualified_name=f"{currency.value}:revenue:task_complete",
normal_balance=Direction.CREDIT,
account_type=AccountType.REVENUE,
- currency=lm.currency,
+ currency=ledger_manager.currency,
)
- lm.get_account_or_create(account=account)
+ ledger_manager.get_account_or_create(account=account)
account = LedgerAccount(
display_name="Operating Cash Account",
@@ -300,7 +299,7 @@ def create_main_accounts(
currency=currency,
)
- lm.get_account_or_create(account=account)
+ ledger_manager.get_account_or_create(account=account)
return _inner
@@ -324,7 +323,7 @@ def delete_ledger_db(thl_web_rw: PostgresManager) -> Callable[..., None]:
@pytest.fixture
def wipe_main_accounts(
- thl_web_rw: PostgresManager, lm: LedgerManager, currency: LedgerCurrency
+ thl_web_rw: PostgresManager, ledger_manager: LedgerManager, currency: LedgerCurrency
) -> Callable[..., None]:
def _inner() -> None:
@@ -394,7 +393,9 @@ def wipe_main_accounts(
@pytest.fixture
-def account_cash(lm: LedgerManager, currency: LedgerCurrency) -> LedgerAccount:
+def account_cash(
+ ledger_manager: LedgerManager, currency: LedgerCurrency
+) -> LedgerAccount:
from generalresearch.models.thl.ledger import (
AccountType,
Direction,
@@ -408,12 +409,12 @@ def account_cash(lm: LedgerManager, currency: LedgerCurrency) -> LedgerAccount:
account_type=AccountType.CASH,
currency=currency,
)
- return lm.get_account_or_create(account=account)
+ return ledger_manager.get_account_or_create(account=account)
@pytest.fixture
def account_revenue_task_complete(
- lm: LedgerManager, currency: LedgerCurrency
+ ledger_manager: LedgerManager, currency: LedgerCurrency
) -> LedgerAccount:
from generalresearch.models.thl.ledger import (
AccountType,
@@ -428,11 +429,13 @@ def account_revenue_task_complete(
account_type=AccountType.REVENUE,
currency=currency,
)
- return lm.get_account_or_create(account=account)
+ return ledger_manager.get_account_or_create(account=account)
@pytest.fixture
-def account_expense_tango(lm: LedgerManager, currency: LedgerCurrency) -> LedgerAccount:
+def account_expense_tango(
+ ledger_manager: LedgerManager, currency: LedgerCurrency
+) -> LedgerAccount:
from generalresearch.models.thl.ledger import (
AccountType,
Direction,
@@ -446,12 +449,12 @@ def account_expense_tango(lm: LedgerManager, currency: LedgerCurrency) -> Ledger
account_type=AccountType.EXPENSE,
currency=currency,
)
- return lm.get_account_or_create(account=account)
+ return ledger_manager.get_account_or_create(account=account)
@pytest.fixture
def user_account_user_wallet(
- lm: LedgerManager, user: User, currency: LedgerCurrency
+ ledger_manager: LedgerManager, user: User, currency: LedgerCurrency
) -> LedgerAccount:
from generalresearch.models.thl.ledger import (
AccountType,
@@ -468,12 +471,12 @@ def user_account_user_wallet(
reference_uuid=user.uuid,
currency=currency,
)
- return lm.get_account_or_create(account=account)
+ return ledger_manager.get_account_or_create(account=account)
@pytest.fixture
def product_account_bp_wallet(
- lm: LedgerManager, product: Product, currency: LedgerCurrency
+ ledger_manager: LedgerManager, product: Product, currency: LedgerCurrency
) -> LedgerAccount:
from generalresearch.models.thl.ledger import (
AccountType,
@@ -492,83 +495,86 @@ def product_account_bp_wallet(
"currency": currency,
}
)
- return lm.get_account_or_create(account=account)
+ return ledger_manager.get_account_or_create(account=account)
@pytest.fixture
def setup_accounts(
product_factory: Callable[..., Product],
- lm: LedgerManager,
+ ledger_manager: LedgerManager,
user: User,
currency: LedgerCurrency,
-) -> None:
+) -> Callable[..., None]:
from generalresearch.models.thl.ledger import (
AccountType,
Direction,
LedgerAccount,
)
- # BP's wallet and a revenue from their commissions account.
- p1 = product_factory()
+ def _inner():
+ # 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(
+ 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,
+ )
+ ledger_manager.get_account_or_create(account=account)
+
+ account = LedgerAccount.model_validate(
+ {
+ "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,
+ }
+ )
+ ledger_manager.get_account_or_create(account=account)
- account = LedgerAccount.model_validate(
- {
- "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,
+ )
+ ledger_manager.get_account_or_create(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,
+ )
+ ledger_manager.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",
+ )
+ ledger_manager.get_account_or_create(account=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)
+ return _inner
@pytest.fixture
@@ -577,7 +583,7 @@ def session_with_tx_factory(
session_manager: SessionManager,
wall_manager: WallManager,
utc_hour_ago: datetime,
- thl_lm: ThlLedgerManager,
+ thl_ledger_manager: ThlLedgerManager,
) -> Callable[..., Session]:
from generalresearch.models.thl.session import (
@@ -618,14 +624,16 @@ def session_with_tx_factory(
status_code_1=status_code_1,
)
- thl_lm.create_tx_task_complete(
+ thl_ledger_manager.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)
+ thl_ledger_manager.create_tx_bp_payment(
+ session=s, created=last_wall.finished, force=True
+ )
return s
@@ -636,7 +644,7 @@ def session_with_tx_factory(
def adj_to_fail_with_tx_factory(
session_manager: SessionManager,
wall_manager: WallManager,
- thl_lm: ThlLedgerManager,
+ thl_ledger_manager: ThlLedgerManager,
) -> Callable[..., None]:
from datetime import timedelta
@@ -669,7 +677,7 @@ def adj_to_fail_with_tx_factory(
adjusted_timestamp=created,
)
- thl_lm.create_tx_task_adjustment(
+ thl_ledger_manager.create_tx_task_adjustment(
wall=w1,
user=session.user,
created=created + timedelta(milliseconds=1),
@@ -678,7 +686,7 @@ def adj_to_fail_with_tx_factory(
session.wall_events = wall_manager.get_wall_events(session_id=session.id)
session_manager.adjust_status(session=session)
- thl_lm.create_tx_bp_adjustment(
+ thl_ledger_manager.create_tx_bp_adjustment(
session=session, created=created + timedelta(milliseconds=2)
)
@@ -689,7 +697,7 @@ def adj_to_fail_with_tx_factory(
def adj_to_complete_with_tx_factory(
session_manager: SessionManager,
wall_manager: WallManager,
- thl_lm: ThlLedgerManager,
+ thl_ledger_manager: ThlLedgerManager,
) -> Callable[..., None]:
from datetime import timedelta
@@ -708,7 +716,7 @@ def adj_to_complete_with_tx_factory(
adjusted_timestamp=created,
)
- thl_lm.create_tx_task_adjustment(
+ thl_ledger_manager.create_tx_task_adjustment(
wall=w1,
user=session.user,
created=created + timedelta(milliseconds=1),
@@ -717,7 +725,7 @@ def adj_to_complete_with_tx_factory(
session.wall_events = wall_manager.get_wall_events(session_id=session.id)
session_manager.adjust_status(session=session)
- thl_lm.create_tx_bp_adjustment(
+ thl_ledger_manager.create_tx_bp_adjustment(
session=session, created=created + timedelta(milliseconds=2)
)
diff --git a/test_utils/models/network/__init__.py b/test_utils/models/network/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/test_utils/models/network/__init__.py
+++ /dev/null
diff --git a/test_utils/models/network/conftest.py b/test_utils/models/network/conftest.py
deleted file mode 100644
index abfbc18..0000000
--- a/test_utils/models/network/conftest.py
+++ /dev/null
@@ -1,144 +0,0 @@
-import os
-from datetime import datetime, timedelta, timezone
-from uuid import uuid4
-
-import pytest
-from fastapi import Request
-
-from generalresearch.managers.network.label import IPLabelManager
-from generalresearch.managers.network.tool_run import ToolRunManager
-from generalresearch.models.network.definitions import IPProtocol
-from generalresearch.models.network.mtr.parser import parse_mtr_output
-from generalresearch.models.network.mtr.result import MTRResult
-from generalresearch.models.network.nmap.parser import parse_nmap_xml
-from generalresearch.models.network.nmap.result import NmapResult
-from generalresearch.models.network.rdns.parser import parse_rdns_output
-from generalresearch.models.network.rdns.result import RDNSResult
-from generalresearch.models.network.tool_run import MTRRun, NmapRun, RDNSRun, Status
-from generalresearch.models.network.tool_run_command import (
- MTRRunCommand,
- MTRRunCommandOptions,
- NmapRunCommand,
- NmapRunCommandOptions,
- RDNSRunCommand,
- RDNSRunCommandOptions,
-)
-from generalresearch.pg_helper import PostgresConfig
-
-
-@pytest.fixture(scope="session")
-def scan_group_id() -> str:
- return uuid4().hex
-
-
-@pytest.fixture(scope="session")
-def iplabel_manager(thl_web_rw: PostgresConfig) -> IPLabelManager:
- return IPLabelManager(pg_config=thl_web_rw)
-
-
-@pytest.fixture(scope="session")
-def toolrun_manager(thl_web_rw: PostgresConfig) -> ToolRunManager:
- return ToolRunManager(pg_config=thl_web_rw)
-
-
-@pytest.fixture(scope="session")
-def nmap_raw_output(request: Request) -> str:
- fp = os.path.join(request.config.rootpath, "data/nmaprun1.xml")
- with open(fp) as f:
- data = f.read()
- return data
-
-
-@pytest.fixture(scope="session")
-def nmap_result(nmap_raw_output: str) -> NmapResult:
- return parse_nmap_xml(nmap_raw_output)
-
-
-@pytest.fixture(scope="session")
-def nmap_run(nmap_result: NmapResult, scan_group_id: str):
- r = nmap_result
- config = NmapRunCommand(
- command="nmap",
- options=NmapRunCommandOptions(
- ip=r.target_ip, ports="22-1000,11000,1100,3389,61232", top_ports=None
- ),
- )
- return NmapRun(
- tool_version=r.version,
- status=Status.SUCCESS,
- ip=r.target_ip,
- started_at=r.started_at,
- finished_at=r.finished_at,
- raw_command=config.to_command_str(),
- scan_group_id=scan_group_id,
- config=config,
- parsed=r,
- )
-
-
-@pytest.fixture(scope="session")
-def dig_raw_output() -> str:
- return "156.32.33.45.in-addr.arpa. 300 IN PTR scanme.nmap.org."
-
-
-@pytest.fixture(scope="session")
-def rdns_result(dig_raw_output: str) -> RDNSResult:
- return parse_rdns_output(ip="45.33.32.156", raw=dig_raw_output)
-
-
-@pytest.fixture(scope="session")
-def rdns_run(rdns_result: RDNSResult, scan_group_id: str):
- r = rdns_result
- ip = "45.33.32.156"
- utc_now = datetime.now(tz=timezone.utc)
- config = RDNSRunCommand(command="dig", options=RDNSRunCommandOptions(ip=ip))
- return RDNSRun(
- tool_version="1.2.3",
- status=Status.SUCCESS,
- ip=ip,
- started_at=utc_now,
- finished_at=utc_now + timedelta(seconds=1),
- raw_command=config.to_command_str(),
- scan_group_id=scan_group_id,
- config=config,
- parsed=r,
- )
-
-
-@pytest.fixture(scope="session")
-def mtr_raw_output(request: Request) -> str:
- fp = os.path.join(request.config.rootpath, "data/mtr_fatbeam.json")
- with open(fp) as f:
- data = f.read()
- return data
-
-
-@pytest.fixture(scope="session")
-def mtr_result(mtr_raw_output: str) -> MTRResult:
- return parse_mtr_output(mtr_raw_output, port=443, protocol=IPProtocol.TCP)
-
-
-@pytest.fixture(scope="session")
-def mtr_run(mtr_result: MTRResult, scan_group_id: str):
- r = mtr_result
- utc_now = datetime.now(tz=timezone.utc)
- config = MTRRunCommand(
- command="mtr",
- options=MTRRunCommandOptions(
- ip=r.destination, protocol=IPProtocol.TCP, port=443
- ),
- )
-
- return MTRRun(
- tool_version="1.2.3",
- status=Status.SUCCESS,
- ip=r.destination,
- started_at=utc_now,
- finished_at=utc_now + timedelta(seconds=1),
- raw_command=config.to_command_str(),
- scan_group_id=scan_group_id,
- config=config,
- parsed=r,
- facility_id=1,
- source_ip="1.2.3.4",
- )
diff --git a/test_utils/models/thl/conftest.py b/test_utils/models/thl/conftest.py
index cf8d2fa..e09eadd 100644
--- a/test_utils/models/thl/conftest.py
+++ b/test_utils/models/thl/conftest.py
@@ -1,149 +1,386 @@
from __future__ import annotations
-from datetime import datetime, timezone
+from collections.abc import Callable
+from datetime import UTC, datetime, timedelta
from decimal import ROUND_DOWN, Decimal
from random import choice as rand_choice
-from random import choice as rchoice
from random import randint, random
-from typing import Any, Callable
+from typing import TYPE_CHECKING, Any
from uuid import uuid4
import faker
import pytest
+from grip_client.enums import AccessType
from pydantic import PositiveInt
-from generalresearch.managers.thl.ipinfo import IPGeonameManager, IPInformationManager
-from generalresearch.managers.thl.payout import UserPayoutEventManager
-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.userhealth import AuditLogManager, IPRecordManager
-from generalresearch.managers.thl.wall import WallManager
-from generalresearch.models import DeviceType
+from generalresearch.currency import USDCent
+from generalresearch.managers.thl.payout import (
+ BusinessPayoutEventManager,
+ UserPayoutEventManager,
+)
from generalresearch.models.custom_types import (
AwareDatetimeISO,
IPvAnyAddressStr,
UUIDStr,
)
-from generalresearch.models.legacy.bucket import Bucket
+from generalresearch.models.definitions import DeviceType, Source
from generalresearch.models.thl.definitions import (
+ WALL_ALLOWED_STATUS_STATUS_CODE,
PayoutStatus,
-)
-from generalresearch.models.thl.ipinfo import IPGeoname, IPInformation, UserType
-from generalresearch.models.thl.payout import UserPayoutEvent
-from generalresearch.models.thl.product import (
- PayoutConfig,
- Product,
- ProfilingConfig,
- SessionConfig,
- SourcesConfig,
- SupplyConfig,
- UserCreateConfig,
- UserHealthConfig,
- UserWalletConfig,
-)
-from generalresearch.models.thl.session import (
- Session,
- Source,
Status,
- Wall,
)
+from generalresearch.models.thl.payout import UserPayoutEvent
from generalresearch.models.thl.user import User
-from generalresearch.models.thl.user_iphistory import IPRecord
-from generalresearch.models.thl.userhealth import AuditLog, AuditLogLevel
-from generalresearch.models.thl.wallet import PayoutType
-from generalresearch.models.thl.wallet.cashout_method import CashMailOrderData
+from generalresearch.models.thl.userhealth import AuditLogLevel
+from generalresearch.models.thl.wallet.definitions import PayoutType
+from generalresearch.pg_helper import PostgresConfig
+
+if TYPE_CHECKING:
+ from generalresearch.managers.thl.ipinfo import (
+ IPGeonameManager,
+ IPInformationManager,
+ )
+ from generalresearch.managers.thl.ledger_manager.thl_ledger import ThlLedgerManager
+ 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.userhealth import AuditLogManager, IPRecordManager
+ from generalresearch.managers.thl.wall import WallManager
+ from generalresearch.models.custom_types import AwareDatetime
+ from generalresearch.models.gr.business import Business
+ from generalresearch.models.gr.team import Team
+ from generalresearch.models.legacy.bucket import Bucket
+ from generalresearch.models.thl.ipinfo import IPGeoname, IPInformation
+ from generalresearch.models.thl.payout import BrokerageProductPayoutEvent
+ from generalresearch.models.thl.product import (
+ PayoutConfig,
+ Product,
+ ProfilingConfig,
+ SessionConfig,
+ SourcesConfig,
+ SupplyConfig,
+ UserCreateConfig,
+ UserHealthConfig,
+ UserWalletConfig,
+ )
+ from generalresearch.models.thl.session import (
+ Session,
+ Wall,
+ )
+ from generalresearch.models.thl.user_iphistory import IPRecord
+ from generalresearch.models.thl.userhealth import AuditLog
+ from generalresearch.models.thl.wallet.cashout_method import CashMailOrderData
fake = faker.Faker()
+# --- Wall ---
+
+
+@pytest.fixture
+def wall_factory(
+ wall_manager: WallManager,
+ bare_session_factory: Callable[..., Session],
+ session_manager: SessionManager,
+ user_factory: Callable[..., User],
+) -> Callable[..., Wall]:
+
+ def _inner(
+ wall_status: Status = Status.FAIL,
+ save: bool = True,
+ session: Session | None = None,
+ session_id: PositiveInt | None = None,
+ user: User | None = None,
+ started: datetime | None = None,
+ source: Source | None = None,
+ req_survey_id: str | None = None,
+ req_cpi: Decimal | None = None,
+ buyer_id: str | None = None,
+ uuid_id: str | None = None,
+ ) -> Wall:
+ """To be used in tests, where we don't care about certain fields"""
+ user = user or user_factory()
+ if save:
+ _wall_started = started or fake.date_time_between(
+ start_date=datetime(year=1900, month=1, day=1, tzinfo=UTC),
+ end_date=datetime.now(tz=UTC),
+ tzinfo=UTC,
+ )
+
+ if session:
+ # If an existing Session was provided, we want to do some
+ # additional validation.
+
+ if session.wall_events:
+ # Subsequent Wall events
+ _last_wall = session.wall_events[-1]
+ assert not _last_wall.finished, (
+ "Can't add new Walls until prior finishes"
+ )
+ _wall_started = _last_wall.started + timedelta(milliseconds=1)
+ else:
+ # First Wall Event in a session
+ _wall_started = session.started + timedelta(milliseconds=1)
+ else:
+ # If a Session was NOT provided, either (1) try to retrieve it
+ # from an optionally provided session_id int, or (2) proceed
+ # forward and make one
+ session = (
+ session_manager.get_from_id(session_id=session_id)
+ if session_id
+ else None
+ ) or bare_session_factory(save=True, user=user)
+
+ assert session, "Wall factory requires Session"
+
+ source = source or rand_choice(list(Source))
+ req_survey_id = req_survey_id or uuid4().hex
+ req_cpi = req_cpi or Decimal(
+ fake.random_int(min=1, max=150) / 100
+ ).quantize(Decimal(".01"), rounding=ROUND_DOWN)
+
+ w = wall_manager.create(
+ session_id=session.id,
+ user_id=session.user_id,
+ started=_wall_started,
+ source=source,
+ req_survey_id=req_survey_id,
+ req_cpi=req_cpi,
+ buyer_id=buyer_id,
+ uuid_id=uuid_id,
+ )
+
+ _status_code_options = list(
+ WALL_ALLOWED_STATUS_STATUS_CODE.get(wall_status, {})
+ )
+ w.finish(
+ finished=w.started + timedelta(seconds=randint(a=60 * 2, b=60 * 10)),
+ status=wall_status,
+ status_code_1=rand_choice(_status_code_options),
+ )
+
+ session.append_wall_event(w=w)
+
+ return w
+
+ else:
+ raise ValueError("Unsaved Wall not yet supported")
+
+ return _inner
+
+
+@pytest.fixture
+def wall(wall_factory: Callable[..., Wall]) -> Wall:
+ return wall_factory(save=True)
+
+
+@pytest.fixture()
+def unsaved_wall(wall_factory: Callable[..., Wall]) -> Wall:
+ return wall_factory(save=False)
+
+
+# --- Wall: Enum(s) ---
+
@pytest.fixture
def wall_status() -> Status:
return Status.COMPLETE
+# --- Session ---
+
+
@pytest.fixture
-def user_factory(user_manager: UserManager) -> Callable[..., User]:
+def bare_session_factory(
+ session_manager: SessionManager, user_factory: Callable[..., User]
+):
+ # Create a session with no wall events
def _inner(
- # --- Create dummy "optional" --- #
- product_user_id: str | None = None,
- # --- Optional --- #
- product_id: UUIDStr | None = None,
- product: Product | None = None,
- created: datetime | None = None,
- ) -> User:
-
- product_user_id = product_user_id or uuid4().hex
+ save: bool = True,
+ # -- Create Dummy "optional" -- #
+ started: datetime | None = None,
+ user: User | None = None,
+ # -- Optional -- #
+ country_iso: str | None = None,
+ device_type: DeviceType | None = None,
+ ip: str | None = None,
+ bucket: Bucket | None = None,
+ url_metadata: dict[str, str] | None = None,
+ uuid_id: str | None = None,
+ ) -> Session:
- return user_manager.create_user(
- product_user_id=product_user_id,
- product_id=product_id,
- product=product,
- created=created,
- )
+ if save:
+ """To be used in tests, where we don't care about certain fields"""
+ started = started or fake.date_time_between(
+ start_date=datetime(year=1900, month=1, day=1, tzinfo=UTC),
+ end_date=datetime(year=2000, month=1, day=1, tzinfo=UTC),
+ tzinfo=UTC,
+ )
+ user = user or user_factory(save=True)
+ assert user.user_id, "Provided User must be saved to the database"
+
+ return session_manager.create(
+ started=started,
+ user=user,
+ country_iso=country_iso,
+ device_type=device_type,
+ ip=ip,
+ bucket=bucket,
+ url_metadata=url_metadata,
+ uuid_id=uuid_id,
+ )
+ else:
+ # user = User(
+ # user_id=fake.random_int(min=1, max=2_147_483_648), uuid=uuid4().hex
+ # )
+ raise ValueError("Unsaved Session not yet supported")
return _inner
+@pytest.fixture()
+def bare_session(bare_session_factory: Callable[..., Session], user) -> Session:
+ # A session with no wall events
+ return bare_session_factory(user=user)
+
+
@pytest.fixture
-def wall_factory(
- wall_manager: WallManager, session_factory: Session
-) -> Callable[..., Wall]:
+def session(
+ bare_session: Session,
+ wall_factory: Callable[..., Wall],
+) -> Session:
+ s = bare_session.model_copy()
+ wall: Wall = wall_factory(
+ session_id=s.id,
+ user=s.user,
+ started=s.started,
+ )
+ s.append_wall_event(w=wall)
+ return s
+
+
+@pytest.fixture
+def session_factory(
+ wall_manager: WallManager,
+ utc_hour_ago: datetime,
+ bare_session_factory: Callable[..., Session],
+ wall_factory: Callable[..., Wall],
+) -> Callable[..., Session]:
def _inner(
- session_id: int | None = None,
- user_id: int | None = None,
- started: datetime | None = None,
- source: Source | None = None,
- req_survey_id: str | None = None,
- req_cpi: Decimal | None = None,
- buyer_id: str | None = None,
- uuid_id: str | None = None,
- ):
- """To be used in tests, where we don't care about certain fields"""
+ user: User,
+ # Wall details
+ wall_count: int = 5,
+ wall_req_cpi: Decimal = Decimal(".50"),
+ wall_req_cpis: list[Decimal] | None = None,
+ wall_statuses: list[Status] | None = None,
+ wall_source: Source = Source.TESTING,
+ # Session details
+ final_status: Status = Status.COMPLETE,
+ started: datetime = utc_hour_ago,
+ ) -> Session:
+ if wall_req_cpis:
+ assert len(wall_req_cpis) == wall_count
+ if wall_statuses:
+ assert len(wall_statuses) == wall_count
+
+ s = bare_session_factory(started=started, user=user, country_iso="us")
+ for idx in range(wall_count):
+ if idx == 0:
+ # First Wall Event in a session
+ wall_started = s.started + timedelta(milliseconds=1)
+ else:
+ # Subsequent Wall events
+ last_wall = s.wall_events[-1]
+ assert last_wall.finished, "Can't add new Walls until prior finishes"
+ wall_started = last_wall.started + timedelta(milliseconds=1)
+
+ w = wall_factory(
+ session_id=s.id,
+ source=wall_source,
+ user=s.user,
+ started=wall_started,
+ req_cpi=wall_req_cpis[idx] if wall_req_cpis else wall_req_cpi,
+ )
+ s.append_wall_event(w=w)
+
+ # If it's the last wall in the session, respect the final_status
+ # value for the Session
+ if wall_statuses:
+ _final_status = wall_statuses[idx]
+ else:
+ _final_status = final_status if idx == wall_count - 1 else Status.FAIL
+
+ options = list(WALL_ALLOWED_STATUS_STATUS_CODE.get(_final_status, {}))
+ wall_manager.finish(
+ wall=w,
+ status=_final_status,
+ status_code_1=rand_choice(options),
+ finished=w.started + timedelta(seconds=randint(a=60 * 2, b=60 * 10)),
+ )
+
+ return s
- user_id = user_id or fake.random_int(min=1, max=2_147_483_648)
- started = started or fake.date_time_between(
- start_date=datetime(year=1900, month=1, day=1, tzinfo=timezone.utc),
- end_date=datetime.now(tz=timezone.utc),
- tzinfo=timezone.utc,
- )
+ return _inner
- if session_id is None:
- # session = SessionManager(pg_config=self.pg_config).create_dummy(
- # started=started
- # )
- session = session_factory()
- session_id = session.id
- source = source or rchoice(list(Source))
- req_survey_id = req_survey_id or uuid4().hex
- req_cpi = req_cpi or Decimal(fake.random_int(min=1, max=150) / 100).quantize(
- Decimal(".01"), rounding=ROUND_DOWN
- )
+@pytest.fixture(scope="function")
+def finished_session_factory(
+ session_factory: Callable[..., Session],
+ session_manager: SessionManager,
+ utc_hour_ago: datetime,
+) -> Callable[..., Session]:
- return wall_manager.create(
- session_id=session_id,
- user_id=user_id,
+ def _inner(
+ user: User,
+ # Wall details
+ wall_count: int = 5,
+ wall_req_cpi: Decimal = Decimal(".50"),
+ wall_req_cpis: list[Decimal] | None = None,
+ wall_statuses: list[Status] | None = None,
+ wall_source: Source = Source.TESTING,
+ # Session details
+ final_status: Status = Status.COMPLETE,
+ started: datetime = utc_hour_ago,
+ ) -> Session:
+ s: Session = session_factory(
+ user=user,
+ wall_count=wall_count,
+ wall_req_cpi=wall_req_cpi,
+ wall_req_cpis=wall_req_cpis,
+ wall_statuses=wall_statuses,
+ wall_source=wall_source,
+ final_status=final_status,
started=started,
- source=source,
- req_survey_id=req_survey_id,
- req_cpi=req_cpi,
- buyer_id=buyer_id,
- uuid_id=uuid_id,
)
+ status, status_code_1 = s.determine_session_status()
+ _, _, bp_pay, user_pay = s.determine_payments()
+ session_manager.finish_with_status(
+ s,
+ finished=s.wall_events[-1].finished,
+ payout=bp_pay,
+ user_payout=user_pay,
+ status=status,
+ status_code_1=status_code_1,
+ )
+ return s
return _inner
-@pytest.fixture
+# --- Product ---
+
+
+@pytest.fixture()
def product_factory(product_manager: ProductManager) -> Callable[..., Product]:
def _inner(
- product_id: UUIDStr | None = None,
+ save: bool = True,
+ team: Team | None = None,
team_id: UUIDStr | None = None,
+ business: Business | None = None,
business_id: UUIDStr | None = None,
+ product_id: UUIDStr | None = None,
name: str | None = None,
redirect_url: str | None = None,
harmonizer_domain: str | None = None,
@@ -157,74 +394,60 @@ def product_factory(product_manager: ProductManager) -> Callable[..., Product]:
user_health_config: UserHealthConfig | None = None,
) -> Product:
"""To be used in tests, where we don't care about certain fields"""
+
product_id = product_id if product_id else uuid4().hex
- team_id = team_id if team_id else uuid4().hex
+
+ team_id = (team.uuid if team else None) or team_id or uuid4().hex
+ business_id = (
+ (business.uuid if business else None) or business_id or uuid4().hex
+ )
+
name = name if name else f"name-{product_id[:12]}"
redirect_url = redirect_url if redirect_url else "https://www.example.com/"
- return product_manager.create(
- product_id=product_id,
- team_id=team_id,
- business_id=business_id,
- name=name,
- redirect_url=redirect_url,
- harmonizer_domain=harmonizer_domain,
- commission_pct=commission_pct,
- sources_config=sources_config,
- payout_config=payout_config,
- session_config=session_config,
- profiling_config=profiling_config,
- user_wallet_config=user_wallet_config,
- user_create_config=user_create_config,
- user_health_config=user_health_config,
- )
+ if save:
+ return product_manager.create(
+ product_id=product_id,
+ team_id=team_id,
+ business_id=business_id,
+ name=name,
+ redirect_url=redirect_url,
+ harmonizer_domain=harmonizer_domain,
+ commission_pct=commission_pct,
+ sources_config=sources_config,
+ payout_config=payout_config,
+ session_config=session_config,
+ profiling_config=profiling_config,
+ user_wallet_config=user_wallet_config,
+ user_create_config=user_create_config,
+ user_health_config=user_health_config,
+ )
+ else:
+ raise ValueError("Unsaved Product not yet supported")
return _inner
-@pytest.fixture
-def session_factory(session_manager: SessionManager):
+@pytest.fixture()
+def product(product_factory: Callable[..., Product]) -> Product:
+ return product_factory(save=True)
- def _inner(
- # -- Create Dummy "optional" -- #
- started: datetime | None = None,
- user: User | None = None,
- # -- Optional -- #
- country_iso: str | None = None,
- device_type: DeviceType | None = None,
- ip: str | None = None,
- bucket: Bucket | None = None,
- url_metadata: dict[str, str] | None = None,
- uuid_id: str | None = None,
- ) -> Session:
- """To be used in tests, where we don't care about certain fields"""
- started = started or fake.date_time_between(
- start_date=datetime(year=1900, month=1, day=1, tzinfo=timezone.utc),
- end_date=datetime(year=2000, month=1, day=1, tzinfo=timezone.utc),
- tzinfo=timezone.utc,
- )
- user = user or User(
- user_id=fake.random_int(min=1, max=2_147_483_648), uuid=uuid4().hex
- )
- return session_manager.create(
- started=started,
- user=user,
- country_iso=country_iso,
- device_type=device_type,
- ip=ip,
- bucket=bucket,
- url_metadata=url_metadata,
- uuid_id=uuid_id,
- )
+@pytest.fixture()
+def unsaved_product(product_factory: Callable[..., Product]) -> Product:
+ return product_factory(save=False)
- return _inner
+
+# --- IP Geoname ---
@pytest.fixture
-def ipgeoname_factory(ipgeoname_manager: IPGeonameManager) -> Callable[..., IPGeoname]:
+def ip_geoname_factory(
+ ip_geoname_manager: IPGeonameManager,
+) -> Callable[..., IPGeoname]:
def _inner(
+ save: bool = True,
geoname_id: PositiveInt | None = None,
continent_code: str | None = None,
continent_name: str | None = None,
@@ -239,31 +462,48 @@ def ipgeoname_factory(ipgeoname_manager: IPGeonameManager) -> Callable[..., IPGe
time_zone: str | None = None,
is_in_european_union: bool | None = None,
) -> IPGeoname:
-
- return ipgeoname_manager.create(
- geoname_id=geoname_id or randint(1, 999_999_999),
- continent_code=continent_code or "na",
- continent_name=continent_name or "North America",
- country_iso=country_iso or "us",
- country_name=country_name or "United States",
- subdivision_1_iso=subdivision_1_iso or "fl",
- subdivision_1_name=subdivision_1_name or "Florida",
- subdivision_2_iso=subdivision_2_iso,
- subdivision_2_name=subdivision_2_name,
- city_name=city_name,
- metro_code=metro_code,
- time_zone=time_zone,
- is_in_european_union=is_in_european_union,
- )
+ if save:
+ return ip_geoname_manager.create(
+ geoname_id=geoname_id or randint(1, 999_999_999),
+ continent_code=continent_code or "na",
+ continent_name=continent_name or "North America",
+ country_iso=country_iso or "us",
+ country_name=country_name or "United States",
+ subdivision_1_iso=subdivision_1_iso or "fl",
+ subdivision_1_name=subdivision_1_name or "Florida",
+ subdivision_2_iso=subdivision_2_iso,
+ subdivision_2_name=subdivision_2_name,
+ city_name=city_name,
+ metro_code=metro_code,
+ time_zone=time_zone,
+ is_in_european_union=is_in_european_union,
+ )
+ else:
+ raise ValueError("Unsaved IPGeoname not yet supported")
return _inner
-def ipinformation_factory(
- ipinformation_manager: IPInformationManager,
+@pytest.fixture()
+def ip_geoname(ip_geoname_factory: Callable[..., IPGeoname]) -> IPGeoname:
+ return ip_geoname_factory(save=True)
+
+
+@pytest.fixture()
+def unsaved_ip_geoname(ip_geoname_factory: Callable[..., IPGeoname]) -> IPGeoname:
+ return ip_geoname_factory(save=True)
+
+
+# --- IP Information ---
+
+
+@pytest.fixture
+def ip_information_factory(
+ ip_information_manager: IPInformationManager,
) -> Callable[..., IPInformation]:
def _inner(
+ save: bool = True,
ip: IPvAnyAddressStr | None = None,
geoname_id: PositiveInt | None = None,
country_iso: str | None = None,
@@ -283,43 +523,186 @@ def ipinformation_factory(
network: str | None = None,
organization: str | None = None,
static_ip_score: float | None = None,
- user_type: UserType | None = None,
+ user_type: AccessType | None = None,
postal_code: str | None = None,
latitude: Decimal | None = None,
longitude: Decimal | None = None,
accuracy_radius: int | None = None,
) -> IPInformation:
- return ipinformation_manager.create(
- ip=ip or fake.ipv4_public(),
- geoname_id=geoname_id,
- country_iso=country_iso or fake.country_code(),
- registered_country_iso=registered_country_iso,
- is_anonymous=is_anonymous,
- is_anonymous_vpn=is_anonymous_vpn,
- is_hosting_provider=is_hosting_provider,
- is_public_proxy=is_public_proxy,
- is_tor_exit_node=is_tor_exit_node,
- is_residential_proxy=is_residential_proxy,
- autonomous_system_number=autonomous_system_number,
- autonomous_system_organization=autonomous_system_organization,
- domain=domain,
- isp=isp,
- mobile_country_code=mobile_country_code,
- mobile_network_code=mobile_network_code,
- network=network,
- organization=organization,
- static_ip_score=static_ip_score,
- user_type=user_type,
- postal_code=postal_code,
- latitude=latitude,
- longitude=longitude,
- accuracy_radius=accuracy_radius,
- )
+ if save:
+ return ip_information_manager.create(
+ ip=ip or fake.ipv4_public(),
+ geoname_id=geoname_id,
+ country_iso=country_iso or fake.country_code(),
+ registered_country_iso=registered_country_iso,
+ is_anonymous=is_anonymous,
+ is_anonymous_vpn=is_anonymous_vpn,
+ is_hosting_provider=is_hosting_provider,
+ is_public_proxy=is_public_proxy,
+ is_tor_exit_node=is_tor_exit_node,
+ is_residential_proxy=is_residential_proxy,
+ autonomous_system_number=autonomous_system_number,
+ autonomous_system_organization=autonomous_system_organization,
+ domain=domain,
+ isp=isp,
+ mobile_country_code=mobile_country_code,
+ mobile_network_code=mobile_network_code,
+ network=network,
+ organization=organization,
+ static_ip_score=static_ip_score,
+ user_type=user_type,
+ postal_code=postal_code,
+ latitude=latitude,
+ longitude=longitude,
+ accuracy_radius=accuracy_radius,
+ )
+ else:
+ raise ValueError("Unsaved IP Information not supported yet")
+
+ return _inner
+
+
+@pytest.fixture
+def ip_information(
+ ip_information_factory: Callable[..., IPInformation],
+) -> IPInformation:
+ return ip_information_factory(save=True)
+
+
+@pytest.fixture()
+def unsaved_ip_information(
+ ip_information_factory: Callable[..., IPInformation],
+) -> IPInformation:
+ return ip_information_factory(save=False)
+
+
+# --- IP Record ---
+
+
+@pytest.fixture()
+def ip_record_factory(ip_record_manager: IPRecordManager) -> Callable[..., IPRecord]:
+
+ def _inner(
+ user_id: PositiveInt,
+ save: bool = True,
+ ip: IPvAnyAddressStr | None = None,
+ forwarded_ip1: IPvAnyAddressStr | None = None,
+ forwarded_ip2: IPvAnyAddressStr | None = None,
+ forwarded_ip3: IPvAnyAddressStr | None = None,
+ forwarded_ip4: IPvAnyAddressStr | None = None,
+ forwarded_ip5: IPvAnyAddressStr | None = None,
+ forwarded_ip6: IPvAnyAddressStr | None = None,
+ ) -> IPRecord:
+
+ if save:
+ return ip_record_manager.create(
+ user_id=user_id,
+ ip=ip or fake.ipv4_public(),
+ forwarded_ip1=(forwarded_ip1 or fake.ipv4_public()),
+ forwarded_ip2=(
+ forwarded_ip2 or fake.ipv6() if random() < 0.5 else None
+ ),
+ forwarded_ip3=(
+ forwarded_ip3 or fake.ipv4_public() if random() < 0.25 else None
+ ),
+ forwarded_ip4=forwarded_ip4,
+ forwarded_ip5=forwarded_ip5,
+ forwarded_ip6=forwarded_ip6,
+ )
+ else:
+ raise ValueError("Unsaved IP Record not supported")
+
+ return _inner
+
+
+@pytest.fixture()
+def ip_record(ip_record_factory: Callable[..., IPRecord]) -> IPRecord:
+ return ip_record_factory(save=True)
+
+
+@pytest.fixture()
+def unsaved_ip_record(ip_record_factory: Callable[..., IPRecord]) -> IPRecord:
+ return ip_record_factory(save=False)
+
+
+# --- User ---
+
+
+@pytest.fixture()
+def user_factory(
+ user_manager: UserManager,
+ thl_web_rr: PostgresConfig,
+ product_factory: Callable[..., Product],
+) -> Callable[..., User]:
+
+ def _inner(
+ save: bool = True,
+ # --- Create dummy "optional" --- #
+ product_user_id: str | None = None,
+ # --- Optional --- #
+ product_id: UUIDStr | None = None,
+ product: Product | None = None,
+ created: datetime | None = None,
+ ) -> User:
+ if save:
+ if product is None:
+ if product_id:
+ raise ValueError("this is broken")
+ product = product_factory()
+
+ product_user_id = product_user_id or uuid4().hex
+
+ u = user_manager.create_user(
+ product_user_id=product_user_id,
+ product_id=product_id,
+ product=product,
+ created=created,
+ )
+
+ u.prefetch_product(pg_config=thl_web_rr)
+ return u
+
+ else:
+ raise ValueError("Unsaved User not supported")
return _inner
+@pytest.fixture()
+def user(
+ user_factory: Callable[..., User],
+) -> User:
+ return user_factory(save=True)
+
+
+@pytest.fixture()
+def unsaved_user(
+ user_factory: Callable[..., User],
+) -> User:
+ return user_factory(save=False)
+
+
+@pytest.fixture
+def user_with_wallet(
+ user_factory: Callable[..., User],
+ product_user_wallet_yes: Product,
+) -> User:
+ # A user on a product with user wallet enabled, but they have no money
+ return user_factory(save=True, product=product_user_wallet_yes)
+
+
+@pytest.fixture
+def user_with_wallet_amt(
+ user_factory: Callable[..., User], product_amt_true: Product
+) -> User:
+ # A user on a product with user wallet enabled, on AMT, but they have no money
+ return user_factory(save=True, product=product_amt_true)
+
+
+# --- User Payout Event ---
+
+
@pytest.fixture
def user_payout_event_factory(
user_payout_event_manager: UserPayoutEventManager,
@@ -374,40 +757,56 @@ def user_payout_event_factory(
return _inner
+@pytest.fixture()
+def user_payout_event(
+ user_payout_event_factory: Callable[..., UserPayoutEvent],
+) -> UserPayoutEvent:
+ return user_payout_event_factory(save=True)
+
+
+@pytest.fixture()
+def unsaved_user_payout_event(
+ user_payout_event_factory: Callable[..., UserPayoutEvent],
+) -> UserPayoutEvent:
+ return user_payout_event_factory(save=True)
+
+
+# -- Brokerage Product Payout Event
+
+
@pytest.fixture
-def iprecord_factory(iprecord_manager: IPRecordManager) -> Callable[..., IPRecord]:
+def brokerage_product_payout_event_factory(
+ thl_ledger_manager: ThlLedgerManager,
+ business_payout_event_manager: BusinessPayoutEventManager,
+ product_factory: Callable[..., Product],
+) -> Callable[..., BrokerageProductPayoutEvent]:
def _inner(
- user_id: PositiveInt,
- ip: IPvAnyAddressStr | None = None,
- forwarded_ip1: IPvAnyAddressStr | None = None,
- forwarded_ip2: IPvAnyAddressStr | None = None,
- forwarded_ip3: IPvAnyAddressStr | None = None,
- forwarded_ip4: IPvAnyAddressStr | None = None,
- forwarded_ip5: IPvAnyAddressStr | None = None,
- forwarded_ip6: IPvAnyAddressStr | None = None,
- ) -> IPRecord:
- return iprecord_manager.create(
- user_id=user_id,
- ip=ip or fake.ipv4_public(),
- forwarded_ip1=(forwarded_ip1 or fake.ipv4_public()),
- forwarded_ip2=(forwarded_ip2 or fake.ipv6() if random() < 0.5 else None),
- forwarded_ip3=(
- forwarded_ip3 or fake.ipv4_public() if random() < 0.25 else None
- ),
- forwarded_ip4=forwarded_ip4,
- forwarded_ip5=forwarded_ip5,
- forwarded_ip6=forwarded_ip6,
+ product: Product | None = None,
+ amount: USDCent | None = None,
+ ext_ref_id: str | None = None,
+ created: AwareDatetime | None = None,
+ ) -> BrokerageProductPayoutEvent:
+
+ product = product or product_factory()
+ amount = amount or USDCent(randint(1, 99_99))
+
+ return business_payout_event_manager.create_bp_payout_event(
+ thl_ledger_manager=thl_ledger_manager,
+ product=product,
+ amount=amount,
+ ext_ref_id=ext_ref_id or uuid4().hex,
+ created=created,
)
return _inner
-# class AuditLogManager(PostgresManager):
+# --- Audit Log Manager ---
-@pytest.fixture
-def auditlog_factory(audit_log_manager: AuditLogManager):
+@pytest.fixture()
+def audit_log_factory(audit_log_manager: AuditLogManager) -> Callable[..., AuditLog]:
def _inner(
user_id: PositiveInt,
@@ -425,10 +824,180 @@ def auditlog_factory(audit_log_manager: AuditLogManager):
return audit_log_manager.create(
user_id=user_id,
- level=level or rchoice(list(AuditLogLevel)),
- event_type=event_type or rchoice(list(event_types)),
+ level=level or rand_choice(list(AuditLogLevel)),
+ event_type=event_type or rand_choice(list(event_types)),
event_msg=event_msg,
event_value=event_value,
)
return _inner
+
+
+@pytest.fixture()
+def audit_log(audit_log_factory: Callable[..., AuditLog], user: User) -> AuditLog:
+ return audit_log_factory(user_id=user.user_id)
+
+
+# --- ---
+
+
+@pytest.fixture(scope="session")
+def profiling_info_json() -> str:
+ return (
+ '[{"property_label": "hispanic", "cardinality": "*", "prop_type": "i", "country_iso": "us", '
+ '"property_id": "05170ae296ab49178a075cab2a2073a6", "item_id": "7911ec1468b146ee870951f8ae9cbac1", '
+ '"item_label": "panamanian", "gold_standard": 1, "options": [{"id": "c358c11e72c74fa2880358f1d4be85ab", '
+ '"label": "not_hispanic"}, {"id": "b1d6c475770849bc8e0200054975dc9c", "label": "yes_hispanic"}, '
+ '{"id": "bd1eb44495d84b029e107c188003c2bd", "label": "other_hispanic"}, '
+ '{"id": "f290ad5e75bf4f4ea94dc847f57c1bd3", "label": "mexican"}, '
+ '{"id": "49f50f2801bd415ea353063bfc02d252", "label": "puerto_rican"}, '
+ '{"id": "dcbe005e522f4b10928773926601f8bf", "label": "cuban"}, '
+ '{"id": "467ef8ddb7ac4edb88ba9ef817cbb7e9", "label": "salvadoran"}, '
+ '{"id": "3c98e7250707403cba2f4dc7b877c963", "label": "dominican"}, '
+ '{"id": "981ee77f6d6742609825ef54fea824a8", "label": "guatemalan"}, '
+ '{"id": "81c8057b809245a7ae1b8a867ea6c91e", "label": "colombian"}, '
+ '{"id": "513656d5f9e249fa955c3b527d483b93", "label": "honduran"}, '
+ '{"id": "afc8cddd0c7b4581bea24ccd64db3446", "label": "ecuadorian"}, '
+ '{"id": "61f34b36e80747a89d85e1eb17536f84", "label": "argentinian"}, '
+ '{"id": "5330cfa681d44aa8ade3a6d0ea198e44", "label": "peruvian"}, '
+ '{"id": "e7bceaffd76e486596205d8545019448", "label": "nicaraguan"}, '
+ '{"id": "b7bbb2ebf8424714962e6c4f43275985", "label": "spanish"}, '
+ '{"id": "8bf539785e7a487892a2f97e52b1932d", "label": "venezuelan"}, '
+ '{"id": "7911ec1468b146ee870951f8ae9cbac1", "label": "panamanian"}], "category": [{"id": '
+ '"4fd8381d5a1c4409ab007ca254ced084", "label": "Demographic", "path": "/Demographic", '
+ '"adwords_vertical_id": null}]}, {"property_label": "ethnic_group", "cardinality": "*", "prop_type": '
+ '"i", "country_iso": "us", "property_id": "15070958225d4132b7f6674fcfc979f6", "item_id": '
+ '"64b7114cf08143949e3bcc3d00a5d8a0", "item_label": "other_ethnicity", "gold_standard": 1, "options": [{'
+ '"id": "a72e97f4055e4014a22bee4632cbf573", "label": "caucasians"}, '
+ '{"id": "4760353bc0654e46a928ba697b102735", "label": "black_or_african_american"}, '
+ '{"id": "20ff0a2969fa4656bbda5c3e0874e63b", "label": "asian"}, '
+ '{"id": "107e0a79e6b94b74926c44e70faf3793", "label": "native_hawaiian_or_other_pacific_islander"}, '
+ '{"id": "900fa12691d5458c8665bf468f1c98c1", "label": "native_americans"}, '
+ '{"id": "64b7114cf08143949e3bcc3d00a5d8a0", "label": "other_ethnicity"}], "category": [{"id": '
+ '"4fd8381d5a1c4409ab007ca254ced084", "label": "Demographic", "path": "/Demographic", '
+ '"adwords_vertical_id": null}]}, {"property_label": "educational_attainment", "cardinality": "?", '
+ '"prop_type": "i", "country_iso": "us", "property_id": "2637783d4b2b4075b93e2a156e16e1d8", "item_id": '
+ '"934e7b81d6744a1baa31bbc51f0965d5", "item_label": "other_education", "gold_standard": 1, "options": [{'
+ '"id": "df35ef9e474b4bf9af520aa86630202d", "label": "3rd_grade_completion"}, '
+ '{"id": "83763370a1064bd5ba76d1b68c4b8a23", "label": "8th_grade_completion"}, '
+ '{"id": "f0c25a0670c340bc9250099dcce50957", "label": "not_high_school_graduate"}, '
+ '{"id": "02ff74c872bd458983a83847e1a9f8fd", "label": "high_school_completion"}, '
+ '{"id": "ba8beb807d56441f8fea9b490ed7561c", "label": "vocational_program_completion"}, '
+ '{"id": "65373a5f348a410c923e079ddbb58e9b", "label": "some_college_completion"}, '
+ '{"id": "2d15d96df85d4cc7b6f58911fdc8d5e2", "label": "associate_academic_degree_completion"}, '
+ '{"id": "497b1fedec464151b063cd5367643ffa", "label": "bachelors_degree_completion"}, '
+ '{"id": "295133068ac84424ae75e973dc9f2a78", "label": "some_graduate_completion"}, '
+ '{"id": "e64f874faeff4062a5aa72ac483b4b9f", "label": "masters_degree_completion"}, '
+ '{"id": "cbaec19a636d476385fb8e7842b044f5", "label": "doctorate_degree_completion"}, '
+ '{"id": "934e7b81d6744a1baa31bbc51f0965d5", "label": "other_education"}], "category": [{"id": '
+ '"4fd8381d5a1c4409ab007ca254ced084", "label": "Demographic", "path": "/Demographic", '
+ '"adwords_vertical_id": null}]}, {"property_label": "household_spoken_language", "cardinality": "*", '
+ '"prop_type": "i", "country_iso": "us", "property_id": "5a844571073d482a96853a0594859a51", "item_id": '
+ '"62b39c1de141422896ad4ab3c4318209", "item_label": "dut", "gold_standard": 1, "options": [{"id": '
+ '"f65cd57b79d14f0f8460761ce41ec173", "label": "ara"}, {"id": "6d49de1f8f394216821310abd29392d9", '
+ '"label": "zho"}, {"id": "be6dc23c2bf34c3f81e96ddace22800d", "label": "eng"}, '
+ '{"id": "ddc81f28752d47a3b1c1f3b8b01a9b07", "label": "fre"}, {"id": "2dbb67b29bd34e0eb630b1b8385542ca", '
+ '"label": "ger"}, {"id": "a747f96952fc4b9d97edeeee5120091b", "label": "hat"}, '
+ '{"id": "7144b04a3219433baac86273677551fa", "label": "hin"}, {"id": "e07ff3e82c7149eaab7ea2b39ee6a6dc", '
+ '"label": "ita"}, {"id": "b681eff81975432ebfb9f5cc22dedaa3", "label": "jpn"}, '
+ '{"id": "5cb20440a8f64c9ca62fb49c1e80cdef", "label": "kor"}, {"id": "171c4b77d4204bc6ac0c2b81e38a10ff", '
+ '"label": "pan"}, {"id": "8c3ec18e6b6c4a55a00dd6052e8e84fb", "label": "pol"}, '
+ '{"id": "3ce074d81d384dd5b96f1fb48f87bf01", "label": "por"}, {"id": "6138dc951990458fa88a666f6ddd907b", '
+ '"label": "rus"}, {"id": "e66e5ecc07df4ebaa546e0b436f034bd", "label": "spa"}, '
+ '{"id": "5a981b3d2f0d402a96dd2d0392ec2fcb", "label": "tgl"}, {"id": "b446251bd211403487806c4d0a904981", '
+ '"label": "vie"}, {"id": "92fb3ee337374e2db875fb23f52eed46", "label": "xxx"}, '
+ '{"id": "8b1f590f12f24cc1924d7bdcbe82081e", "label": "ind"}, {"id": "bf3f4be556a34ff4b836420149fd2037", '
+ '"label": "tur"}, {"id": "87ca815c43ba4e7f98cbca98821aa508", "label": "zul"}, '
+ '{"id": "0adbf915a7a64d67a87bb3ce5d39ca54", "label": "may"}, {"id": "62b39c1de141422896ad4ab3c4318209", '
+ '"label": "dut"}], "category": [{"id": "4fd8381d5a1c4409ab007ca254ced084", "label": "Demographic", '
+ '"path": "/Demographic", "adwords_vertical_id": null}]}, {"property_label": "gender", "cardinality": '
+ '"?", "prop_type": "i", "country_iso": "us", "property_id": "73175402104741549f21de2071556cd7", '
+ '"item_id": "093593e316344cd3a0ac73669fca8048", "item_label": "other_gender", "gold_standard": 1, '
+ '"options": [{"id": "b9fc5ea07f3a4252a792fd4a49e7b52b", "label": "male"}, '
+ '{"id": "9fdb8e5e18474a0b84a0262c21e17b56", "label": "female"}, '
+ '{"id": "093593e316344cd3a0ac73669fca8048", "label": "other_gender"}], "category": [{"id": '
+ '"4fd8381d5a1c4409ab007ca254ced084", "label": "Demographic", "path": "/Demographic", '
+ '"adwords_vertical_id": null}]}, {"property_label": "age_in_years", "cardinality": "?", "prop_type": '
+ '"n", "country_iso": "us", "property_id": "94f7379437874076b345d76642d4ce6d", "item_id": null, '
+ '"item_label": null, "gold_standard": 1, "category": [{"id": "4fd8381d5a1c4409ab007ca254ced084", '
+ '"label": "Demographic", "path": "/Demographic", "adwords_vertical_id": null}]}, {"property_label": '
+ '"children_age_gender", "cardinality": "*", "prop_type": "i", "country_iso": "us", "property_id": '
+ '"e926142fcea94b9cbbe13dc7891e1e7f", "item_id": "b7b8074e95334b008e8958ccb0a204f1", "item_label": '
+ '"female_18", "gold_standard": 1, "options": [{"id": "16a6448ec24c48d4993d78ebee33f9b4", '
+ '"label": "male_under_1"}, {"id": "809c04cb2e3b4a3bbd8077ab62cdc220", "label": "female_under_1"}, '
+ '{"id": "295e05bb6a0843bc998890b24c99841e", "label": "no_children"}, '
+ '{"id": "142cb948d98c4ae8b0ef2ef10978e023", "label": "male_0"}, '
+ '{"id": "5a5c1b0e9abc48a98b3bc5f817d6e9d0", "label": "male_1"}, '
+ '{"id": "286b1a9afb884bdfb676dbb855479d1e", "label": "male_2"}, '
+ '{"id": "942ca3cda699453093df8cbabb890607", "label": "male_3"}, '
+ '{"id": "995818d432f643ec8dd17e0809b24b56", "label": "male_4"}, '
+ '{"id": "f38f8b57f25f4cdea0f270297a1e7a5c", "label": "male_5"}, '
+ '{"id": "975df709e6d140d1a470db35023c432d", "label": "male_6"}, '
+ '{"id": "f60bd89bbe0f4e92b90bccbc500467c2", "label": "male_7"}, '
+ '{"id": "6714ceb3ed5042c0b605f00b06814207", "label": "male_8"}, '
+ '{"id": "c03c2f8271d443cf9df380e84b4dea4c", "label": "male_9"}, '
+ '{"id": "11690ee0f5a54cb794f7ddd010d74fa2", "label": "male_10"}, '
+ '{"id": "17bef9a9d14b4197b2c5609fa94b0642", "label": "male_11"}, '
+ '{"id": "e79c8338fe28454f89ccc78daf6f409a", "label": "male_12"}, '
+ '{"id": "3a4f87acb3fa41f4ae08dfe2858238c1", "label": "male_13"}, '
+ '{"id": "36ffb79d8b7840a7a8cb8d63bbc8df59", "label": "male_14"}, '
+ '{"id": "1401a508f9664347aee927f6ec5b0a40", "label": "male_15"}, '
+ '{"id": "6e0943c5ec4a4f75869eb195e3eafa50", "label": "male_16"}, '
+ '{"id": "47d4b27b7b5242758a9fff13d3d324cf", "label": "male_17"}, '
+ '{"id": "9ce886459dd44c9395eb77e1386ab181", "label": "female_0"}, '
+ '{"id": "6499ccbf990d4be5b686aec1c7353fd8", "label": "female_1"}, '
+ '{"id": "d85ceaa39f6d492abfc8da49acfd14f2", "label": "female_2"}, '
+ '{"id": "18edb45c138e451d8cb428aefbb80f9c", "label": "female_3"}, '
+ '{"id": "bac6f006ed9f4ccf85f48e91e99fdfd1", "label": "female_4"}, '
+ '{"id": "5a6a1a8ad00c4ce8be52dcb267b034ff", "label": "female_5"}, '
+ '{"id": "6bff0acbf6364c94ad89507bcd5f4f45", "label": "female_6"}, '
+ '{"id": "d0d56a0a6b6f4516a366a2ce139b4411", "label": "female_7"}, '
+ '{"id": "bda6028468044b659843e2bef4db2175", "label": "female_8"}, '
+ '{"id": "dbb6d50325464032b456357b1a6e5e9c", "label": "female_9"}, '
+ '{"id": "b87a93d7dc1348edac5e771684d63fb8", "label": "female_10"}, '
+ '{"id": "11449d0d98f14e27ba47de40b18921d7", "label": "female_11"}, '
+ '{"id": "16156501e97b4263962cbbb743840292", "label": "female_12"}, '
+ '{"id": "04ee971c89a345cc8141a45bce96050c", "label": "female_13"}, '
+ '{"id": "e818d310bfbc4faba4355e5d2ed49d4f", "label": "female_14"}, '
+ '{"id": "440d25e078924ba0973163153c417ed6", "label": "female_15"}, '
+ '{"id": "78ff804cc9b441c5a524bd91e3d1f8bf", "label": "female_16"}, '
+ '{"id": "4b04d804d7d84786b2b1c22e4ed440f5", "label": "female_17"}, '
+ '{"id": "28bc848cd3ff44c3893c76bfc9bc0c4e", "label": "male_18"}, '
+ '{"id": "b7b8074e95334b008e8958ccb0a204f1", "label": "female_18"}], "category": [{"id": '
+ '"e18ba6e9d51e482cbb19acf2e6f505ce", "label": "Parenting", "path": "/People & Society/Family & '
+ 'Relationships/Family/Parenting", "adwords_vertical_id": "58"}]}, {"property_label": "home_postal_code", '
+ '"cardinality": "?", "prop_type": "x", "country_iso": "us", "property_id": '
+ '"f3b32ebe78014fbeb1ed6ff77d6338bf", "item_id": null, "item_label": null, "gold_standard": 1, '
+ '"category": [{"id": "4fd8381d5a1c4409ab007ca254ced084", "label": "Demographic", "path": "/Demographic", '
+ '"adwords_vertical_id": null}]}, {"property_label": "household_income", "cardinality": "?", "prop_type": '
+ '"n", "country_iso": "us", "property_id": "ff5b1d4501d5478f98de8c90ef996ac1", "item_id": null, '
+ '"item_label": null, "gold_standard": 1, "category": [{"id": "4fd8381d5a1c4409ab007ca254ced084", '
+ '"label": "Demographic", "path": "/Demographic", "adwords_vertical_id": null}]}]'
+ )
+
+
+@pytest.fixture(scope="session")
+def profiling_user_info_json() -> str:
+ return (
+ '{"user_profile_knowledge": [], "marketplace_profile_knowledge": [{"source": "d", "question_id": '
+ '"1", "answer": ["1"], "created": "2023-11-07T16:41:05.234096Z"}, {"source": "pr", '
+ '"question_id": "3", "answer": ["1"], "created": "2023-11-07T16:41:05.234096Z"}, {"source": '
+ '"h", "question_id": "60", "answer": ["58"], "created": "2023-11-07T16:41:05.234096Z"}, '
+ '{"source": "c", "question_id": "43", "answer": ["1"], "created": "2023-11-07T16:41:05.234096Z"}, '
+ '{"source": "s", "question_id": "211", "answer": ["111"], "created": '
+ '"2023-11-07T16:41:05.234096Z"}, {"source": "s", "question_id": "1843", "answer": ["111"], '
+ '"created": "2023-11-07T16:41:05.234096Z"}, {"source": "h", "question_id": "13959", "answer": ['
+ '"244155"], "created": "2023-11-07T16:41:05.234096Z"}, {"source": "c", "question_id": "33092", '
+ '"answer": ["1"], "created": "2023-11-07T16:41:05.234096Z"}, {"source": "c", "question_id": "gender", '
+ '"answer": ["10682"], "created": "2023-11-07T16:41:05.234096Z"}, {"source": "e", "question_id": '
+ '"gender", "answer": ["male"], "created": "2023-11-07T16:41:05.234096Z"}, {"source": "f", '
+ '"question_id": "gender", "answer": ["male"], "created": "2023-11-07T16:41:05.234096Z"}, {"source": '
+ '"i", "question_id": "gender", "answer": ["1"], "created": "2023-11-07T16:41:05.234096Z"}, '
+ '{"source": "c", "question_id": "137510", "answer": ["1"], "created": "2023-11-07T16:41:05.234096Z"}, '
+ '{"source": "m", "question_id": "gender", "answer": ["1"], "created": '
+ '"2023-11-07T16:41:05.234096Z"}, {"source": "o", "question_id": "gender", "answer": ["male"], '
+ '"created": "2023-11-07T16:41:05.234096Z"}, {"source": "c", "question_id": "gender_plus", "answer": ['
+ '"7657644"], "created": "2023-11-07T16:41:05.234096Z"}, {"source": "i", "question_id": '
+ '"gender_plus", "answer": ["1"], "created": "2023-11-07T16:41:05.234096Z"}, {"source": "c", '
+ '"question_id": "income_level", "answer": ["9071"], "created": "2023-11-07T16:41:05.234096Z"}]}'
+ )
diff --git a/test_utils/models/upk/conftest.py b/test_utils/models/upk/conftest.py
index c8855da..ad96bbb 100644
--- a/test_utils/models/upk/conftest.py
+++ b/test_utils/models/upk/conftest.py
@@ -2,17 +2,15 @@ from __future__ import annotations
import os
import time
-from typing import TYPE_CHECKING
+from collections.abc import Callable
from uuid import UUID
import pandas as pd
import pytest
+from generalresearch.managers.thl.category import CategoryManager
from generalresearch.pg_helper import PostgresConfig
-if TYPE_CHECKING:
- from generalresearch.managers.thl.category import CategoryManager
-
def insert_data_from_csv(
thl_web_rw: PostgresConfig,
@@ -169,9 +167,13 @@ def upk_data(
propertymarketplaceassociation_data,
propertyitemrange_data,
question_data,
-) -> None:
- # Wait a second to make sure the HarmonizerCache refresh loop pulls these in
- time.sleep(2)
+) -> Callable[..., None]:
+
+ def _inner():
+ # Wait a second to make sure the HarmonizerCache refresh loop pulls these in
+ time.sleep(2)
+
+ return _inner
def test_fixtures(upk_data):
diff --git a/test_utils/managers/network/__init__.py b/test_utils/precision/__init__.py
index e69de29..e69de29 100644
--- a/test_utils/managers/network/__init__.py
+++ b/test_utils/precision/__init__.py
diff --git a/test_utils/precision/conftest.py b/test_utils/precision/conftest.py
new file mode 100644
index 0000000..7acfe6a
--- /dev/null
+++ b/test_utils/precision/conftest.py
@@ -0,0 +1,129 @@
+from typing import Any
+
+import pytest
+
+
+@pytest.fixture(scope="session")
+def precision_survey_json() -> dict[str, Any]:
+ return {
+ "cpi": "1.44",
+ "country_isos": "ca",
+ "language_isos": "eng",
+ "country_iso": "ca",
+ "language_iso": "eng",
+ "buyer_id": "7047",
+ "bid_loi": 1200,
+ "bid_ir": 0.45,
+ "source": "e",
+ "used_question_ids": ["age", "country_iso", "gender", "gender_1"],
+ "survey_id": "0000",
+ "group_id": "633473",
+ "status": "open",
+ "name": "beauty survey",
+ "survey_guid": "c7f375c5077d4c6c8209ff0b539d7183",
+ "category_id": "-1",
+ "global_conversion": None,
+ "desired_count": 96,
+ "achieved_count": 0,
+ "allowed_devices": "1,2,3",
+ "entry_link": "https://www.opinionetwork.com/survey/entry.aspx?mid=[%MID%]&project=633473&key=%%key%%",
+ "excluded_surveys": "470358,633286",
+ "quotas": [
+ {
+ "name": "25-34,Male,Quebec",
+ "id": "2324110",
+ "guid": "23b5760d24994bc08de451b3e62e77c7",
+ "status": "open",
+ "desired_count": 48,
+ "achieved_count": 0,
+ "termination_count": 0,
+ "overquota_count": 0,
+ "condition_hashes": ["b41e1a3", "bc89ee8", "4124366", "9f32c61"],
+ },
+ {
+ "name": "25-34,Female,Quebec",
+ "id": "2324111",
+ "guid": "0706f1a88d7e4f11ad847c03012e68d2",
+ "status": "open",
+ "desired_count": 48,
+ "achieved_count": 0,
+ "termination_count": 4,
+ "overquota_count": 0,
+ "condition_hashes": ["b41e1a3", "0cdc304", "500af2c", "9f32c61"],
+ },
+ ],
+ "conditions": {
+ "b41e1a3": {
+ "logical_operator": "OR",
+ "value_type": 1,
+ "negate": False,
+ "question_id": "country_iso",
+ "values": ["ca"],
+ "criterion_hash": "b41e1a3",
+ "value_len": 1,
+ "sizeof": 2,
+ },
+ "bc89ee8": {
+ "logical_operator": "OR",
+ "value_type": 1,
+ "negate": False,
+ "question_id": "gender",
+ "values": ["male"],
+ "criterion_hash": "bc89ee8",
+ "value_len": 1,
+ "sizeof": 4,
+ },
+ "4124366": {
+ "logical_operator": "OR",
+ "value_type": 1,
+ "negate": False,
+ "question_id": "gender_1",
+ "values": ["male"],
+ "criterion_hash": "4124366",
+ "value_len": 1,
+ "sizeof": 4,
+ },
+ "9f32c61": {
+ "logical_operator": "OR",
+ "value_type": 1,
+ "negate": False,
+ "question_id": "age",
+ "values": ["25", "26", "27", "28", "29", "30", "31", "32", "33", "34"],
+ "criterion_hash": "9f32c61",
+ "value_len": 10,
+ "sizeof": 20,
+ },
+ "0cdc304": {
+ "logical_operator": "OR",
+ "value_type": 1,
+ "negate": False,
+ "question_id": "gender",
+ "values": ["female"],
+ "criterion_hash": "0cdc304",
+ "value_len": 1,
+ "sizeof": 6,
+ },
+ "500af2c": {
+ "logical_operator": "OR",
+ "value_type": 1,
+ "negate": False,
+ "question_id": "gender_1",
+ "values": ["female"],
+ "criterion_hash": "500af2c",
+ "value_len": 1,
+ "sizeof": 6,
+ },
+ },
+ "expected_end_date": "2024-06-28T10:40:33.000000Z",
+ "created": None,
+ "updated": None,
+ "is_live": True,
+ "all_hashes": [
+ "0cdc304",
+ "b41e1a3",
+ "9f32c61",
+ "bc89ee8",
+ "4124366",
+ "500af2c",
+ ],
+ }
diff --git a/test_utils/spectrum/conftest.py b/test_utils/spectrum/conftest.py
index 0afc3f5..cc91cff 100644
--- a/test_utils/spectrum/conftest.py
+++ b/test_utils/spectrum/conftest.py
@@ -1,7 +1,9 @@
-import logging
+from __future__ import annotations
+
import time
-from datetime import datetime, timezone
-from typing import TYPE_CHECKING
+from datetime import UTC, datetime
+from decimal import Decimal
+from typing import TYPE_CHECKING, Any
import pytest
@@ -9,21 +11,24 @@ from generalresearch.managers.spectrum.survey import (
SpectrumCriteriaManager,
SpectrumSurveyManager,
)
-from generalresearch.models.spectrum.survey import SpectrumSurvey
+from generalresearch.models.definitions import (
+ LogicalOperator,
+)
+from generalresearch.models.spectrum.survey import (
+ SpectrumCondition,
+ SpectrumSurvey,
+)
+from generalresearch.models.thl.survey.condition import ConditionValueType
from generalresearch.sql_helper import SqlHelper
-from .surveys_json import CONDITIONS, SURVEYS_JSON
-
if TYPE_CHECKING:
from generalresearch.config import GRLBaseSettings
@pytest.fixture(scope="session")
-def spectrum_rw(settings: "GRLBaseSettings") -> SqlHelper:
- logging.info(f"{settings.spectrum_rw_db=}")
-
+def spectrum_rw(settings: GRLBaseSettings) -> SqlHelper:
assert settings.spectrum_rw_db is not None
- assert "/unittest-" in settings.spectrum_rw_db.path
+ assert "/unittest-" in str(settings.spectrum_rw_db.path)
return SqlHelper(
dsn=settings.spectrum_rw_db,
@@ -35,27 +40,36 @@ def spectrum_rw(settings: "GRLBaseSettings") -> SqlHelper:
@pytest.fixture(scope="session")
def spectrum_criteria_manager(spectrum_rw: SqlHelper) -> SpectrumCriteriaManager:
+ assert spectrum_rw.dsn
+ assert spectrum_rw.dsn.path
assert "/unittest-" in spectrum_rw.dsn.path
return SpectrumCriteriaManager(spectrum_rw)
@pytest.fixture(scope="session")
def spectrum_survey_manager(spectrum_rw: SqlHelper) -> SpectrumSurveyManager:
+ assert spectrum_rw.dsn
+ assert spectrum_rw.dsn.path
assert "/unittest-" in spectrum_rw.dsn.path
return SpectrumSurveyManager(spectrum_rw)
@pytest.fixture(scope="session")
def setup_spectrum_surveys(
- spectrum_rw: SqlHelper, spectrum_survey_manager, spectrum_criteria_manager
+ spectrum_rw: SqlHelper,
+ spectrum_survey_manager: SpectrumSurveyManager,
+ spectrum_criteria_manager: SpectrumCriteriaManager,
+ spectrum_conditions: list[SpectrumCondition],
+ spectrum_api_surveys_json: list[str],
) -> None:
- now = datetime.now(timezone.utc)
+ now = datetime.now(UTC)
# make sure these example surveys exist in db
- surveys = [SpectrumSurvey.model_validate_json(x) for x in SURVEYS_JSON]
+ surveys = [SpectrumSurvey.model_validate_json(x) for x in spectrum_api_surveys_json]
for s in surveys:
- s.modified_api = datetime.now(tz=timezone.utc)
+ s.modified_api = datetime.now(tz=UTC)
+
spectrum_survey_manager.create_or_update(surveys)
- spectrum_criteria_manager.update(CONDITIONS)
+ spectrum_criteria_manager.update(spectrum_conditions)
# and make sure they have allocation for 687
spectrum_rw.execute_sql_query(
@@ -66,10 +80,10 @@ def setup_spectrum_surveys(
["687", "GRL", "x", "x", "x", "x"],
commit=True,
)
- supplier687_pk = spectrum_rw.execute_sql_query(
- f"""
- select id from `{spectrum_rw.db}`.spectrum_supplier where supplier_id = '687'"""
- )[0]["id"]
+ supplier687_pk = spectrum_rw.execute_sql_query(f"""
+ select id from `{spectrum_rw.db}`.spectrum_supplier where supplier_id = '687'""")[
+ 0
+ ]["id"]
conn = spectrum_rw.make_connection()
c = conn.cursor()
c.executemany(
@@ -83,3 +97,206 @@ def setup_spectrum_surveys(
conn.commit()
# Wait a second to make sure the spectrum-grpc pulls these from the db into global-vars
time.sleep(1)
+
+
+@pytest.fixture(scope="session")
+def spectrum_api_surveys_json() -> list[str]:
+ return [
+ (
+ '{"cpi":"3.90","country_isos":["us"],"language_isos":["eng"],"buyer_id":"215","bid_loi":780,"source":"s",'
+ '"used_question_ids":["1235","212"],"survey_id":"111111","survey_name":"Exciting New Survey #14472374",'
+ '"status":22,"field_end_date":"2023-03-02T07:05:36.261000Z","category_code":"232","calculation_type":"COMPLETES",'
+ '"requires_pii":false,"survey_exclusions":"13947261,14126487,14361592,14376811,14385771,14387789,14472374",'
+ '"exclusion_period":30,"bid_ir":0.2,"overall_loi":null,"overall_ir":null,"last_block_loi":null,'
+ '"last_block_ir":null,"project_last_complete_date":null,"country_iso":"us","language_iso":"eng",'
+ '"include_psids":null,"exclude_psids":null'
+ ',"qualifications":["ee5e842","e6e0b0b"],"quotas":[{"remaining_count":100,'
+ '"condition_hashes":["32cbf31"]}],"conditions":null,"created_api":"2023-02-28T07:05:36.698000Z",'
+ '"modified_api":"2024-03-10T09:43:40.030000Z","updated":"2024-05-30T21:52:46.431612Z","is_live":true'
+ "}"
+ ),
+ (
+ '{"cpi":"3.90","country_isos":["us"],"language_isos":["eng"],"buyer_id":"215","bid_loi":780,"source":"s",'
+ '"used_question_ids":["1235","212"],"survey_id":"14472374","survey_name":"Exciting New Survey #14472374",'
+ '"status":22,"field_end_date":"2023-03-02T07:05:36.261000Z","category_code":"232","calculation_type":"COMPLETES",'
+ '"requires_pii":false,"survey_exclusions":"13947261,14126487,14361592,14376811,14385771,14387789,14472374",'
+ '"exclusion_period":30,"bid_ir":0.2,"overall_loi":null,"overall_ir":null,"last_block_loi":null,'
+ '"last_block_ir":null,"project_last_complete_date":null,"country_iso":"us","language_iso":"eng",'
+ '"include_psids":null,"exclude_psids":"0408319875e9dbffdc09e86671ad5636,23c4c66ecbc465906d0b0fd798740e64,'
+ '861df4603df3b7f754b8d4b89cbdb313","qualifications":["ee5e842","e6e0b0b"],"quotas":[{"remaining_count":100,'
+ '"condition_hashes":["32cbf31"]}],"conditions":null,"created_api":"2023-02-28T07:05:36.698000Z",'
+ '"modified_api":"2024-03-10T09:43:40.030000Z","updated":"2024-05-30T21:52:46.431612Z","is_live":true'
+ "}"
+ ),
+ (
+ '{"cpi":"3.90","country_isos":["us"],"language_isos":["eng"],"buyer_id":"215","bid_loi":780,"source":"s",'
+ '"used_question_ids":["1235","212"],"survey_id":"12345","survey_name":"Exciting New Survey #14472374",'
+ '"status":22,"field_end_date":"2023-03-02T07:05:36.261000Z","category_code":"232","calculation_type":"COMPLETES",'
+ '"requires_pii":false,"survey_exclusions":"13947261,14126487,14361592,14376811,14385771,14387789,14472374",'
+ '"exclusion_period":30,"bid_ir":0.2,"overall_loi":null,"overall_ir":null,"last_block_loi":null,'
+ '"last_block_ir":null,"project_last_complete_date":null,"country_iso":"us","language_iso":"eng",'
+ '"include_psids":"7d043991b1494dbbb57786b11c88239c","exclude_psids":null'
+ ',"qualifications":["ee5e842","e6e0b0b"],"quotas":[{"remaining_count":100,'
+ '"condition_hashes":["32cbf31"]}],"conditions":null,"created_api":"2023-02-28T07:05:36.698000Z",'
+ '"modified_api":"2024-03-10T09:43:40.030000Z","updated":"2024-05-30T21:52:46.431612Z","is_live":true'
+ "}"
+ ),
+ (
+ '{"cpi":"1.40","country_isos":["us"],"language_isos":["eng"],"buyer_id":"233","bid_loi":null,"source":"s",'
+ '"used_question_ids":["245","244","212","211","225"],"survey_id":"14970164","survey_name":"Exciting New Survey '
+ '#14970164","status":22,"field_end_date":"2024-05-07T16:18:33.000000Z","category_code":"232",'
+ '"calculation_type":"COMPLETES","requires_pii":false,"survey_exclusions":"14970164,29690277",'
+ '"exclusion_period":30,"bid_ir":null,"overall_loi":900,"overall_ir":0.56,"last_block_loi":600,'
+ '"last_block_ir":0.01,"project_last_complete_date":"2024-05-28T04:12:56.297000Z","country_iso":"us",'
+ '"language_iso":"eng","include_psids":null,"exclude_psids":"01c7156fd9639737effbbdebd7fd66f6,'
+ "0508b88f4991bac8b10e9de74ce80194,0a51c627d77cef41f802e51a00126697,15b888176ac4781c2c978a9a05c396f8,"
+ "17bc146b4f7fb05c7058d25da70c6a44,29935289c1f86a4144aab2e12652f305,2fe9d1d451efca10eba4fa4e5e2b74c9,"
+ "c3527b7ef570a1571ea19870f3c25600,cdf2771d57cda9f1bf334382b2b7afd8,cebf3ec50395d973310ea526457dd5a0,"
+ "cf3877cfc15e2e6ef2a56a7a7a37f3d3,dfa691e6d060e3643d5731df30be9f69,e0cb49537182660826aa351e1187809f,"
+ 'edb6d280113ca49561f25fdcb500fde6,fbfba66cfad602f1c26e61e6174eb1f7,fd4307b16fd15e8534a4551c9b6872fc",'
+ '"qualifications":["1ab337d","a01aa68","437774f","dc6065b","82b6ad6"],"quotas":[{"remaining_count":242,'
+ '"condition_hashes":["c23c0b9"]},{"remaining_count":0,"condition_hashes":["5b8c6cf"]},{"remaining_count":126,'
+ '"condition_hashes":["ac35a6e"]},{"remaining_count":110,"condition_hashes":["5e7e5aa"]},{"remaining_count":108,'
+ '"condition_hashes":["9a7aef3"]},{"remaining_count":127,"condition_hashes":["4f75127"]},{"remaining_count":0,'
+ '"condition_hashes":["95437ed"]},{"remaining_count":17,"condition_hashes":["b4b7b95"]},{"remaining_count":16,'
+ '"condition_hashes":["0ab0ae6"]},{"remaining_count":8,"condition_hashes":["6e86fb5"]},{"remaining_count":12,'
+ '"condition_hashes":["24de31e"]},{"remaining_count":69,"condition_hashes":["6bdf350"]},{"remaining_count":411,'
+ '"condition_hashes":["c94d422"]}],"conditions":null,"created_api":"2023-03-30T22:47:36.324000Z",'
+ '"modified_api":"2024-05-30T13:07:16.489000Z","updated":"2024-05-30T21:52:37.493282Z","is_live":true,'
+ '"all_hashes":["c94d422","b4b7b95","6bdf350","6e86fb5","82b6ad6","24de31e","1ab337d","c23c0b9","9a7aef3",'
+ '"ac35a6e","95437ed","5b8c6cf","437774f","a01aa68","5e7e5aa","4f75127","0ab0ae6","dc6065b"]}'
+ ),
+ (
+ '{"cpi":"1.23","country_isos":["au"],"language_isos":["eng"],"buyer_id":"215","bid_loi":780,"source":"s",'
+ '"used_question_ids":[],"survey_id":"69420","survey_name":"Everyone is eligible AU",'
+ '"status":22,"field_end_date":"2023-03-02T07:05:36.261000Z","category_code":"232","calculation_type":"COMPLETES",'
+ '"requires_pii":false,"survey_exclusions":"13947261,14126487,14361592,14376811,14385771,14387789,14472374",'
+ '"exclusion_period":30,"bid_ir":0.2,"overall_loi":null,"overall_ir":null,"last_block_loi":null,'
+ '"last_block_ir":null,"project_last_complete_date":null,"country_iso":"au","language_iso":"eng",'
+ '"include_psids":null,"exclude_psids":null'
+ ',"qualifications":[],"quotas":[{"remaining_count":100,'
+ '"condition_hashes":[]}],"conditions":null,"created_api":"2023-02-28T07:05:36.698000Z",'
+ '"modified_api":"2024-03-10T09:43:40.030000Z","updated":"2024-05-30T21:52:46.431612Z","is_live":true'
+ "}"
+ ),
+ (
+ '{"cpi":"1.23","country_isos":["us"],"language_isos":["eng"],"buyer_id":"215","bid_loi":780,"source":"s",'
+ '"used_question_ids":[],"survey_id":"69421","survey_name":"Everyone is eligible US",'
+ '"status":22,"field_end_date":"2023-03-02T07:05:36.261000Z","category_code":"232","calculation_type":"COMPLETES",'
+ '"requires_pii":false,"survey_exclusions":"13947261,14126487,14361592,14376811,14385771,14387789,14472374",'
+ '"exclusion_period":30,"bid_ir":0.2,"overall_loi":null,"overall_ir":null,"last_block_loi":null,'
+ '"last_block_ir":null,"project_last_complete_date":null,"country_iso":"us","language_iso":"eng",'
+ '"include_psids":null,"exclude_psids":null'
+ ',"qualifications":[],"quotas":[{"remaining_count":100,'
+ '"condition_hashes":[]}],"conditions":null,"created_api":"2023-02-28T07:05:36.698000Z",'
+ '"modified_api":"2024-03-10T09:43:40.030000Z","updated":"2024-05-30T21:52:46.431612Z","is_live":true'
+ "}"
+ ),
+ # For partial eligibility
+ (
+ '{"cpi":"1.23","country_isos":["us"],"language_isos":["eng"],"buyer_id":"215","bid_loi":780,"source":"s",'
+ '"used_question_ids":["1031", "212"],"survey_id":"999000","survey_name":"Pet owners",'
+ '"status":22,"field_end_date":"2023-03-02T07:05:36.261000Z","category_code":"232","calculation_type":"COMPLETES",'
+ '"requires_pii":false,"survey_exclusions":"13947261",'
+ '"exclusion_period":30,"bid_ir":0.2,"overall_loi":null,"overall_ir":null,"last_block_loi":null,'
+ '"last_block_ir":null,"project_last_complete_date":null,"country_iso":"us","language_iso":"eng",'
+ '"include_psids":null,"exclude_psids":null'
+ ',"qualifications":["0039b0c", "00f60a8"],"quotas":[{"remaining_count":100,'
+ '"condition_hashes":[]}],"conditions":null,"created_api":"2023-02-28T07:05:36.698000Z",'
+ '"modified_api":"2024-03-10T09:43:40.030000Z","updated":"2024-05-30T21:52:46.431612Z","is_live":true'
+ "}"
+ ),
+ ]
+
+
+def spectrum_conditions() -> list[SpectrumCondition]:
+ # make sure hashes for 111111 are in db
+ c1 = SpectrumCondition(
+ question_id="1001",
+ value_type=ConditionValueType.LIST,
+ values=["a", "b", "c"],
+ negate=False,
+ logical_operator=LogicalOperator.OR,
+ )
+ c2 = SpectrumCondition(
+ question_id="1001",
+ value_type=ConditionValueType.LIST,
+ values=["a"],
+ negate=False,
+ logical_operator=LogicalOperator.OR,
+ )
+ c3 = SpectrumCondition(
+ question_id="1002",
+ value_type=ConditionValueType.RANGE,
+ values=["18-24", "30-32"],
+ negate=False,
+ logical_operator=LogicalOperator.OR,
+ )
+ c4 = SpectrumCondition(
+ question_id="212",
+ value_type=ConditionValueType.LIST,
+ values=["23", "24"],
+ negate=False,
+ logical_operator=LogicalOperator.OR,
+ )
+ c5 = SpectrumCondition(
+ question_id="1031",
+ value_type=ConditionValueType.LIST,
+ values=["113", "114", "121"],
+ negate=False,
+ logical_operator=LogicalOperator.OR,
+ )
+ return [c1, c2, c3, c4, c5]
+
+
+@pytest.fixture(scope="session")
+def spectrum_api_survey_json() -> dict[str, Any]:
+ return {
+ "survey_id": 29333264,
+ "survey_name": "#29333264",
+ "survey_status": 22,
+ "field_end_date": datetime(2024, 5, 23, 18, 18, 31, tzinfo=UTC),
+ "category": "Exciting New",
+ "category_code": 232,
+ "crtd_on": datetime(2024, 5, 20, 17, 48, 13, tzinfo=UTC),
+ "mod_on": datetime(2024, 5, 20, 18, 18, 31, tzinfo=UTC),
+ "soft_launch": False,
+ "click_balancing": 0,
+ "price_type": 1,
+ "pii": False,
+ "buyer_message": "",
+ "buyer_id": 4726,
+ "incl_excl": 0,
+ "cpi": Decimal("1.20"),
+ "last_complete_date": None,
+ "project_last_complete_date": None,
+ "quotas": [
+ {
+ "quota_id": "c2bc961e-4f26-4223-b409-ebe9165cfdf5",
+ "quantities": {"currently_open": 491, "remaining": 495, "achieved": 0},
+ "criteria": [
+ {
+ "qualification_code": 214,
+ "range_sets": [{"units": 311, "to": 64, "from": 18}],
+ }
+ ],
+ }
+ ],
+ "qualifications": [
+ {
+ "range_sets": [{"units": 311, "to": 64, "from": 18}],
+ "qualification_code": 212,
+ },
+ {"condition_codes": ["111", "117", "112"], "qualification_code": 1202},
+ ],
+ "country_iso": "fr",
+ "language_iso": "fre",
+ "bid_ir": 0.4,
+ "bid_loi": 600,
+ "overall_ir": None,
+ "overall_loi": None,
+ "last_block_ir": None,
+ "last_block_loi": None,
+ "survey_exclusions": set(),
+ "exclusion_period": 0,
+ }
diff --git a/test_utils/spectrum/surveys_json.py b/test_utils/spectrum/surveys_json.py
deleted file mode 100644
index eb747a5..0000000
--- a/test_utils/spectrum/surveys_json.py
+++ /dev/null
@@ -1,140 +0,0 @@
-from generalresearch.models import LogicalOperator
-from generalresearch.models.spectrum.survey import (
- SpectrumCondition,
- SpectrumSurvey,
-)
-from generalresearch.models.thl.survey.condition import ConditionValueType
-
-SURVEYS_JSON = [
- '{"cpi":"3.90","country_isos":["us"],"language_isos":["eng"],"buyer_id":"215","bid_loi":780,"source":"s",'
- '"used_question_ids":["1235","212"],"survey_id":"111111","survey_name":"Exciting New Survey #14472374",'
- '"status":22,"field_end_date":"2023-03-02T07:05:36.261000Z","category_code":"232","calculation_type":"COMPLETES",'
- '"requires_pii":false,"survey_exclusions":"13947261,14126487,14361592,14376811,14385771,14387789,14472374",'
- '"exclusion_period":30,"bid_ir":0.2,"overall_loi":null,"overall_ir":null,"last_block_loi":null,'
- '"last_block_ir":null,"project_last_complete_date":null,"country_iso":"us","language_iso":"eng",'
- '"include_psids":null,"exclude_psids":null'
- ',"qualifications":["ee5e842","e6e0b0b"],"quotas":[{"remaining_count":100,'
- '"condition_hashes":["32cbf31"]}],"conditions":null,"created_api":"2023-02-28T07:05:36.698000Z",'
- '"modified_api":"2024-03-10T09:43:40.030000Z","updated":"2024-05-30T21:52:46.431612Z","is_live":true'
- "}",
- '{"cpi":"3.90","country_isos":["us"],"language_isos":["eng"],"buyer_id":"215","bid_loi":780,"source":"s",'
- '"used_question_ids":["1235","212"],"survey_id":"14472374","survey_name":"Exciting New Survey #14472374",'
- '"status":22,"field_end_date":"2023-03-02T07:05:36.261000Z","category_code":"232","calculation_type":"COMPLETES",'
- '"requires_pii":false,"survey_exclusions":"13947261,14126487,14361592,14376811,14385771,14387789,14472374",'
- '"exclusion_period":30,"bid_ir":0.2,"overall_loi":null,"overall_ir":null,"last_block_loi":null,'
- '"last_block_ir":null,"project_last_complete_date":null,"country_iso":"us","language_iso":"eng",'
- '"include_psids":null,"exclude_psids":"0408319875e9dbffdc09e86671ad5636,23c4c66ecbc465906d0b0fd798740e64,'
- '861df4603df3b7f754b8d4b89cbdb313","qualifications":["ee5e842","e6e0b0b"],"quotas":[{"remaining_count":100,'
- '"condition_hashes":["32cbf31"]}],"conditions":null,"created_api":"2023-02-28T07:05:36.698000Z",'
- '"modified_api":"2024-03-10T09:43:40.030000Z","updated":"2024-05-30T21:52:46.431612Z","is_live":true'
- "}",
- '{"cpi":"3.90","country_isos":["us"],"language_isos":["eng"],"buyer_id":"215","bid_loi":780,"source":"s",'
- '"used_question_ids":["1235","212"],"survey_id":"12345","survey_name":"Exciting New Survey #14472374",'
- '"status":22,"field_end_date":"2023-03-02T07:05:36.261000Z","category_code":"232","calculation_type":"COMPLETES",'
- '"requires_pii":false,"survey_exclusions":"13947261,14126487,14361592,14376811,14385771,14387789,14472374",'
- '"exclusion_period":30,"bid_ir":0.2,"overall_loi":null,"overall_ir":null,"last_block_loi":null,'
- '"last_block_ir":null,"project_last_complete_date":null,"country_iso":"us","language_iso":"eng",'
- '"include_psids":"7d043991b1494dbbb57786b11c88239c","exclude_psids":null'
- ',"qualifications":["ee5e842","e6e0b0b"],"quotas":[{"remaining_count":100,'
- '"condition_hashes":["32cbf31"]}],"conditions":null,"created_api":"2023-02-28T07:05:36.698000Z",'
- '"modified_api":"2024-03-10T09:43:40.030000Z","updated":"2024-05-30T21:52:46.431612Z","is_live":true'
- "}",
- '{"cpi":"1.40","country_isos":["us"],"language_isos":["eng"],"buyer_id":"233","bid_loi":null,"source":"s",'
- '"used_question_ids":["245","244","212","211","225"],"survey_id":"14970164","survey_name":"Exciting New Survey '
- '#14970164","status":22,"field_end_date":"2024-05-07T16:18:33.000000Z","category_code":"232",'
- '"calculation_type":"COMPLETES","requires_pii":false,"survey_exclusions":"14970164,29690277",'
- '"exclusion_period":30,"bid_ir":null,"overall_loi":900,"overall_ir":0.56,"last_block_loi":600,'
- '"last_block_ir":0.01,"project_last_complete_date":"2024-05-28T04:12:56.297000Z","country_iso":"us",'
- '"language_iso":"eng","include_psids":null,"exclude_psids":"01c7156fd9639737effbbdebd7fd66f6,'
- "0508b88f4991bac8b10e9de74ce80194,0a51c627d77cef41f802e51a00126697,15b888176ac4781c2c978a9a05c396f8,"
- "17bc146b4f7fb05c7058d25da70c6a44,29935289c1f86a4144aab2e12652f305,2fe9d1d451efca10eba4fa4e5e2b74c9,"
- "c3527b7ef570a1571ea19870f3c25600,cdf2771d57cda9f1bf334382b2b7afd8,cebf3ec50395d973310ea526457dd5a0,"
- "cf3877cfc15e2e6ef2a56a7a7a37f3d3,dfa691e6d060e3643d5731df30be9f69,e0cb49537182660826aa351e1187809f,"
- 'edb6d280113ca49561f25fdcb500fde6,fbfba66cfad602f1c26e61e6174eb1f7,fd4307b16fd15e8534a4551c9b6872fc",'
- '"qualifications":["1ab337d","a01aa68","437774f","dc6065b","82b6ad6"],"quotas":[{"remaining_count":242,'
- '"condition_hashes":["c23c0b9"]},{"remaining_count":0,"condition_hashes":["5b8c6cf"]},{"remaining_count":126,'
- '"condition_hashes":["ac35a6e"]},{"remaining_count":110,"condition_hashes":["5e7e5aa"]},{"remaining_count":108,'
- '"condition_hashes":["9a7aef3"]},{"remaining_count":127,"condition_hashes":["4f75127"]},{"remaining_count":0,'
- '"condition_hashes":["95437ed"]},{"remaining_count":17,"condition_hashes":["b4b7b95"]},{"remaining_count":16,'
- '"condition_hashes":["0ab0ae6"]},{"remaining_count":8,"condition_hashes":["6e86fb5"]},{"remaining_count":12,'
- '"condition_hashes":["24de31e"]},{"remaining_count":69,"condition_hashes":["6bdf350"]},{"remaining_count":411,'
- '"condition_hashes":["c94d422"]}],"conditions":null,"created_api":"2023-03-30T22:47:36.324000Z",'
- '"modified_api":"2024-05-30T13:07:16.489000Z","updated":"2024-05-30T21:52:37.493282Z","is_live":true,'
- '"all_hashes":["c94d422","b4b7b95","6bdf350","6e86fb5","82b6ad6","24de31e","1ab337d","c23c0b9","9a7aef3",'
- '"ac35a6e","95437ed","5b8c6cf","437774f","a01aa68","5e7e5aa","4f75127","0ab0ae6","dc6065b"]}',
- '{"cpi":"1.23","country_isos":["au"],"language_isos":["eng"],"buyer_id":"215","bid_loi":780,"source":"s",'
- '"used_question_ids":[],"survey_id":"69420","survey_name":"Everyone is eligible AU",'
- '"status":22,"field_end_date":"2023-03-02T07:05:36.261000Z","category_code":"232","calculation_type":"COMPLETES",'
- '"requires_pii":false,"survey_exclusions":"13947261,14126487,14361592,14376811,14385771,14387789,14472374",'
- '"exclusion_period":30,"bid_ir":0.2,"overall_loi":null,"overall_ir":null,"last_block_loi":null,'
- '"last_block_ir":null,"project_last_complete_date":null,"country_iso":"au","language_iso":"eng",'
- '"include_psids":null,"exclude_psids":null'
- ',"qualifications":[],"quotas":[{"remaining_count":100,'
- '"condition_hashes":[]}],"conditions":null,"created_api":"2023-02-28T07:05:36.698000Z",'
- '"modified_api":"2024-03-10T09:43:40.030000Z","updated":"2024-05-30T21:52:46.431612Z","is_live":true'
- "}",
- '{"cpi":"1.23","country_isos":["us"],"language_isos":["eng"],"buyer_id":"215","bid_loi":780,"source":"s",'
- '"used_question_ids":[],"survey_id":"69421","survey_name":"Everyone is eligible US",'
- '"status":22,"field_end_date":"2023-03-02T07:05:36.261000Z","category_code":"232","calculation_type":"COMPLETES",'
- '"requires_pii":false,"survey_exclusions":"13947261,14126487,14361592,14376811,14385771,14387789,14472374",'
- '"exclusion_period":30,"bid_ir":0.2,"overall_loi":null,"overall_ir":null,"last_block_loi":null,'
- '"last_block_ir":null,"project_last_complete_date":null,"country_iso":"us","language_iso":"eng",'
- '"include_psids":null,"exclude_psids":null'
- ',"qualifications":[],"quotas":[{"remaining_count":100,'
- '"condition_hashes":[]}],"conditions":null,"created_api":"2023-02-28T07:05:36.698000Z",'
- '"modified_api":"2024-03-10T09:43:40.030000Z","updated":"2024-05-30T21:52:46.431612Z","is_live":true'
- "}",
- # For partial eligibility
- '{"cpi":"1.23","country_isos":["us"],"language_isos":["eng"],"buyer_id":"215","bid_loi":780,"source":"s",'
- '"used_question_ids":["1031", "212"],"survey_id":"999000","survey_name":"Pet owners",'
- '"status":22,"field_end_date":"2023-03-02T07:05:36.261000Z","category_code":"232","calculation_type":"COMPLETES",'
- '"requires_pii":false,"survey_exclusions":"13947261",'
- '"exclusion_period":30,"bid_ir":0.2,"overall_loi":null,"overall_ir":null,"last_block_loi":null,'
- '"last_block_ir":null,"project_last_complete_date":null,"country_iso":"us","language_iso":"eng",'
- '"include_psids":null,"exclude_psids":null'
- ',"qualifications":["0039b0c", "00f60a8"],"quotas":[{"remaining_count":100,'
- '"condition_hashes":[]}],"conditions":null,"created_api":"2023-02-28T07:05:36.698000Z",'
- '"modified_api":"2024-03-10T09:43:40.030000Z","updated":"2024-05-30T21:52:46.431612Z","is_live":true'
- "}",
-]
-
-# make sure hashes for 111111 are in db
-c1 = SpectrumCondition(
- question_id="1001",
- value_type=ConditionValueType.LIST,
- values=["a", "b", "c"],
- negate=False,
- logical_operator=LogicalOperator.OR,
-)
-c2 = SpectrumCondition(
- question_id="1001",
- value_type=ConditionValueType.LIST,
- values=["a"],
- negate=False,
- logical_operator=LogicalOperator.OR,
-)
-c3 = SpectrumCondition(
- question_id="1002",
- value_type=ConditionValueType.RANGE,
- values=["18-24", "30-32"],
- negate=False,
- logical_operator=LogicalOperator.OR,
-)
-c4 = SpectrumCondition(
- question_id="212",
- value_type=ConditionValueType.LIST,
- values=["23", "24"],
- negate=False,
- logical_operator=LogicalOperator.OR,
-)
-c5 = SpectrumCondition(
- question_id="1031",
- value_type=ConditionValueType.LIST,
- values=["113", "114", "121"],
- negate=False,
- logical_operator=LogicalOperator.OR,
-)
-CONDITIONS = [c1, c2, c3, c4, c5]
-survey = SpectrumSurvey.model_validate_json(SURVEYS_JSON[0])
-assert c1.criterion_hash in survey.qualifications
-assert c3.criterion_hash in survey.qualifications