geo/backend/tests/test_services/test_attribution.py

147 lines
5.6 KiB
Python

import uuid
from datetime import UTC, datetime, timedelta
import pytest
import pytest_asyncio
from unittest.mock import patch, AsyncMock, MagicMock
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine, async_sessionmaker
from sqlalchemy.pool import StaticPool
from app.database import Base
from app.models.attribution_record import AttributionRecord
from app.services.attribution.attribution_engine import AttributionEngine
@pytest.fixture
def engine():
return create_async_engine(
"sqlite+aiosqlite:///:memory:",
connect_args={"check_same_thread": False},
poolclass=StaticPool,
)
@pytest_asyncio.fixture
async def session(engine):
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
session_maker = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
async with session_maker() as s:
yield s
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.drop_all)
await engine.dispose()
class TestAttributionWindowDefault:
def test_default_window_days_is_28(self):
with patch("app.services.attribution.attribution_engine.settings") as mock_settings:
mock_settings.ATTRIBUTION_WINDOW_DAYS = 28
engine = AttributionEngine()
assert engine.window_days == 28
@pytest.mark.asyncio
async def test_start_tracking_uses_default_28_days(self, session):
with patch("app.services.attribution.attribution_engine.settings") as mock_settings:
mock_settings.ATTRIBUTION_WINDOW_DAYS = 28
eng = AttributionEngine()
with patch.object(eng, "_get_latest_score", return_value=50.0):
record = await eng.start_tracking(
db=session,
brand_id=uuid.uuid4(),
content_id=uuid.uuid4(),
user_id="test_user",
)
assert record.attribution_window_days == 28
expected_end = record.published_at + timedelta(days=28)
assert record.window_end_at is not None
time_diff = abs((record.window_end_at - expected_end).total_seconds())
assert time_diff < 2
class TestAttributionWindowCustom:
def test_custom_window_days_14(self):
with patch("app.services.attribution.attribution_engine.settings") as mock_settings:
mock_settings.ATTRIBUTION_WINDOW_DAYS = 14
eng = AttributionEngine()
assert eng.window_days == 14
@pytest.mark.asyncio
async def test_start_tracking_uses_custom_14_days(self, session):
with patch("app.services.attribution.attribution_engine.settings") as mock_settings:
mock_settings.ATTRIBUTION_WINDOW_DAYS = 14
eng = AttributionEngine()
with patch.object(eng, "_get_latest_score", return_value=50.0):
record = await eng.start_tracking(
db=session,
brand_id=uuid.uuid4(),
content_id=uuid.uuid4(),
user_id="test_user",
)
assert record.attribution_window_days == 14
expected_end = record.published_at + timedelta(days=14)
assert record.window_end_at is not None
time_diff = abs((record.window_end_at - expected_end).total_seconds())
assert time_diff < 2
@pytest.mark.asyncio
async def test_start_tracking_uses_custom_7_days(self, session):
with patch("app.services.attribution.attribution_engine.settings") as mock_settings:
mock_settings.ATTRIBUTION_WINDOW_DAYS = 7
eng = AttributionEngine()
with patch.object(eng, "_get_latest_score", return_value=50.0):
record = await eng.start_tracking(
db=session,
brand_id=uuid.uuid4(),
content_id=uuid.uuid4(),
user_id="test_user",
)
assert record.attribution_window_days == 7
expected_end = record.published_at + timedelta(days=7)
assert record.window_end_at is not None
time_diff = abs((record.window_end_at - expected_end).total_seconds())
assert time_diff < 2
class TestAttributionWindowConfigDriven:
def test_config_attribute_window_days_default(self):
from app.config import Settings
test_settings = Settings(
JWT_SECRET="a" * 32,
ATTRIBUTION_WINDOW_DAYS=28,
)
assert test_settings.ATTRIBUTION_WINDOW_DAYS == 28
def test_config_attribute_window_days_custom(self):
from app.config import Settings
test_settings = Settings(
JWT_SECRET="a" * 32,
ATTRIBUTION_WINDOW_DAYS=14,
)
assert test_settings.ATTRIBUTION_WINDOW_DAYS == 14
@pytest.mark.asyncio
async def test_window_end_matches_window_days(self, session):
with patch("app.services.attribution.attribution_engine.settings") as mock_settings:
mock_settings.ATTRIBUTION_WINDOW_DAYS = 21
eng = AttributionEngine()
with patch.object(eng, "_get_latest_score", return_value=50.0):
record = await eng.start_tracking(
db=session,
brand_id=uuid.uuid4(),
content_id=uuid.uuid4(),
user_id="test_user",
)
assert record.attribution_window_days == 21
delta = record.window_end_at - record.published_at
assert delta.days == 21