147 lines
5.6 KiB
Python
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
|