BuyingAgent/backend/tests/conftest.py
Lars 46857715df
Some checks failed
Deploy Development / deploy (push) Successful in 1m49s
Test Suite / pytest-backend (push) Failing after 32s
Test Suite / smoke-dev (push) Successful in 0s
Test Suite / frontend-checks (push) Successful in 30s
Lerne Taxonomie und Trefferliste live von Geizhals, damit Wizard und Kandidaten echte Quelldaten nutzen.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-04 14:53:08 +02:00

164 lines
5.5 KiB
Python

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"),
)