BuyingAgent/backend/tests/conftest.py

164 lines
5.5 KiB
Python
Raw Permalink Normal View History

import json
import os
from datetime import datetime, timezone
from decimal import Decimal
from pathlib import Path
from uuid import UUID, uuid4
import pytest
os.environ.setdefault("GEIZHALS_USE_FIXTURES", "1")
from app.domain.enums import Availability, NormalizationStatus, OfferCondition, ValueType
from app.domain.models import (
AttributeDefinition,
AttributeStatement,
Offer,
PriceObservation,
Product,
ProductVariant,
)
from app.domain.validation import parse_profile, parse_scoring_model
from app.settings import get_settings
ROOT = Path(__file__).resolve().parents[1]
@pytest.fixture(autouse=True)
def _geizhals_fixtures(monkeypatch):
monkeypatch.setenv("GEIZHALS_USE_FIXTURES", "1")
get_settings.cache_clear()
yield
get_settings.cache_clear()
AS_OF = datetime(2026, 10, 3, 12, 0, tzinfo=timezone.utc)
CUTOFF = datetime(2026, 10, 3, 12, 0, tzinfo=timezone.utc)
@pytest.fixture
def scoring_model():
definition = json.loads((ROOT / "rules" / "generic-v1.json").read_text(encoding="utf-8"))
return parse_scoring_model(definition)
@pytest.fixture
def definitions() -> dict[str, AttributeDefinition]:
return {
"ram_gb": AttributeDefinition("ram_gb", ValueType.NUMBER, "GB", Decimal("0"), None, None),
"storage_gb": AttributeDefinition("storage_gb", ValueType.NUMBER, "GB", Decimal("0"), None, None),
"weight_kg": AttributeDefinition("weight_kg", ValueType.NUMBER, "kg", Decimal("0"), None, None),
"battery_score": AttributeDefinition("battery_score", ValueType.NUMBER, None, Decimal("0"), Decimal("100"), None),
"display_score": AttributeDefinition("display_score", ValueType.NUMBER, None, Decimal("0"), Decimal("100"), None),
"cpu_score": AttributeDefinition("cpu_score", ValueType.NUMBER, None, Decimal("0"), Decimal("100"), None),
"ports": AttributeDefinition("ports", ValueType.STRING_LIST, None, None, None, None),
"color": AttributeDefinition("color", ValueType.STRING, None, None, None, None),
"has_lte": AttributeDefinition("has_lte", ValueType.BOOLEAN, None, None, None, None),
}
@pytest.fixture
def profile(scoring_model, definitions):
raw = json.loads((ROOT / "profiles" / "mobile-development-notebook.json").read_text(encoding="utf-8"))
return parse_profile(
raw,
attribute_definitions=definitions,
scoring_versions={scoring_model.version},
supported_strategies=set(scoring_model.supported_preference_strategies),
)
def utc(year, month, day, hour=10, minute=0) -> datetime:
return datetime(year, month, day, hour, minute, tzinfo=timezone.utc)
def statement(attribute: str, value, *, confidence="1.0", status="normalized", observed=None, ingested=None, row_id=None):
return AttributeStatement(
id=row_id or uuid4(),
variant_id=UUID("22222222-2222-4222-8222-222222222222"),
attribute=attribute,
raw_value=value,
normalized_value=None if status != "normalized" else value,
unit="GB" if attribute.endswith("_gb") else ("kg" if attribute.endswith("_kg") else None),
normalization_status=NormalizationStatus(status),
normalizer_version="manual-v1",
confidence=Decimal(str(confidence)),
source_id="manual",
source_reference=f"test/{attribute}",
observed_at=observed or utc(2026, 10, 3, 9),
ingested_at=ingested or utc(2026, 10, 3, 9, 1),
)
def product_a() -> Product:
return Product(
id=UUID("11111111-1111-4111-8111-111111111111"),
category="notebook",
brand="Example",
name="Example Mobile 13",
model="EM13",
created_at=utc(2026, 10, 1, 8),
)
def variant_a() -> ProductVariant:
return ProductVariant(
id=UUID("22222222-2222-4222-8222-222222222222"),
product_id=UUID("11111111-1111-4111-8111-111111111111"),
name="32/1024",
manufacturer_sku="EM13-32-1T",
identifiers={},
created_at=utc(2026, 10, 1, 8),
)
def offer_a() -> Offer:
return Offer(
id=UUID("33333333-3333-4333-8333-333333333333"),
variant_id=UUID("22222222-2222-4222-8222-222222222222"),
source_id="manual",
external_id="demo-offer-a",
seller="DemoShop",
url=None,
market="DE",
currency="EUR",
condition=OfferCondition.NEW,
created_at=utc(2026, 10, 1, 8),
)
def observation(day, total, *, offer_id=None, obs_id=None, availability="in_stock", confidence="1.0", hour=10):
return PriceObservation(
id=obs_id or uuid4(),
offer_id=offer_id or UUID("33333333-3333-4333-8333-333333333333"),
source_id="manual",
external_observation_id=str(obs_id or uuid4()),
observed_at=utc(2026, day[0], day[1], hour) if isinstance(day, tuple) else day,
ingested_at=utc(2026, 10, 3, 11),
item_price=Decimal(str(total)),
shipping_price=Decimal("0.00"),
currency="EUR",
availability=Availability(availability),
raw_payload={"total": str(total)},
confidence=Decimal(str(confidence)),
)
def case_a_attributes():
return (
statement("ram_gb", 32),
statement("storage_gb", 1024),
statement("weight_kg", Decimal("1.2")),
statement("battery_score", 90),
statement("display_score", 90),
statement("cpu_score", 90),
)
def case_a_prices():
return (
observation((9, 1), "1599.00"),
observation((9, 15), "1599.00"),
observation((10, 1), "1599.00"),
observation((10, 3), "1298.00"),
)